summaryrefslogtreecommitdiff
path: root/vcl/headless/svpgdi.cxx
diff options
context:
space:
mode:
authorArmin Le Grand <Armin.Le.Grand@cib.de>2018-08-24 13:01:08 +0200
committerArmin Le Grand <Armin.Le.Grand@cib.de>2018-08-30 19:48:46 +0200
commitb9fa01a8d1137a95af9865a3e47995734c40da6e (patch)
tree6d1e0a3e44b1a96fe5302d779c00fbee55cf8d24 /vcl/headless/svpgdi.cxx
parentf4a9ce33415a85d0b86ced3a0bf780f4ec61e25f (diff)
Support buffering SystemDependent GraphicData
This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
Diffstat (limited to 'vcl/headless/svpgdi.cxx')
-rw-r--r--vcl/headless/svpgdi.cxx289
1 files changed, 238 insertions, 51 deletions
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index f8ada21f8acd..fdf2295f367f 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -35,6 +35,8 @@
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
#if ENABLE_CAIRO_CANVAS
# if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
@@ -720,8 +722,15 @@ void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
aPoly.setClosed(false);
- drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
- css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/);
+ drawPolyLine(
+ basegfx::B2DHomMatrix(),
+ aPoly,
+ 0.0,
+ basegfx::B2DVector(1.0, 1.0),
+ basegfx::B2DLineJoin::Miter,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default*/,
+ false);
}
void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
@@ -879,18 +888,141 @@ void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
releaseCairoContext(cr, false, extents);
}
-basegfx::B2DRange SvpSalGraphics::drawPolyLine(
+class SystemDependentData_CairoPath : public basegfx::SystemDependentData
+{
+private:
+ cairo_path_t* mpCairoPath;
+
+public:
+ SystemDependentData_CairoPath(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ cairo_path_t* pCairoPath);
+ virtual ~SystemDependentData_CairoPath() override;
+
+ cairo_path_t* getCairoPath() { return mpCairoPath; }
+};
+
+SystemDependentData_CairoPath::SystemDependentData_CairoPath(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ cairo_path_t* pCairoPath)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ mpCairoPath(pCairoPath)
+{
+}
+
+SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
+{
+ if(nullptr != mpCairoPath)
+ {
+ cairo_path_destroy(mpCairoPath);
+ mpCairoPath = nullptr;
+ }
+}
+
+bool SvpSalGraphics::drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine,
+ double fTransparency,
+ const basegfx::B2DVector& rLineWidths,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ // short circuit if there is nothing to do
+ if(0 == rPolyLine.count())
+ {
+ return true;
+ }
+
+ // Wrap call to static verion of ::drawPolyLine by
+ // preparing/getting some local data and parameters
+ // due to usage in vcl/unx/generic/gdi/salgdi.cxx.
+ // This is mainly about extended handling of extents
+ // and the way destruction of CairoContext is handled
+ // due to current XOR stuff
+ cairo_t* cr = getCairoContext(false);
+ basegfx::B2DRange aExtents;
+ clipRegion(cr);
+
+ bool bRetval(
+ drawPolyLine(
+ cr,
+ &aExtents,
+ m_aLineColor,
+ getAntiAliasB2DDraw(),
+ rObjectToDevice,
+ rPolyLine,
+ fTransparency,
+ rLineWidths,
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline));
+
+ releaseCairoContext(cr, false, aExtents);
+
+ return bRetval;
+}
+
+bool SvpSalGraphics::drawPolyLine(
cairo_t* cr,
+ basegfx::B2DRange* pExtents,
const Color& rLineColor,
bool bAntiAliasB2DDraw,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
const basegfx::B2DPolygon& rPolyLine,
double fTransparency,
const basegfx::B2DVector& rLineWidths,
basegfx::B2DLineJoin eLineJoin,
css::drawing::LineCap eLineCap,
- double fMiterMinimumAngle)
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
{
- const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0));
+ // short circuit if there is nothing to do
+ if(0 == rPolyLine.count())
+ {
+ return true;
+ }
+
+ // need to check/handle LineWidth when ObjectToDevice transformation is used
+ basegfx::B2DVector aLineWidths(rLineWidths);
+ const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
+ const basegfx::B2DVector aDeviceLineWidths(bObjectToDeviceIsIdentity ? rLineWidths : rObjectToDevice * rLineWidths);
+ const bool bCorrectLineWidth(!bObjectToDeviceIsIdentity && aDeviceLineWidths.getX() < 1.0 && aLineWidths.getX() >= 1.0);
+
+ // on-demand inverse of ObjectToDevice transformation
+ basegfx::B2DHomMatrix aObjectToDeviceInv;
+
+ if(bCorrectLineWidth)
+ {
+ if(aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
+
+ // calculate-back logical LineWidth for a hairline
+ aLineWidths = aObjectToDeviceInv * basegfx::B2DVector(1.0, 1.0);
+ }
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ // set ObjectToDevice transformation
+ cairo_matrix_t aMatrix;
+
+ cairo_matrix_init(
+ &aMatrix,
+ rObjectToDevice.get( 0, 0 ),
+ rObjectToDevice.get( 1, 0 ),
+ rObjectToDevice.get( 0, 1 ),
+ rObjectToDevice.get( 1, 1 ),
+ rObjectToDevice.get( 0, 2 ),
+ rObjectToDevice.get( 1, 2 ));
+ cairo_set_matrix(cr, &aMatrix);
+ }
+
+ const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(aLineWidths.getX(), 0.0)));
// setup line attributes
cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
@@ -933,76 +1065,131 @@ basegfx::B2DRange SvpSalGraphics::drawPolyLine(
}
}
- cairo_set_source_rgba(cr, rLineColor.GetRed()/255.0,
- rLineColor.GetGreen()/255.0,
- rLineColor.GetBlue()/255.0,
- 1.0-fTransparency);
+ cairo_set_source_rgba(
+ cr,
+ rLineColor.GetRed()/255.0,
+ rLineColor.GetGreen()/255.0,
+ rLineColor.GetBlue()/255.0,
+ 1.0-fTransparency);
cairo_set_line_join(cr, eCairoLineJoin);
cairo_set_line_cap(cr, eCairoLineCap);
- cairo_set_line_width(cr, rLineWidths.getX());
+ cairo_set_line_width(cr, aLineWidths.getX());
cairo_set_miter_limit(cr, fMiterLimit);
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
+ rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
- basegfx::B2DRange extents(0, 0, 0, 0);
+ if(pSystemDependentData_CairoPath)
+ {
+ // check data validity
+ if(nullptr == pSystemDependentData_CairoPath->getCairoPath())
+ {
+ // data invalid, forget
+ pSystemDependentData_CairoPath.reset();
+ }
+ }
- if (!bNoJoin)
+ if(pSystemDependentData_CairoPath)
{
- AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true);
- extents = getClippedStrokeDamage(cr);
- cairo_stroke(cr);
+ // re-use data
+ cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
}
else
{
- const int nPointCount = rPolyLine.count();
- // emulate rendering::PathJoinType::NONE by painting single edges
- const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
- basegfx::B2DPolygon aEdge;
- aEdge.append(rPolyLine.getB2DPoint(0));
- aEdge.append(basegfx::B2DPoint(0.0, 0.0));
+ // create data
+ basegfx::B2DPolygon aPolyLine(rPolyLine);
- for (sal_uInt32 i = 0; i < nEdgeCount; ++i)
+ if(bPixelSnapHairline)
{
- const sal_uInt32 nNextIndex((i + 1) % nPointCount);
- aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
- aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i % nPointCount));
- aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
+ // Need to take care of PixelSnapHairline now. The 'short' version
+ // will manipulate the Polygon by using the known tooling at
+ // basegfx. To do this correct, this needs to be done in device
+ // coordinates, so when the transformation is used, transform
+ // to device first, execute, transform back using the inverse.
+ // The important part for buffering the result and not need to
+ // do this at each repaint (for now) is to change a copy of the
+ // Polygon to create the CairoData, but to buffer it at the original
+ // unmodified Polygon.
+ // The 'long' version would be to add this to AddPolygonToPath
+ // equal as done in Win version (see impPixelSnap), should be done
+ // later
+ if(!bObjectToDeviceIsIdentity)
+ {
+ aPolyLine.transform(rObjectToDevice);
+ }
- AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true);
+ aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
- extents.expand(getStrokeDamage(cr));
+ if(!bObjectToDeviceIsIdentity)
+ {
+ if(aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
- cairo_stroke(cr);
+ aPolyLine.transform(aObjectToDeviceInv);
+ }
+ }
- // prepare next step
- aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
+ if (!bNoJoin)
+ {
+ AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true);
}
+ else
+ {
+ const sal_uInt32 nPointCount(rPolyLine.count());
+ const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DPolygon aEdge;
- extents.intersect(getClipBox(cr));
- }
+ aEdge.append(rPolyLine.getB2DPoint(0));
+ aEdge.append(basegfx::B2DPoint(0.0, 0.0));
- return extents;
-}
+ for (sal_uInt32 i(0); i < nEdgeCount; i++)
+ {
+ const sal_uInt32 nNextIndex((i + 1) % nPointCount);
+ aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
+ aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i));
+ aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
-bool SvpSalGraphics::drawPolyLine(
- const basegfx::B2DPolygon& rPolyLine,
- double fTransparency,
- const basegfx::B2DVector& rLineWidths,
- basegfx::B2DLineJoin eLineJoin,
- css::drawing::LineCap eLineCap,
- double fMiterMinimumAngle)
-{
- // short circuit if there is nothing to do
- if (rPolyLine.count() <= 0)
- return true;
+ AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true);
- cairo_t* cr = getCairoContext(false);
- clipRegion(cr);
+ // prepare next step
+ aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
+ }
+ }
- basegfx::B2DRange extents = drawPolyLine(cr, m_aLineColor, getAntiAliasB2DDraw(), rPolyLine,
- fTransparency, rLineWidths, eLineJoin, eLineCap, fMiterMinimumAngle);
+ // copy and add to buffering mechanism
+ rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
+ SalGraphics::getSystemDependentDataManager(),
+ cairo_copy_path(cr));
+ }
- releaseCairoContext(cr, false, extents);
+ // extract extents
+ if(nullptr != pExtents)
+ {
+ *pExtents = getClippedStrokeDamage(cr);
+ }
+
+ // draw and consume
+ cairo_stroke(cr);
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ // reset ObjectToDevice transformation if was set (safe, may
+ // be better suited at ::getCairoContext)
+ cairo_identity_matrix(cr);
+ }
+
+ if(nullptr != pExtents && !pExtents->isEmpty() && !bObjectToDeviceIsIdentity)
+ {
+ // transform extents to DeviceCoordiinates if used. These
+ // were calculated with ObjectToDevice transformation actively set,
+ // but use DeviceCoordinates locally
+ pExtents->transform(rObjectToDevice);
+ }
return true;
}