From 4ba82e5cea74e5f7c2ea27cf29cf27dcf13844b9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 6 May 2016 16:32:56 +0530 Subject: [PATCH] Do not use QPainter for overlay operations QPainter requires a QApplication. We do not want to depend on a QApplication in utils/img.py --- src/calibre/utils/imageops/imageops.cpp | 66 ++++++++++++++++++++++--- src/calibre/utils/imageops/imageops.h | 1 + src/calibre/utils/imageops/imageops.sip | 7 +++ src/calibre/utils/img.py | 36 +++++++++----- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/src/calibre/utils/imageops/imageops.cpp b/src/calibre/utils/imageops/imageops.cpp index 9b9a0e3968..f772f14c3a 100644 --- a/src/calibre/utils/imageops/imageops.cpp +++ b/src/calibre/utils/imageops/imageops.cpp @@ -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(img.constScanLine(r)); + dest = reinterpret_cast(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(img.constScanLine(r)); + dest = reinterpret_cast(canvas.scanLine(r + top)); + memcpy(dest + left, src, (right - left) * sizeof(QRgb)); + } + } + +} // }}} diff --git a/src/calibre/utils/imageops/imageops.h b/src/calibre/utils/imageops/imageops.h index d9691ce3a6..a31c3c5d7c 100644 --- a/src/calibre/utils/imageops/imageops.h +++ b/src/calibre/utils/imageops/imageops.h @@ -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); diff --git a/src/calibre/utils/imageops/imageops.sip b/src/calibre/utils/imageops/imageops.sip index 09cae8a617..3bd615f443 100644 --- a/src/calibre/utils/imageops/imageops.sip +++ b/src/calibre/utils/imageops/imageops.sip @@ -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 diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index d18429faa8..53a0f7c2c7 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -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)