Rework of sampler

This commit is contained in:
Jakub Melka 2021-02-12 20:09:46 +01:00
parent 3da8cf2252
commit 5cd493d19d
2 changed files with 273 additions and 14 deletions

View File

@ -742,7 +742,7 @@ QImage PDFTransparencyRenderer::toImage(bool use16Bit, bool usePaper, PDFRGB pap
PDFFloatBitmap softMask(paperImage.getWidth(), paperImage.getHeight(), PDFPixelFormat::createOpacityMask());
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);
return toImageImpl(paperImage, use16Bit);
@ -755,9 +755,6 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
{
Q_UNUSED(fillRule);
PDFPainterPathSampler clipSampler(m_painterStateStack.top().clipPath, m_settings.samplesCount, 1.0f,
m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
QMatrix worldMatrix = getCurrentWorldMatrix();
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.
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();
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.
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();
for (int x = strokeRect.left(); x < strokeRect.right(); ++x)
@ -918,6 +917,14 @@ void PDFTransparencyRenderer::performUpdateGraphicsState(const PDFPageContentPro
m_mappedFillColor.dirty();
}
if (stateFlags.testFlag(PDFPageContentProcessorState::StateSoftMask))
{
if (getGraphicState()->getSoftMask())
{
reportRenderErrorOnce(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Soft mask not implemented."));
}
}
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
{
return getGraphicState()->getAlphaIsShape() ? getGraphicState()->getAlphaStroking() : 1.0;
@ -1532,25 +1553,32 @@ PDFInkMapping PDFInkMapper::createMapping(const PDFAbstractColorSpace* sourceCol
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_samplesCount(samplesCount),
m_precise(precise),
m_path(qMove(path))
m_samplesCount(qMax(samplesCount, 1)),
m_path(qMove(path)),
m_fillRect(fillRect),
m_precise(precise)
{
if (!precise)
{
m_fillPolygon = m_path.toFillPolygon();
prepareScanLines();
}
}
PDFColorComponent PDFPainterPathSampler::sample(QPoint point) const
{
if (m_path.isEmpty())
if (m_path.isEmpty() || !m_fillRect.contains(point))
{
return m_defaultShape;
}
if (!m_scanLineInfo.empty())
{
return sampleByScanLine(point);
}
const qreal coordX1 = point.x();
const qreal coordX2 = coordX1 + 1.0;
const qreal coordY1 = point.y();
@ -1639,6 +1667,181 @@ PDFColorComponent PDFPainterPathSampler::sample(QPoint point) const
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)
{
m_activeColors |= activeColors;

View File

@ -329,21 +329,75 @@ class PDFPainterPathSampler
public:
/// Creates new painter path sampler, using given painter path,
/// 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 samplesCount Samples count in one direction
/// \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)
PDFPainterPathSampler(QPainterPath path, int samplesCount, PDFColorComponent defaultShape, bool precise);
/// \param fillRect Fill rectangle (sample point must be in this rectangle)
/// \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
PDFColorComponent sample(QPoint point) const;
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;
int m_samplesCount = 0; ///< Samples count in one direction
bool m_precise = false;
QPainterPath m_path;
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
@ -457,6 +511,8 @@ public:
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
virtual void performTextEnd(ProcessOrder order) override;
virtual void performImagePainting(const QImage& image) override;
virtual void performMeshPainting(const PDFMesh& mesh);
private: