summaryrefslogtreecommitdiff
path: root/vcl/win/gdi/gdiimpl.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/win/gdi/gdiimpl.cxx')
-rw-r--r--vcl/win/gdi/gdiimpl.cxx362
1 files changed, 249 insertions, 113 deletions
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx
index 915cd9351521..16fd8128a8e8 100644
--- a/vcl/win/gdi/gdiimpl.cxx
+++ b/vcl/win/gdi/gdiimpl.cxx
@@ -35,6 +35,7 @@
#include <vcl/salbtype.hxx>
#include <win/salframe.h>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/tools/systemdependentdata.hxx>
#include "outdata.hxx"
#include "win/salids.hrc"
@@ -1895,69 +1896,127 @@ bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt
return bRet;
}
+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);
+}
+
void impAddB2DPolygonToGDIPlusGraphicsPathReal(
Gdiplus::GraphicsPath& rGraphicsPath,
const basegfx::B2DPolygon& rPolygon,
- bool bNoLineJoin)
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ bool bNoLineJoin,
+ bool bPixelSnapHairline)
{
sal_uInt32 nCount(rPolygon.count());
if(nCount)
{
const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
- const bool bControls(rPolygon.areControlPointsUsed());
- basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
- for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ if(nEdgeCount)
{
- const sal_uInt32 nNextIndex((a + 1) % nCount);
- const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
- const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
- const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
+ const bool bControls(rPolygon.areControlPointsUsed());
+ basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
+ basegfx::B2DHomMatrix aObjectToDeviceInv;
- if(b1stControlPointUsed || b2ndControlPointUsed)
+ if(bPixelSnapHairline)
{
- basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
- basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));
-
- // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
- // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
- // no 1st or 2nd control point, despite that these are mathematicaly correct definitions
- // (basegfx can handle that). To solve, create replacement vectors to thre resp. next
- // control point with 1/3rd of length (the default control vector for these cases).
- // Only one of this can happen here, else the is(Next|Prev)ControlPointUsed wopuld have
- // both been false.
- // Caution: This error (and it's correction) might be necessary for other graphical
- // sub-systems in a similar way
- if(!b1stControlPointUsed)
- {
- aCa = aCurr + ((aCb - aCurr) * 0.3);
- }
- else if(!b2ndControlPointUsed)
+ aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
+ }
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nCount);
+ basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
+ const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
+ const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
+
+ if(bPixelSnapHairline)
{
- aCb = aNext + ((aCa - aNext) * 0.3);
+ aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
}
- rGraphicsPath.AddBezier(
- static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
- static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
- static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
- static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
- }
- else
- {
- rGraphicsPath.AddLine(
- static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
- static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
- }
+ if(b1stControlPointUsed || b2ndControlPointUsed)
+ {
+ basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
+ basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));
+
+ // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
+ // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
+ // no 1st or 2nd control point, despite that these are mathematicaly correct definitions
+ // (basegfx can handle that).
+ // Caution: This error (and it's correction) might be necessary for other graphical
+ // sub-systems in a similar way.
+ // 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(!b1stControlPointUsed)
+ {
+ aCa = aCurr + ((aCb - aCurr) * 0.0005);
+ }
+ else if(!b2ndControlPointUsed)
+ {
+ aCb = aNext + ((aCa - aNext) * 0.0005);
+ }
- if(a + 1 < nEdgeCount)
- {
- aCurr = aNext;
+ rGraphicsPath.AddBezier(
+ static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
+ static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
+ static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
+ static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
+ }
+ else
+ {
+ rGraphicsPath.AddLine(
+ static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
+ static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
+ }
- if(bNoLineJoin)
+ if(a + 1 < nEdgeCount)
{
- rGraphicsPath.StartFigure();
+ aCurr = aNext;
+
+ if(bNoLineJoin)
+ {
+ rGraphicsPath.StartFigure();
+ }
}
}
}
@@ -1984,7 +2043,12 @@ bool WinSalGraphicsImpl::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPo
aGraphicsPath.StartFigure();
}
- impAddB2DPolygonToGDIPlusGraphicsPathReal(aGraphicsPath, rPolyPolygon.getB2DPolygon(a), false);
+ impAddB2DPolygonToGDIPlusGraphicsPathReal(
+ aGraphicsPath,
+ rPolyPolygon.getB2DPolygon(a),
+ basegfx::B2DHomMatrix(),
+ false,
+ false);
aGraphicsPath.CloseFigure();
}
@@ -2024,99 +2088,171 @@ bool WinSalGraphicsImpl::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPo
return true;
}
+class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
+{
+private:
+ Gdiplus::GraphicsPath maGraphicsPath;
+ bool mbPixelSnapHairline;
+
+public:
+ SystemDependentData_GraphicsPath(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager);
+ virtual ~SystemDependentData_GraphicsPath() override;
+
+ Gdiplus::GraphicsPath& getGraphicsPath() { return maGraphicsPath; }
+
+ bool getPixelSnapHairline() const { return mbPixelSnapHairline; }
+ void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; }
+};
+
+SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ maGraphicsPath(),
+ mbPixelSnapHairline(false)
+{
+}
+
+SystemDependentData_GraphicsPath::~SystemDependentData_GraphicsPath()
+{
+}
+
bool WinSalGraphicsImpl::drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
const basegfx::B2DPolygon& rPolygon,
double fTransparency,
const basegfx::B2DVector& rLineWidths,
basegfx::B2DLineJoin eLineJoin,
css::drawing::LineCap eLineCap,
- double fMiterMinimumAngle)
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
{
- const sal_uInt32 nCount(rPolygon.count());
-
- if(mbPen && nCount)
+ if(!mbPen || 0 == rPolygon.count())
{
- Gdiplus::Graphics aGraphics(mrParent.getHDC());
- const sal_uInt8 aTrans = (sal_uInt8)basegfx::fround( 255 * (1.0 - fTransparency) );
- const Gdiplus::Color aTestColor(aTrans, SALCOLOR_RED(maLineColor), SALCOLOR_GREEN(maLineColor), SALCOLOR_BLUE(maLineColor));
- Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX()));
- Gdiplus::GraphicsPath aGraphicsPath(Gdiplus::FillModeAlternate);
- bool bNoLineJoin(false);
+ return true;
+ }
+
+ Gdiplus::Graphics aGraphics(mrParent.getHDC());
+ const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
+ const Gdiplus::Color aTestColor(aTrans, SALCOLOR_RED(maLineColor), SALCOLOR_GREEN(maLineColor), SALCOLOR_BLUE(maLineColor));
+ Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX()));
+ bool bNoLineJoin(false);
+ Gdiplus::Matrix aMatrix;
+
+ // Set full (Object-to-Device) transformation
+ aMatrix.SetElements(
+ rObjectToDevice.get(0, 0),
+ rObjectToDevice.get(1, 0),
+ rObjectToDevice.get(0, 1),
+ rObjectToDevice.get(1, 1),
+ rObjectToDevice.get(0, 2),
+ rObjectToDevice.get(1, 2));
+ aGraphics.SetTransform(&aMatrix);
- switch(eLineJoin)
+ switch(eLineJoin)
+ {
+ case basegfx::B2DLineJoin::NONE :
{
- case basegfx::B2DLineJoin::NONE :
- {
- if(basegfx::fTools::more(rLineWidths.getX(), 0.0))
- {
- bNoLineJoin = true;
- }
- break;
- }
- case basegfx::B2DLineJoin::Bevel :
+ if(basegfx::fTools::more(rLineWidths.getX(), 0.0))
{
- aPen.SetLineJoin(Gdiplus::LineJoinBevel);
- break;
- }
- case basegfx::B2DLineJoin::Miter :
- {
- const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));
-
- aPen.SetMiterLimit(aMiterLimit);
- // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
- // graphics, somewhere clipped in some distance from the edge point, dependent
- // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
- // that instead
- aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
- break;
- }
- case basegfx::B2DLineJoin::Round :
- {
- aPen.SetLineJoin(Gdiplus::LineJoinRound);
- break;
+ bNoLineJoin = true;
}
+ break;
}
-
- switch(eLineCap)
+ case basegfx::B2DLineJoin::Bevel :
{
- default: /*css::drawing::LineCap_BUTT*/
- {
- // nothing to do
- break;
- }
- case css::drawing::LineCap_ROUND:
- {
- aPen.SetStartCap(Gdiplus::LineCapRound);
- aPen.SetEndCap(Gdiplus::LineCapRound);
- break;
- }
- case css::drawing::LineCap_SQUARE:
- {
- aPen.SetStartCap(Gdiplus::LineCapSquare);
- aPen.SetEndCap(Gdiplus::LineCapSquare);
- break;
- }
+ aPen.SetLineJoin(Gdiplus::LineJoinBevel);
+ break;
}
+ case basegfx::B2DLineJoin::Miter :
+ {
+ const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));
+
+ aPen.SetMiterLimit(aMiterLimit);
+ // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
+ // graphics, somewhere clipped in some distance from the edge point, dependent
+ // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
+ // that instead
+ aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
+ break;
+ }
+ case basegfx::B2DLineJoin::Round :
+ {
+ aPen.SetLineJoin(Gdiplus::LineJoinRound);
+ break;
+ }
+ }
- impAddB2DPolygonToGDIPlusGraphicsPathReal(aGraphicsPath, rPolygon, bNoLineJoin);
-
- if(rPolygon.isClosed() && !bNoLineJoin)
+ switch(eLineCap)
+ {
+ default: /*css::drawing::LineCap_BUTT*/
{
- // #i101491# needed to create the correct line joins
- aGraphicsPath.CloseFigure();
+ // nothing to do
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ aPen.SetStartCap(Gdiplus::LineCapRound);
+ aPen.SetEndCap(Gdiplus::LineCapRound);
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ aPen.SetStartCap(Gdiplus::LineCapSquare);
+ aPen.SetEndCap(Gdiplus::LineCapSquare);
+ break;
}
+ }
- if(mrParent.getAntiAliasB2DDraw())
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
+ rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());
+
+ if(pSystemDependentData_GraphicsPath)
+ {
+ // check data validity
+ if(pSystemDependentData_GraphicsPath->getPixelSnapHairline() != bPixelSnapHairline)
{
- aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
+ // data invalid, forget
+ pSystemDependentData_GraphicsPath.reset();
}
- else
+ }
+
+ if(!pSystemDependentData_GraphicsPath)
+ {
+ // add to buffering mechanism
+ pSystemDependentData_GraphicsPath = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
+ SalGraphics::getSystemDependentDataManager());
+
+ // fill data of buffered data
+ pSystemDependentData_GraphicsPath->setPixelSnapHairline(bPixelSnapHairline);
+
+ impAddB2DPolygonToGDIPlusGraphicsPathReal(
+ pSystemDependentData_GraphicsPath->getGraphicsPath(),
+ rPolygon,
+ rObjectToDevice,
+ bNoLineJoin,
+ bPixelSnapHairline);
+
+ if(rPolygon.isClosed() && !bNoLineJoin)
{
- aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
+ // #i101491# needed to create the correct line joins
+ pSystemDependentData_GraphicsPath->getGraphicsPath().CloseFigure();
}
+ }
- aGraphics.DrawPath(&aPen, &aGraphicsPath);
+ if(mrParent.getAntiAliasB2DDraw())
+ {
+ aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
}
+ else
+ {
+ aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
+ }
+
+ aGraphics.DrawPath(
+ &aPen,
+ &pSystemDependentData_GraphicsPath->getGraphicsPath());
return true;
}