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 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 M_EPSILON 1.0e-6
#define M_SQ2PI 2.50662827463100024161235523934010416269302368164062
#ifndef M_PI
#define M_PI 3.14159265358979323846
#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
{
@ -54,12 +60,6 @@ unsigned int read_border_row(const QImage &img, const unsigned int width, const
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) {
int *buf = NULL;
QImage img = image, timg;
@ -576,3 +576,57 @@ QImage despeckle(const QImage &image) {
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_blur(const QImage &img, const float radius, const float sigma);
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));
IMAGEOPS_SUFFIX
%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
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.constants import iswindows, plugins
@ -54,19 +54,32 @@ def image_and_format_from_data(data):
return r.read(), fmt
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.fill(QColor(border_color))
p = QPainter(nimg)
p.drawImage(left, top, img)
p.end()
overlay(img, nimg, left, top)
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'):
nimg = QImage(img.size(), QImage.Format_RGB32)
nimg.fill(QColor(bgcolor))
p = QPainter(nimg)
p.drawImage(0, 0, img)
p.end()
overlay(img, nimg)
return nimg
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
nimg = QImage(width, height, QImage.Format_RGB32)
nimg.fill(QColor(bgcolor))
p = QPainter(nimg)
p.drawImage((width - w)//2, (height - h)//2, img)
p.end()
overlay(img, nimg, (width - w)//2, (height - h)//2)
return nimg
class Canvas(object):
@ -190,15 +201,14 @@ class Canvas(object):
self.img.fill(QColor(bgcolor))
def __enter__(self):
self.painter = QPainter(self.img)
return self
def __exit__(self, *args):
self.painter.end()
pass
def compose(self, img, x=0, y=0):
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):
return image_to_data(self.img, compression_quality=compression_quality, fmt=fmt)