Rendering tool + benchmarking tool

This commit is contained in:
Jakub Melka 2020-10-28 16:35:16 +01:00
parent 428e5dc2ad
commit 3c512d0e98
10 changed files with 423 additions and 44 deletions

View File

@ -374,7 +374,7 @@ public:
/// Font cache which caches both fonts, and realized fonts. Cache has individual limit /// Font cache which caches both fonts, and realized fonts. Cache has individual limit
/// for fonts, and realized fonts. /// for fonts, and realized fonts.
class PDFFontCache class PDFFORQTLIBSHARED_EXPORT PDFFontCache
{ {
public: public:
inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) : inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) :

View File

@ -400,7 +400,7 @@ void PDFRasterizerPool::render(const std::vector<PDFInteger>& pageIndices,
QElapsedTimer timer; QElapsedTimer timer;
timer.start(); timer.start();
emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Start at %1...").arg(QTime::currentTime().toString(Qt::TextDate)))); emit renderError(PDFCatalog::INVALID_PAGE_INDEX, PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Start at %1...").arg(QTime::currentTime().toString(Qt::TextDate))));
if (progress) if (progress)
{ {
@ -419,19 +419,27 @@ void PDFRasterizerPool::render(const std::vector<PDFInteger>& pageIndices,
{ {
progress->step(); progress->step();
} }
emit renderError(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 not found.").arg(pageIndex))); emit renderError(pageIndex, PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 not found.").arg(pageIndex)));
return; return;
} }
QElapsedTimer totalPageTimer;
totalPageTimer.start();
QElapsedTimer pageTimer;
pageTimer.start();
// Precompile the page // Precompile the page
PDFPrecompiledPage precompiledPage; PDFPrecompiledPage precompiledPage;
PDFCMSPointer cms = m_cmsManager->getCurrentCMS(); PDFCMSPointer cms = m_cmsManager->getCurrentCMS();
PDFRenderer renderer(m_document, m_fontCache, cms.data(), m_optionalContentActivity, m_features, m_meshQualitySettings); PDFRenderer renderer(m_document, m_fontCache, cms.data(), m_optionalContentActivity, m_features, m_meshQualitySettings);
renderer.compile(&precompiledPage, pageIndex); renderer.compile(&precompiledPage, pageIndex);
for (const PDFRenderError error : precompiledPage.getErrors()) qint64 pageCompileTime = pageTimer.restart();
for (const PDFRenderError& error : precompiledPage.getErrors())
{ {
emit renderError(PDFRenderError(error.type, PDFTranslationContext::tr("Page %1: %2").arg(pageIndex + 1).arg(error.message))); emit renderError(pageIndex, error);
} }
// We can const-cast here, because we do not modify the document in annotation manager. // We can const-cast here, because we do not modify the document in annotation manager.
@ -443,12 +451,22 @@ void PDFRasterizerPool::render(const std::vector<PDFInteger>& pageIndices,
annotationManager.setDocument(modifiedDocument); annotationManager.setDocument(modifiedDocument);
// Render page to image // Render page to image
pageTimer.restart();
PDFRasterizer* rasterizer = acquire(); PDFRasterizer* rasterizer = acquire();
qint64 pageWaitTime = pageTimer.restart();
QImage image = rasterizer->render(pageIndex, page, &precompiledPage, imageSizeGetter(page), m_features, &annotationManager); QImage image = rasterizer->render(pageIndex, page, &precompiledPage, imageSizeGetter(page), m_features, &annotationManager);
qint64 pageRenderTime = pageTimer.elapsed();
release(rasterizer); release(rasterizer);
// Now, process the image // Now, process the image
processImage(pageIndex, qMove(image)); PDFRenderedPageImage renderedPageImage;
renderedPageImage.pageIndex = pageIndex;
renderedPageImage.pageImage = qMove(image);
renderedPageImage.pageCompileTime = pageCompileTime;
renderedPageImage.pageWaitTime = pageWaitTime;
renderedPageImage.pageRenderTime = pageRenderTime;
renderedPageImage.pageTotalTime = totalPageTimer.elapsed();
processImage(renderedPageImage);
if (progress) if (progress)
{ {
@ -462,14 +480,19 @@ void PDFRasterizerPool::render(const std::vector<PDFInteger>& pageIndices,
progress->finish(); progress->finish();
} }
emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Finished at %1...").arg(QTime::currentTime().toString(Qt::TextDate)))); emit renderError(PDFCatalog::INVALID_PAGE_INDEX, 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()))); emit renderError(PDFCatalog::INVALID_PAGE_INDEX, PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("%1 miliseconds elapsed to render %2 pages...").arg(timer.nsecsElapsed() / 1000000).arg(pageIndices.size())));
} }
int PDFRasterizerPool::getDefaultRasterizerCount() int PDFRasterizerPool::getDefaultRasterizerCount()
{ {
int hint = QThread::idealThreadCount() / 2; int hint = QThread::idealThreadCount() / 2;
return qBound(1, hint, 16); return getCorrectedRasterizerCount(hint);
}
int PDFRasterizerPool::getCorrectedRasterizerCount(int rasterizerCount)
{
return qBound(1, rasterizerCount, 16);
} }
PDFImageWriterSettings::PDFImageWriterSettings() PDFImageWriterSettings::PDFImageWriterSettings()
@ -833,7 +856,7 @@ std::vector<PDFInteger> PDFPageImageExportSettings::getPages() const
return result; return result;
} }
QString PDFPageImageExportSettings::getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat) QString PDFPageImageExportSettings::getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat) const
{ {
QString fileName = m_fileTemplate; QString fileName = m_fileTemplate;
fileName.replace('%', QString::number(pageIndex + 1)); fileName.replace('%', QString::number(pageIndex + 1));

View File

@ -26,6 +26,7 @@
#include <QSemaphore> #include <QSemaphore>
#include <QImageWriter> #include <QImageWriter>
#include <QSurfaceFormat> #include <QSurfaceFormat>
#include <QImage>
class QPainter; class QPainter;
class QOpenGLContext; class QOpenGLContext;
@ -168,6 +169,17 @@ private:
QOpenGLFramebufferObject* m_fbo; QOpenGLFramebufferObject* m_fbo;
}; };
/// Simple structure for storing rendered page images
struct PDFRenderedPageImage
{
qint64 pageCompileTime = 0;
qint64 pageWaitTime = 0;
qint64 pageRenderTime = 0;
qint64 pageTotalTime = 0;
PDFInteger pageIndex;
QImage pageImage;
};
/// Pool of page image renderers. It can use predefined number of renderers to /// 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 - /// 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 /// first one is as standard object pool, second one is to directly render
@ -181,8 +193,9 @@ private:
public: public:
using PageImageSizeGetter = std::function<QSize(const PDFPage*)>; using PageImageSizeGetter = std::function<QSize(const PDFPage*)>;
using ProcessImageMethod = std::function<void(PDFInteger, QImage&&)>; using ProcessImageMethod = std::function<void(PDFRenderedPageImage&)>;
/// Creates new rasterizer pool /// Creates new rasterizer pool
/// \param document Document /// \param document Document
@ -229,8 +242,15 @@ public:
/// Returns default rasterizer count /// Returns default rasterizer count
static int getDefaultRasterizerCount(); static int getDefaultRasterizerCount();
/// Returns corrected rasterizer count (so, if user
/// select too high or too low rasterizer count, this function
/// corrects it to acceptable number.
/// \param rasterizerCount Requested number of rasterizers
/// \returns Corrected number of rasterizers
static int getCorrectedRasterizerCount(int rasterizerCount);
signals: signals:
void renderError(PDFRenderError error); void renderError(PDFInteger pageIndex, PDFRenderError error);
private: private:
const PDFDocument* m_document; const PDFDocument* m_document;
@ -344,7 +364,7 @@ public:
std::vector<PDFInteger> getPages() const; std::vector<PDFInteger> getPages() const;
/// Returns output file name for given page /// Returns output file name for given page
QString getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat); QString getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat) const;
static constexpr int getMinDPIResolution() { return 72; } static constexpr int getMinDPIResolution() { return 72; }
static constexpr int getMaxDPIResolution() { return 6000; } static constexpr int getMaxDPIResolution() { return 6000; }

