mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
a9e79dbe57
commit
4ba82e5cea
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
} // }}}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
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()
|
||||
return nimg
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user