mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Path sampling, painting paths
This commit is contained in:
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user