View File

@ -243,9 +243,9 @@ void PDFRenderToImagesDialog::onProgressiveScanWriteChanged(bool value)
m_imageWriterSettings.setProgressiveScanWrite(value); m_imageWriterSettings.setProgressiveScanWrite(value);
} }
void PDFRenderToImagesDialog::onRenderError(pdf::PDFRenderError error) void PDFRenderToImagesDialog::onRenderError(pdf::PDFInteger pageIndex, pdf::PDFRenderError error)
{ {
ui->progressMessagesEdit->setPlainText(QString("%1\n%2").arg(ui->progressMessagesEdit->toPlainText()).arg(error.message)); ui->progressMessagesEdit->setPlainText(QString("%1\n%2").arg(ui->progressMessagesEdit->toPlainText()).arg(tr("Page %1: %2").arg(pageIndex + 1).arg(error.message)));
} }
void PDFRenderToImagesDialog::onRenderingFinished() void PDFRenderToImagesDialog::onRenderingFinished()
@ -319,9 +319,9 @@ void PDFRenderToImagesDialog::on_buttonBox_clicked(QAbstractButton* button)
return QSize(); return QSize();
}; };
auto processImage = [this](const pdf::PDFInteger pageIndex, QImage&& image) auto processImage = [this](pdf::PDFRenderedPageImage& renderedPageImage)
{ {
QString fileName = m_imageExportSettings.getOutputFileName(pageIndex, m_imageWriterSettings.getCurrentFormat()); QString fileName = m_imageExportSettings.getOutputFileName(renderedPageImage.pageIndex, m_imageWriterSettings.getCurrentFormat());
QImageWriter imageWriter(fileName, m_imageWriterSettings.getCurrentFormat()); QImageWriter imageWriter(fileName, m_imageWriterSettings.getCurrentFormat());
imageWriter.setSubType(m_imageWriterSettings.getCurrentSubtype()); imageWriter.setSubType(m_imageWriterSettings.getCurrentSubtype());
@ -331,9 +331,9 @@ void PDFRenderToImagesDialog::on_buttonBox_clicked(QAbstractButton* button)
imageWriter.setOptimizedWrite(m_imageWriterSettings.hasOptimizedWrite()); imageWriter.setOptimizedWrite(m_imageWriterSettings.hasOptimizedWrite());
imageWriter.setProgressiveScanWrite(m_imageWriterSettings.hasProgressiveScanWrite()); imageWriter.setProgressiveScanWrite(m_imageWriterSettings.hasProgressiveScanWrite());
if (!imageWriter.write(image)) if (!imageWriter.write(renderedPageImage.pageImage))
{ {
emit m_rasterizerPool->renderError(pdf::PDFRenderError(pdf::RenderErrorType::Error, tr("Can't write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString()))); emit m_rasterizerPool->renderError(renderedPageImage.pageIndex, pdf::PDFRenderError(pdf::RenderErrorType::Error, tr("Cannot write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString())));
} }
}; };

View File

@ -76,7 +76,7 @@ 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); void onRenderError(pdf::PDFInteger pageIndex, pdf::PDFRenderError error);
void onRenderingFinished(); void onRenderingFinished();
Ui::PDFRenderToImagesDialog* ui; Ui::PDFRenderToImagesDialog* ui;

View File

@ -17,12 +17,13 @@
#include "pdftoolabstractapplication.h" #include "pdftoolabstractapplication.h"
#include <QCoreApplication> #include <QGuiApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication a(argc, argv); QGuiApplication a(argc, argv);
QGuiApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
QCoreApplication::setOrganizationName("MelkaJ"); QCoreApplication::setOrganizationName("MelkaJ");
QCoreApplication::setApplicationName("PdfTool"); QCoreApplication::setApplicationName("PdfTool");
QCoreApplication::setApplicationVersion("1.0.0"); QCoreApplication::setApplicationVersion("1.0.0");

View File

@ -286,6 +286,10 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser*
{ {
parser->addOption(QCommandLineOption(info.option, info.description, "bool", defaultFeatures.testFlag(info.feature) ? "1" : "0")); parser->addOption(QCommandLineOption(info.option, info.description, "bool", defaultFeatures.testFlag(info.feature) ? "1" : "0"));
} }
parser->addOption(QCommandLineOption("render-hw-accel", "Use hardware acceleration (using GPU).", "bool", "1"));
parser->addOption(QCommandLineOption("render-msaa-samples", "MSAA sample count for GPU rendering.", "samples", "4"));
parser->addOption(QCommandLineOption("render-rasterizers", "Number of rasterizer contexts.", "rasterizers", QString::number(pdf::PDFRasterizerPool::getDefaultRasterizerCount())));
} }
} }
@ -756,6 +760,40 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser
PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown bool value '%1'. Default value is used.").arg(textValue), options.outputCodec); PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown bool value '%1'. Default value is used.").arg(textValue), options.outputCodec);
} }
} }
QString textValue = parser->value("render-hw-accel");
bool ok = false;
bool value = textValue.toInt(&ok);
if (ok)
{
options.renderUseHardwareRendering = value;
}
else
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown bool value '%1'. GPU rendering is used as default.").arg(textValue), options.outputCodec);
}
textValue = parser->value("render-msaa-samples");
options.renderMSAAsamples = textValue.toInt(&ok);
if (!ok)
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown MSAA sample count '%1'. 4 samples are used as default.").arg(textValue), options.outputCodec);
options.renderMSAAsamples = 4;
}
textValue = parser->value("render-rasterizers");
options.renderRasterizerCount = textValue.toInt(&ok);
if (!ok)
{
options.renderRasterizerCount = pdf::PDFRasterizerPool::getDefaultRasterizerCount();
PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown rasterizer count '%1'. %2 rasterizers are used as default.").arg(textValue).arg(options.renderRasterizerCount), options.outputCodec);
}
int correctedRasterizerCount = pdf::PDFRasterizerPool::getCorrectedRasterizerCount(options.renderRasterizerCount);
if (correctedRasterizerCount != options.renderRasterizerCount)
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Invalid raterizer count: %1. Correcting to use %2 rasterizers.").arg(options.renderRasterizerCount).arg(correctedRasterizerCount), options.outputCodec);
options.renderRasterizerCount = correctedRasterizerCount;
}
} }
return options; return options;

