mirror of https://github.com/JakubMelka/PDF4QT.git
Optimization (multithreading)
This commit is contained in:
parent
14cd4c3dd0
commit
f020098435
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue