diff --git a/Pdf4QtLib/sources/pdfcms.cpp b/Pdf4QtLib/sources/pdfcms.cpp
index 05cf43b..f7b80b5 100644
--- a/Pdf4QtLib/sources/pdfcms.cpp
+++ b/Pdf4QtLib/sources/pdfcms.cpp
@@ -16,6 +16,7 @@
// along with Pdf4Qt. If not, see .
#include "pdfcms.h"
+#include "pdfexecutionpolicy.h"
#include
#include
@@ -329,7 +330,52 @@ bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams&
inputPixelCount == outputPixelCount)
{
PDFColorBuffer outputBuffer = params.output;
- cmsDoTransform(transform, inputColors, outputBuffer.begin(), inputPixelCount);
+
+ if (inputPixelCount > params.multithreadingThreshold)
+ {
+ struct TransformInfo
+ {
+ inline TransformInfo(const float* source, float* target, cmsUInt32Number pixelCount) :
+ source(source),
+ target(target),
+ pixelCount(pixelCount)
+ {
+
+ }
+
+ const float* source = nullptr;
+ float* target = nullptr;
+ cmsUInt32Number pixelCount = 0;
+ };
+
+ const cmsUInt32Number blockSize = 4096;
+ std::vector infos;
+ infos.reserve(inputPixelCount / blockSize + 1);
+
+ const float* sourcePointer = inputColors;
+ float* targetPointer = outputBuffer.begin();
+
+ cmsUInt32Number remainingPixelCount = inputPixelCount;
+ while (remainingPixelCount > 0)
+ {
+ const cmsUInt32Number currentPixelCount = qMin(blockSize, remainingPixelCount);
+ infos.emplace_back(sourcePointer, targetPointer, currentPixelCount);
+ sourcePointer += currentPixelCount * inputChannels;
+ targetPointer += currentPixelCount * outputChannels;
+ remainingPixelCount -= currentPixelCount;
+ }
+
+ auto processEntry = [transform](const TransformInfo& info)
+ {
+ cmsDoTransform(transform, info.source, info.target, info.pixelCount);
+ };
+ PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, infos.begin(), infos.end(), processEntry);
+ }
+ else
+ {
+ // Single-threaded transform
+ cmsDoTransform(transform, inputColors, outputBuffer.begin(), inputPixelCount);
+ }
if (isOutputCMYK)
{
diff --git a/Pdf4QtLib/sources/pdfcms.h b/Pdf4QtLib/sources/pdfcms.h
index 78fe568..4336971 100644
--- a/Pdf4QtLib/sources/pdfcms.h
+++ b/Pdf4QtLib/sources/pdfcms.h
@@ -225,6 +225,8 @@ public:
PDFColorBuffer output;
RenderingIntent intent = RenderingIntent::Unknown;
+
+ PDFInteger multithreadingThreshold = 4096;
};
/// Transforms color between two color spaces. Doesn't do soft-proofing,
diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp
index 73cc8a5..e0f4038 100644
--- a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp
+++ b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp
@@ -18,6 +18,7 @@
#include "pdftransparencyrenderer.h"
#include "pdfdocument.h"
#include "pdfcms.h"
+#include "pdfexecutionpolicy.h"
namespace pdf
{
@@ -566,10 +567,12 @@ PDFTransparencyRenderer::PDFTransparencyRenderer(const PDFPage* page,
const PDFCMS* cms,
const PDFOptionalContentActivity* optionalContentActivity,
const PDFInkMapper* inkMapper,
+ PDFTransparencyRendererSettings settings,
QMatrix pagePointToDevicePointMatrix) :
BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, PDFMeshQualitySettings()),
m_inkMapper(inkMapper),
- m_active(false)
+ m_active(false),
+ m_settings(settings)
{
m_deviceColorSpace.reset(new PDFDeviceRGBColorSpace());
m_processColorSpace.reset(new PDFDeviceCMYKColorSpace());
@@ -751,6 +754,39 @@ QImage PDFTransparencyRenderer::toImage(bool use16Bit, bool usePaper, PDFRGB pap
return image;
}
+void PDFTransparencyRenderer::performPixelSampling(const PDFReal shape,
+ const PDFReal opacity,
+ const uint8_t shapeChannel,
+ const uint8_t opacityChannel,
+ const uint8_t colorChannelStart,
+ const uint8_t colorChannelEnd,
+ int x,
+ int y,
+ const PDFMappedColor& fillColor,
+ const PDFPainterPathSampler& clipSampler,
+ const PDFPainterPathSampler& pathSampler)
+{
+ const PDFColorComponent clipValue = clipSampler.sample(QPoint(x, y));
+ const PDFColorComponent objectShapeValue = pathSampler.sample(QPoint(x, y));
+ const PDFColorComponent shapeValue = objectShapeValue * clipValue * shape;
+
+ if (shapeValue > 0.0f)
+ {
+ // We consider old object shape - we use Union function to
+ // set shape channel value.
+
+ PDFColorBuffer pixel = m_drawBuffer.getPixel(x, y);
+ pixel[shapeChannel] = PDFBlendFunction::blend_Union(shapeValue, pixel[shapeChannel]);
+ pixel[opacityChannel] = pixel[shapeChannel] * opacity;
+
+ // Copy color
+ for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex)
+ {
+ pixel[colorChannelIndex] = fillColor.mappedColor[colorChannelIndex];
+ }
+ }
+}
+
void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
Q_UNUSED(fillRule);
@@ -784,28 +820,42 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, fillRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
const PDFMappedColor& fillColor = getMappedFillColor();
- for (int x = fillRect.left(); x < fillRect.right(); ++x)
+ if (isMultithreadedPathSamplingUsed(fillRect))
{
- for (int y = fillRect.top(); y < fillRect.bottom(); ++y)
+ if (fillRect.width() > fillRect.height())
{
- const PDFColorComponent clipValue = clipSampler.sample(QPoint(x, y));
- const PDFColorComponent objectShapeValue = pathSampler.sample(QPoint(x, y));
- const PDFColorComponent shapeValue = objectShapeValue * clipValue * shapeFilling;
-
- if (shapeValue > 0.0f)
+ // Columns
+ PDFIntegerRange range(fillRect.left(), fillRect.right() + 1);
+ auto processEntry = [&, this](int x)
{
- // We consider old object shape - we use Union function to
- // set shape channel value.
-
- PDFColorBuffer pixel = m_drawBuffer.getPixel(x, y);
- pixel[shapeChannel] = PDFBlendFunction::blend_Union(shapeValue, pixel[shapeChannel]);
- pixel[opacityChannel] = pixel[shapeChannel] * opacityFilling;
-
- // Copy color
- for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex)
+ for (int y = fillRect.top(); y <= fillRect.bottom(); ++y)
{
- pixel[colorChannelIndex] = fillColor.mappedColor[colorChannelIndex];
+ performPixelSampling(shapeFilling, opacityFilling, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, fillColor, clipSampler, pathSampler);
}
+ };
+ PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
+ }
+ else
+ {
+ // Rows
+ PDFIntegerRange range(fillRect.top(), fillRect.bottom() + 1);
+ auto processEntry = [&, this](int y)
+ {
+ for (int x = fillRect.left(); x <= fillRect.right(); ++x)
+ {
+ performPixelSampling(shapeFilling, opacityFilling, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, fillColor, clipSampler, pathSampler);
+ }
+ };
+ PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
+ }
+ }
+ else
+ {
+ for (int x = fillRect.left(); x <= fillRect.right(); ++x)
+ {
+ for (int y = fillRect.top(); y <= fillRect.bottom(); ++y)
+ {
+ performPixelSampling(shapeFilling, opacityFilling, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, fillColor, clipSampler, pathSampler);
}
}
}
@@ -843,28 +893,42 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, strokeRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
const PDFMappedColor& strokeColor = getMappedStrokeColor();
- for (int x = strokeRect.left(); x < strokeRect.right(); ++x)
+ if (isMultithreadedPathSamplingUsed(strokeRect))
{
- for (int y = strokeRect.top(); y < strokeRect.bottom(); ++y)
+ if (strokeRect.width() > strokeRect.height())
{
- const PDFColorComponent clipValue = clipSampler.sample(QPoint(x, y));
- const PDFColorComponent objectShapeValue = pathSampler.sample(QPoint(x, y));
- const PDFColorComponent shapeValue = objectShapeValue * clipValue * shapeStroking;
-
- if (shapeValue > 0.0f)
+ // Columns
+ PDFIntegerRange range(strokeRect.left(), strokeRect.right() + 1);
+ auto processEntry = [&, this](int x)
{
- // We consider old object shape - we use Union function to
- // set shape channel value.
-
- PDFColorBuffer pixel = m_drawBuffer.getPixel(x, y);
- pixel[shapeChannel] = PDFBlendFunction::blend_Union(shapeValue, pixel[shapeChannel]);
- pixel[opacityChannel] = pixel[shapeChannel] * opacityStroking;
-
- // Copy color
- for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex)
+ for (int y = strokeRect.top(); y <= strokeRect.bottom(); ++y)
{
- pixel[colorChannelIndex] = strokeColor.mappedColor[colorChannelIndex];
+ performPixelSampling(shapeStroking, opacityStroking, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, strokeColor, clipSampler, pathSampler);
}
+ };
+ PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
+ }
+ else
+ {
+ // Rows
+ PDFIntegerRange range(strokeRect.top(), strokeRect.bottom() + 1);
+ auto processEntry = [&, this](int y)
+ {
+ for (int x = strokeRect.left(); x <= strokeRect.right(); ++x)
+ {
+ performPixelSampling(shapeStroking, opacityStroking, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, strokeColor, clipSampler, pathSampler);
+ }
+ };
+ PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
+ }
+ }
+ else
+ {
+ for (int x = strokeRect.left(); x <= strokeRect.right(); ++x)
+ {
+ for (int y = strokeRect.top(); y <= strokeRect.bottom(); ++y)
+ {
+ performPixelSampling(shapeStroking, opacityStroking, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, strokeColor, clipSampler, pathSampler);
}
}
}
@@ -1349,6 +1413,16 @@ void PDFTransparencyRenderer::flushDrawBuffer()
}
}
+bool PDFTransparencyRenderer::isMultithreadedPathSamplingUsed(QRect fillRect) const
+{
+ if (!m_settings.flags.testFlag(PDFTransparencyRendererSettings::MultithreadedPathSampler))
+ {
+ return false;
+ }
+
+ return fillRect.width() * fillRect.height() > m_settings.multithreadingPathSampleThreshold && fillRect.width() > 1;
+}
+
PDFInkMapper::PDFInkMapper(const PDFDocument* document) :
m_document(document)
{
@@ -1746,7 +1820,7 @@ void PDFPainterPathSampler::prepareScanLines()
return;
}
- for (int yOffset = m_fillRect.top(); yOffset < m_fillRect.bottom(); ++yOffset)
+ for (int yOffset = m_fillRect.top(); yOffset <= m_fillRect.bottom(); ++yOffset)
{
const qreal coordY1 = yOffset;
const qreal coordY2 = coordY1 + 1.0;
diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.h b/Pdf4QtLib/sources/pdftransparencyrenderer.h
index 8b23eda..93427d7 100644
--- a/Pdf4QtLib/sources/pdftransparencyrenderer.h
+++ b/Pdf4QtLib/sources/pdftransparencyrenderer.h
@@ -439,13 +439,24 @@ struct PDFTransparencyRendererSettings
/// Sample count for MSAA antialiasing
int samplesCount = 16;
+ /// Threshold for turning on painter path
+ /// multithreaded painting. When number of potential
+ /// pixels of painter path is greater than this constant,
+ /// and MultithreadedPathSampler flag is turned on,
+ /// multithreaded painting is performed.
+ int multithreadingPathSampleThreshold = 128;
+
enum Flag
{
None = 0x0000,
/// Use precise path sampler, which uses paths instead
/// of filling polygon.
- PrecisePathSampler = 0x0001
+ PrecisePathSampler = 0x0001,
+
+ /// Use multithreading when painter paths are painted?
+ /// Multithreading is used to
+ MultithreadedPathSampler = 0x0002
};
Q_DECLARE_FLAGS(Flags, Flag)
@@ -471,6 +482,7 @@ public:
const PDFCMS* cms,
const PDFOptionalContentActivity* optionalContentActivity,
const PDFInkMapper* inkMapper,
+ PDFTransparencyRendererSettings settings,
QMatrix pagePointToDevicePointMatrix);
/// Sets device color space. This is final color space, to which
@@ -596,6 +608,37 @@ private:
/// Flushes draw buffer
void flushDrawBuffer();
+ /// Returns true, if multithreaded painter path sampling should be used
+ /// for a given fill rectangle.
+ /// \param fillRect Fill rectangle
+ /// \returns true, if multithreading should be used
+ bool isMultithreadedPathSamplingUsed(QRect fillRect) const;
+
+ /// Performs sampling of single pixel. Sampled pixel is painted
+ /// into the draw buffer.
+ /// \param shape Constant shape value
+ /// \param opacity Constant opacity value
+ /// \param x Horizontal coordinate of the pixel
+ /// \param y Vertical coordinate of the pixel
+ /// \param shapeChannel Shape channel (draw buffer)
+ /// \param opacityChannel Opacity channel (draw buffer)
+ /// \param colorChannelStart Color channel start (draw buffer)
+ /// \param colorChannelEnd Color channel end (draw buffer)
+ /// \param fillColor Fill color
+ /// \param clipSample Clipping sampler
+ /// \param pathSampler Path sampler
+ void performPixelSampling(const PDFReal shape,
+ const PDFReal opacity,
+ const uint8_t shapeChannel,
+ const uint8_t opacityChannel,
+ const uint8_t colorChannelStart,
+ const uint8_t colorChannelEnd,
+ int x,
+ int y,
+ const PDFMappedColor& fillColor,
+ const PDFPainterPathSampler& clipSampler,
+ const PDFPainterPathSampler& pathSampler);
+
PDFColorSpacePointer m_deviceColorSpace; ///< Device color space (color space for final result)
PDFColorSpacePointer m_processColorSpace; ///< Process color space (color space, in which is page graphic's blended)
std::unique_ptr m_pageTransparencyGroupGuard;
diff --git a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp
index aa0ae3b..089b9d2 100644
--- a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp
+++ b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp
@@ -71,10 +71,17 @@ void OutputPreviewDialog::updateImage()
ui->imageLabel->setPixmap(QPixmap());
}
+ pdf::PDFTransparencyRendererSettings settings;
+
+ // Jakub Melka: debug is very slow, use multithreading
+#ifdef QT_DEBUG
+ settings.flags.setFlag(pdf::PDFTransparencyRendererSettings::MultithreadedPathSampler, true);
+#endif
+
QMatrix pagePointToDevicePoint = pdf::PDFRenderer::createPagePointToDevicePointMatrix(page, QRect(QPoint(0, 0), imageSize));
pdf::PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();
pdf::PDFCMSPointer cms = proxy->getCMSManager()->getCurrentCMS();
- pdf::PDFTransparencyRenderer renderer(page, m_document, proxy->getFontCache(), cms.data(), proxy->getOptionalContentActivity(), &m_inkMapper, pagePointToDevicePoint);
+ pdf::PDFTransparencyRenderer renderer(page, m_document, proxy->getFontCache(), cms.data(), proxy->getOptionalContentActivity(), &m_inkMapper, settings, pagePointToDevicePoint);
renderer.beginPaint(imageSize);
renderer.processContents();