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();