Do not use QPainter for overlay operations

QPainter requires a QApplication. We do not want to depend on a
QApplication in utils/img.py
This commit is contained in:
Kovid Goyal 2016-05-06 16:32:56 +05:30
parent a9e79dbe57
commit 4ba82e5cea
4 changed files with 91 additions and 19 deletions

View File

@ -12,12 +12,18 @@
#define SQUARE(x) (x)*(x) #define SQUARE(x) (x)*(x)
#define MAX(x, y) ((x) > (y)) ? (x) : (y) #define MAX(x, y) ((x) > (y)) ? (x) : (y)
#define MIN(x, y) ((x) < (y)) ? (x) : (y)
#define DISTANCE(r, g, b) (SQUARE(r - red_average) + SQUARE(g - green_average) + SQUARE(b - blue_average)) #define DISTANCE(r, g, b) (SQUARE(r - red_average) + SQUARE(g - green_average) + SQUARE(b - blue_average))
#define M_EPSILON 1.0e-6 #define M_EPSILON 1.0e-6
#define M_SQ2PI 2.50662827463100024161235523934010416269302368164062 #define M_SQ2PI 2.50662827463100024161235523934010416269302368164062
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
#endif #endif
#define ENSURE32(img) \
if (img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_ARGB32) { \
img = img.convertToFormat(img.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32); \
if (img.isNull()) throw std::bad_alloc(); \
} \
typedef struct typedef struct
{ {
@ -54,12 +60,6 @@ unsigned int read_border_row(const QImage &img, const unsigned int width, const
return ans; return ans;
} }
#define ENSURE32(img) \
if (img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_ARGB32) { \
img = img.convertToFormat(img.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32); \
if (img.isNull()) throw std::bad_alloc(); \
} \
QImage remove_borders(const QImage &image, double fuzz) { QImage remove_borders(const QImage &image, double fuzz) {
int *buf = NULL; int *buf = NULL;
QImage img = image, timg; QImage img = image, timg;
@ -576,3 +576,57 @@ QImage despeckle(const QImage &image) {
return(img); return(img);
} }
// }}} // }}}
// overlay() {{{
static inline unsigned int BYTE_MUL(unsigned int x, unsigned int a) {
quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a;
t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8;
t &= 0x00ff00ff00ff00ff;
return ((unsigned int)(t)) | ((unsigned int)(t >> 24));
}
void overlay(const QImage &image, QImage &canvas, unsigned int left, unsigned int top) {
QImage img(image);
unsigned int cw = canvas.width(), ch = canvas.height(), iw = img.width(), ih = img.height(), r, c, right = 0, bottom = 0, height, width, s;
const QRgb* src;
QRgb* dest;
ENSURE32(canvas)
if (canvas.isNull() || cw < 1 || ch < 1) throw std::out_of_range("The canvas cannot be a null image");
if (canvas.hasAlphaChannel()) throw std::out_of_range("The canvas must not have transparent pixels");
left = MIN(cw - 1, left);
top = MIN(ch - 1, top);
right = MIN(left + iw, cw);
bottom = MIN(top + ih, ch);
height = bottom - top; width = right - left;
if (img.hasAlphaChannel()) {
if (img.format() != QImage::Format_ARGB32_Premultiplied) {
img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
if (img.isNull()) throw std::bad_alloc();
}
for (r = 0; r < height; r++) {
src = reinterpret_cast<const QRgb*>(img.constScanLine(r));
dest = reinterpret_cast<QRgb*>(canvas.scanLine(r + top));
for (c = 0; c < width; c++) {
// Optimized Alpha blending, taken from qt_blend_argb32_on_argb32
// Since the canvas has no transparency
// the composite pixel is: canvas*(1-alpha) + src * alpha
// but src is pre-multiplied, so it is:
// canvas*(1-alpha) + src
s = src[c];
if (s >= 0xff000000) dest[left+c] = s;
else if (s != 0) dest[left+c] = s + BYTE_MUL(dest[left+c], qAlpha(~s));
}
}
} else {
ENSURE32(img);
for (r = 0; r < bottom; r++) {
src = reinterpret_cast<const QRgb*>(img.constScanLine(r));
dest = reinterpret_cast<QRgb*>(canvas.scanLine(r + top));
memcpy(dest + left, src, (right - left) * sizeof(QRgb));
}
}
} // }}}

View File

@ -15,4 +15,5 @@ QImage grayscale(const QImage &image);
QImage gaussian_sharpen(const QImage &img, const float radius, const float sigma, const bool high_quality=true); QImage gaussian_sharpen(const QImage &img, const float radius, const float sigma, const bool high_quality=true);
QImage gaussian_blur(const QImage &img, const float radius, const float sigma); QImage gaussian_blur(const QImage &img, const float radius, const float sigma);
QImage despeckle(const QImage &image); QImage despeckle(const QImage &image);
void overlay(const QImage &image, QImage &canvas, unsigned int left, unsigned int top);

View File

@ -51,3 +51,10 @@ QImage despeckle(const QImage &image);
sipRes = new QImage(despeckle(*a0)); sipRes = new QImage(despeckle(*a0));
IMAGEOPS_SUFFIX IMAGEOPS_SUFFIX
%End %End
void overlay(const QImage &image, QImage &canvas, unsigned int left, unsigned int top);
%MethodCode
IMAGEOPS_PREFIX
overlay(*a0, *a1, a2, a3);
IMAGEOPS_SUFFIX
%End

View File

@ -7,7 +7,7 @@ from __future__ import (unicode_literals, division, absolute_import,
import os, subprocess, errno, shutil, tempfile import os, subprocess, errno, shutil, tempfile
from threading import Thread from threading import Thread
from PyQt5.Qt import QImage, QByteArray, QBuffer, Qt, QPainter, QImageReader, QColor from PyQt5.Qt import QImage, QByteArray, QBuffer, Qt, QImageReader, QColor
from calibre import fit_image, force_unicode from calibre import fit_image, force_unicode
from calibre.constants import iswindows, plugins from calibre.constants import iswindows, plugins
@ -54,19 +54,32 @@ def image_and_format_from_data(data):
return r.read(), fmt return r.read(), fmt
def add_borders(img, left=0, top=0, right=0, bottom=0, border_color='#ffffff'): def add_borders(img, left=0, top=0, right=0, bottom=0, border_color='#ffffff'):
if not (left > 0 or right > 0 or top > 0 or bottom > 0):
return img
nimg = QImage(img.width() + left + right, img.height() + top + bottom, QImage.Format_RGB32) nimg = QImage(img.width() + left + right, img.height() + top + bottom, QImage.Format_RGB32)
nimg.fill(QColor(border_color)) nimg.fill(QColor(border_color))
p = QPainter(nimg) overlay(img, nimg, left, top)
p.drawImage(left, top, img)
p.end()
return nimg return nimg
def overlay(img, canvas=None, left=0, top=0):
if canvas is None:
canvas = QImage(img.size(), QImage.Format_RGB32)
canvas.fill(Qt.white)
if imageops is None:
from PyQt5.Qt import QPainter
from calibre.gui2 import ensure_app
ensure_app()
p = QPainter(canvas)
p.drawImage(left, top, img)
p.end()
else:
imageops.overlay(img, canvas, left, top)
return canvas
def blend_image(img, bgcolor='#ffffff'): def blend_image(img, bgcolor='#ffffff'):
nimg = QImage(img.size(), QImage.Format_RGB32) nimg = QImage(img.size(), QImage.Format_RGB32)
nimg.fill(QColor(bgcolor)) nimg.fill(QColor(bgcolor))
p = QPainter(nimg) overlay(img, nimg)
p.drawImage(0, 0, img)
p.end()
return nimg return nimg
def image_to_data(img, compression_quality=95, fmt='JPEG'): def image_to_data(img, compression_quality=95, fmt='JPEG'):
@ -178,9 +191,7 @@ def blend_on_canvas(img, width, height, bgcolor='#ffffff'):
w, h = nw, nh w, h = nw, nh
nimg = QImage(width, height, QImage.Format_RGB32) nimg = QImage(width, height, QImage.Format_RGB32)
nimg.fill(QColor(bgcolor)) nimg.fill(QColor(bgcolor))
p = QPainter(nimg) overlay(img, nimg, (width - w)//2, (height - h)//2)
p.drawImage((width - w)//2, (height - h)//2, img)
p.end()
return nimg return nimg
class Canvas(object): class Canvas(object):
@ -190,15 +201,14 @@ class Canvas(object):
self.img.fill(QColor(bgcolor)) self.img.fill(QColor(bgcolor))
def __enter__(self): def __enter__(self):
self.painter = QPainter(self.img)
return self return self
def __exit__(self, *args): def __exit__(self, *args):
self.painter.end() pass
def compose(self, img, x=0, y=0): def compose(self, img, x=0, y=0):
img = image_from_data(img) img = image_from_data(img)
self.painter.drawImage(x, y, img) overlay(img, self.img, x, y)
def export(self, fmt='JPEG', compression_quality=95): def export(self, fmt='JPEG', compression_quality=95):
return image_to_data(self.img, compression_quality=compression_quality, fmt=fmt) return image_to_data(self.img, compression_quality=compression_quality, fmt=fmt)