mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Thumbnails rendering using OpenGL
This commit is contained in:
@ -396,7 +396,8 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
|
|||||||
m_horizontalScrollbar(nullptr),
|
m_horizontalScrollbar(nullptr),
|
||||||
m_verticalScrollbar(nullptr),
|
m_verticalScrollbar(nullptr),
|
||||||
m_features(PDFRenderer::getDefaultFeatures()),
|
m_features(PDFRenderer::getDefaultFeatures()),
|
||||||
m_compiler(new PDFAsynchronousPageCompiler(this))
|
m_compiler(new PDFAsynchronousPageCompiler(this)),
|
||||||
|
m_rasterizer(new PDFRasterizer(this))
|
||||||
{
|
{
|
||||||
m_controller = new PDFDrawSpaceController(this);
|
m_controller = new PDFDrawSpaceController(this);
|
||||||
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
|
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
|
||||||
@ -675,12 +676,18 @@ QImage PDFDrawWidgetProxy::drawThumbnailImage(PDFInteger pageIndex, int pixelSiz
|
|||||||
|
|
||||||
if (imageSize.isValid())
|
if (imageSize.isValid())
|
||||||
{
|
{
|
||||||
image = QImage(imageSize, QImage::Format_RGBA8888_Premultiplied);
|
const PDFPrecompiledPage* compiledPage = m_compiler->getPrecompiledCache(pageIndex, true);
|
||||||
image.fill(Qt::white);
|
if (compiledPage && compiledPage->isValid())
|
||||||
|
{
|
||||||
|
// Rasterize the image.
|
||||||
|
image = m_rasterizer->render(page, compiledPage, imageSize, m_features);
|
||||||
|
}
|
||||||
|
|
||||||
QPainter painter(&image);
|
if (image.isNull())
|
||||||
PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity(), m_features, m_meshQualitySettings);
|
{
|
||||||
renderer.render(&painter, QRect(QPoint(0, 0), imageSize), pageIndex);
|
image = QImage(imageSize, QImage::Format_RGBA8888_Premultiplied);
|
||||||
|
image.fill(Qt::white);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -945,6 +952,11 @@ bool PDFDrawWidgetProxy::isBlockMode() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat)
|
||||||
|
{
|
||||||
|
m_rasterizer->reset(useOpenGL, surfaceFormat);
|
||||||
|
}
|
||||||
|
|
||||||
void PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged(int value)
|
void PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged(int value)
|
||||||
{
|
{
|
||||||
if (!m_updateDisabled && !m_horizontalScrollbar->isHidden())
|
if (!m_updateDisabled && !m_horizontalScrollbar->isHidden())
|
||||||
|
@ -254,6 +254,12 @@ public:
|
|||||||
/// or continuous mode (single block with continuous list of separated pages).
|
/// or continuous mode (single block with continuous list of separated pages).
|
||||||
bool isBlockMode() const;
|
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;
|
static constexpr PDFReal ZOOM_STEP = 1.2;
|
||||||
|
|
||||||
const PDFDocument* getDocument() const { return m_controller->getDocument(); }
|
const PDFDocument* getDocument() const { return m_controller->getDocument(); }
|
||||||
@ -393,6 +399,9 @@ private:
|
|||||||
|
|
||||||
/// Page compiler
|
/// Page compiler
|
||||||
PDFAsynchronousPageCompiler* m_compiler;
|
PDFAsynchronousPageCompiler* m_compiler;
|
||||||
|
|
||||||
|
/// Page image rasterizer for thumbnails
|
||||||
|
PDFRasterizer* m_rasterizer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -52,6 +52,7 @@ PDFWidget::PDFWidget(RendererEngine engine, int samplesCount, QWidget* parent) :
|
|||||||
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
|
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
|
||||||
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
|
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
|
||||||
connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged);
|
connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged);
|
||||||
|
updateRendererImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFWidget::~PDFWidget()
|
PDFWidget::~PDFWidget()
|
||||||
@ -92,6 +93,7 @@ void PDFWidget::updateRenderer(RendererEngine engine, int samplesCount)
|
|||||||
openglDrawWidget->setFormat(format);
|
openglDrawWidget->setFormat(format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateRendererImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
int PDFWidget::getPageRenderingErrorCount() const
|
int PDFWidget::getPageRenderingErrorCount() const
|
||||||
@ -104,6 +106,12 @@ int PDFWidget::getPageRenderingErrorCount() const
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFWidget::updateRendererImpl()
|
||||||
|
{
|
||||||
|
PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast<PDFOpenGLDrawWidget*>(m_drawWidget->getWidget());
|
||||||
|
m_proxy->updateRenderer(openglDrawWidget != nullptr, openglDrawWidget ? openglDrawWidget->format() : QSurfaceFormat::defaultFormat());
|
||||||
|
}
|
||||||
|
|
||||||
void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors)
|
void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors)
|
||||||
{
|
{
|
||||||
// Empty list of error should not be reported!
|
// Empty list of error should not be reported!
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
#ifndef PDFDRAWWIDGET_H
|
#ifndef PDFDRAWWIDGET_H
|
||||||
#define PDFDRAWWIDGET_H
|
#define PDFDRAWWIDGET_H
|
||||||
|
|
||||||
@ -79,6 +78,7 @@ signals:
|
|||||||
void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount);
|
void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateRendererImpl();
|
||||||
void onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
|
void onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
|
||||||
void onPageImageChanged(bool all, const std::vector<PDFInteger>& pages);
|
void onPageImageChanged(bool all, const std::vector<PDFInteger>& pages);
|
||||||
|
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
#include "pdfdocument.h"
|
#include "pdfdocument.h"
|
||||||
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QOffscreenSurface>
|
||||||
|
#include <QOpenGLPaintDevice>
|
||||||
|
#include <QOpenGLFramebufferObject>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -143,4 +147,183 @@ void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex)
|
|||||||
timer.invalidate();
|
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
|
} // namespace pdf
|
||||||
|
@ -22,7 +22,12 @@
|
|||||||
#include "pdfexception.h"
|
#include "pdfexception.h"
|
||||||
#include "pdfmeshqualitysettings.h"
|
#include "pdfmeshqualitysettings.h"
|
||||||
|
|
||||||
|
#include <QSurfaceFormat>
|
||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
|
class QOpenGLContext;
|
||||||
|
class QOffscreenSurface;
|
||||||
|
class QOpenGLFramebufferObject;
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -86,6 +91,59 @@ private:
|
|||||||
PDFMeshQualitySettings m_meshQualitySettings;
|
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
|
} // namespace pdf
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)
|
||||||
|
Reference in New Issue
Block a user