View File

@ -124,6 +124,9 @@ struct PDFToolOptions
// For option 'RenderFlags' // For option 'RenderFlags'
pdf::PDFRenderer::Features renderFeatures = pdf::PDFRenderer::getDefaultFeatures(); pdf::PDFRenderer::Features renderFeatures = pdf::PDFRenderer::getDefaultFeatures();
bool renderUseHardwareRendering = true;
int renderMSAAsamples = 4;
int renderRasterizerCount = pdf::PDFRasterizerPool::getDefaultRasterizerCount();
/// Returns page range. If page range is invalid, then \p errorMessage is empty. /// Returns page range. If page range is invalid, then \p errorMessage is empty.
/// \param pageCount Page count /// \param pageCount Page count

View File

@ -16,6 +16,10 @@
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>. // along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdftoolrender.h" #include "pdftoolrender.h"
#include "pdffont.h"
#include "pdfconstants.h"
#include <QElapsedTimer>
namespace pdftool namespace pdftool
{ {
@ -49,6 +53,43 @@ PDFToolAbstractApplication::Options PDFToolRender::getOptionsFlags() const
return ConsoleFormat | OpenDocument | PageSelector | ImageWriterSettings | ImageExportSettingsFiles | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags; return ConsoleFormat | OpenDocument | PageSelector | ImageWriterSettings | ImageExportSettingsFiles | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags;
} }
void PDFToolRender::finish(const PDFToolOptions& options)
{
PDFOutputFormatter formatter(options.outputStyle, options.outputCodec);
formatter.beginDocument("render", PDFToolTranslationContext::tr("Render document %1").arg(options.document));
formatter.endl();
writeStatistics(formatter);
writeErrors(formatter);
formatter.endDocument();
PDFConsole::writeText(formatter.getString(), options.outputCodec);
}
void PDFToolRender::onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage)
{
writePageInfoStatistics(renderedPageImage);
QString fileName = options.imageExportSettings.getOutputFileName(renderedPageImage.pageIndex, options.imageWriterSettings.getCurrentFormat());
QElapsedTimer imageWriterTimer;
imageWriterTimer.start();
QImageWriter imageWriter(fileName, options.imageWriterSettings.getCurrentFormat());
imageWriter.setSubType(options.imageWriterSettings.getCurrentSubtype());
imageWriter.setCompression(options.imageWriterSettings.getCompression());
imageWriter.setQuality(options.imageWriterSettings.getQuality());
imageWriter.setGamma(options.imageWriterSettings.getGamma());
imageWriter.setOptimizedWrite(options.imageWriterSettings.hasOptimizedWrite());
imageWriter.setProgressiveScanWrite(options.imageWriterSettings.hasProgressiveScanWrite());
if (!imageWriter.write(renderedPageImage.pageImage))
{
m_pageInfo[renderedPageImage.pageIndex].errors.emplace_back(pdf::PDFRenderError(pdf::RenderErrorType::Error, PDFToolTranslationContext::tr("Cannot write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString())));
}
m_pageInfo[renderedPageImage.pageIndex].pageWriteTime = imageWriterTimer.elapsed();
}
QString PDFToolBenchmark::getStandardString(PDFToolAbstractApplication::StandardString standardString) const QString PDFToolBenchmark::getStandardString(PDFToolAbstractApplication::StandardString standardString) const
{ {
switch (standardString) switch (standardString)
@ -75,6 +116,26 @@ PDFToolAbstractApplication::Options PDFToolBenchmark::getOptionsFlags() const
return ConsoleFormat | OpenDocument | PageSelector | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags; return ConsoleFormat | OpenDocument | PageSelector | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags;
} }
void PDFToolBenchmark::finish(const PDFToolOptions& options)
{
PDFOutputFormatter formatter(options.outputStyle, options.outputCodec);
formatter.beginDocument("benchmark", PDFToolTranslationContext::tr("Benchmark rendering of document %1").arg(options.document));
formatter.endl();
writeStatistics(formatter);
writePageStatistics(formatter);
writeErrors(formatter);
formatter.endDocument();
PDFConsole::writeText(formatter.getString(), options.outputCodec);
}
void PDFToolBenchmark::onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage)
{
Q_UNUSED(options);
writePageInfoStatistics(renderedPageImage);
}
int PDFToolRenderBase::execute(const PDFToolOptions& options) int PDFToolRenderBase::execute(const PDFToolOptions& options)
{ {
pdf::PDFDocument document; pdf::PDFDocument document;
@ -85,7 +146,7 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options)
} }
QString parseError; QString parseError;
std::vector<pdf::PDFInteger> pages = options.getPageRange(document.getCatalog()->getPageCount(), parseError, true); std::vector<pdf::PDFInteger> pageIndices = options.getPageRange(document.getCatalog()->getPageCount(), parseError, true);
if (!parseError.isEmpty()) if (!parseError.isEmpty())
{ {
@ -103,11 +164,39 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options)
// We are ready to render the document // We are ready to render the document
pdf::PDFOptionalContentActivity optionalContentActivity(&document, pdf::OCUsage::Export, nullptr); pdf::PDFOptionalContentActivity optionalContentActivity(&document, pdf::OCUsage::Export, nullptr);
m_cms = m_proxy->getCMSManager()->getCurrentCMS(); pdf::PDFCMSManager cmsManager(nullptr);
pdf::PDFRasterizerPool rasterizerPool(&document, m_proxy->getFontCache(), m_proxy->getCMSManager(), cmsManager.setSettings(options.cmsSettings);
&optionalContentActivity, m_proxy->getFeatures(), m_proxy->getMeshQualitySettings(), pdf::PDFMeshQualitySettings meshQualitySettings;
pdf::PDFRasterizerPool::getDefaultRasterizerCount(), m_proxy->isUsingOpenGL(), m_proxy->getSurfaceFormat(), this); pdf::PDFFontCache fontCache(pdf::DEFAULT_FONT_CACHE_LIMIT, pdf::DEFAULT_REALIZED_FONT_CACHE_LIMIT);
connect(&rasterizerPool, &pdf::PDFRasterizerPool::renderError, this, &PDFRenderToImagesDialog::onRenderError); pdf::PDFModifiedDocument md(&document, &optionalContentActivity);
fontCache.setDocument(md);
fontCache.setCacheShrinkEnabled(nullptr, false);
QSurfaceFormat surfaceFormat;
if (options.renderUseHardwareRendering)
{
surfaceFormat = QSurfaceFormat::defaultFormat();
surfaceFormat.setProfile(QSurfaceFormat::CoreProfile);
surfaceFormat.setSamples(options.renderMSAAsamples);
surfaceFormat.setColorSpace(QSurfaceFormat::sRGBColorSpace);
surfaceFormat.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
}
m_pageInfo.resize(document.getCatalog()->getPageCount());
pdf::PDFRasterizerPool rasterizerPool(&document, &fontCache, &cmsManager,
&optionalContentActivity, options.renderFeatures, meshQualitySettings,
pdf::PDFRasterizerPool::getCorrectedRasterizerCount(options.renderRasterizerCount),
options.renderUseHardwareRendering, surfaceFormat, nullptr);
auto onRenderError = [this](pdf::PDFInteger pageIndex, pdf::PDFRenderError error)
{
if (pageIndex != pdf::PDFCatalog::INVALID_PAGE_INDEX)
{
m_pageInfo[pageIndex].errors.emplace_back(qMove(error));
}
};
QObject holder;
QObject::connect(&rasterizerPool, &pdf::PDFRasterizerPool::renderError, &holder, onRenderError, Qt::DirectConnection);
auto imageSizeGetter = [&options](const pdf::PDFPage* page) -> QSize auto imageSizeGetter = [&options](const pdf::PDFPage* page) -> QSize
{ {
@ -138,27 +227,198 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options)
return QSize(); return QSize();
}; };
auto processImage = [this](const pdf::PDFInteger pageIndex, QImage&& image) QElapsedTimer timer;
{ timer.start();
QString fileName = m_imageExportSettings.getOutputFileName(pageIndex, m_imageWriterSettings.getCurrentFormat());
QImageWriter imageWriter(fileName, m_imageWriterSettings.getCurrentFormat()); rasterizerPool.render(pageIndices, imageSizeGetter, std::bind(&PDFToolRenderBase::onPageRendered, this, options, std::placeholders::_1), nullptr);
imageWriter.setSubType(m_imageWriterSettings.getCurrentSubtype());
imageWriter.setCompression(m_imageWriterSettings.getCompression());
imageWriter.setQuality(m_imageWriterSettings.getQuality());
imageWriter.setGamma(m_imageWriterSettings.getGamma());
imageWriter.setOptimizedWrite(m_imageWriterSettings.hasOptimizedWrite());
imageWriter.setProgressiveScanWrite(m_imageWriterSettings.hasProgressiveScanWrite());
if (!imageWriter.write(image)) m_wallTime = timer.elapsed();
{
emit m_rasterizerPool->renderError(pdf::PDFRenderError(pdf::RenderErrorType::Error, tr("Can't write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString())));
}
};
rasterizerPool.render(pageIndices, imageSizeGetter, processImage, m_progress); fontCache.setCacheShrinkEnabled(nullptr, true);
finish(options);
return ExitSuccess; return ExitSuccess;
} }
void PDFToolRenderBase::writePageInfoStatistics(const pdf::PDFRenderedPageImage& renderedPageImage)
{
PageInfo& info = m_pageInfo[renderedPageImage.pageIndex];
info.isRendered = true;
info.pageCompileTime = renderedPageImage.pageCompileTime;
info.pageWaitTime = renderedPageImage.pageWaitTime;
info.pageRenderTime = renderedPageImage.pageRenderTime;
info.pageTotalTime = renderedPageImage.pageTotalTime;
info.pageIndex = renderedPageImage.pageIndex;
}
void PDFToolRenderBase::writeStatistics(PDFOutputFormatter& formatter)
{
// Jakub Melka: Write overall statistics
qint64 pagesRendered = 0;
qint64 pageCompileTime = 0;
qint64 pageWaitTime = 0;
qint64 pageRenderTime = 0;
qint64 pageTotalTime = 0;
qint64 pageWriteTime = 0;
for (const PageInfo& info : m_pageInfo)
{
if (!info.isRendered)
{
continue;
}
++pagesRendered;
pageCompileTime += info.pageCompileTime;
pageWaitTime += info.pageWaitTime;
pageRenderTime += info.pageRenderTime;
pageTotalTime += info.pageTotalTime + info.pageWriteTime;
pageWriteTime += info.pageWriteTime;
}
if (pagesRendered > 0 && pageTotalTime > 0 && m_wallTime > 0)
{
QLocale locale;
double renderingSpeedPerCore = double(pagesRendered) / (double(pageTotalTime) / 1000.0);
double renderingSpeedWallTime = double(pagesRendered) / (double(m_wallTime) / 1000.0);
double compileRatio = 100.0 * double(pageCompileTime) / double(pageTotalTime);
double waitRatio = 100.0 * double(pageWaitTime) / double(pageTotalTime);
double renderRatio = 100.0 * double(pageRenderTime) / double(pageTotalTime);
double writeRatio = 100.0 * double(pageWriteTime) / double(pageTotalTime);
formatter.beginTable("statistics", PDFToolTranslationContext::tr("Statistics"));
formatter.beginTableHeaderRow("header");
formatter.writeTableHeaderColumn("description", PDFToolTranslationContext::tr("Description"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("value", PDFToolTranslationContext::tr("Value"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("unit", PDFToolTranslationContext::tr("Unit"), Qt::AlignLeft);
formatter.endTableHeaderRow();
auto writeValue = [&formatter](QString name, QString description, QString value, QString unit)
{
formatter.beginTableRow(name);
formatter.writeTableColumn("description", description);
formatter.writeTableColumn("value", value, Qt::AlignRight);
formatter.writeTableColumn("unit", unit);
formatter.endTableRow();
};
writeValue("pages-rendered", PDFToolTranslationContext::tr("Pages rendered"), locale.toString(pagesRendered), PDFToolTranslationContext::tr("-"));
writeValue("compile-time", PDFToolTranslationContext::tr("Total compile time"), locale.toString(pageCompileTime), PDFToolTranslationContext::tr("msec"));
writeValue("render-time", PDFToolTranslationContext::tr("Total render time"), locale.toString(pageRenderTime), PDFToolTranslationContext::tr("msec"));
writeValue("wait-time", PDFToolTranslationContext::tr("Total wait time"), locale.toString(pageWaitTime), PDFToolTranslationContext::tr("msec"));
writeValue("write-time", PDFToolTranslationContext::tr("Total write time"), locale.toString(pageWriteTime), PDFToolTranslationContext::tr("msec"));
writeValue("total-time", PDFToolTranslationContext::tr("Total time"), locale.toString(pageTotalTime), PDFToolTranslationContext::tr("msec"));
writeValue("wall-time", PDFToolTranslationContext::tr("Wall time"), locale.toString(m_wallTime), PDFToolTranslationContext::tr("msec"));
writeValue("pages-per-second-core", PDFToolTranslationContext::tr("Rendering speed (per core)"), locale.toString(renderingSpeedPerCore, 'f', 3), PDFToolTranslationContext::tr("pages / sec (one core)"));
writeValue("pages-per-second-wall", PDFToolTranslationContext::tr("Rendering speed (wall time)"), locale.toString(renderingSpeedWallTime, 'f', 3), PDFToolTranslationContext::tr("pages / sec"));
writeValue("compile-time-ratio", PDFToolTranslationContext::tr("Compile time ratio"), locale.toString(compileRatio, 'f', 2), PDFToolTranslationContext::tr("%"));
writeValue("render-time-ratio", PDFToolTranslationContext::tr("Render time ratio"), locale.toString(renderRatio, 'f', 2), PDFToolTranslationContext::tr("%"));
writeValue("wait-time-ratio", PDFToolTranslationContext::tr("Wait time ratio"), locale.toString(waitRatio, 'f', 2), PDFToolTranslationContext::tr("%"));
writeValue("write-time-ratio", PDFToolTranslationContext::tr("Write time ratio"), locale.toString(writeRatio, 'f', 2), PDFToolTranslationContext::tr("%"));
formatter.endTable();
formatter.endl();
}
}
void PDFToolRenderBase::writePageStatistics(PDFOutputFormatter& formatter)
{
formatter.beginTable("page-statistics", PDFToolTranslationContext::tr("Page Statistics"));
formatter.beginTableHeaderRow("header");
formatter.writeTableHeaderColumn("page-no", PDFToolTranslationContext::tr("Page No."), Qt::AlignLeft);
formatter.writeTableHeaderColumn("compile-time", PDFToolTranslationContext::tr("Compile Time [msec]"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("render-time", PDFToolTranslationContext::tr("Render Time [msec]"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("wait-time", PDFToolTranslationContext::tr("Wait Time [msec]"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("write-time", PDFToolTranslationContext::tr("Write Time [msec]"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("total-time", PDFToolTranslationContext::tr("Total Time [msec]"), Qt::AlignLeft);
formatter.endTableHeaderRow();
QLocale locale;
for (const PageInfo& info : m_pageInfo)
{
if (!info.isRendered)
{
continue;
}
formatter.beginTableRow("page", info.pageIndex + 1);
formatter.writeTableColumn("page-no", locale.toString(info.pageIndex + 1), Qt::AlignRight);
formatter.writeTableColumn("compile-time", locale.toString(info.pageCompileTime), Qt::AlignRight);
formatter.writeTableColumn("render-time", locale.toString(info.pageRenderTime), Qt::AlignRight);
formatter.writeTableColumn("wait-time", locale.toString(info.pageWaitTime), Qt::AlignRight);
formatter.writeTableColumn("write-time", locale.toString(info.pageWaitTime), Qt::AlignRight);
formatter.writeTableColumn("total-time", locale.toString(info.pageTotalTime), Qt::AlignRight);
formatter.endTableRow();
}
formatter.endTable();
formatter.endl();
}
void PDFToolRenderBase::writeErrors(PDFOutputFormatter& formatter)
{
formatter.beginTable("rendering-errors", PDFToolTranslationContext::tr("Rendering Errors"));
formatter.beginTableHeaderRow("header");
formatter.writeTableHeaderColumn("page-no", PDFToolTranslationContext::tr("Page No."), Qt::AlignLeft);
formatter.writeTableHeaderColumn("type", PDFToolTranslationContext::tr("Type"), Qt::AlignLeft);
formatter.writeTableHeaderColumn("message", PDFToolTranslationContext::tr("Message"), Qt::AlignLeft);
formatter.endTableHeaderRow();
QLocale locale;
for (const PageInfo& info : m_pageInfo)
{
if (!info.isRendered)
{
continue;
}
for (const pdf::PDFRenderError& error : info.errors)
{
QString type;
switch (error.type)
{
case pdf::RenderErrorType::Error:
type = PDFToolTranslationContext::tr("Error");
break;
case pdf::RenderErrorType::Warning:
type = PDFToolTranslationContext::tr("Warning");
break;
case pdf::RenderErrorType::NotImplemented:
type = PDFToolTranslationContext::tr("Not implemented");
break;
case pdf::RenderErrorType::NotSupported:
type = PDFToolTranslationContext::tr("Not supported");
break;
case pdf::RenderErrorType::Information:
type = PDFToolTranslationContext::tr("Information");
break;
default:
Q_ASSERT(false);
break;
}
formatter.beginTableRow("page", info.pageIndex + 1);
formatter.writeTableColumn("page-no", locale.toString(info.pageIndex + 1), Qt::AlignRight);
formatter.writeTableColumn("type", type, Qt::AlignLeft);
formatter.writeTableColumn("message", error.message, Qt::AlignLeft);
formatter.endTableRow();
}
}
formatter.endTable();
formatter.endl();
}
} // namespace pdftool } // namespace pdftool

View File

@ -19,6 +19,7 @@
#define PDFTOOLRENDER_H #define PDFTOOLRENDER_H
#include "pdftoolabstractapplication.h" #include "pdftoolabstractapplication.h"
#include "pdfexception.h"
namespace pdftool namespace pdftool
{ {
@ -27,6 +28,31 @@ class PDFToolRenderBase : public PDFToolAbstractApplication
{ {
public: public:
virtual int execute(const PDFToolOptions& options) override; virtual int execute(const PDFToolOptions& options) override;
protected:
virtual void finish(const PDFToolOptions& options) = 0;
virtual void onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) = 0;
void writePageInfoStatistics(const pdf::PDFRenderedPageImage& renderedPageImage);
void writeStatistics(PDFOutputFormatter& formatter);
void writePageStatistics(PDFOutputFormatter& formatter);
void writeErrors(PDFOutputFormatter& formatter);
struct PageInfo
{
bool isRendered = false;
pdf::PDFInteger pageIndex = 0;
qint64 pageCompileTime = 0;
qint64 pageWaitTime = 0;
qint64 pageRenderTime = 0;
qint64 pageTotalTime = 0;
qint64 pageWriteTime = 0;
std::vector<pdf::PDFRenderError> errors;
};
std::vector<PageInfo> m_pageInfo;
qint64 m_wallTime = 0;
}; };
class PDFToolRender : public PDFToolRenderBase class PDFToolRender : public PDFToolRenderBase
@ -34,6 +60,10 @@ class PDFToolRender : public PDFToolRenderBase
public: public:
virtual QString getStandardString(StandardString standardString) const override; virtual QString getStandardString(StandardString standardString) const override;
virtual Options getOptionsFlags() const override; virtual Options getOptionsFlags() const override;
protected:
virtual void finish(const PDFToolOptions& options) override;
virtual void onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) override;
}; };
class PDFToolBenchmark : public PDFToolRenderBase class PDFToolBenchmark : public PDFToolRenderBase
@ -41,6 +71,10 @@ class PDFToolBenchmark : public PDFToolRenderBase
public: public:
virtual QString getStandardString(StandardString standardString) const override; virtual QString getStandardString(StandardString standardString) const override;
virtual Options getOptionsFlags() const override; virtual Options getOptionsFlags() const override;
protected:
virtual void finish(const PDFToolOptions& options) override;
virtual void onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) override;
}; };
} // namespace pdftool } // namespace pdftool