Path sampling, painting paths

This commit is contained in:
Jakub Melka
2021-02-07 18:14:36 +01:00
parent 33df634a15
commit 6a9cc5c5d6
4 changed files with 351 additions and 2 deletions

View File

@ -444,7 +444,17 @@ void PDFPageContentProcessor::performOutputCharacter(const PDFTextCharacterInfo&
Q_UNUSED(info);
}
bool PDFPageContentProcessor::isContentKindSuppressed(PDFPageContentProcessor::ContentKind kind) const
void PDFPageContentProcessor::performTextBegin(ProcessOrder order)
{
Q_UNUSED(order);
}
void PDFPageContentProcessor::performTextEnd(ProcessOrder order)
{
Q_UNUSED(order);
}
bool PDFPageContentProcessor::isContentKindSuppressed(ContentKind kind) const
{
Q_UNUSED(kind);
return false;
@ -2580,11 +2590,13 @@ void PDFPageContentProcessor::operatorColorSetDeviceCMYKFilling(PDFReal c, PDFRe
void PDFPageContentProcessor::operatorTextBegin()
{
performTextBegin(ProcessOrder::BeforeOperation);
m_graphicState.setTextMatrix(QMatrix());
m_graphicState.setTextLineMatrix(QMatrix());
updateGraphicState();
++m_textBeginEndState;
performTextBegin(ProcessOrder::AfterOperation);
if (m_textBeginEndState > 1)
{
@ -2599,12 +2611,14 @@ void PDFPageContentProcessor::operatorTextEnd()
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Text object ended more than once."));
}
performTextEnd(ProcessOrder::BeforeOperation);
if (!m_textClippingPath.isEmpty())
{
QPainterPath clippingPath = m_graphicState.getCurrentTransformationMatrix().inverted().map(m_textClippingPath);
performClipping(clippingPath, clippingPath.fillRule());
m_textClippingPath = QPainterPath();
}
performTextEnd(ProcessOrder::AfterOperation);
}
void PDFPageContentProcessor::operatorTextSetCharacterSpacing(PDFReal charSpacing)

View File

@ -569,6 +569,12 @@ protected:
/// Implement to react on character printing
virtual void performOutputCharacter(const PDFTextCharacterInfo& info);
/// Implement to respond to text begin operator
virtual void performTextBegin(ProcessOrder order);
/// Implement to respond to text end operator
virtual void performTextEnd(ProcessOrder order);
enum class ContentKind
{
Shapes, ///< General shapes (they can be also shaded / tiled)

View File

@ -719,7 +719,129 @@ QImage PDFTransparencyRenderer::toImage(bool use16Bit) const
void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
PDFPainterPathSampler clipSampler(m_painterStateStack.top().clipPath, m_settings.samplesCount, 1.0f);
QMatrix worldMatrix = getCurrentWorldMatrix();
const PDFReal shapeStroking = getShapeStroking();
const PDFReal opacityStroking = getOpacityStroking();
const PDFReal shapeFilling = getShapeFilling();
const PDFReal opacityFilling = getOpacityFilling();
PDFPixelFormat format = m_drawBuffer.getPixelFormat();
Q_ASSERT(format.hasShapeChannel());
Q_ASSERT(format.hasOpacityChannel());
const uint8_t shapeChannel = format.getShapeChannelIndex();
const uint8_t opacityChannel = format.getOpacityChannelIndex();
const uint8_t colorChannelStart = format.getColorChannelIndexStart();
const uint8_t colorChannelEnd = format.getColorChannelIndexEnd();
if (fill)
{
QPainterPath worldPath = worldMatrix.map(path);
QRect fillRect = getActualFillRect(worldPath.controlPointRect());
// Fill rect may be, or may not be valid. It depends on the painter path
// and world matrix. Path can be translated outside of the paint area.
if (fillRect.isValid())
{
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f);
const PDFMappedColor& fillColor = getMappedFillColor();
for (int x = fillRect.left(); x < fillRect.right(); ++x)
{
for (int y = fillRect.top(); y < fillRect.bottom(); ++y)
{
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)
{
// 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)
{
pixel[colorChannelIndex] = fillColor.mappedColor[colorChannelIndex];
}
}
}
}
m_drawBuffer.markActiveColors(fillColor.activeChannels);
m_drawBuffer.modify(fillRect);
}
}
if (stroke)
{
// We must stroke the path.
QPainterPathStroker stroker;
stroker.setCapStyle(m_graphicState.getLineCapStyle());
stroker.setWidth(m_graphicState.getLineWidth());
stroker.setMiterLimit(m_graphicState.getMitterLimit());
stroker.setJoinStyle(m_graphicState.getLineJoinStyle());
const PDFLineDashPattern& lineDashPattern = m_graphicState.getLineDashPattern();
if (!lineDashPattern.isSolid())
{
stroker.setDashPattern(QVector<PDFReal>::fromStdVector(lineDashPattern.getDashArray()));
stroker.setDashOffset(lineDashPattern.getDashOffset());
}
QPainterPath strokedPath = stroker.createStroke(path);
QPainterPath worldPath = worldMatrix.map(strokedPath);
QRect strokeRect = getActualFillRect(worldPath.controlPointRect());
// Fill rect may be, or may not be valid. It depends on the painter path
// and world matrix. Path can be translated outside of the paint area.
if (strokeRect.isValid())
{
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f);
const PDFMappedColor& strokeColor = getMappedStrokeColor();
for (int x = strokeRect.left(); x < strokeRect.right(); ++x)
{
for (int y = strokeRect.top(); y < strokeRect.bottom(); ++y)
{
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)
{
// 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)
{
pixel[colorChannelIndex] = strokeColor.mappedColor[colorChannelIndex];
}
}
}
}
m_drawBuffer.markActiveColors(strokeColor.activeChannels);
m_drawBuffer.modify(strokeRect);
}
}
if (!text || !getGraphicState()->getTextKnockout())
{
flushDrawBuffer();
}
}
void PDFTransparencyRenderer::performClipping(const QPainterPath& path, Qt::FillRule fillRule)
@ -837,6 +959,9 @@ void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order,
// in the immediate backdrop, so we will make it transparent.
data.immediateBackdrop.makeTransparent();
// Create draw buffer
m_drawBuffer = PDFDrawBuffer(data.immediateBackdrop.getWidth(), data.immediateBackdrop.getHeight(), data.immediateBackdrop.getPixelFormat());
m_transparencyGroupDataStack.emplace_back(qMove(data));
invalidateCachedItems();
}
@ -860,10 +985,22 @@ void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, co
PDFFloatBitmap::blend(sourceData.immediateBackdrop, targetData.immediateBackdrop, *getBackdrop(), *getInitialBackdrop(), sourceData.softMask,
sourceData.alphaIsShape, sourceData.alphaFill, BlendMode::Normal, targetData.group.knockout, 0xFFFF, PDFFloatBitmap::OverprintMode::NoOveprint);
// Create draw buffer
PDFFloatBitmapWithColorSpace* backdrop = getImmediateBackdrop();
m_drawBuffer = PDFDrawBuffer(backdrop->getWidth(), backdrop->getHeight(), backdrop->getPixelFormat());
invalidateCachedItems();
}
}
void PDFTransparencyRenderer::performTextEnd(ProcessOrder order)
{
if (order == ProcessOrder::AfterOperation)
{
flushDrawBuffer();
}
}
PDFReal PDFTransparencyRenderer::getShapeStroking() const
{
return getGraphicState()->getAlphaIsShape() ? getGraphicState()->getAlphaStroking() : 1.0;
@ -1085,6 +1222,30 @@ PDFTransparencyRenderer::PDFMappedColor PDFTransparencyRenderer::getMappedFillCo
return createMappedColor(sourceColor, sourceColorSpace);
}
QRect PDFTransparencyRenderer::getPaintRect() const
{
return QRect(0, 0, getBackdrop()->getWidth(), getBackdrop()->getHeight());
}
QRect PDFTransparencyRenderer::getActualFillRect(QRectF& fillRect) const
{
int xLeft = qFloor(fillRect.left()) - 1;
int xRight = qCeil(fillRect.right()) + 1;
int yTop = qFloor(fillRect.top()) - 1;
int yBottom = qCeil(fillRect.bottom()) + 1;
QRect drawRect(xLeft, yTop, xRight - xLeft, yBottom - yTop);
return getPaintRect().intersected(drawRect);
}
void PDFTransparencyRenderer::flushDrawBuffer()
{
if (m_drawBuffer.isModified())
{
m_drawBuffer.clear();
}
}
PDFInkMapper::PDFInkMapper(const PDFDocument* document) :
m_document(document)
{
@ -1289,4 +1450,109 @@ PDFInkMapping PDFInkMapper::createMapping(const PDFAbstractColorSpace* sourceCol
return mapping;
}
PDFPainterPathSampler::PDFPainterPathSampler(QPainterPath path, int samplesCount, PDFColorComponent defaultShape) :
m_defaultShape(defaultShape),
m_samplesCount(samplesCount),
m_path(qMove(path))
{
}
PDFColorComponent PDFPainterPathSampler::sample(QPoint point) const
{
if (m_path.isEmpty())
{
return m_defaultShape;
}
const qreal coordX1 = point.x();
const qreal coordX2 = coordX1 + 1.0;
const qreal coordY1 = point.x();
const qreal coordY2 = coordX1 + 1.0;
const qreal centerX = (coordX1 + coordX2) * 0.5;
const qreal centerY = (coordY1 + coordY2) * 0.5;
const QPointF topLeft(coordX1, coordY1);
const QPointF topRight(coordX2, coordY1);
const QPointF bottomLeft(coordX1, coordY2);
const QPointF bottomRight(coordX2, coordY2);
if (m_samplesCount <= 1)
{
// Jakub Melka: Just one sample
return m_path.contains(QPointF(centerX, centerY)) ? 1.0f : 0.0f;
}
int cornerHits = 0;
cornerHits += m_path.contains(topLeft) ? 1 : 0;
cornerHits += m_path.contains(topRight) ? 1 : 0;
cornerHits += m_path.contains(bottomLeft) ? 1 : 0;
cornerHits += m_path.contains(bottomRight) ? 1 : 0;
if (cornerHits == 4)
{
// Completely inside
return 1.0;
}
if (cornerHits == 0)
{
// Completely outside
return 0.0;
}
// Otherwise we must use regular sample grid
const qreal offset = 1.0f / PDFColorComponent(m_samplesCount + 1);
PDFColorComponent sampleValue = 0.0f;
const PDFColorComponent sampleGain = 1.0f / PDFColorComponent(m_samplesCount * m_samplesCount);
for (int ix = 0; ix < m_samplesCount; ++ix)
{
const qreal x = offset * (ix + 1) + coordX1;
for (int iy = 0; iy < m_samplesCount; ++iy)
{
const qreal y = offset * (iy + 1) + coordY1;
if (m_path.contains(QPointF(x, y)))
{
sampleValue += sampleGain;
}
}
}
return sampleValue;
}
void PDFDrawBuffer::markActiveColors(uint32_t activeColors)
{
m_activeColors |= activeColors;
}
void PDFDrawBuffer::clear()
{
if (!m_modifiedRect.isValid())
{
return;
}
for (int x = m_modifiedRect.left(); x <= m_modifiedRect.right(); ++x)
{
for (int y = m_modifiedRect.top(); y <= m_modifiedRect.bottom(); ++y)
{
PDFColorBuffer buffer = getPixel(x, y);
std::fill(buffer.begin(), buffer.end(), 0.0f);
}
}
m_activeColors = 0;
m_modifiedRect = QRect();
}
void PDFDrawBuffer::modify(QRect rect)
{
m_modifiedRect = m_modifiedRect.united(rect);
}
} // namespace pdf

