mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Optimization (multithreading)
This commit is contained in:
		| @@ -16,6 +16,7 @@ | ||||
| //    along with Pdf4Qt.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| #include "pdfcms.h" | ||||
| #include "pdfexecutionpolicy.h" | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QReadWriteLock> | ||||
| @@ -329,7 +330,52 @@ bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& | ||||
|         inputPixelCount == outputPixelCount) | ||||
|     { | ||||
|         PDFColorBuffer outputBuffer = params.output; | ||||
|  | ||||
|         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<TransformInfo> 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) | ||||
|         { | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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<int> 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<int> 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<int> 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<int> 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; | ||||
|   | ||||
| @@ -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<PDFTransparencyGroupGuard> m_pageTransparencyGroupGuard; | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user