vcl/headless/CairoCommon.cxx | 207 +++++++++++++++++++++++++++++ vcl/headless/SvpGraphicsBackend.cxx | 31 ++++ vcl/headless/svpgdi.cxx | 250 ------------------------------------ vcl/inc/headless/CairoCommon.hxx | 12 + vcl/inc/headless/svpgdi.hxx | 1 5 files changed, 248 insertions(+), 253 deletions(-)
New commits: commit f1cbb458e158c8f62c9c47917f8ab0ef76ba0ced Author: Tomaž Vajngerl <[email protected]> AuthorDate: Fri Nov 26 19:53:59 2021 +0100 Commit: Tomaž Vajngerl <[email protected]> CommitDate: Thu Dec 30 16:20:39 2021 +0100 vcl: move drawLine to SvpGraphicsBackend Also move getClippedStrokeDamage, AddPolygonToPath, impPixelSnap to CairoCommon, as it is needed by the move. Change-Id: I002f0094935e5f5d4836bb973f7cf7bea0218ff2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/127710 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <[email protected]> diff --git a/vcl/headless/CairoCommon.cxx b/vcl/headless/CairoCommon.cxx index 5ba1ebb0edf1..e79a6b2d6a9b 100644 --- a/vcl/headless/CairoCommon.cxx +++ b/vcl/headless/CairoCommon.cxx @@ -91,6 +91,213 @@ basegfx::B2DRange getClippedFillDamage(cairo_t* cr) return aDamageRect; } +basegfx::B2DRange getStrokeDamage(cairo_t* cr) +{ + double x1, y1, x2, y2; + + // less accurate, but much faster + cairo_path_extents(cr, &x1, &y1, &x2, &y2); + + // support B2DRange::isEmpty() + if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); +} + +basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr) +{ + basegfx::B2DRange aDamageRect(getStrokeDamage(cr)); + aDamageRect.intersect(getClipBox(cr)); + return aDamageRect; +} + +// Remove bClosePath: Checked that the already used mechanism for Win using +// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace +// this. +// For PixelSnap we need the ObjectToDevice transformation here now. This is a +// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in +// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we +// need the ObjectToDevice transformation *without* that offset here to do the +// same. The LineDraw-Offset will be applied by the callers using a linear +// transformation for Cairo now +// For support of PixelSnapHairline we also need the ObjectToDevice transformation +// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g. +// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!) +// tdf#129845 add reply value to allow counting a point/byte/size measurement to +// be included +size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + const sal_uInt32 nPointCount(rPolygon.count()); + size_t nSizeMeasure(0); + + if (0 == nPointCount) + { + return nSizeMeasure; + } + + const bool bHasCurves(rPolygon.areControlPointsUsed()); + const bool bClosePath(rPolygon.isClosed()); + const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity()); + basegfx::B2DHomMatrix aObjectToDeviceInv; + basegfx::B2DPoint aLast; + + for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++) + { + int nClosedIdx = nPointIdx; + if (nPointIdx >= nPointCount) + { + // prepare to close last curve segment if needed + if (bClosePath && (nPointIdx == nPointCount)) + { + nClosedIdx = 0; + } + else + { + break; + } + } + + basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx)); + + if (bPixelSnap) + { + // snap device coordinates to full pixels + if (bObjectToDeviceUsed) + { + // go to DeviceCoordinates + aPoint *= rObjectToDevice; + } + + // snap by rounding + aPoint.setX(basegfx::fround(aPoint.getX())); + aPoint.setY(basegfx::fround(aPoint.getY())); + + if (bObjectToDeviceUsed) + { + if (aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + // go back to ObjectCoordinates + aPoint *= aObjectToDeviceInv; + } + } + + if (bPixelSnapHairline) + { + // snap horizontal and vertical lines (mainly used in Chart for + // 'nicer' AAing) + aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx); + } + + if (!nPointIdx) + { + // first point => just move there + cairo_move_to(cr, aPoint.getX(), aPoint.getY()); + aLast = aPoint; + continue; + } + + bool bPendingCurve(false); + + if (bHasCurves) + { + bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx); + bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx); + } + + if (!bPendingCurve) // line segment + { + cairo_line_to(cr, aPoint.getX(), aPoint.getY()); + nSizeMeasure++; + } + else // cubic bezier segment + { + basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx); + basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx); + + // tdf#99165 if the control points are 'empty', create the mathematical + // correct replacement ones to avoid problems with the graphical sub-system + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if (aCP1.equal(aLast)) + { + aCP1 = aLast + ((aCP2 - aLast) * 0.0005); + } + + if (aCP2.equal(aPoint)) + { + aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005); + } + + cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(), + aPoint.getY()); + // take some bigger measure for curve segments - too expensive to subdivide + // here and that precision not needed, but four (2 points, 2 control-points) + // would be a too low weight + nSizeMeasure += 10; + } + + aLast = aPoint; + } + + if (bClosePath) + { + cairo_close_path(cr); + } + + return nSizeMeasure; +} + +basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple( + basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple( + basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if (bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if (rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + cairo_user_data_key_t* CairoCommon::getDamageKey() { static cairo_user_data_key_t aDamageKey; diff --git a/vcl/headless/SvpGraphicsBackend.cxx b/vcl/headless/SvpGraphicsBackend.cxx index c4bfe42d4844..d3a53fefe3f1 100644 --- a/vcl/headless/SvpGraphicsBackend.cxx +++ b/vcl/headless/SvpGraphicsBackend.cxx @@ -119,9 +119,36 @@ void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color aColor) m_rCairoCommon.releaseCairoContext(cr, true, extents); } -void SvpGraphicsBackend::drawLine(tools::Long /*nX1*/, tools::Long /*nY1*/, tools::Long /*nX2*/, - tools::Long /*nY2*/) +void SvpGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, + tools::Long nY2) { + basegfx::B2DPolygon aPoly; + + // PixelOffset used: To not mix with possible PixelSnap, cannot do + // directly on coordinates as tried before - despite being already 'snapped' + // due to being integer. If it would be directly added here, it would be + // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset + aPoly.append(basegfx::B2DPoint(nX1, nY1)); + aPoly.append(basegfx::B2DPoint(nX2, nY2)); + + cairo_t* cr = m_rCairoCommon.getCairoContext(false, getAntiAlias()); + m_rCairoCommon.clipRegion(cr); + + // PixelOffset used: Set PixelOffset as linear transformation + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + + AddPolygonToPath(cr, aPoly, basegfx::B2DHomMatrix(), !getAntiAlias(), false); + + m_rCairoCommon.applyColor(cr, m_rCairoCommon.m_aLineColor); + + basegfx::B2DRange extents = getClippedStrokeDamage(cr); + extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); + + cairo_stroke(cr); + + m_rCairoCommon.releaseCairoContext(cr, false, extents); } void SvpGraphicsBackend::drawRect(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/, diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx index 4c2695b5c999..9e50652928d9 100644 --- a/vcl/headless/svpgdi.cxx +++ b/vcl/headless/svpgdi.cxx @@ -57,32 +57,6 @@ # endif #endif -namespace -{ - basegfx::B2DRange getStrokeDamage(cairo_t* cr) - { - double x1, y1, x2, y2; - - // less accurate, but much faster - cairo_path_extents(cr, &x1, &y1, &x2, &y2); - - // support B2DRange::isEmpty() - if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) - { - return basegfx::B2DRange(x1, y1, x2, y2); - } - - return basegfx::B2DRange(); - } - - basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr) - { - basegfx::B2DRange aDamageRect(getStrokeDamage(cr)); - aDamageRect.intersect(getClipBox(cr)); - return aDamageRect; - } -} - bool SvpSalGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& /*rBitmap*/ ) { return false; @@ -976,230 +950,6 @@ void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly, 0.0); } -static basegfx::B2DPoint impPixelSnap( - const basegfx::B2DPolygon& rPolygon, - const basegfx::B2DHomMatrix& rObjectToDevice, - basegfx::B2DHomMatrix& rObjectToDeviceInv, - sal_uInt32 nIndex) -{ - const sal_uInt32 nCount(rPolygon.count()); - - // get the data - const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); - const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); - const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); - const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); - - // get the states - const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); - const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); - const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); - const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); - const bool bSnapX(bPrevVertical || bNextVertical); - const bool bSnapY(bPrevHorizontal || bNextHorizontal); - - if(bSnapX || bSnapY) - { - basegfx::B2DPoint aSnappedPoint( - bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), - bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); - - if(rObjectToDeviceInv.isIdentity()) - { - rObjectToDeviceInv = rObjectToDevice; - rObjectToDeviceInv.invert(); - } - - aSnappedPoint *= rObjectToDeviceInv; - - return aSnappedPoint; - } - - return rPolygon.getB2DPoint(nIndex); -} - -// Remove bClosePath: Checked that the already used mechanism for Win using -// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace -// this. -// For PixelSnap we need the ObjectToDevice transformation here now. This is a -// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in -// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we -// need the ObjectToDevice transformation *without* that offset here to do the -// same. The LineDraw-Offset will be applied by the callers using a linear -// transformation for Cairo now -// For support of PixelSnapHairline we also need the ObjectToDevice transformation -// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g. -// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!) -// tdf#129845 add reply value to allow counting a point/byte/size measurement to -// be included -static size_t AddPolygonToPath( - cairo_t* cr, - const basegfx::B2DPolygon& rPolygon, - const basegfx::B2DHomMatrix& rObjectToDevice, - bool bPixelSnap, - bool bPixelSnapHairline) -{ - // short circuit if there is nothing to do - const sal_uInt32 nPointCount(rPolygon.count()); - size_t nSizeMeasure(0); - - if(0 == nPointCount) - { - return nSizeMeasure; - } - - const bool bHasCurves(rPolygon.areControlPointsUsed()); - const bool bClosePath(rPolygon.isClosed()); - const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity()); - basegfx::B2DHomMatrix aObjectToDeviceInv; - basegfx::B2DPoint aLast; - - for( sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ ) - { - int nClosedIdx = nPointIdx; - if( nPointIdx >= nPointCount ) - { - // prepare to close last curve segment if needed - if( bClosePath && (nPointIdx == nPointCount) ) - { - nClosedIdx = 0; - } - else - { - break; - } - } - - basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx)); - - if(bPixelSnap) - { - // snap device coordinates to full pixels - if(bObjectToDeviceUsed) - { - // go to DeviceCoordinates - aPoint *= rObjectToDevice; - } - - // snap by rounding - aPoint.setX( basegfx::fround( aPoint.getX() ) ); - aPoint.setY( basegfx::fround( aPoint.getY() ) ); - - if(bObjectToDeviceUsed) - { - if(aObjectToDeviceInv.isIdentity()) - { - aObjectToDeviceInv = rObjectToDevice; - aObjectToDeviceInv.invert(); - } - - // go back to ObjectCoordinates - aPoint *= aObjectToDeviceInv; - } - } - - if(bPixelSnapHairline) - { - // snap horizontal and vertical lines (mainly used in Chart for - // 'nicer' AAing) - aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx); - } - - if( !nPointIdx ) - { - // first point => just move there - cairo_move_to(cr, aPoint.getX(), aPoint.getY()); - aLast = aPoint; - continue; - } - - bool bPendingCurve(false); - - if( bHasCurves ) - { - bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx ); - bPendingCurve |= rPolygon.isPrevControlPointUsed( nClosedIdx ); - } - - if( !bPendingCurve ) // line segment - { - cairo_line_to(cr, aPoint.getX(), aPoint.getY()); - nSizeMeasure++; - } - else // cubic bezier segment - { - basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx ); - basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx ); - - // tdf#99165 if the control points are 'empty', create the mathematical - // correct replacement ones to avoid problems with the graphical sub-system - // tdf#101026 The 1st attempt to create a mathematically correct replacement control - // vector was wrong. Best alternative is one as close as possible which means short. - if (aCP1.equal(aLast)) - { - aCP1 = aLast + ((aCP2 - aLast) * 0.0005); - } - - if(aCP2.equal(aPoint)) - { - aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005); - } - - cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), - aPoint.getX(), aPoint.getY()); - // take some bigger measure for curve segments - too expensive to subdivide - // here and that precision not needed, but four (2 points, 2 control-points) - // would be a too low weight - nSizeMeasure += 10; - } - - aLast = aPoint; - } - - if( bClosePath ) - { - cairo_close_path(cr); - } - - return nSizeMeasure; -} - -void SvpSalGraphics::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) -{ - basegfx::B2DPolygon aPoly; - - // PixelOffset used: To not mix with possible PixelSnap, cannot do - // directly on coordinates as tried before - despite being already 'snapped' - // due to being integer. If it would be directly added here, it would be - // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset - aPoly.append(basegfx::B2DPoint(nX1, nY1)); - aPoly.append(basegfx::B2DPoint(nX2, nY2)); - - cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias()); - clipRegion(cr); - - // PixelOffset used: Set PixelOffset as linear transformation - cairo_matrix_t aMatrix; - cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); - cairo_set_matrix(cr, &aMatrix); - - AddPolygonToPath( - cr, - aPoly, - basegfx::B2DHomMatrix(), - !getAntiAlias(), - false); - - m_aCairoCommon.applyColor(cr, m_aCairoCommon.m_aLineColor); - - basegfx::B2DRange extents = getClippedStrokeDamage(cr); - extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); - - cairo_stroke(cr); - - m_aCairoCommon.releaseCairoContext(cr, false, extents); -} - namespace { class SystemDependentData_CairoPath : public basegfx::SystemDependentData diff --git a/vcl/inc/headless/CairoCommon.hxx b/vcl/inc/headless/CairoCommon.hxx index 3642a2361d84..36acef3c659d 100644 --- a/vcl/inc/headless/CairoCommon.hxx +++ b/vcl/inc/headless/CairoCommon.hxx @@ -30,6 +30,7 @@ #include <basegfx/range/b2drange.hxx> #include <basegfx/range/b2irange.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> //Using formats that match cairo's formats. For android we patch cairo, //which is internal in that case, to swap the rgb components so that @@ -71,6 +72,17 @@ VCL_DLLPUBLIC void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, d VCL_DLLPUBLIC basegfx::B2DRange getFillDamage(cairo_t* cr); VCL_DLLPUBLIC basegfx::B2DRange getClipBox(cairo_t* cr); VCL_DLLPUBLIC basegfx::B2DRange getClippedFillDamage(cairo_t* cr); +VCL_DLLPUBLIC basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr); +VCL_DLLPUBLIC basegfx::B2DRange getStrokeDamage(cairo_t* cr); + +VCL_DLLPUBLIC size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap, + bool bPixelSnapHairline); + +VCL_DLLPUBLIC basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex); enum class PaintMode { diff --git a/vcl/inc/headless/svpgdi.hxx b/vcl/inc/headless/svpgdi.hxx index dc042c499e76..5f5be07a43a5 100644 --- a/vcl/inc/headless/svpgdi.hxx +++ b/vcl/inc/headless/svpgdi.hxx @@ -137,7 +137,6 @@ public: virtual void DrawTextLayout( const GenericSalLayout& ) override; virtual bool supportsOperation( OutDevSupportType ) const override; - virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override; virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; virtual bool drawPolyPolygon(
