Issue #123: Blend2D painting implementation

This commit is contained in:
Jakub Melka
2024-02-10 18:04:58 +01:00
parent d314683d38
commit f3e1a94e1c
25 changed files with 844 additions and 778 deletions

View File

@ -0,0 +1,755 @@
// Copyright (C) 2024 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#include "pdfblpainter.h"
#include "pdffont.h"
#include <QThread>
#include <QPainterPath>
#include <QPaintEngine>
#include <Blend2d.h>
namespace pdf
{
class PDFBLPaintEngine : public QPaintEngine
{
public:
explicit PDFBLPaintEngine(QImage& qtOffscreenBuffer, bool isMultithreaded);
virtual bool begin(QPaintDevice*) override;
virtual bool end() override;
virtual void updateState(const QPaintEngineState& updatedState) override;
virtual void drawRects(const QRect* rects, int rectCount) override;
virtual void drawRects(const QRectF* rects, int rectCount) override;
virtual void drawLines(const QLine* lines, int lineCount) override;
virtual void drawLines(const QLineF* lines, int lineCount) override;
virtual void drawEllipse(const QRectF& r) override;
virtual void drawEllipse(const QRect& r) override;
virtual void drawPath(const QPainterPath& path) override;
virtual void drawPoints(const QPointF* points, int pointCount) override;
virtual void drawPoints(const QPoint* points, int pointCount) override;
virtual void drawPolygon(const QPointF* points, int pointCount, PolygonDrawMode mode) override;
virtual void drawPolygon(const QPoint* points, int pointCount, PolygonDrawMode mode) override;
virtual void drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr) override;
virtual void drawTextItem(const QPointF& p, const QTextItem& textItem) override;
virtual void drawTiledPixmap(const QRectF& r, const QPixmap& pixmap, const QPointF& s) override;
virtual void drawImage(const QRectF& r, const QImage& pm, const QRectF& sr, Qt::ImageConversionFlags flags) override;
virtual Type type() const override;
static PaintEngineFeatures getStaticFeatures();
private:
/// Get BL matrix from transformation
static BLMatrix2D getBLMatrix(QTransform transform);
static BLPoint getBLPoint(const QPoint& point);
static BLPoint getBLPoint(const QPointF& point);
/// Get BL rect from regular rect
static BLRectI getBLRect(QRect rect);
/// Get BL rect from regular rect
static BLRect getBLRect(QRectF rect);
/// Get BL path from path
static BLPath getBLPath(const QPainterPath& path);
/// Set pen to the context
static void setBLPen(BLContext& context, const QPen& pen);
/// Set brush to the context
static void setBLBrush(BLContext& context, const QBrush& brush);
/// Load font
static bool loadBLFont(BLFont& font, QString fontName, PDFReal fontSize);
/// Returns composition operator
static BLCompOp getBLCompOp(QPainter::CompositionMode mode);
bool isStrokeActive() const { return m_currentPen.style() != Qt::NoPen; }
bool isFillActive() const { return m_currentBrush.style() != Qt::NoBrush; }
QImage& m_qtOffscreenBuffer;
std::optional<BLContext> m_blContext;
std::optional<BLImage> m_blOffscreenBuffer;
bool m_isMultithreaded;
QPen m_currentPen;
QBrush m_currentBrush;
QFont m_currentFont;
};
PDFBLPaintDevice::PDFBLPaintDevice(QImage& offscreenBuffer, bool isMultithreaded) :
m_offscreenBuffer(offscreenBuffer),
m_paintEngine(new PDFBLPaintEngine(offscreenBuffer, isMultithreaded))
{
}
PDFBLPaintDevice::~PDFBLPaintDevice()
{
delete m_paintEngine;
m_paintEngine = nullptr;
}
int PDFBLPaintDevice::devType() const
{
return QInternal::CustomRaster;
}
QPaintEngine* PDFBLPaintDevice::paintEngine() const
{
return m_paintEngine;
}
int PDFBLPaintDevice::metric(PaintDeviceMetric metric) const
{
switch (metric)
{
case QPaintDevice::PdmWidth:
return m_offscreenBuffer.width();
case QPaintDevice::PdmHeight:
return m_offscreenBuffer.height();
case QPaintDevice::PdmWidthMM:
return m_offscreenBuffer.widthMM();
case QPaintDevice::PdmHeightMM:
return m_offscreenBuffer.heightMM();
case QPaintDevice::PdmNumColors:
return m_offscreenBuffer.colorCount();
case QPaintDevice::PdmDepth:
return m_offscreenBuffer.depth();
case QPaintDevice::PdmDpiX:
return m_offscreenBuffer.logicalDpiX();
case QPaintDevice::PdmDpiY:
return m_offscreenBuffer.logicalDpiY();
case QPaintDevice::PdmPhysicalDpiX:
return m_offscreenBuffer.physicalDpiX();
case QPaintDevice::PdmPhysicalDpiY:
return m_offscreenBuffer.physicalDpiY();
case QPaintDevice::PdmDevicePixelRatio:
return m_offscreenBuffer.devicePixelRatio();
case QPaintDevice::PdmDevicePixelRatioScaled:
return m_offscreenBuffer.devicePixelRatioFScale();
default:
Q_ASSERT(false);
break;
}
return 0;
}
PDFBLPaintEngine::PDFBLPaintEngine(QImage& qtOffscreenBuffer, bool isMultithreaded) :
QPaintEngine(getStaticFeatures()),
m_qtOffscreenBuffer(qtOffscreenBuffer),
m_isMultithreaded(isMultithreaded)
{
}
bool PDFBLPaintEngine::begin(QPaintDevice*)
{
if (isActive())
{
return false;
}
m_blContext.emplace();
m_blOffscreenBuffer.emplace();
BLContextCreateInfo info{};
if (m_isMultithreaded)
{
info.flags = BL_CONTEXT_CREATE_FLAG_FALLBACK_TO_SYNC;
info.threadCount = QThread::idealThreadCount();
}
m_blContext->setHint(BL_CONTEXT_HINT_RENDERING_QUALITY, BL_RENDERING_QUALITY_MAX_VALUE);
m_blOffscreenBuffer->createFromData(m_qtOffscreenBuffer.width(), m_qtOffscreenBuffer.height(), BL_FORMAT_PRGB32, m_qtOffscreenBuffer.bits(), m_qtOffscreenBuffer.bytesPerLine());
if (m_blContext->begin(m_blOffscreenBuffer.value(), info) == BL_SUCCESS)
{
m_blContext->clearAll();
return true;
}
else
{
m_blContext.reset();
m_blOffscreenBuffer.reset();
}
return false;
}
bool PDFBLPaintEngine::end()
{
if (!isActive())
{
return false;
}
m_blContext->end();
m_blContext.reset();
m_blOffscreenBuffer.reset();
return true;
}
void PDFBLPaintEngine::updateState(const QPaintEngineState& updatedState)
{
/* DirtyBrushOrigin = 0x0004,
DirtyFont = 0x0008,
DirtyBackground = 0x0010,
DirtyBackgroundMode = 0x0020,
DirtyClipRegion = 0x0080,
DirtyClipPath = 0x0100,
DirtyHints = 0x0200,
DirtyClipEnabled = 0x0800,*/
if (updatedState.state().testFlag(QPaintEngine::DirtyPen))
{
m_currentPen = updatedState.pen();
setBLPen(m_blContext.value(), updatedState.pen());
}
if (updatedState.state().testFlag(QPaintEngine::DirtyBrush))
{
m_currentBrush = updatedState.brush();
setBLBrush(m_blContext.value(), updatedState.brush());
}
if (updatedState.state().testFlag(QPaintEngine::DirtyCompositionMode))
{
m_blContext->setCompOp(getBLCompOp(updatedState.compositionMode()));
}
if (updatedState.state().testFlag(QPaintEngine::DirtyOpacity))
{
m_blContext->setGlobalAlpha(updatedState.opacity());
}
if (updatedState.state().testFlag(QPaintEngine::DirtyTransform))
{
m_blContext->setMatrix(getBLMatrix(updatedState.transform()));
}
}
void PDFBLPaintEngine::drawRects(const QRect* rects, int rectCount)
{
BLArray<BLRectI> blRects;
blRects.reserve(rectCount);
for (int i = 0; i < rectCount; ++i)
{
blRects.append(getBLRect(rects[i]));
}
if (isFillActive())
{
m_blContext->fillRectArray(blRects.view());
}
if (isStrokeActive())
{
m_blContext->strokeRectArray(blRects.view());
}
}
void PDFBLPaintEngine::drawRects(const QRectF* rects, int rectCount)
{
BLArray<BLRect> blRects;
blRects.reserve(rectCount);
for (int i = 0; i < rectCount; ++i)
{
blRects.append(getBLRect(rects[i]));
}
if (isFillActive())
{
m_blContext->fillRectArray(blRects.view());
}
if (isStrokeActive())
{
m_blContext->strokeRectArray(blRects.view());
}
}
void PDFBLPaintEngine::drawLines(const QLine* lines, int lineCount)
{
if (!isStrokeActive())
{
return;
}
for (int i = 0; i < lineCount; ++i)
{
const QLine& line = lines[i];
m_blContext->strokeLine(line.x1(), line.y1(), line.x2(), line.y2());
}
}
void PDFBLPaintEngine::drawLines(const QLineF* lines, int lineCount)
{
if (!isStrokeActive())
{
return;
}
for (int i = 0; i < lineCount; ++i)
{
const QLineF& line = lines[i];
m_blContext->strokeLine(line.x1(), line.y1(), line.x2(), line.y2());
}
}
void PDFBLPaintEngine::drawEllipse(const QRectF& r)
{
QPointF c = r.center();
BLEllipse blEllipse(c.x(), c.y(), r.width() * 0.5, r.height() * 0.5);
if (isFillActive())
{
m_blContext->fillEllipse(blEllipse);
}
if (isStrokeActive())
{
m_blContext->strokeEllipse(blEllipse);
}
}
void PDFBLPaintEngine::drawEllipse(const QRect& r)
{
QPointF c = r.center();
BLEllipse blEllipse(c.x(), c.y(), r.width() * 0.5, r.height() * 0.5);
if (isFillActive())
{
m_blContext->fillEllipse(blEllipse);
}
if (isStrokeActive())
{
m_blContext->strokeEllipse(blEllipse);
}
}
void PDFBLPaintEngine::drawPath(const QPainterPath& path)
{
BLPath blPath = getBLPath(path);
BLFillRule fillRule{};
switch (path.fillRule())
{
case Qt::OddEvenFill:
fillRule = BL_FILL_RULE_EVEN_ODD;
break;
case Qt::WindingFill:
fillRule = BL_FILL_RULE_NON_ZERO;
break;
default:
Q_ASSERT(false);
break;
}
m_blContext->setFillRule(fillRule);
if (isFillActive())
{
m_blContext->fillPath(blPath);
}
if (isStrokeActive())
{
m_blContext->strokePath(blPath);
}
}
void PDFBLPaintEngine::drawPoints(const QPointF* points, int pointCount)
{
}
void PDFBLPaintEngine::drawPoints(const QPoint* points, int pointCount)
{
}
void PDFBLPaintEngine::drawPolygon(const QPointF* points, int pointCount, PolygonDrawMode mode)
{
}
void PDFBLPaintEngine::drawPolygon(const QPoint* points, int pointCount, PolygonDrawMode mode)
{
}
void PDFBLPaintEngine::drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr)
{
}
void PDFBLPaintEngine::drawTextItem(const QPointF& p, const QTextItem& textItem)
{
}
void PDFBLPaintEngine::drawTiledPixmap(const QRectF& r, const QPixmap& pixmap, const QPointF& s)
{
}
void PDFBLPaintEngine::drawImage(const QRectF& r, const QImage& pm, const QRectF& sr, Qt::ImageConversionFlags flags)
{
QImage image = pm;
if (image.format() != QImage::Format_ARGB32_Premultiplied)
{
image.convertTo(QImage::Format_ARGB32_Premultiplied);
}
BLImage blImage;
blImage.createFromData(image.width(), image.height(), BL_FORMAT_PRGB32, image.bits(), image.bytesPerLine());
BLImage blDrawImage;
blDrawImage.assignDeep(blImage);
m_blContext->blitImage(BLRect(r.x(), r.y(), r.width(), r.height()),
blDrawImage,
BLRectI(sr.x(), sr.y(), sr.width(), sr.height()));
}
QPaintEngine::Type PDFBLPaintEngine::type() const
{
return User;
}
QPaintEngine::PaintEngineFeatures PDFBLPaintEngine::getStaticFeatures()
{
return PrimitiveTransform | PixmapTransform | LinearGradientFill |
RadialGradientFill | ConicalGradientFill | AlphaBlend |
PorterDuff | PainterPaths | Antialiasing | ConstantOpacity |
BlendModes | PaintOutsidePaintEvent;
}
BLMatrix2D PDFBLPaintEngine::getBLMatrix(QTransform transform)
{
BLMatrix2D matrix;
matrix.reset(transform.m11(), transform.m12(), transform.m21(), transform.m22(), transform.dx(), transform.dy());
return matrix;
}
BLPoint PDFBLPaintEngine::getBLPoint(const QPoint& point)
{
return BLPoint(point.x(), point.y());
}
BLPoint PDFBLPaintEngine::getBLPoint(const QPointF& point)
{
return BLPoint(point.x(), point.y());
}
BLRectI PDFBLPaintEngine::getBLRect(QRect rect)
{
return BLRectI(rect.x(), rect.y(), rect.width(), rect.height());
}
BLRect PDFBLPaintEngine::getBLRect(QRectF rect)
{
return BLRect(rect.x(), rect.y(), rect.width(), rect.height());
}
BLPath PDFBLPaintEngine::getBLPath(const QPainterPath& path)
{
BLPath blPath;
int elementCount = path.elementCount();
for (int i = 0; i < elementCount; ++i) {
const QPainterPath::Element& element = path.elementAt(i);
switch (element.type)
{
case QPainterPath::MoveToElement:
blPath.moveTo(element.x, element.y);
break;
case QPainterPath::LineToElement:
blPath.lineTo(element.x, element.y);
break;
case QPainterPath::CurveToElement:
if (i + 2 < elementCount)
{
const QPainterPath::Element& ctrlPoint1 = path.elementAt(i++);
const QPainterPath::Element& ctrlPoint2 = path.elementAt(i++);
const QPainterPath::Element& endPoint = path.elementAt(i);
blPath.cubicTo(ctrlPoint1.x, ctrlPoint1.y, ctrlPoint2.x, ctrlPoint2.y, endPoint.x, endPoint.y);
}
break;
case QPainterPath::CurveToDataElement:
Q_ASSERT(false);
break;
}
}
return blPath;
}
void PDFBLPaintEngine::setBLPen(BLContext& context, const QPen& pen)
{
const Qt::PenCapStyle capStyle = pen.capStyle();
const Qt::PenJoinStyle joinStyle = pen.joinStyle();
const QColor color = pen.color();
const qreal width = pen.widthF();
const qreal miterLimit = pen.miterLimit();
const qreal dashOffset = pen.dashOffset();
const QList<qreal> customDashPattern = pen.dashPattern();
const Qt::PenStyle penStyle = pen.style();
context.setStrokeAlpha(pen.color().alphaF());
context.setStrokeWidth(width);
context.setStrokeMiterLimit(miterLimit);
switch (capStyle)
{
case Qt::FlatCap:
context.setStrokeCaps(BL_STROKE_CAP_BUTT);
break;
case Qt::SquareCap:
context.setStrokeCaps(BL_STROKE_CAP_SQUARE);
break;
case Qt::RoundCap:
context.setStrokeCaps(BL_STROKE_CAP_ROUND);
break;
}
BLArray<double> dashArray;
for (double value : customDashPattern)
{
dashArray.append(value);
}
context.setStrokeDashOffset(dashOffset);
context.setStrokeDashArray(dashArray);
switch (joinStyle)
{
case Qt::MiterJoin:
context.setStrokeJoin(BL_STROKE_JOIN_MITER_CLIP);
break;
case Qt::BevelJoin:
context.setStrokeJoin(BL_STROKE_JOIN_BEVEL);
break;
case Qt::RoundJoin:
context.setStrokeJoin(BL_STROKE_JOIN_ROUND);
break;
case Qt::SvgMiterJoin:
context.setStrokeJoin(BL_STROKE_JOIN_MITER_CLIP);
break;
}
context.setStrokeStyle(BLRgba32(color.rgba()));
BLStrokeOptions strokeOptions = context.strokeOptions();
switch (penStyle)
{
case Qt::SolidLine:
strokeOptions.dashArray.clear();
strokeOptions.dashOffset = 0.0;
break;
case Qt::DashLine:
{
constexpr double dashPattern[] = {4, 4};
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
break;
}
case Qt::DotLine:
{
constexpr double dashPattern[] = {1, 3};
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
break;
}
case Qt::DashDotLine:
{
constexpr double dashPattern[] = {4, 2, 1, 2};
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
break;
}
case Qt::DashDotDotLine:
{
constexpr double dashPattern[] = {4, 2, 1, 2, 1, 2};
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
break;
}
default:
break;
}
context.setStrokeOptions(strokeOptions);
}
void PDFBLPaintEngine::setBLBrush(BLContext& context, const QBrush& brush)
{
auto setGradientStops = [](BLGradient& blGradient, const auto& qGradient)
{
QVector<BLGradientStop> stops;
for (const auto& stop : qGradient.stops())
{
stops.append(BLGradientStop(stop.first, BLRgba32(stop.second.red(), stop.second.green(), stop.second.blue(), stop.second.alpha())));
}
blGradient.assignStops(stops.constData(), stops.size());
};
switch (brush.style())
{
default:
case Qt::SolidPattern:
{
QColor color = brush.color();
BLRgba32 blColor = BLRgba32(color.red(), color.green(), color.blue(), color.alpha());
context.setFillStyle(blColor);
break;
}
case Qt::LinearGradientPattern:
{
const QGradient* gradient = brush.gradient();
if (gradient && gradient->type() == QGradient::LinearGradient)
{
const QLinearGradient* linearGradient = static_cast<const QLinearGradient*>(gradient);
BLLinearGradientValues blLinearGradient;
blLinearGradient.x0 = linearGradient->start().x();
blLinearGradient.y0 = linearGradient->start().y();
blLinearGradient.x1 = linearGradient->finalStop().x();
blLinearGradient.y1 = linearGradient->finalStop().y();
BLGradient blGradient(blLinearGradient);
setGradientStops(blGradient, *gradient);
context.setFillStyle(blGradient);
}
break;
}
case Qt::RadialGradientPattern:
{
const QGradient* gradient = brush.gradient();
if (gradient && gradient->type() == QGradient::RadialGradient)
{
const QRadialGradient* radialGradient = static_cast<const QRadialGradient*>(gradient);
BLRadialGradientValues blRadialGradientValues;
blRadialGradientValues.x0 = radialGradient->center().x();
blRadialGradientValues.y0 = radialGradient->center().y();
blRadialGradientValues.x1 = radialGradient->focalPoint().x();
blRadialGradientValues.y1 = radialGradient->focalPoint().y();
blRadialGradientValues.r0 = radialGradient->radius();
BLGradient blGradient(blRadialGradientValues);
setGradientStops(blGradient, *gradient);
context.setFillStyle(blGradient);
}
break;
}
}
}
bool PDFBLPaintEngine::loadBLFont(BLFont& font, QString fontName, PDFReal fontSize)
{
QByteArray data = PDFSystemFont::getFontData(fontName.toLatin1());
BLFontData blFontData;
if (blFontData.createFromData(data.data(), data.size()) == BL_SUCCESS)
{
BLFontFace fontFace;
if (fontFace.createFromData(blFontData, 0) == BL_SUCCESS)
{
if (font.createFromFace(fontFace, fontSize) == BL_SUCCESS)
{
return true;
}
}
}
return false;
}
BLCompOp PDFBLPaintEngine::getBLCompOp(QPainter::CompositionMode mode)
{
switch (mode)
{
case QPainter::CompositionMode_SourceOver:
return BL_COMP_OP_SRC_OVER;
case QPainter::CompositionMode_DestinationOver:
return BL_COMP_OP_DST_OVER;
case QPainter::CompositionMode_Clear:
return BL_COMP_OP_CLEAR;
case QPainter::CompositionMode_Source:
return BL_COMP_OP_SRC_COPY;
case QPainter::CompositionMode_Destination:
return BL_COMP_OP_DST_COPY;
case QPainter::CompositionMode_SourceIn:
return BL_COMP_OP_SRC_IN;
case QPainter::CompositionMode_DestinationIn:
return BL_COMP_OP_DST_IN;
case QPainter::CompositionMode_SourceOut:
return BL_COMP_OP_SRC_OUT;
case QPainter::CompositionMode_DestinationOut:
return BL_COMP_OP_DST_OUT;
case QPainter::CompositionMode_SourceAtop:
return BL_COMP_OP_SRC_ATOP;
case QPainter::CompositionMode_DestinationAtop:
return BL_COMP_OP_DST_ATOP;
case QPainter::CompositionMode_Xor:
return BL_COMP_OP_XOR;
case QPainter::CompositionMode_Plus:
return BL_COMP_OP_PLUS;
case QPainter::CompositionMode_Multiply:
return BL_COMP_OP_MULTIPLY;
case QPainter::CompositionMode_Screen:
return BL_COMP_OP_SCREEN;
case QPainter::CompositionMode_Overlay:
return BL_COMP_OP_OVERLAY;
case QPainter::CompositionMode_Darken:
return BL_COMP_OP_DARKEN;
case QPainter::CompositionMode_Lighten:
return BL_COMP_OP_LIGHTEN;
case QPainter::CompositionMode_ColorDodge:
return BL_COMP_OP_COLOR_DODGE;
case QPainter::CompositionMode_ColorBurn:
return BL_COMP_OP_COLOR_BURN;
case QPainter::CompositionMode_HardLight:
return BL_COMP_OP_HARD_LIGHT;
case QPainter::CompositionMode_SoftLight:
return BL_COMP_OP_SOFT_LIGHT;
case QPainter::CompositionMode_Difference:
return BL_COMP_OP_DIFFERENCE;
case QPainter::CompositionMode_Exclusion:
return BL_COMP_OP_EXCLUSION;
default:
break;
}
return BL_COMP_OP_SRC_OVER;
}
} // namespace pdf