View File

@ -320,6 +320,56 @@ private:
size_t m_activeSpotColors = 0;
};
/// Painter path sampler. Returns shape value of pixel. This sampler
/// uses MSAA with regular grid.
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.
/// \param path Sampled path
/// \param samplesCount Samples count in one direction
/// \param defaultShape Default shape returned, if path is empty
PDFPainterPathSampler(QPainterPath path, int samplesCount, PDFColorComponent defaultShape);
/// Return sample value for a given pixel
PDFColorComponent sample(QPoint point) const;
private:
PDFColorComponent m_defaultShape = 0.0;
int m_samplesCount; ///< Samples count in one direction
QPainterPath m_path;
};
/// Represents draw buffer, into which is current graphics drawn
class PDFDrawBuffer : public PDFFloatBitmap
{
public:
using PDFFloatBitmap::PDFFloatBitmap;
/// Marks color channels as active
void markActiveColors(uint32_t activeColors);
/// Clears the draw buffer
void clear();
/// Marks given area as modified
void modify(QRect rect);
/// Returns true, if draw buffer is modified and needs to be flushed
bool isModified() const { return m_modifiedRect.isValid(); }
private:
uint32_t m_activeColors = 0;
QRect m_modifiedRect;
};
struct PDFTransparencyRendererSettings
{
/// Sample count for MSAA antialiasing
int samplesCount = 16;
};
/// Renders PDF pages with transparency, using 32-bit floating point precision.
/// Both device color space and blending color space can be defined. It implements
/// page blending space and device blending space. So, painted graphics is being
@ -374,6 +424,7 @@ public:
virtual void performRestoreGraphicState(ProcessOrder order) override;
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
virtual void performTextEnd(ProcessOrder order) override;
private:
@ -382,7 +433,6 @@ private:
PDFReal getShapeFilling() const;
PDFReal getOpacityFilling() const;
struct PDFTransparencyGroupPainterData
{
PDFTransparencyGroup group;
@ -436,6 +486,17 @@ private:
PDFMappedColor getMappedStrokeColorImpl();
PDFMappedColor getMappedFillColorImpl();
/// Returns painting rectangle (i.e. rectangle, which has topleft coordinate 0,0
/// and has width/height equal to bitmap width/height)
QRect getPaintRect() const;
/// Returns fill area from fill rectangle
/// \param fillRect Fill rectangle
QRect getActualFillRect(QRectF& fillRect) const;
/// Flushes draw buffer
void flushDrawBuffer();
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;
@ -445,6 +506,8 @@ private:
bool m_active;
PDFCachedItem<PDFMappedColor> m_mappedStrokeColor;
PDFCachedItem<PDFMappedColor> m_mappedFillColor;
PDFTransparencyRendererSettings m_settings;
PDFDrawBuffer m_drawBuffer;
};
} // namespace pdf