commit 000902528ae33c0f170d8ddc4336bd210b897b23 Author: Albert Astals Cid Date: Thu Oct 5 19:02:38 2017 +0200 qt5: Add callback for renderToImage to signal the app to update with a partial image diff --git a/qt5/src/poppler-page.cc b/qt5/src/poppler-page.cc index 3ff676ec..49bc07f2 100644 --- a/qt5/src/poppler-page.cc +++ b/qt5/src/poppler-page.cc @@ -68,6 +68,120 @@ namespace Poppler { +class Qt5SplashOutputDev : public SplashOutputDev +{ +public: + Qt5SplashOutputDev(SplashColorMode colorMode, int bitmapRowPad, + GBool reverseVideo, bool ignorePaperColorA, SplashColorPtr paperColor, + GBool bitmapTopDown, SplashThinLineMode thinLineMode, + GBool overprintPreview) + : SplashOutputDev(colorMode, bitmapRowPad, reverseVideo, paperColor, bitmapTopDown, thinLineMode, overprintPreview) + , partialUpdateCallback(nullptr) + , shouldDoPartialUpdateCallback(nullptr) + , ignorePaperColor(ignorePaperColorA) + { + } + + void setPartialUpdateCallbackData(Page::RenderToImagePartialUpdateFunc callback, Page::ShouldRenderToImagePartialQueryFunc shouldDoCallback, const QVariant &payloadA) + { + partialUpdateCallback = callback; + shouldDoPartialUpdateCallback = shouldDoCallback; + payload = payloadA; + } + + void dump() override + { + if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) { + partialUpdateCallback(getXBGRImage( false /* takeImageData */), payload); + } + } + + QImage getXBGRImage(bool takeImageData) + { + SplashBitmap *bitmap = getBitmap(); + + const int bw = bitmap->getWidth(); + const int bh = bitmap->getHeight(); + const int brs = bitmap->getRowSize(); + + // If we use DeviceN8, convert to XBGR8. + // If requested, also transfer Splash's internal alpha channel. + const SplashBitmap::ConversionMode mode = ignorePaperColor + ? SplashBitmap::conversionAlphaPremultiplied + : SplashBitmap::conversionOpaque; + + const QImage::Format format = ignorePaperColor + ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGB32; + + if (bitmap->convertToXBGR(mode)) { + SplashColorPtr data = takeImageData ? bitmap->takeData() : bitmap->getDataPtr(); + + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { + // Convert byte order from RGBX to XBGR. + for (int i = 0; i < bh; ++i) { + for (int j = 0; j < bw; ++j) { + SplashColorPtr pixel = &data[i * brs + j]; + + qSwap(pixel[0], pixel[3]); + qSwap(pixel[1], pixel[2]); + } + } + } + + if (takeImageData) { + // Construct a Qt image holding (and also owning) the raw bitmap data. + return QImage(data, bw, bh, brs, format, gfree, data); + } else { + return QImage(data, bw, bh, brs, format).copy(); + } + } + + return QImage(); + } + +private: + Page::RenderToImagePartialUpdateFunc partialUpdateCallback; + Page::ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback; + QVariant payload; + bool ignorePaperColor; +}; + + +class QImageDumpingArthurOutputDev : public ArthurOutputDev +{ +public: + QImageDumpingArthurOutputDev(QPainter *painter, QImage *i) + : ArthurOutputDev(painter) + , partialUpdateCallback(nullptr) + , shouldDoPartialUpdateCallback(nullptr) + , image(i) + { + } + + void setPartialUpdateCallbackData(Page::RenderToImagePartialUpdateFunc callback, Page::ShouldRenderToImagePartialQueryFunc shouldDoCallback, const QVariant &payloadA) + { + partialUpdateCallback = callback; + shouldDoPartialUpdateCallback = shouldDoCallback; + payload = payloadA; + } + + void dump() override + { + if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) { + partialUpdateCallback(*image, payload); + } + } + +private: + Page::RenderToImagePartialUpdateFunc partialUpdateCallback; + Page::ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback; + QVariant payload; + QImage *image; +}; + + + Link* PageData::convertLinkActionToLink(::LinkAction * a, const QRectF &linkArea) { return convertLinkActionToLink(a, parentDoc, linkArea); @@ -296,7 +410,58 @@ Page::~Page() delete m_page; } +static bool renderToArthur(ArthurOutputDev *arthur_output, QPainter *painter, PageData *page, double xres, double yres, int x, int y, int w, int h, Page::Rotation rotate, Page::PainterFlags flags) +{ + const bool savePainter = !(flags & Page:: DontSaveAndRestore); + if (savePainter) + painter->save(); + if (page->parentDoc->m_hints & Document::Antialiasing) + painter->setRenderHint(QPainter::Antialiasing); + if (page->parentDoc->m_hints & Document::TextAntialiasing) + painter->setRenderHint(QPainter::TextAntialiasing); + painter->translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y); + + arthur_output->startDoc(page->parentDoc->doc->getXRef()); + + const GBool hideAnnotations = page->parentDoc->m_hints & Document::HideAnnotations; + + // Callback that filters out everything but form fields + auto annotDisplayDecideCbk = [](Annot *annot, void *user_data) + { + // Hide everything but forms + return (annot->getType() == Annot::typeWidget); + }; + + // A nullptr, but with the type of a function pointer + // Needed to make the ternary operator below happy. + GBool (*nullCallBack)(Annot *annot, void *user_data) = nullptr; + + page->parentDoc->doc->displayPageSlice(arthur_output, + page->index + 1, + xres, + yres, + (int)rotate * 90, + false, + true, + false, + x, + y, + w, + h, + nullptr, + nullptr, + (hideAnnotations) ? annotDisplayDecideCbk : nullCallBack); + if (savePainter) + painter->restore(); + return true; +} + QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate) const +{ + return renderToImage(xres, yres, x, y, w, h, rotate, nullptr, nullptr, QVariant()); +} + +QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback, const QVariant &payload) const { int rotation = (int)rotate * 90; QImage img; @@ -350,14 +515,17 @@ QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, const bool ignorePaperColor = m_page->parentDoc->m_hints & Document::IgnorePaperColor; - SplashOutputDev splash_output( + Qt5SplashOutputDev splash_output( colorMode, 4, gFalse, + ignorePaperColor, ignorePaperColor ? NULL : bgColor, gTrue, thinLineMode, overprintPreview); + splash_output.setPartialUpdateCallbackData(partialUpdateCallback, shouldDoPartialUpdateCallback, payload); + splash_output.setFontAntialias(m_page->parentDoc->m_hints & Document::TextAntialiasing ? gTrue : gFalse); splash_output.setVectorAntialias(m_page->parentDoc->m_hints & Document::Antialiasing ? gTrue : gFalse); splash_output.setFreeTypeHinting(m_page->parentDoc->m_hints & Document::TextHinting ? gTrue : gFalse, @@ -384,40 +552,7 @@ QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, (hideAnnotations) ? annotDisplayDecideCbk : nullCallBack, nullptr, gTrue); - SplashBitmap *bitmap = splash_output.getBitmap(); - - const int bw = bitmap->getWidth(); - const int bh = bitmap->getHeight(); - const int brs = bitmap->getRowSize(); - - // If we use DeviceN8, convert to XBGR8. - // If requested, also transfer Splash's internal alpha channel. - const SplashBitmap::ConversionMode mode = ignorePaperColor - ? SplashBitmap::conversionAlphaPremultiplied - : SplashBitmap::conversionOpaque; - - const QImage::Format format = ignorePaperColor - ? QImage::Format_ARGB32_Premultiplied - : QImage::Format_RGB32; - - if (bitmap->convertToXBGR(mode)) { - SplashColorPtr data = bitmap->takeData(); - - if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { - // Convert byte order from RGBX to XBGR. - for (int i = 0; i < bh; ++i) { - for (int j = 0; j < bw; ++j) { - SplashColorPtr pixel = &data[i * brs + j]; - - qSwap(pixel[0], pixel[3]); - qSwap(pixel[1], pixel[2]); - } - } - } - - // Construct a Qt image holding (and also owning) the raw bitmap data. - img = QImage(data, bw, bh, brs, format, gfree, data); - } + img = splash_output.getXBGRImage( true /* takeImageData */); #endif break; } @@ -434,7 +569,9 @@ QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, tmpimg.fill(bgColor); QPainter painter(&tmpimg); - renderToPainter(&painter, xres, yres, x, y, w, h, rotate, DontSaveAndRestore); + QImageDumpingArthurOutputDev arthur_output(&painter, &tmpimg); + arthur_output.setPartialUpdateCallbackData(partialUpdateCallback, shouldDoPartialUpdateCallback, payload); + renderToArthur(&arthur_output, &painter, m_page, xres, yres, x, y, w, h, rotate, DontSaveAndRestore); painter.end(); img = tmpimg; break; @@ -455,48 +592,8 @@ bool Page::renderToPainter(QPainter* painter, double xres, double yres, int x, i return false; case Poppler::Document::ArthurBackend: { - const bool savePainter = !(flags & DontSaveAndRestore); - if (savePainter) - painter->save(); - if (m_page->parentDoc->m_hints & Document::Antialiasing) - painter->setRenderHint(QPainter::Antialiasing); - if (m_page->parentDoc->m_hints & Document::TextAntialiasing) - painter->setRenderHint(QPainter::TextAntialiasing); - painter->translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y); - ArthurOutputDev arthur_output(painter); - arthur_output.startDoc(m_page->parentDoc->doc->getXRef()); - - const GBool hideAnnotations = m_page->parentDoc->m_hints & Document::HideAnnotations; - - // Callback that filters out everything but form fields - auto annotDisplayDecideCbk = [](Annot *annot, void *user_data) - { - // Hide everything but forms - return (annot->getType() == Annot::typeWidget); - }; - - // A nullptr, but with the type of a function pointer - // Needed to make the ternary operator below happy. - GBool (*nullCallBack)(Annot *annot, void *user_data) = nullptr; - - m_page->parentDoc->doc->displayPageSlice(&arthur_output, - m_page->index + 1, - xres, - yres, - (int)rotate * 90, - false, - true, - false, - x, - y, - w, - h, - nullptr, - nullptr, - (hideAnnotations) ? annotDisplayDecideCbk : nullCallBack); - if (savePainter) - painter->restore(); - return true; + ArthurOutputDev arthur_output(painter); + return renderToArthur(&arthur_output, painter, m_page, xres, yres, x, y, w, h, rotate, flags); } } return false; diff --git a/qt5/src/poppler-qt5.h b/qt5/src/poppler-qt5.h index 58a7925a..1bc4f3fe 100644 --- a/qt5/src/poppler-qt5.h +++ b/qt5/src/poppler-qt5.h @@ -491,6 +491,83 @@ delete it; */ QImage renderToImage(double xres=72.0, double yres=72.0, int x=-1, int y=-1, int w=-1, int h=-1, Rotation rotate = Rotate0) const; + /** + Partial Update renderToImage callback. + + This function type is used for doing partial rendering updates; + the first parameter is the image as rendered up to now, the second is the unaltered + closure argument which was passed to the renderToImage call. + + \since 0.62 + */ + typedef void (*RenderToImagePartialUpdateFunc)(const QImage & /*image*/, const QVariant & /*closure*/); + + /** + Partial Update query renderToImage callback. + + This function type is used for query if the partial rendering update should happen; + the parameter is the unaltered closure argument which was passed to the renderToImage call. + + \since 0.62 + */ + typedef bool (*ShouldRenderToImagePartialQueryFunc)(const QVariant & /*closure*/); + + /** + Render the page to a QImage using the current + \link Document::renderBackend() Document renderer\endlink. + + If \p x = \p y = \p w = \p h = -1, the method will automatically + compute the size of the image from the horizontal and vertical + resolutions specified in \p xres and \p yres. Otherwise, the + method renders only a part of the page, specified by the + parameters (\p x, \p y, \p w, \p h) in pixel coordinates. The returned + QImage then has size (\p w, \p h), independent of the page + size. + + \param x specifies the left x-coordinate of the box, in + pixels. + + \param y specifies the top y-coordinate of the box, in + pixels. + + \param w specifies the width of the box, in pixels. + + \param h specifies the height of the box, in pixels. + + \param xres horizontal resolution of the graphics device, + in dots per inch + + \param yres vertical resolution of the graphics device, in + dots per inch + + \param rotate how to rotate the page + + \param partialUpdateCallback callback that will be called to + report a partial rendering update + + \param shouldDoPartialUpdateCallback callback that will be called + to ask if a partial rendering update is wanted. This exists + because doing a partial rendering update needs to copy the image + buffer so if it is not wanted it is better skipped early. + + \param closure opaque structure that will be passed + back to partialUpdateCallback and shouldDoPartialUpdateCallback. + + \warning The parameter (\p x, \p y, \p w, \p h) are not + well-tested. Unusual or meaningless parameters may lead to + rather unexpected results. + + \returns a QImage of the page, or a null image on failure. + + \since 0.62 + */ + QImage renderToImage(double xres, double yres, + int x, int y, int w, int h, Rotation rotate, + RenderToImagePartialUpdateFunc partialUpdateCallback, + ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback, + const QVariant &closure + ) const; + /** Render the page to the specified QPainter using the current \link Document::renderBackend() Document renderer\endlink.