mirror of https://github.com/JakubMelka/PDF4QT.git
Rework of sampler
This commit is contained in:
parent
3da8cf2252
commit
5cd493d19d
|
@ -742,7 +742,7 @@ QImage PDFTransparencyRenderer::toImage(bool use16Bit, bool usePaper, PDFRGB pap
|
||||||
PDFFloatBitmap softMask(paperImage.getWidth(), paperImage.getHeight(), PDFPixelFormat::createOpacityMask());
|
PDFFloatBitmap softMask(paperImage.getWidth(), paperImage.getHeight(), PDFPixelFormat::createOpacityMask());
|
||||||
softMask.makeOpaque();
|
softMask.makeOpaque();
|
||||||
|
|
||||||
QRect blendRegion(0, 0, floatImage.getWidth(), floatImage.getHeight());
|
QRect blendRegion(0, 0, int(floatImage.getWidth()), int(floatImage.getHeight()));
|
||||||
PDFFloatBitmapWithColorSpace::blend(floatImage, paperImage, paperImage, paperImage, softMask, false, 1.0f, BlendMode::Normal, false, 0xFFFF, PDFFloatBitmap::OverprintMode::NoOveprint, blendRegion);
|
PDFFloatBitmapWithColorSpace::blend(floatImage, paperImage, paperImage, paperImage, softMask, false, 1.0f, BlendMode::Normal, false, 0xFFFF, PDFFloatBitmap::OverprintMode::NoOveprint, blendRegion);
|
||||||
|
|
||||||
return toImageImpl(paperImage, use16Bit);
|
return toImageImpl(paperImage, use16Bit);
|
||||||
|
@ -755,9 +755,6 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
|
||||||
{
|
{
|
||||||
Q_UNUSED(fillRule);
|
Q_UNUSED(fillRule);
|
||||||
|
|
||||||
PDFPainterPathSampler clipSampler(m_painterStateStack.top().clipPath, m_settings.samplesCount, 1.0f,
|
|
||||||
m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
|
||||||
|
|
||||||
QMatrix worldMatrix = getCurrentWorldMatrix();
|
QMatrix worldMatrix = getCurrentWorldMatrix();
|
||||||
|
|
||||||
const PDFReal shapeStroking = getShapeStroking();
|
const PDFReal shapeStroking = getShapeStroking();
|
||||||
|
@ -783,7 +780,8 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
|
||||||
// and world matrix. Path can be translated outside of the paint area.
|
// and world matrix. Path can be translated outside of the paint area.
|
||||||
if (fillRect.isValid())
|
if (fillRect.isValid())
|
||||||
{
|
{
|
||||||
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
PDFPainterPathSampler clipSampler(m_painterStateStack.top().clipPath, m_settings.samplesCount, 1.0f, fillRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
||||||
|
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, fillRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
||||||
const PDFMappedColor& fillColor = getMappedFillColor();
|
const PDFMappedColor& fillColor = getMappedFillColor();
|
||||||
|
|
||||||
for (int x = fillRect.left(); x < fillRect.right(); ++x)
|
for (int x = fillRect.left(); x < fillRect.right(); ++x)
|
||||||
|
@ -841,7 +839,8 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
|
||||||
// and world matrix. Path can be translated outside of the paint area.
|
// and world matrix. Path can be translated outside of the paint area.
|
||||||
if (strokeRect.isValid())
|
if (strokeRect.isValid())
|
||||||
{
|
{
|
||||||
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
PDFPainterPathSampler clipSampler(m_painterStateStack.top().clipPath, m_settings.samplesCount, 1.0f, strokeRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
||||||
|
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, strokeRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
|
||||||
const PDFMappedColor& strokeColor = getMappedStrokeColor();
|
const PDFMappedColor& strokeColor = getMappedStrokeColor();
|
||||||
|
|
||||||
for (int x = strokeRect.left(); x < strokeRect.right(); ++x)
|
for (int x = strokeRect.left(); x < strokeRect.right(); ++x)
|
||||||
|
@ -918,6 +917,14 @@ void PDFTransparencyRenderer::performUpdateGraphicsState(const PDFPageContentPro
|
||||||
m_mappedFillColor.dirty();
|
m_mappedFillColor.dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stateFlags.testFlag(PDFPageContentProcessorState::StateSoftMask))
|
||||||
|
{
|
||||||
|
if (getGraphicState()->getSoftMask())
|
||||||
|
{
|
||||||
|
reportRenderErrorOnce(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Soft mask not implemented."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BaseClass::performUpdateGraphicsState(state);
|
BaseClass::performUpdateGraphicsState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1045,6 +1052,20 @@ void PDFTransparencyRenderer::performTextEnd(ProcessOrder order)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFTransparencyRenderer::performImagePainting(const QImage& image)
|
||||||
|
{
|
||||||
|
Q_UNUSED(image);
|
||||||
|
|
||||||
|
reportRenderErrorOnce(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image painting not implemented."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFTransparencyRenderer::performMeshPainting(const PDFMesh& mesh)
|
||||||
|
{
|
||||||
|
Q_UNUSED(mesh);
|
||||||
|
|
||||||
|
reportRenderErrorOnce(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Mesh painting not implemented."));
|
||||||
|
}
|
||||||
|
|
||||||
PDFReal PDFTransparencyRenderer::getShapeStroking() const
|
PDFReal PDFTransparencyRenderer::getShapeStroking() const
|
||||||
{
|
{
|
||||||
return getGraphicState()->getAlphaIsShape() ? getGraphicState()->getAlphaStroking() : 1.0;
|
return getGraphicState()->getAlphaIsShape() ? getGraphicState()->getAlphaStroking() : 1.0;
|
||||||
|
@ -1532,25 +1553,32 @@ PDFInkMapping PDFInkMapper::createMapping(const PDFAbstractColorSpace* sourceCol
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFPainterPathSampler::PDFPainterPathSampler(QPainterPath path, int samplesCount, PDFColorComponent defaultShape, bool precise) :
|
PDFPainterPathSampler::PDFPainterPathSampler(QPainterPath path, int samplesCount, PDFColorComponent defaultShape, QRect fillRect, bool precise) :
|
||||||
m_defaultShape(defaultShape),
|
m_defaultShape(defaultShape),
|
||||||
m_samplesCount(samplesCount),
|
m_samplesCount(qMax(samplesCount, 1)),
|
||||||
m_precise(precise),
|
m_path(qMove(path)),
|
||||||
m_path(qMove(path))
|
m_fillRect(fillRect),
|
||||||
|
m_precise(precise)
|
||||||
{
|
{
|
||||||
if (!precise)
|
if (!precise)
|
||||||
{
|
{
|
||||||
m_fillPolygon = m_path.toFillPolygon();
|
m_fillPolygon = m_path.toFillPolygon();
|
||||||
|
prepareScanLines();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFColorComponent PDFPainterPathSampler::sample(QPoint point) const
|
PDFColorComponent PDFPainterPathSampler::sample(QPoint point) const
|
||||||
{
|
{
|
||||||
if (m_path.isEmpty())
|
if (m_path.isEmpty() || !m_fillRect.contains(point))
|
||||||
{
|
{
|
||||||
return m_defaultShape;
|
return m_defaultShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_scanLineInfo.empty())
|
||||||
|
{
|
||||||
|
return sampleByScanLine(point);
|
||||||
|
}
|
||||||
|
|
||||||
const qreal coordX1 = point.x();
|
const qreal coordX1 = point.x();
|
||||||
const qreal coordX2 = coordX1 + 1.0;
|
const qreal coordX2 = coordX1 + 1.0;
|
||||||
const qreal coordY1 = point.y();
|
const qreal coordY1 = point.y();
|
||||||
|
@ -1639,6 +1667,181 @@ PDFColorComponent PDFPainterPathSampler::sample(QPoint point) const
|
||||||
return sampleValue;
|
return sampleValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFColorComponent PDFPainterPathSampler::sampleByScanLine(QPoint point) const
|
||||||
|
{
|
||||||
|
int scanLinePosition = point.y() - m_fillRect.y();
|
||||||
|
|
||||||
|
size_t scanLineCountPerPixel = getScanLineCountPerPixel();
|
||||||
|
size_t scanLineTopRow = scanLinePosition * scanLineCountPerPixel;
|
||||||
|
size_t scanLineBottomRow = scanLineTopRow + scanLineCountPerPixel - 1;
|
||||||
|
size_t scanLineGridRowTop = scanLineTopRow + 1;
|
||||||
|
|
||||||
|
Qt::FillRule fillRule = m_path.fillRule();
|
||||||
|
|
||||||
|
auto performSampling = [&](size_t scanLineIndex, PDFReal firstOrdinate, int sampleCount, PDFReal step, PDFReal gain)
|
||||||
|
{
|
||||||
|
ScanLineInfo info = m_scanLineInfo[scanLineIndex];
|
||||||
|
auto it = std::next(m_scanLineSamples.cbegin(), info.indexStart);
|
||||||
|
auto itEnd = std::next(m_scanLineSamples.cbegin(), info.indexEnd);
|
||||||
|
|
||||||
|
PDFReal ordinate = firstOrdinate;
|
||||||
|
PDFColorComponent value = 0.0;
|
||||||
|
auto ordinateIt = std::lower_bound(it, itEnd, ordinate);
|
||||||
|
for (int i = 0; i < sampleCount; ++i)
|
||||||
|
{
|
||||||
|
int windingNumber = ordinateIt->windingNumber;
|
||||||
|
|
||||||
|
const bool inside = (fillRule == Qt::WindingFill) ? windingNumber != 0 : windingNumber % 2 != 0;
|
||||||
|
if (inside)
|
||||||
|
{
|
||||||
|
value += gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
ordinate += step;
|
||||||
|
ordinateIt = std::lower_bound(ordinateIt, itEnd, ordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const qreal coordX1 = point.x();
|
||||||
|
const PDFReal cornerValue = performSampling(scanLineTopRow, coordX1, 2, 1.0, 1.0) + performSampling(scanLineBottomRow, coordX1, 2, 1.0, 1.0);
|
||||||
|
|
||||||
|
if (qFuzzyIsNull(4.0 - cornerValue))
|
||||||
|
{
|
||||||
|
// Whole inside
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qFuzzyIsNull(cornerValue))
|
||||||
|
{
|
||||||
|
// Whole outside
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qreal offset = 1.0f / PDFColorComponent(m_samplesCount + 1);
|
||||||
|
PDFColorComponent sampleValue = 0.0f;
|
||||||
|
const PDFColorComponent sampleGain = 1.0f / PDFColorComponent(m_samplesCount * m_samplesCount);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m_samplesCount; ++i)
|
||||||
|
{
|
||||||
|
sampleValue += performSampling(scanLineGridRowTop++, coordX1 + offset, m_samplesCount, offset, sampleGain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sampleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PDFPainterPathSampler::getScanLineCountPerPixel() const
|
||||||
|
{
|
||||||
|
return m_samplesCount + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFPainterPathSampler::prepareScanLines()
|
||||||
|
{
|
||||||
|
if (m_fillPolygon.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int yOffset = m_fillRect.top(); yOffset < m_fillRect.bottom(); ++yOffset)
|
||||||
|
{
|
||||||
|
const qreal coordY1 = yOffset;
|
||||||
|
const qreal coordY2 = coordY1 + 1.0;
|
||||||
|
|
||||||
|
// Top pixel line
|
||||||
|
if (m_scanLineInfo.empty())
|
||||||
|
{
|
||||||
|
m_scanLineInfo.emplace_back(createScanLine(coordY1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_scanLineInfo.emplace_back(m_scanLineInfo.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample grid
|
||||||
|
const qreal offset = 1.0f / PDFColorComponent(m_samplesCount + 1);
|
||||||
|
for (int iy = 0; iy < m_samplesCount; ++iy)
|
||||||
|
{
|
||||||
|
const qreal y = offset * (iy + 1) + coordY1;
|
||||||
|
m_scanLineInfo.emplace_back(createScanLine(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom pixel line
|
||||||
|
m_scanLineInfo.emplace_back(createScanLine(coordY2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFPainterPathSampler::ScanLineInfo PDFPainterPathSampler::createScanLine(qreal y)
|
||||||
|
{
|
||||||
|
ScanLineInfo result;
|
||||||
|
result.indexStart = m_scanLineSamples.size();
|
||||||
|
|
||||||
|
// Add start item
|
||||||
|
m_scanLineSamples.emplace_back(-std::numeric_limits<PDFReal>::infinity(), 0);
|
||||||
|
|
||||||
|
// Traverse polygon, add sample for each polygon line, we must
|
||||||
|
// also implicitly close last edge (if polygon is not closed)
|
||||||
|
for (int i = 1; i < m_fillPolygon.size(); ++i)
|
||||||
|
{
|
||||||
|
createScanLineSample(m_fillPolygon[i - 1], m_fillPolygon[i], y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fillPolygon.front() != m_fillPolygon.back())
|
||||||
|
{
|
||||||
|
createScanLineSample(m_fillPolygon.back(), m_fillPolygon.front(), y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add end item
|
||||||
|
m_scanLineSamples.emplace_back(+std::numeric_limits<PDFReal>::infinity(), 0);
|
||||||
|
|
||||||
|
result.indexEnd = m_scanLineSamples.size();
|
||||||
|
|
||||||
|
auto it = std::next(m_scanLineSamples.begin(), result.indexStart);
|
||||||
|
auto itEnd = std::next(m_scanLineSamples.begin(), result.indexEnd);
|
||||||
|
|
||||||
|
// Jakub Melka: now, sort the line samples and compute properly the winding number
|
||||||
|
std::sort(it, itEnd);
|
||||||
|
|
||||||
|
int currentWindingNumber = 0;
|
||||||
|
for (; it != itEnd; ++it)
|
||||||
|
{
|
||||||
|
currentWindingNumber += it->windingNumber;
|
||||||
|
it->windingNumber = currentWindingNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFPainterPathSampler::createScanLineSample(const QPointF& p1, const QPointF& p2, qreal y)
|
||||||
|
{
|
||||||
|
PDFReal y1 = p1.y();
|
||||||
|
PDFReal y2 = p2.y();
|
||||||
|
|
||||||
|
if (qFuzzyIsNull(y2 - y1))
|
||||||
|
{
|
||||||
|
// Ignore horizontal lines
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFReal x1 = p1.x();
|
||||||
|
PDFReal x2 = p2.x();
|
||||||
|
|
||||||
|
int windingNumber = 1;
|
||||||
|
if (y2 < y1)
|
||||||
|
{
|
||||||
|
std::swap(y1, y2);
|
||||||
|
std::swap(x1, x2);
|
||||||
|
windingNumber = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have intercept?
|
||||||
|
if (y1 <= y && y < y2)
|
||||||
|
{
|
||||||
|
const PDFReal x = interpolate(y, y1, y2, x1, x2);
|
||||||
|
m_scanLineSamples.emplace_back(x, windingNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PDFDrawBuffer::markActiveColors(uint32_t activeColors)
|
void PDFDrawBuffer::markActiveColors(uint32_t activeColors)
|
||||||
{
|
{
|
||||||
m_activeColors |= activeColors;
|
m_activeColors |= activeColors;
|
||||||
|
|
|
@ -329,21 +329,75 @@ class PDFPainterPathSampler
|
||||||
public:
|
public:
|
||||||
/// Creates new painter path sampler, using given painter path,
|
/// Creates new painter path sampler, using given painter path,
|
||||||
/// sample count (in one direction) and default shape used, when painter path is empty.
|
/// sample count (in one direction) and default shape used, when painter path is empty.
|
||||||
|
/// Fill rectangle is used to precompute winding numbers for samples. Points outside
|
||||||
|
/// of fill rectangle are considered as outside and defaultShape is returned.
|
||||||
/// \param path Sampled path
|
/// \param path Sampled path
|
||||||
/// \param samplesCount Samples count in one direction
|
/// \param samplesCount Samples count in one direction
|
||||||
/// \param defaultShape Default shape returned, if path is empty
|
/// \param defaultShape Default shape returned, if path is empty
|
||||||
/// \param precise Use precise sampling (using spline curves), or fill polygon (fast, but not too precise)
|
/// \param fillRect Fill rectangle (sample point must be in this rectangle)
|
||||||
PDFPainterPathSampler(QPainterPath path, int samplesCount, PDFColorComponent defaultShape, bool precise);
|
/// \param precise Use precise painter path computation
|
||||||
|
PDFPainterPathSampler(QPainterPath path,
|
||||||
|
int samplesCount,
|
||||||
|
PDFColorComponent defaultShape,
|
||||||
|
QRect fillRect,
|
||||||
|
bool precise);
|
||||||
|
|
||||||
/// Return sample value for a given pixel
|
/// Return sample value for a given pixel
|
||||||
PDFColorComponent sample(QPoint point) const;
|
PDFColorComponent sample(QPoint point) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ScanLineSample
|
||||||
|
{
|
||||||
|
inline constexpr ScanLineSample() = default;
|
||||||
|
inline constexpr ScanLineSample(PDFReal x, int windingNumber) :
|
||||||
|
x(x),
|
||||||
|
windingNumber(windingNumber)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const ScanLineSample& other) const { return x < other.x; }
|
||||||
|
bool operator<(PDFReal ordinate) const { return x < ordinate; }
|
||||||
|
|
||||||
|
PDFReal x = 0.0;
|
||||||
|
int windingNumber = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScanLineInfo
|
||||||
|
{
|
||||||
|
size_t indexStart = 0;
|
||||||
|
size_t indexEnd = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Compute sample by using scan lines
|
||||||
|
PDFColorComponent sampleByScanLine(QPoint point) const;
|
||||||
|
|
||||||
|
/// Returns number of scan lines per pixel
|
||||||
|
size_t getScanLineCountPerPixel() const;
|
||||||
|
|
||||||
|
/// Creates scan lines using fill rectangle
|
||||||
|
void prepareScanLines();
|
||||||
|
|
||||||
|
/// Creates scan line for given y coordinate and
|
||||||
|
/// returns info about this scan line
|
||||||
|
/// \param y Vertical coordinate of the sample line
|
||||||
|
ScanLineInfo createScanLine(qreal y);
|
||||||
|
|
||||||
|
/// Creates scan line sample (if horizontal line of y coordinate
|
||||||
|
/// is intersecting boundary line segment p1-p2.
|
||||||
|
/// \param p1 First point of the oriented boundary line segment
|
||||||
|
/// \param p2 Second point of the oriented boundary line segment
|
||||||
|
/// \param y Vertical coordinate of the sample line
|
||||||
|
void createScanLineSample(const QPointF& p1, const QPointF& p2, qreal y);
|
||||||
|
|
||||||
PDFColorComponent m_defaultShape = 0.0;
|
PDFColorComponent m_defaultShape = 0.0;
|
||||||
int m_samplesCount = 0; ///< Samples count in one direction
|
int m_samplesCount = 0; ///< Samples count in one direction
|
||||||
bool m_precise = false;
|
|
||||||
QPainterPath m_path;
|
QPainterPath m_path;
|
||||||
QPolygonF m_fillPolygon;
|
QPolygonF m_fillPolygon;
|
||||||
|
QRect m_fillRect;
|
||||||
|
std::vector<ScanLineSample> m_scanLineSamples;
|
||||||
|
std::vector<ScanLineInfo> m_scanLineInfo;
|
||||||
|
bool m_precise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents draw buffer, into which is current graphics drawn
|
/// Represents draw buffer, into which is current graphics drawn
|
||||||
|
@ -457,6 +511,8 @@ public:
|
||||||
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
|
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
|
||||||
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
|
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
|
||||||
virtual void performTextEnd(ProcessOrder order) override;
|
virtual void performTextEnd(ProcessOrder order) override;
|
||||||
|
virtual void performImagePainting(const QImage& image) override;
|
||||||
|
virtual void performMeshPainting(const PDFMesh& mesh);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue