mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Rendering pdf to images (first part)
This commit is contained in:
@ -393,7 +393,8 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
|
|||||||
m_compiler(new PDFAsynchronousPageCompiler(this)),
|
m_compiler(new PDFAsynchronousPageCompiler(this)),
|
||||||
m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)),
|
m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)),
|
||||||
m_rasterizer(new PDFRasterizer(this)),
|
m_rasterizer(new PDFRasterizer(this)),
|
||||||
m_progress(nullptr)
|
m_progress(nullptr),
|
||||||
|
m_useOpenGL(false)
|
||||||
{
|
{
|
||||||
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);
|
||||||
@ -1117,6 +1118,8 @@ bool PDFDrawWidgetProxy::isBlockMode() const
|
|||||||
|
|
||||||
void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat)
|
void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat)
|
||||||
{
|
{
|
||||||
|
m_useOpenGL = useOpenGL;
|
||||||
|
m_surfaceFormat = surfaceFormat;
|
||||||
m_rasterizer->reset(useOpenGL, surfaceFormat);
|
m_rasterizer->reset(useOpenGL, surfaceFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,6 +320,8 @@ public:
|
|||||||
void setProgress(PDFProgress* progress) { m_progress = progress; }
|
void setProgress(PDFProgress* progress) { m_progress = progress; }
|
||||||
PDFAsynchronousTextLayoutCompiler* getTextLayoutCompiler() const { return m_textLayoutCompiler; }
|
PDFAsynchronousTextLayoutCompiler* getTextLayoutCompiler() const { return m_textLayoutCompiler; }
|
||||||
PDFWidget* getWidget() const { return m_widget; }
|
PDFWidget* getWidget() const { return m_widget; }
|
||||||
|
bool isUsingOpenGL() const { return m_useOpenGL; }
|
||||||
|
const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; }
|
||||||
|
|
||||||
void setFeatures(PDFRenderer::Features features);
|
void setFeatures(PDFRenderer::Features features);
|
||||||
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
||||||
@ -470,6 +472,12 @@ private:
|
|||||||
|
|
||||||
/// Additional drawing interfaces
|
/// Additional drawing interfaces
|
||||||
std::set<IDocumentDrawInterface*> m_drawInterfaces;
|
std::set<IDocumentDrawInterface*> m_drawInterfaces;
|
||||||
|
|
||||||
|
/// Use OpenGL for rendering?
|
||||||
|
bool m_useOpenGL;
|
||||||
|
|
||||||
|
/// Surface format for OpenGL
|
||||||
|
QSurfaceFormat m_surfaceFormat;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -44,7 +44,8 @@ enum RenderErrorType
|
|||||||
Error,
|
Error,
|
||||||
Warning,
|
Warning,
|
||||||
NotImplemented,
|
NotImplemented,
|
||||||
NotSupported
|
NotSupported,
|
||||||
|
Information
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PDFRenderError
|
struct PDFRenderError
|
||||||
|
@ -18,7 +18,10 @@
|
|||||||
#include "pdfrenderer.h"
|
#include "pdfrenderer.h"
|
||||||
#include "pdfpainter.h"
|
#include "pdfpainter.h"
|
||||||
#include "pdfdocument.h"
|
#include "pdfdocument.h"
|
||||||
|
#include "pdfexecutionpolicy.h"
|
||||||
|
#include "pdfprogress.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
#include <QOffscreenSurface>
|
#include <QOffscreenSurface>
|
||||||
@ -336,10 +339,120 @@ void PDFRasterizer::releaseOpenGL()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFRasterizer* PDFRasterizerPool::acquire()
|
||||||
|
{
|
||||||
|
m_semaphore.acquire();
|
||||||
|
|
||||||
|
QMutexLocker guard(&m_mutex);
|
||||||
|
Q_ASSERT(!m_rasterizers.empty());
|
||||||
|
PDFRasterizer* rasterizer = m_rasterizers.back();
|
||||||
|
m_rasterizers.pop_back();
|
||||||
|
return rasterizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFRasterizerPool::release(pdf::PDFRasterizer* rasterizer)
|
||||||
|
{
|
||||||
|
QMutexLocker guard(&m_mutex);
|
||||||
|
Q_ASSERT(std::find(m_rasterizers.cbegin(), m_rasterizers.cend(), rasterizer) == m_rasterizers.cend());
|
||||||
|
m_rasterizers.push_back(rasterizer);
|
||||||
|
|
||||||
|
// Jakub Melka: we must release it at the end, to ensure rasterizer is in the array before
|
||||||
|
// semaphore is released, to avoid race condition.
|
||||||
|
m_semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFRasterizerPool::render(const std::vector<PDFInteger>& pageIndices,
|
||||||
|
const PDFRasterizerPool::PageImageSizeGetter& imageSizeGetter,
|
||||||
|
const PDFRasterizerPool::ProcessImageMethod& processImage,
|
||||||
|
PDFProgress* progress)
|
||||||
|
{
|
||||||
|
if (pageIndices.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(imageSizeGetter);
|
||||||
|
Q_ASSERT(processImage);
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Start at %1...").arg(QTime::currentTime().toString(Qt::TextDate))));
|
||||||
|
|
||||||
|
if (progress)
|
||||||
|
{
|
||||||
|
ProgressStartupInfo info;
|
||||||
|
info.showDialog = true;
|
||||||
|
info.text = PDFTranslationContext::tr("Rendering document into images.");
|
||||||
|
progress->start(pageIndices.size(), qMove(info));
|
||||||
|
}
|
||||||
|
auto processPage = [this, progress, &imageSizeGetter, &processImage](const PDFInteger pageIndex)
|
||||||
|
{
|
||||||
|
const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
|
||||||
|
|
||||||
|
if (!page)
|
||||||
|
{
|
||||||
|
if (progress)
|
||||||
|
{
|
||||||
|
progress->step();
|
||||||
|
}
|
||||||
|
emit renderError(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 not found.").arg(pageIndex)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precompile the page
|
||||||
|
PDFPrecompiledPage precompiledPage;
|
||||||
|
PDFRenderer renderer(m_document, m_fontCache, m_cms, m_optionalContentActivity, m_features, m_meshQualitySettings);
|
||||||
|
renderer.compile(&precompiledPage, pageIndex);
|
||||||
|
|
||||||
|
for (const PDFRenderError error : precompiledPage.getErrors())
|
||||||
|
{
|
||||||
|
emit renderError(PDFRenderError(error.type, PDFTranslationContext::tr("Page %1: %2").arg(pageIndex + 1).arg(error.message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render page to image
|
||||||
|
PDFRasterizer* rasterizer = acquire();
|
||||||
|
QImage image = rasterizer->render(page, &precompiledPage, imageSizeGetter(page), m_features);
|
||||||
|
release(rasterizer);
|
||||||
|
|
||||||
|
// Now, process the image
|
||||||
|
processImage(pageIndex, qMove(image));
|
||||||
|
|
||||||
|
if (progress)
|
||||||
|
{
|
||||||
|
progress->step();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageIndices.cbegin(), pageIndices.cend(), processPage);
|
||||||
|
|
||||||
|
if (progress)
|
||||||
|
{
|
||||||
|
progress->finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Finished at %1...").arg(QTime::currentTime().toString(Qt::TextDate))));
|
||||||
|
emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("%1 miliseconds elapsed to render %2 pages...").arg(timer.nsecsElapsed() / 1000000).arg(pageIndices.size())));
|
||||||
|
}
|
||||||
|
|
||||||
|
int PDFRasterizerPool::getDefaultRasterizerCount()
|
||||||
|
{
|
||||||
|
int hint = QThread::idealThreadCount() / 2;
|
||||||
|
return qBound(1, hint, 16);
|
||||||
|
}
|
||||||
|
|
||||||
PDFImageWriterSettings::PDFImageWriterSettings()
|
PDFImageWriterSettings::PDFImageWriterSettings()
|
||||||
{
|
{
|
||||||
m_formats = QImageWriter::supportedImageFormats();
|
m_formats = QImageWriter::supportedImageFormats();
|
||||||
selectFormat(m_formats.front());
|
|
||||||
|
constexpr const char* DEFAULT_FORMAT = "png";
|
||||||
|
if (m_formats.count(DEFAULT_FORMAT))
|
||||||
|
{
|
||||||
|
selectFormat(DEFAULT_FORMAT);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectFormat(m_formats.front());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDFImageWriterSettings::selectFormat(const QByteArray& format)
|
void PDFImageWriterSettings::selectFormat(const QByteArray& format)
|
||||||
@ -460,7 +573,8 @@ void PDFImageWriterSettings::setCurrentSubtype(const QByteArray& currentSubtype)
|
|||||||
m_currentSubtype = currentSubtype;
|
m_currentSubtype = currentSubtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFPageImageExportSettings::PDFPageImageExportSettings()
|
PDFPageImageExportSettings::PDFPageImageExportSettings(const PDFDocument* document) :
|
||||||
|
m_document(document)
|
||||||
{
|
{
|
||||||
m_fileTemplate = PDFTranslationContext::tr("Image_%");
|
m_fileTemplate = PDFTranslationContext::tr("Image_%");
|
||||||
}
|
}
|
||||||
@ -535,4 +649,174 @@ void PDFPageImageExportSettings::setPixelResolution(int pixelResolution)
|
|||||||
m_pixelResolution = pixelResolution;
|
m_pixelResolution = pixelResolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PDFPageImageExportSettings::validate(QString* errorMessagePtr)
|
||||||
|
{
|
||||||
|
QString dummy;
|
||||||
|
QString& errorMessage = errorMessagePtr ? *errorMessagePtr : dummy;
|
||||||
|
|
||||||
|
if (m_directory.isEmpty())
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("Target directory is empty.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check, if target directory exists
|
||||||
|
QDir directory(m_directory);
|
||||||
|
if (!directory.exists())
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("Target directory '%1' doesn't exist.").arg(m_directory);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fileTemplate.isEmpty())
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("File template is empty.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_fileTemplate.contains("%"))
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("File template must contain character '%' for page number.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check page selection
|
||||||
|
if (m_pageSelectionMode == PageSelectionMode::Selection)
|
||||||
|
{
|
||||||
|
std::vector<PDFInteger> pages = getPages();
|
||||||
|
if (pages.empty())
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("Page list is invalid. It should have form such as '1-12,17,24,27-29'.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pages.back() >= PDFInteger(m_document->getCatalog()->getPageCount()))
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("Page list contains page, which is not in the document (%1).").arg(pages.back());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_resolutionMode == ResolutionMode::DPI && (m_dpiResolution < getMinDPIResolution() || m_dpiResolution > getMaxDPIResolution()))
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("DPI resolution should be in range %1 to %2.").arg(getMinDPIResolution()).arg(getMaxDPIResolution());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_resolutionMode == ResolutionMode::Pixels && (m_pixelResolution < getMinPixelResolution() || m_pixelResolution > getMaxPixelResolution()))
|
||||||
|
{
|
||||||
|
errorMessage = PDFTranslationContext::tr("Pixel resolution should be in range %1 to %2.").arg(getMinPixelResolution()).arg(getMaxPixelResolution());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PDFInteger> PDFPageImageExportSettings::getPages() const
|
||||||
|
{
|
||||||
|
std::vector<PDFInteger> result;
|
||||||
|
|
||||||
|
switch (m_pageSelectionMode)
|
||||||
|
{
|
||||||
|
case PageSelectionMode::All:
|
||||||
|
{
|
||||||
|
result.resize(m_document->getCatalog()->getPageCount(), 0);
|
||||||
|
std::iota(result.begin(), result.end(), 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PageSelectionMode::Selection:
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
QStringList parts = m_pageSelection.split(QChar(','), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||||
|
for (const QString& part : parts)
|
||||||
|
{
|
||||||
|
QStringList numbers = part.split(QChar('-'), Qt::KeepEmptyParts, Qt::CaseSensitive);
|
||||||
|
switch (numbers.size())
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
const QString& numberString = numbers.front();
|
||||||
|
result.push_back(numberString.toLongLong(&ok));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
bool ok1 = false;
|
||||||
|
bool ok2 = false;
|
||||||
|
const QString& lowString = numbers.front();
|
||||||
|
const QString& highString = numbers.back();
|
||||||
|
const PDFInteger low = lowString.toLongLong(&ok1);
|
||||||
|
const PDFInteger high = highString.toLongLong(&ok2);
|
||||||
|
ok = ok1 && ok2 && low <= high;
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
const PDFInteger count = high - low + 1;
|
||||||
|
result.resize(result.size() + count, 0);
|
||||||
|
std::iota(std::prev(result.end(), count), result.end(), low);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If error is detected, do not continue in parsing
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must remove duplicate pages
|
||||||
|
qSort(result);
|
||||||
|
result.erase(std::unique(result.begin(), result.end()), result.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
result.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFRasterizerPool::PDFRasterizerPool(const PDFDocument* document,
|
||||||
|
const PDFFontCache* fontCache,
|
||||||
|
const PDFCMS* cms,
|
||||||
|
const PDFOptionalContentActivity* optionalContentActivity,
|
||||||
|
PDFRenderer::Features features,
|
||||||
|
const PDFMeshQualitySettings& meshQualitySettings,
|
||||||
|
int rasterizerCount,
|
||||||
|
bool useOpenGL,
|
||||||
|
const QSurfaceFormat& surfaceFormat,
|
||||||
|
QObject* parent) :
|
||||||
|
BaseClass(parent),
|
||||||
|
m_document(document),
|
||||||
|
m_fontCache(fontCache),
|
||||||
|
m_cms(cms),
|
||||||
|
m_optionalContentActivity(optionalContentActivity),
|
||||||
|
m_features(features),
|
||||||
|
m_meshQualitySettings(meshQualitySettings),
|
||||||
|
m_semaphore(rasterizerCount)
|
||||||
|
{
|
||||||
|
m_rasterizers.reserve(rasterizerCount);
|
||||||
|
for (int i = 0; i < rasterizerCount; ++i)
|
||||||
|
{
|
||||||
|
m_rasterizers.push_back(new PDFRasterizer(this));
|
||||||
|
m_rasterizers.back()->reset(useOpenGL, surfaceFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2019-2020 Jakub Melka
|
// Copyright (C) 2019-2020 Jakub Melka
|
||||||
//
|
//
|
||||||
// This file is part of PdfForQt.
|
// This file is part of PdfForQt.
|
||||||
//
|
//
|
||||||
@ -22,6 +22,8 @@
|
|||||||
#include "pdfexception.h"
|
#include "pdfexception.h"
|
||||||
#include "pdfmeshqualitysettings.h"
|
#include "pdfmeshqualitysettings.h"
|
||||||
|
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QSemaphore>
|
||||||
#include <QImageWriter>
|
#include <QImageWriter>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
|
|
||||||
@ -33,6 +35,7 @@ class QOpenGLFramebufferObject;
|
|||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
class PDFCMS;
|
class PDFCMS;
|
||||||
|
class PDFProgress;
|
||||||
class PDFFontCache;
|
class PDFFontCache;
|
||||||
class PDFPrecompiledPage;
|
class PDFPrecompiledPage;
|
||||||
class PDFOptionalContentActivity;
|
class PDFOptionalContentActivity;
|
||||||
@ -105,6 +108,7 @@ private:
|
|||||||
/// Renders PDF pages to bitmap images (QImage). It can use OpenGL for painting,
|
/// 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
|
/// if it is enabled, if this is the case, offscreen rendering to framebuffer
|
||||||
/// is used.
|
/// is used.
|
||||||
|
/// \note Construct this object only in main GUI thread
|
||||||
class PDFRasterizer : public QObject
|
class PDFRasterizer : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -114,7 +118,7 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PDFRasterizer(QObject* parent);
|
explicit PDFRasterizer(QObject* parent);
|
||||||
~PDFRasterizer();
|
virtual ~PDFRasterizer() override;
|
||||||
|
|
||||||
/// Resets the renderer. This function must be called from main GUI thread,
|
/// Resets the renderer. This function must be called from main GUI thread,
|
||||||
/// it cannot be called from deferred threads, because it can create hidden
|
/// it cannot be called from deferred threads, because it can create hidden
|
||||||
@ -155,6 +159,83 @@ private:
|
|||||||
QOpenGLFramebufferObject* m_fbo;
|
QOpenGLFramebufferObject* m_fbo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Pool of page image renderers. It can use predefined number of renderers to
|
||||||
|
/// render page images asynchronously. You can use this object in two ways -
|
||||||
|
/// first one is as standard object pool, second one is to directly render
|
||||||
|
/// page images asynchronously.
|
||||||
|
class PDFFORQTLIBSHARED_EXPORT PDFRasterizerPool : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
using BaseClass = QObject;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using PageImageSizeGetter = std::function<QSize(const PDFPage*)>;
|
||||||
|
using ProcessImageMethod = std::function<void(PDFInteger, QImage&&)>;
|
||||||
|
|
||||||
|
/// Creates new rasterizer pool
|
||||||
|
/// \param document Document
|
||||||
|
/// \param fontCache Font cache
|
||||||
|
/// \param cms Color management system
|
||||||
|
/// \param optionalContentActivity Optional content activity
|
||||||
|
/// \param features Renderer features
|
||||||
|
/// \param meshQualitySettings Mesh quality settings
|
||||||
|
/// \param rasterizerCount Number of rasterizers
|
||||||
|
/// \param useOpenGL Use OpenGL for rendering?
|
||||||
|
/// \param surfaceFormat Surface format
|
||||||
|
/// \param parent Parent object
|
||||||
|
explicit PDFRasterizerPool(const PDFDocument* document,
|
||||||
|
const PDFFontCache* fontCache,
|
||||||
|
const PDFCMS* cms,
|
||||||
|
const PDFOptionalContentActivity* optionalContentActivity,
|
||||||
|
PDFRenderer::Features features,
|
||||||
|
const PDFMeshQualitySettings& meshQualitySettings,
|
||||||
|
int rasterizerCount,
|
||||||
|
bool useOpenGL,
|
||||||
|
const QSurfaceFormat& surfaceFormat,
|
||||||
|
QObject* parent);
|
||||||
|
|
||||||
|
/// Acquire rasterizer. This function is thread safe.
|
||||||
|
PDFRasterizer* acquire();
|
||||||
|
|
||||||
|
/// Return back (release) rasterizer into rasterizer pool
|
||||||
|
/// This function is thread safe.
|
||||||
|
/// \param rasterizer Rasterizer
|
||||||
|
void release(PDFRasterizer* rasterizer);
|
||||||
|
|
||||||
|
/// Renders pages asynchronously to images, using given page indices,
|
||||||
|
/// function which returns rendered size and process image function,
|
||||||
|
/// which processes rendered images.
|
||||||
|
/// \param pageIndices Page indices for rendered pages
|
||||||
|
/// \param imageSizeGetter Getter, which computes image size from page index
|
||||||
|
/// \param processImage Method, which processes rendered page images
|
||||||
|
/// \param progress Progress indicator
|
||||||
|
void render(const std::vector<PDFInteger>& pageIndices,
|
||||||
|
const PageImageSizeGetter& imageSizeGetter,
|
||||||
|
const ProcessImageMethod& processImage,
|
||||||
|
PDFProgress* progress);
|
||||||
|
|
||||||
|
/// Returns default rasterizer count
|
||||||
|
static int getDefaultRasterizerCount();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void renderError(PDFRenderError error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PDFDocument* m_document;
|
||||||
|
const PDFFontCache* m_fontCache;
|
||||||
|
const PDFCMS* m_cms;
|
||||||
|
const PDFOptionalContentActivity* m_optionalContentActivity;
|
||||||
|
PDFRenderer::Features m_features;
|
||||||
|
const PDFMeshQualitySettings& m_meshQualitySettings;
|
||||||
|
|
||||||
|
QSemaphore m_semaphore;
|
||||||
|
QMutex m_mutex;
|
||||||
|
std::vector<PDFRasterizer*> m_rasterizers;
|
||||||
|
};
|
||||||
|
|
||||||
/// Settings object for image writer
|
/// Settings object for image writer
|
||||||
class PDFFORQTLIBSHARED_EXPORT PDFImageWriterSettings
|
class PDFFORQTLIBSHARED_EXPORT PDFImageWriterSettings
|
||||||
{
|
{
|
||||||
@ -211,7 +292,7 @@ private:
|
|||||||
class PDFFORQTLIBSHARED_EXPORT PDFPageImageExportSettings
|
class PDFFORQTLIBSHARED_EXPORT PDFPageImageExportSettings
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit PDFPageImageExportSettings();
|
explicit PDFPageImageExportSettings(const PDFDocument* document);
|
||||||
|
|
||||||
enum class PageSelectionMode
|
enum class PageSelectionMode
|
||||||
{
|
{
|
||||||
@ -246,7 +327,20 @@ public:
|
|||||||
int getPixelResolution() const;
|
int getPixelResolution() const;
|
||||||
void setPixelResolution(int pixelResolution);
|
void setPixelResolution(int pixelResolution);
|
||||||
|
|
||||||
|
/// Validates the settings, if they can be used for image generation
|
||||||
|
bool validate(QString* errorMessagePtr);
|
||||||
|
|
||||||
|
/// Returns list of selected pages
|
||||||
|
std::vector<PDFInteger> getPages() const;
|
||||||
|
|
||||||
|
static constexpr int getMinDPIResolution() { return 72; }
|
||||||
|
static constexpr int getMaxDPIResolution() { return 6000; }
|
||||||
|
|
||||||
|
static constexpr int getMinPixelResolution() { return 100; }
|
||||||
|
static constexpr int getMaxPixelResolution() { return 16384; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const PDFDocument* m_document;
|
||||||
ResolutionMode m_resolutionMode = ResolutionMode::DPI;
|
ResolutionMode m_resolutionMode = ResolutionMode::DPI;
|
||||||
PageSelectionMode m_pageSelectionMode = PageSelectionMode::All;
|
PageSelectionMode m_pageSelectionMode = PageSelectionMode::All;
|
||||||
QString m_directory;
|
QString m_directory;
|
||||||
|
@ -25,6 +25,7 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
|
QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
|
||||||
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||||
|
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
|
||||||
QApplication application(argc, argv);
|
QApplication application(argc, argv);
|
||||||
|
|
||||||
QCoreApplication::setOrganizationName("MelkaJ");
|
QCoreApplication::setOrganizationName("MelkaJ");
|
||||||
|
@ -18,17 +18,29 @@
|
|||||||
#include "pdfrendertoimagesdialog.h"
|
#include "pdfrendertoimagesdialog.h"
|
||||||
#include "ui_pdfrendertoimagesdialog.h"
|
#include "ui_pdfrendertoimagesdialog.h"
|
||||||
|
|
||||||
|
#include "pdfcms.h"
|
||||||
#include "pdfutils.h"
|
#include "pdfutils.h"
|
||||||
#include "pdfwidgetutils.h"
|
#include "pdfwidgetutils.h"
|
||||||
|
#include "pdfoptionalcontent.h"
|
||||||
|
#include "pdfdrawspacecontroller.h"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
namespace pdfviewer
|
namespace pdfviewer
|
||||||
{
|
{
|
||||||
|
|
||||||
PDFRenderToImagesDialog::PDFRenderToImagesDialog(QWidget* parent) :
|
PDFRenderToImagesDialog::PDFRenderToImagesDialog(const pdf::PDFDocument* document,
|
||||||
|
pdf::PDFDrawWidgetProxy* proxy,
|
||||||
|
pdf::PDFProgress* progress,
|
||||||
|
QWidget* parent) :
|
||||||
QDialog(parent),
|
QDialog(parent),
|
||||||
ui(new Ui::PDFRenderToImagesDialog),
|
ui(new Ui::PDFRenderToImagesDialog),
|
||||||
|
m_document(document),
|
||||||
|
m_proxy(proxy),
|
||||||
|
m_progress(progress),
|
||||||
|
m_imageExportSettings(document),
|
||||||
m_isLoadingData(false)
|
m_isLoadingData(false)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
@ -56,6 +68,9 @@ PDFRenderToImagesDialog::PDFRenderToImagesDialog(QWidget* parent) :
|
|||||||
connect(ui->optimizedWriteCheckBox, &QCheckBox::clicked, this, &PDFRenderToImagesDialog::onOptimizedWriteChanged);
|
connect(ui->optimizedWriteCheckBox, &QCheckBox::clicked, this, &PDFRenderToImagesDialog::onOptimizedWriteChanged);
|
||||||
connect(ui->progressiveScanWriteCheckBox, &QCheckBox::clicked, this, &PDFRenderToImagesDialog::onProgressiveScanWriteChanged);
|
connect(ui->progressiveScanWriteCheckBox, &QCheckBox::clicked, this, &PDFRenderToImagesDialog::onProgressiveScanWriteChanged);
|
||||||
|
|
||||||
|
ui->resolutionDPIEdit->setRange(pdf::PDFPageImageExportSettings::getMinDPIResolution(), pdf::PDFPageImageExportSettings::getMaxDPIResolution());
|
||||||
|
ui->resolutionPixelsEdit->setRange(pdf::PDFPageImageExportSettings::getMinPixelResolution(), pdf::PDFPageImageExportSettings::getMaxPixelResolution());
|
||||||
|
|
||||||
loadImageWriterSettings();
|
loadImageWriterSettings();
|
||||||
loadImageExportSettings();
|
loadImageExportSettings();
|
||||||
|
|
||||||
@ -219,6 +234,11 @@ void PDFRenderToImagesDialog::onProgressiveScanWriteChanged(bool value)
|
|||||||
m_imageWriterSettings.setProgressiveScanWrite(value);
|
m_imageWriterSettings.setProgressiveScanWrite(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFRenderToImagesDialog::onRenderError(pdf::PDFRenderError error)
|
||||||
|
{
|
||||||
|
ui->progressMessagesEdit->setPlainText(QString("%1\n%2").arg(ui->progressMessagesEdit->toPlainText()).arg(error.message));
|
||||||
|
}
|
||||||
|
|
||||||
void PDFRenderToImagesDialog::on_selectDirectoryButton_clicked()
|
void PDFRenderToImagesDialog::on_selectDirectoryButton_clicked()
|
||||||
{
|
{
|
||||||
QString directory = QFileDialog::getExistingDirectory(this, tr("Select output directory"), ui->directoryEdit->text());
|
QString directory = QFileDialog::getExistingDirectory(this, tr("Select output directory"), ui->directoryEdit->text());
|
||||||
@ -228,6 +248,67 @@ void PDFRenderToImagesDialog::on_selectDirectoryButton_clicked()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFRenderToImagesDialog::on_buttonBox_clicked(QAbstractButton* button)
|
||||||
|
{
|
||||||
|
if (button == ui->buttonBox->button(QDialogButtonBox::Apply))
|
||||||
|
{
|
||||||
|
QString message;
|
||||||
|
if (m_imageExportSettings.validate(&message))
|
||||||
|
{
|
||||||
|
// We are ready to render the document
|
||||||
|
std::vector<pdf::PDFInteger> pageIndices = m_imageExportSettings.getPages();
|
||||||
|
|
||||||
|
pdf::PDFOptionalContentActivity optionalContentActivity(m_document, pdf::OCUsage::Export, nullptr);
|
||||||
|
pdf::PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS();
|
||||||
|
pdf::PDFRasterizerPool rasterizerPool(m_document, m_proxy->getFontCache(), cms.data(),
|
||||||
|
&optionalContentActivity, m_proxy->getFeatures(), m_proxy->getMeshQualitySettings(),
|
||||||
|
pdf::PDFRasterizerPool::getDefaultRasterizerCount(), m_proxy->isUsingOpenGL(), m_proxy->getSurfaceFormat(), this);
|
||||||
|
connect(&rasterizerPool, &pdf::PDFRasterizerPool::renderError, this, &PDFRenderToImagesDialog::onRenderError);
|
||||||
|
|
||||||
|
auto imageSizeGetter = [this](const pdf::PDFPage* page) -> QSize
|
||||||
|
{
|
||||||
|
Q_ASSERT(page);
|
||||||
|
|
||||||
|
switch (m_imageExportSettings.getResolutionMode())
|
||||||
|
{
|
||||||
|
case pdf::PDFPageImageExportSettings::ResolutionMode::DPI:
|
||||||
|
{
|
||||||
|
QSizeF size = page->getRotatedMediaBox().size() * m_imageExportSettings.getDpiResolution();
|
||||||
|
return size.toSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
case pdf::PDFPageImageExportSettings::ResolutionMode::Pixels:
|
||||||
|
{
|
||||||
|
int pixelResolution = m_imageExportSettings.getPixelResolution();
|
||||||
|
QSizeF size = page->getRotatedMediaBox().size().scaled(pixelResolution, pixelResolution, Qt::KeepAspectRatio);
|
||||||
|
return size.toSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
Q_ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto processImage = [](const pdf::PDFInteger pageIndex, QImage&& image)
|
||||||
|
{
|
||||||
|
Q_UNUSED(pageIndex);
|
||||||
|
Q_UNUSED(image);
|
||||||
|
};
|
||||||
|
|
||||||
|
setEnabled(false);
|
||||||
|
rasterizerPool.render(pageIndices, imageSizeGetter, processImage, m_progress);
|
||||||
|
setEnabled(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Error"), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdfviewer
|
} // namespace pdfviewer
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,11 +22,19 @@
|
|||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QAbstractButton;
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class PDFRenderToImagesDialog;
|
class PDFRenderToImagesDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace pdf
|
||||||
|
{
|
||||||
|
class PDFProgress;
|
||||||
|
class PDFDrawWidgetProxy;
|
||||||
|
}
|
||||||
|
|
||||||
namespace pdfviewer
|
namespace pdfviewer
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -35,11 +43,15 @@ class PDFRenderToImagesDialog : public QDialog
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PDFRenderToImagesDialog(QWidget* parent);
|
explicit PDFRenderToImagesDialog(const pdf::PDFDocument* document,
|
||||||
|
pdf::PDFDrawWidgetProxy* proxy,
|
||||||
|
pdf::PDFProgress* progress,
|
||||||
|
QWidget* parent);
|
||||||
virtual ~PDFRenderToImagesDialog() override;
|
virtual ~PDFRenderToImagesDialog() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_selectDirectoryButton_clicked();
|
void on_selectDirectoryButton_clicked();
|
||||||
|
void on_buttonBox_clicked(QAbstractButton* button);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Loads image writer settings to the ui
|
/// Loads image writer settings to the ui
|
||||||
@ -62,8 +74,12 @@ private:
|
|||||||
void onGammaChanged(double value);
|
void onGammaChanged(double value);
|
||||||
void onOptimizedWriteChanged(bool value);
|
void onOptimizedWriteChanged(bool value);
|
||||||
void onProgressiveScanWriteChanged(bool value);
|
void onProgressiveScanWriteChanged(bool value);
|
||||||
|
void onRenderError(pdf::PDFRenderError error);
|
||||||
|
|
||||||
Ui::PDFRenderToImagesDialog* ui;
|
Ui::PDFRenderToImagesDialog* ui;
|
||||||
|
const pdf::PDFDocument* m_document;
|
||||||
|
pdf::PDFDrawWidgetProxy* m_proxy;
|
||||||
|
pdf::PDFProgress* m_progress;
|
||||||
pdf::PDFImageWriterSettings m_imageWriterSettings;
|
pdf::PDFImageWriterSettings m_imageWriterSettings;
|
||||||
pdf::PDFPageImageExportSettings m_imageExportSettings;
|
pdf::PDFPageImageExportSettings m_imageExportSettings;
|
||||||
bool m_isLoadingData;
|
bool m_isLoadingData;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>690</width>
|
<width>690</width>
|
||||||
<height>593</height>
|
<height>602</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -234,7 +234,14 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="plainTextEdit"/>
|
<widget class="QPlainTextEdit" name="progressMessagesEdit">
|
||||||
|
<property name="undoRedoEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
@ -1279,11 +1279,8 @@ void PDFViewerMainWindow::on_actionPrint_triggered()
|
|||||||
|
|
||||||
void PDFViewerMainWindow::on_actionRender_to_Images_triggered()
|
void PDFViewerMainWindow::on_actionRender_to_Images_triggered()
|
||||||
{
|
{
|
||||||
PDFRenderToImagesDialog dialog(this);
|
PDFRenderToImagesDialog dialog(m_pdfDocument.data(), m_pdfWidget->getDrawWidgetProxy(), m_progress, this);
|
||||||
if (dialog.exec() == QDialog::Accepted)
|
dialog.exec();
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace pdfviewer
|
} // namespace pdfviewer
|
||||||
|
Reference in New Issue
Block a user