diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp index c0459ae..f1e539b 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp @@ -396,7 +396,8 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) : m_horizontalScrollbar(nullptr), m_verticalScrollbar(nullptr), m_features(PDFRenderer::getDefaultFeatures()), - m_compiler(new PDFAsynchronousPageCompiler(this)) + m_compiler(new PDFAsynchronousPageCompiler(this)), + m_rasterizer(new PDFRasterizer(this)) { m_controller = new PDFDrawSpaceController(this); connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update); @@ -675,12 +676,18 @@ QImage PDFDrawWidgetProxy::drawThumbnailImage(PDFInteger pageIndex, int pixelSiz if (imageSize.isValid()) { - image = QImage(imageSize, QImage::Format_RGBA8888_Premultiplied); - image.fill(Qt::white); + const PDFPrecompiledPage* compiledPage = m_compiler->getPrecompiledCache(pageIndex, true); + if (compiledPage && compiledPage->isValid()) + { + // Rasterize the image. + image = m_rasterizer->render(page, compiledPage, imageSize, m_features); + } - QPainter painter(&image); - PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity(), m_features, m_meshQualitySettings); - renderer.render(&painter, QRect(QPoint(0, 0), imageSize), pageIndex); + if (image.isNull()) + { + image = QImage(imageSize, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::white); + } } } @@ -945,6 +952,11 @@ bool PDFDrawWidgetProxy::isBlockMode() const return false; } +void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat) +{ + m_rasterizer->reset(useOpenGL, surfaceFormat); +} + void PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged(int value) { if (!m_updateDisabled && !m_horizontalScrollbar->isHidden()) diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.h b/PdfForQtLib/sources/pdfdrawspacecontroller.h index 5fe4b9e..081119c 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.h +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.h @@ -254,6 +254,12 @@ public: /// or continuous mode (single block with continuous list of separated pages). bool isBlockMode() const; + /// Updates renderer (in current implementation, renderer for page thumbnails) + /// using given parameters. + /// \param useOpenGL Use OpenGL for rendering? + /// \param surfaceFormat Surface format for OpenGL rendering + void updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat); + static constexpr PDFReal ZOOM_STEP = 1.2; const PDFDocument* getDocument() const { return m_controller->getDocument(); } @@ -393,6 +399,9 @@ private: /// Page compiler PDFAsynchronousPageCompiler* m_compiler; + + /// Page image rasterizer for thumbnails + PDFRasterizer* m_rasterizer; }; } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdrawwidget.cpp b/PdfForQtLib/sources/pdfdrawwidget.cpp index e474b9d..86a76f5 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.cpp +++ b/PdfForQtLib/sources/pdfdrawwidget.cpp @@ -52,6 +52,7 @@ PDFWidget::PDFWidget(RendererEngine engine, int samplesCount, QWidget* parent) : connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError); connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update)); connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged); + updateRendererImpl(); } PDFWidget::~PDFWidget() @@ -92,6 +93,7 @@ void PDFWidget::updateRenderer(RendererEngine engine, int samplesCount) openglDrawWidget->setFormat(format); } } + updateRendererImpl(); } int PDFWidget::getPageRenderingErrorCount() const @@ -104,6 +106,12 @@ int PDFWidget::getPageRenderingErrorCount() const return count; } +void PDFWidget::updateRendererImpl() +{ + PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast(m_drawWidget->getWidget()); + m_proxy->updateRenderer(openglDrawWidget != nullptr, openglDrawWidget ? openglDrawWidget->format() : QSurfaceFormat::defaultFormat()); +} + void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList& errors) { // Empty list of error should not be reported! diff --git a/PdfForQtLib/sources/pdfdrawwidget.h b/PdfForQtLib/sources/pdfdrawwidget.h index 5c3bbb8..de5af25 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.h +++ b/PdfForQtLib/sources/pdfdrawwidget.h @@ -15,7 +15,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with PDFForQt. If not, see . - #ifndef PDFDRAWWIDGET_H #define PDFDRAWWIDGET_H @@ -79,6 +78,7 @@ signals: void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount); private: + void updateRendererImpl(); void onRenderingError(PDFInteger pageIndex, const QList& errors); void onPageImageChanged(bool all, const std::vector& pages); diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index b41028a..b9a5039 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -20,6 +20,10 @@ #include "pdfdocument.h" #include +#include +#include +#include +#include namespace pdf { @@ -143,4 +147,183 @@ void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex) timer.invalidate(); } +PDFRasterizer::PDFRasterizer(QObject* parent) : + BaseClass(parent), + m_features(), + m_surfaceFormat(), + m_surface(nullptr), + m_context(nullptr), + m_fbo(nullptr) +{ + +} + +PDFRasterizer::~PDFRasterizer() +{ + releaseOpenGL(); +} + +void PDFRasterizer::reset(bool useOpenGL, const QSurfaceFormat& surfaceFormat) +{ + if (useOpenGL != m_features.testFlag(UseOpenGL) || surfaceFormat != m_surfaceFormat) + { + // In either case, we must reset OpenGL + releaseOpenGL(); + + m_features.setFlag(UseOpenGL, useOpenGL); + m_surfaceFormat = surfaceFormat; + + // We create new OpenGL renderer, but only if it hasn't failed (we do not try + // again to create new OpenGL renderer. + if (m_features.testFlag(UseOpenGL) && !m_features.testFlag(FailedOpenGL)) + { + initializeOpenGL(); + } + } +} + +QImage PDFRasterizer::render(const PDFPage* page, const PDFPrecompiledPage* compiledPage, QSize size, PDFRenderer::Features features) +{ + QImage image; + + QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, QRect(QPoint(0, 0), size)); + if (m_features.testFlag(UseOpenGL) && m_features.testFlag(ValidOpenGL)) + { + // We have valid OpenGL context, try to select it and possibly create framebuffer object + // for target image (to paint it using paint device). + Q_ASSERT(m_surface && m_context); + if (m_context->makeCurrent(m_surface)) + { + if (!m_fbo || m_fbo->width() != size.width() || m_fbo->height() != size.height()) + { + // Delete old framebuffer object + delete m_fbo; + + // Create a new framebuffer object + QOpenGLFramebufferObjectFormat format; + format.setSamples(m_surfaceFormat.samples()); + m_fbo = new QOpenGLFramebufferObject(size.width(), size.height(), format); + } + + Q_ASSERT(m_fbo); + if (m_fbo->isValid() && m_fbo->bind()) + { + // Now, we have bind the buffer. + { + QOpenGLPaintDevice device(size); + QPainter painter(&device); + painter.fillRect(QRect(QPoint(0, 0), size), Qt::white); + compiledPage->draw(&painter, page->getCropBox(), matrix, features); + } + + m_fbo->release(); + + image = m_fbo->toImage(); + } + else + { + m_features.setFlag(FailedOpenGL, true); + m_features.setFlag(ValidOpenGL, false); + } + + m_context->doneCurrent(); + } + } + + if (image.isNull()) + { + // If we can't use OpenGL, or user doesn't want to use OpenGL, then fallback + // to standard software rasterizer. + image = QImage(size, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::white); + + QPainter painter(&image); + compiledPage->draw(&painter, page->getCropBox(), matrix, features); + } + + // Convert the image into format Format_ARGB32_Premultiplied for fast drawing. + // If this format is used, then no image conversion is performed while drawing. + if (image.format() != QImage::Format_ARGB32_Premultiplied) + { + image.convertTo(QImage::Format_ARGB32_Premultiplied); + } + + return image; +} + +void PDFRasterizer::initializeOpenGL() +{ + Q_ASSERT(!m_surface); + Q_ASSERT(!m_context); + Q_ASSERT(!m_fbo); + + m_features.setFlag(ValidOpenGL, false); + m_features.setFlag(FailedOpenGL, false); + + // Create context + m_context = new QOpenGLContext(this); + m_context->setFormat(m_surfaceFormat); + if (!m_context->create()) + { + m_features.setFlag(FailedOpenGL, true); + + delete m_context; + m_context = nullptr; + } + + // Create surface + m_surface = new QOffscreenSurface(nullptr, this); + m_surface->setFormat(m_surfaceFormat); + m_surface->create(); + if (!m_surface->isValid()) + { + m_features.setFlag(FailedOpenGL, true); + + delete m_context; + delete m_surface; + + m_context = nullptr; + m_surface = nullptr; + } + + // Check, if we can make it current + if (m_context->makeCurrent(m_surface)) + { + m_features.setFlag(ValidOpenGL, true); + m_context->doneCurrent(); + } + else + { + m_features.setFlag(FailedOpenGL, true); + releaseOpenGL(); + } +} + +void PDFRasterizer::releaseOpenGL() +{ + if (m_surface) + { + Q_ASSERT(m_context); + Q_ASSERT(m_fbo); + + // Delete framebuffer + m_context->makeCurrent(m_surface); + delete m_fbo; + m_fbo = nullptr; + m_context->doneCurrent(); + + // Delete OpenGL context + delete m_context; + m_context = nullptr; + + // Delete surface + m_surface->destroy(); + delete m_surface; + m_surface = nullptr; + + // Set flag, that we do not have valid OpenGL + m_features.setFlag(ValidOpenGL, false); + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfrenderer.h b/PdfForQtLib/sources/pdfrenderer.h index 2125ebd..f07a147 100644 --- a/PdfForQtLib/sources/pdfrenderer.h +++ b/PdfForQtLib/sources/pdfrenderer.h @@ -22,7 +22,12 @@ #include "pdfexception.h" #include "pdfmeshqualitysettings.h" +#include + class QPainter; +class QOpenGLContext; +class QOffscreenSurface; +class QOpenGLFramebufferObject; namespace pdf { @@ -86,6 +91,59 @@ private: PDFMeshQualitySettings m_meshQualitySettings; }; +/// Renders PDF pages to bitmap images (QImage). It can use OpenGL for painting, +/// if it is enabled, if this is the case, offscreen rendering to framebuffer +/// is used. +class PDFRasterizer : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFRasterizer(QObject* parent); + ~PDFRasterizer(); + + /// Resets the renderer. This function must be called from main GUI thread, + /// it cannot be called from deferred threads, because it can create hidden + /// window (offscreen surface). + /// \param useOpenGL Use OpenGL for rendering + /// \param surfaceFormat Surface format to render + void reset(bool useOpenGL, const QSurfaceFormat& surfaceFormat); + + enum Feature + { + UseOpenGL = 0x0001, ///< Use OpenGL for rendering + ValidOpenGL = 0x0002, ///< OpenGL is initialized and valid + FailedOpenGL = 0x0004, ///< OpenGL creation has failed + }; + + Q_DECLARE_FLAGS(Features, Feature) + + /// Renders page to the image of given size. If some error occurs, then + /// empty image is returned. Warning: this function can modify this object, + /// so it is not const and is not thread safe. + /// \param page Page + /// \param compiledPage Compiled page contents + /// \param size Size of the target image + /// \param features Renderer features + QImage render(const PDFPage* page, + const PDFPrecompiledPage* compiledPage, + QSize size, + PDFRenderer::Features features); + +private: + void initializeOpenGL(); + void releaseOpenGL(); + + Features m_features; + QSurfaceFormat m_surfaceFormat; + QOffscreenSurface* m_surface; + QOpenGLContext* m_context; + QOpenGLFramebufferObject* m_fbo; +}; + } // namespace pdf Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)