diff --git a/qpageview/export.py b/qpageview/export.py index 3fb1e56..83916d5 100644 --- a/qpageview/export.py +++ b/qpageview/export.py @@ -243,11 +243,25 @@ def export(self): res = self.resolution if self.oversample != 1: res *= self.oversample - i = self.page().image(self._rect, res, res, self.paperColor) + if self.grayscale: + # convertToFormat() does weird things with the alpha channel, + # so we always render grayscale images with a solid background + # and handle transparent areas manually if needed + paperColor = self.paperColor or Qt.GlobalColor.white + else: + paperColor = self.paperColor + i = self.page().image(self._rect, res, res, paperColor) if self.oversample != 1: i = i.scaled(i.size() / self.oversample, transformMode=Qt.TransformationMode.SmoothTransformation) if self.grayscale: i = i.convertToFormat(QImage.Format.Format_Grayscale8) + if self.paperColor is None: + # restore the original transparent background + # since our alpha map here is just this image's negative, + # we save memory by doing this instead of making a copy() + i.invertPixels(QImage.InvertMode.InvertRgb) + i.setAlphaChannel(i) + i.invertPixels(QImage.InvertMode.InvertRgb) if self.autocrop: i = i.copy(util.autoCropRect(i)) # needed for correct resolution metadata; see issue #44 diff --git a/qpageview/pdf.py b/qpageview/pdf.py index a765ea8..9822a7d 100644 --- a/qpageview/pdf.py +++ b/qpageview/pdf.py @@ -303,10 +303,6 @@ def draw(self, page, painter, key, tile, paperColor=None): Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) - # Erase the target area and draw the image - painter.eraseRect(target) - if paperColor: - painter.fillRect(target, paperColor) painter.drawImage(target, image, QRectF(image.rect())) diff --git a/qpageview/render.py b/qpageview/render.py index 89d4b7a..cad42bf 100644 --- a/qpageview/render.py +++ b/qpageview/render.py @@ -219,17 +219,15 @@ def render(self, page, key, tile, paperColor=None): The default implementation prepares the image, a painter and then calls draw() to actually draw the contents. - If the paperColor is not specified, it will be read from the Page's - paperColor attribute (if not None) or else from the renderer's - paperColor attribute. + If the paperColor is not specified, the returned image will have a + transparent background, and the caller is responsible to paint or + erase the background as needed before calling this method. """ - if paperColor is None: - paperColor = page.paperColor or self.paperColor - i = QImage(tile.w, tile.h, self.imageFormat) - if paperColor: - i.fill(paperColor) + # leave the background transparent if no paperColor is specified, + # since we don't necessarily want to fill it e.g. when printing + i.fill(paperColor or Qt.GlobalColor.transparent) painter = QPainter(i) # rotate the painter accordingly @@ -371,6 +369,8 @@ def paint(self, page, painter, rect, callback=None): for (r, image, source) in reversed(images): # scale the target rect back to the paint device target = QRectF(r.x() / info.ratio, r.y() / info.ratio, r.width() / info.ratio, r.height() / info.ratio) + # job() already filled the background when rendering the page + # so we don't have to do that separately, which can cause flicker painter.drawImage(target, image, source) def schedule(self, page, key, tiles, callback): @@ -398,7 +398,9 @@ def job(self, page, key, tile): exception = [] def work(): try: - return self.render(page, key, tile) + # filling the background here is an optimization for paint() + return self.render(page, key, tile, + page.paperColor or self.paperColor) except Exception: exception.extend(sys.exc_info()) return QImage()