1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <sal/config.h>
12 #include <drawinglayer/processor2d/cairopixelprocessor2d.hxx>
13 #include <sal/log.hxx>
14 #include <vcl/BitmapTools.hxx>
15 #include <vcl/cairo.hxx>
16 #include <vcl/outdev.hxx>
17 #include <vcl/svapp.hxx>
18 #include <basegfx/polygon/b2dpolygontools.hxx>
19 #include <basegfx/polygon/b2dpolypolygontools.hxx>
20 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
21 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
22 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
23 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
24 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
25 #include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
26 #include <drawinglayer/primitive2d/baseprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
32 #include <drawinglayer/primitive2d/Tools.hxx>
33 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
34 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
35 #include <drawinglayer/converters.hxx>
36 #include <basegfx/curve/b2dcubicbezier.hxx>
37 #include <basegfx/matrix/b2dhommatrixtools.hxx>
38 #include <basegfx/utils/systemdependentdata.hxx>
39 #include <vcl/BitmapReadAccess.hxx>
41 using namespace com::sun::star
;
45 basegfx::B2DPoint
impPixelSnap(const basegfx::B2DPolygon
& rPolygon
,
46 const drawinglayer::geometry::ViewInformation2D
& rViewInformation
,
49 const sal_uInt32
nCount(rPolygon
.count());
52 const basegfx::B2ITuple
aPrevTuple(
53 basegfx::fround(rViewInformation
.getObjectToViewTransformation()
54 * rPolygon
.getB2DPoint((nIndex
+ nCount
- 1) % nCount
)));
55 const basegfx::B2DPoint
aCurrPoint(rViewInformation
.getObjectToViewTransformation()
56 * rPolygon
.getB2DPoint(nIndex
));
57 const basegfx::B2ITuple
aCurrTuple(basegfx::fround(aCurrPoint
));
58 const basegfx::B2ITuple
aNextTuple(
59 basegfx::fround(rViewInformation
.getObjectToViewTransformation()
60 * rPolygon
.getB2DPoint((nIndex
+ 1) % nCount
)));
63 const bool bPrevVertical(aPrevTuple
.getX() == aCurrTuple
.getX());
64 const bool bNextVertical(aNextTuple
.getX() == aCurrTuple
.getX());
65 const bool bPrevHorizontal(aPrevTuple
.getY() == aCurrTuple
.getY());
66 const bool bNextHorizontal(aNextTuple
.getY() == aCurrTuple
.getY());
67 const bool bSnapX(bPrevVertical
|| bNextVertical
);
68 const bool bSnapY(bPrevHorizontal
|| bNextHorizontal
);
72 basegfx::B2DPoint
aSnappedPoint(bSnapX
? aCurrTuple
.getX() : aCurrPoint
.getX(),
73 bSnapY
? aCurrTuple
.getY() : aCurrPoint
.getY());
75 aSnappedPoint
*= rViewInformation
.getInverseObjectToViewTransformation();
80 return rPolygon
.getB2DPoint(nIndex
);
83 void addB2DPolygonToPathGeometry(cairo_t
* cr
, const basegfx::B2DPolygon
& rPolygon
,
84 const drawinglayer::geometry::ViewInformation2D
* pViewInformation
)
86 // short circuit if there is nothing to do
87 const sal_uInt32
nPointCount(rPolygon
.count());
89 const bool bHasCurves(rPolygon
.areControlPointsUsed());
90 const bool bClosePath(rPolygon
.isClosed());
91 basegfx::B2DPoint aLast
;
93 for (sal_uInt32 nPointIdx
= 0, nPrevIdx
= 0;; nPrevIdx
= nPointIdx
++)
95 int nClosedIdx
= nPointIdx
;
96 if (nPointIdx
>= nPointCount
)
98 // prepare to close last curve segment if needed
99 if (bClosePath
&& (nPointIdx
== nPointCount
))
109 const basegfx::B2DPoint
aPoint(nullptr == pViewInformation
110 ? rPolygon
.getB2DPoint(nClosedIdx
)
111 : impPixelSnap(rPolygon
, *pViewInformation
, nClosedIdx
));
115 // first point => just move there
116 cairo_move_to(cr
, aPoint
.getX(), aPoint
.getY());
121 bool bPendingCurve(false);
125 bPendingCurve
= rPolygon
.isNextControlPointUsed(nPrevIdx
);
126 bPendingCurve
|= rPolygon
.isPrevControlPointUsed(nClosedIdx
);
129 if (!bPendingCurve
) // line segment
131 cairo_line_to(cr
, aPoint
.getX(), aPoint
.getY());
133 else // cubic bezier segment
135 basegfx::B2DPoint aCP1
= rPolygon
.getNextControlPoint(nPrevIdx
);
136 basegfx::B2DPoint aCP2
= rPolygon
.getPrevControlPoint(nClosedIdx
);
138 // tdf#99165 if the control points are 'empty', create the mathematical
139 // correct replacement ones to avoid problems with the graphical sub-system
140 // tdf#101026 The 1st attempt to create a mathematically correct replacement control
141 // vector was wrong. Best alternative is one as close as possible which means short.
142 if (aCP1
.equal(aLast
))
144 aCP1
= aLast
+ ((aCP2
- aLast
) * 0.0005);
147 if (aCP2
.equal(aPoint
))
149 aCP2
= aPoint
+ ((aCP1
- aPoint
) * 0.0005);
152 cairo_curve_to(cr
, aCP1
.getX(), aCP1
.getY(), aCP2
.getX(), aCP2
.getY(), aPoint
.getX(),
161 cairo_close_path(cr
);
165 // split alpha remains as a constant irritant
166 std::vector
<sal_uInt8
> createBitmapData(const BitmapEx
& rBitmapEx
)
168 const Size
& rSizePixel(rBitmapEx
.GetSizePixel());
169 const bool bAlpha(rBitmapEx
.IsAlpha());
170 const sal_uInt32 nStride
171 = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32
, rSizePixel
.Width());
172 std::vector
<sal_uInt8
> aData(nStride
* rSizePixel
.Height());
176 Bitmap
aSrcAlpha(rBitmapEx
.GetAlphaMask().GetBitmap());
177 Bitmap::ScopedReadAccess
pReadAccess(const_cast<Bitmap
&>(rBitmapEx
.GetBitmap()));
178 Bitmap::ScopedReadAccess
pAlphaReadAccess(aSrcAlpha
.AcquireReadAccess(), aSrcAlpha
);
179 const tools::Long
nHeight(pReadAccess
->Height());
180 const tools::Long
nWidth(pReadAccess
->Width());
182 for (tools::Long y
= 0; y
< nHeight
; ++y
)
184 unsigned char* pPixelData
= aData
.data() + (nStride
* y
);
185 for (tools::Long x
= 0; x
< nWidth
; ++x
)
187 const BitmapColor
aColor(pReadAccess
->GetColor(y
, x
));
188 const BitmapColor
aAlpha(pAlphaReadAccess
->GetColor(y
, x
));
189 const sal_uInt16
nAlpha(255 - aAlpha
.GetRed());
191 pPixelData
[SVP_CAIRO_RED
] = vcl::bitmap::premultiply(nAlpha
, aColor
.GetRed());
192 pPixelData
[SVP_CAIRO_GREEN
] = vcl::bitmap::premultiply(nAlpha
, aColor
.GetGreen());
193 pPixelData
[SVP_CAIRO_BLUE
] = vcl::bitmap::premultiply(nAlpha
, aColor
.GetBlue());
194 pPixelData
[SVP_CAIRO_ALPHA
] = nAlpha
;
201 Bitmap::ScopedReadAccess
pReadAccess(const_cast<Bitmap
&>(rBitmapEx
.GetBitmap()));
202 const tools::Long
nHeight(pReadAccess
->Height());
203 const tools::Long
nWidth(pReadAccess
->Width());
205 for (tools::Long y
= 0; y
< nHeight
; ++y
)
207 unsigned char* pPixelData
= aData
.data() + (nStride
* y
);
208 for (tools::Long x
= 0; x
< nWidth
; ++x
)
210 const BitmapColor
aColor(pReadAccess
->GetColor(y
, x
));
211 pPixelData
[SVP_CAIRO_RED
] = aColor
.GetRed();
212 pPixelData
[SVP_CAIRO_GREEN
] = aColor
.GetGreen();
213 pPixelData
[SVP_CAIRO_BLUE
] = aColor
.GetBlue();
214 pPixelData
[SVP_CAIRO_ALPHA
] = 255;
224 namespace drawinglayer::processor2d
226 CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D
& rViewInformation
)
227 : BaseProcessor2D(rViewInformation
)
228 , maBColorModifierStack()
233 CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D
& rViewInformation
,
234 cairo_surface_t
* pTarget
)
235 : BaseProcessor2D(rViewInformation
)
236 , maBColorModifierStack()
241 cairo_t
* pRT
= cairo_create(pTarget
);
242 cairo_set_antialias(pRT
, rViewInformation
.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
243 : CAIRO_ANTIALIAS_NONE
);
244 setRenderTarget(pRT
);
248 CairoPixelProcessor2D::~CairoPixelProcessor2D()
254 void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
255 const primitive2d::PolygonHairlinePrimitive2D
& rPolygonHairlinePrimitive2D
)
257 const basegfx::B2DPolygon
& rPolygon(rPolygonHairlinePrimitive2D
.getB2DPolygon());
259 if (!rPolygon
.count())
264 cairo_matrix_t aMatrix
;
265 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
266 const basegfx::B2DHomMatrix
& rObjectToView(
267 getViewInformation2D().getObjectToViewTransformation());
268 cairo_matrix_init(&aMatrix
, rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
269 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
270 rObjectToView
.f() + fAAOffset
);
272 // set linear transformation
273 cairo_set_matrix(mpRT
, &aMatrix
);
275 const basegfx::BColor
aHairlineColor(
276 maBColorModifierStack
.getModifiedColor(rPolygonHairlinePrimitive2D
.getBColor()));
277 cairo_set_source_rgb(mpRT
, aHairlineColor
.getRed(), aHairlineColor
.getGreen(),
278 aHairlineColor
.getBlue());
280 // TODO: Unfortunately Direct2D paint of one pixel wide lines does not
281 // correctly and completely blend 100% over the background. Experimenting
282 // shows that a value around/slightly below 2.0 is needed which hints that
283 // alpha blending the half-shifted lines (see fAAOffset above) is involved.
284 // To get correct blending I try to use just wider hairlines for now. This
285 // may need to be improved - or balanced (trying sqrt(2) now...)
286 cairo_set_line_width(mpRT
, 1.44f
);
288 addB2DPolygonToPathGeometry(mpRT
, rPolygon
, &getViewInformation2D());
295 void CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D(
296 const primitive2d::PolyPolygonColorPrimitive2D
& rPolyPolygonColorPrimitive2D
)
298 const basegfx::B2DPolyPolygon
& rPolyPolygon(rPolyPolygonColorPrimitive2D
.getB2DPolyPolygon());
299 const sal_uInt32
nCount(rPolyPolygon
.count());
306 cairo_matrix_t aMatrix
;
307 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
308 const basegfx::B2DHomMatrix
& rObjectToView(
309 getViewInformation2D().getObjectToViewTransformation());
310 cairo_matrix_init(&aMatrix
, rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
311 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
312 rObjectToView
.f() + fAAOffset
);
314 // set linear transformation
315 cairo_set_matrix(mpRT
, &aMatrix
);
317 const basegfx::BColor
aFillColor(
318 maBColorModifierStack
.getModifiedColor(rPolyPolygonColorPrimitive2D
.getBColor()));
319 cairo_set_source_rgb(mpRT
, aFillColor
.getRed(), aFillColor
.getGreen(), aFillColor
.getBlue());
321 for (const auto& rPolygon
: rPolyPolygon
)
322 addB2DPolygonToPathGeometry(mpRT
, rPolygon
, &getViewInformation2D());
329 void CairoPixelProcessor2D::processBitmapPrimitive2D(
330 const primitive2d::BitmapPrimitive2D
& rBitmapCandidate
)
332 // check if graphic content is inside discrete local ViewPort
333 const basegfx::B2DRange
& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
334 const basegfx::B2DHomMatrix
aLocalTransform(
335 getViewInformation2D().getObjectToViewTransformation() * rBitmapCandidate
.getTransform());
337 if (!rDiscreteViewPort
.isEmpty())
339 basegfx::B2DRange
aUnitRange(0.0, 0.0, 1.0, 1.0);
341 aUnitRange
.transform(aLocalTransform
);
343 if (!aUnitRange
.overlaps(rDiscreteViewPort
))
345 // content is outside discrete local ViewPort
350 BitmapEx
aBitmapEx(rBitmapCandidate
.getBitmap());
352 if (aBitmapEx
.IsEmpty() || aBitmapEx
.GetSizePixel().IsEmpty())
357 if (maBColorModifierStack
.count())
359 aBitmapEx
= aBitmapEx
.ModifyBitmapEx(maBColorModifierStack
);
361 if (aBitmapEx
.IsEmpty())
363 // color gets completely replaced, get it
364 const basegfx::BColor
aModifiedColor(
365 maBColorModifierStack
.getModifiedColor(basegfx::BColor()));
367 // use unit geometry as fallback object geometry. Do *not*
368 // transform, the below used method will use the already
369 // correctly initialized local ViewInformation
370 basegfx::B2DPolygon
aPolygon(basegfx::utils::createUnitPolygon());
372 rtl::Reference
<primitive2d::PolyPolygonColorPrimitive2D
> xTemp(
373 new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon
),
375 processPolyPolygonColorPrimitive2D(*xTemp
);
380 // nasty copy of bitmap data
381 std::vector
<sal_uInt8
> aPixelData(createBitmapData(aBitmapEx
));
382 const Size
& rSizePixel(aBitmapEx
.GetSizePixel());
383 cairo_surface_t
* pBitmapSurface
= cairo_image_surface_create_for_data(
384 aPixelData
.data(), CAIRO_FORMAT_ARGB32
, rSizePixel
.Width(), rSizePixel
.Height(),
385 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32
, rSizePixel
.Width()));
389 cairo_matrix_t aMatrix
;
390 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
391 cairo_matrix_init(&aMatrix
, aLocalTransform
.a(), aLocalTransform
.b(), aLocalTransform
.c(),
392 aLocalTransform
.d(), aLocalTransform
.e() + fAAOffset
,
393 aLocalTransform
.f() + fAAOffset
);
395 // set linear transformation
396 cairo_set_matrix(mpRT
, &aMatrix
);
398 // destinationRectangle is part of transformation above, so use UnitRange
399 cairo_rectangle(mpRT
, 0, 0, 1, 1);
402 cairo_set_source_surface(mpRT
, pBitmapSurface
, 0, 0);
403 // get the pattern created by cairo_set_source_surface
404 cairo_pattern_t
* sourcepattern
= cairo_get_source(mpRT
);
405 cairo_pattern_get_matrix(sourcepattern
, &aMatrix
);
406 // scale to match the current transformation
407 cairo_matrix_scale(&aMatrix
, rSizePixel
.Width(), rSizePixel
.Height());
408 cairo_pattern_set_matrix(sourcepattern
, &aMatrix
);
412 cairo_surface_destroy(pBitmapSurface
);
419 // This bit-tweaking looping is unpleasant and unfortunate
420 void LuminanceToAlpha(cairo_surface_t
* pMask
)
422 cairo_surface_flush(pMask
);
424 int nWidth
= cairo_image_surface_get_width(pMask
);
425 int nHeight
= cairo_image_surface_get_height(pMask
);
426 int nStride
= cairo_image_surface_get_stride(pMask
);
427 unsigned char* mask_surface_data
= cairo_image_surface_get_data(pMask
);
429 // include/basegfx/color/bcolormodifier.hxx
430 const double nRedMul
= 0.2125 / 255.0;
431 const double nGreenMul
= 0.7154 / 255.0;
432 const double nBlueMul
= 0.0721 / 255.0;
433 for (int y
= 0; y
< nHeight
; ++y
)
435 unsigned char* pMaskPixelData
= mask_surface_data
+ (nStride
* y
);
436 for (int x
= 0; x
< nWidth
; ++x
)
438 double fLuminance
= pMaskPixelData
[SVP_CAIRO_RED
] * nRedMul
439 + pMaskPixelData
[SVP_CAIRO_GREEN
] * nGreenMul
440 + pMaskPixelData
[SVP_CAIRO_BLUE
] * nBlueMul
;
441 // Only this alpha channel is taken into account by cairo_mask_surface
442 // so reuse this surface for the alpha result
443 pMaskPixelData
[SVP_CAIRO_ALPHA
] = 255.0 * fLuminance
;
448 cairo_surface_mark_dirty(pMask
);
452 void CairoPixelProcessor2D::processTransparencePrimitive2D(
453 const primitive2d::TransparencePrimitive2D
& rTransCandidate
)
455 if (rTransCandidate
.getChildren().empty())
458 if (rTransCandidate
.getTransparence().empty())
461 cairo_surface_t
* pTarget
= cairo_get_target(mpRT
);
463 double clip_x1
, clip_x2
, clip_y1
, clip_y2
;
464 cairo_clip_extents(mpRT
, &clip_x1
, &clip_y1
, &clip_x2
, &clip_y2
);
466 // calculate visible range, create only for that range
467 basegfx::B2DRange
aDiscreteRange(
468 rTransCandidate
.getChildren().getB2DRange(getViewInformation2D()));
469 aDiscreteRange
.transform(getViewInformation2D().getObjectToViewTransformation());
470 const basegfx::B2DRange
aViewRange(basegfx::B2DPoint(clip_x1
, clip_y1
),
471 basegfx::B2DPoint(clip_x2
, clip_y2
));
472 basegfx::B2DRange
aVisibleRange(aDiscreteRange
);
473 aVisibleRange
.intersect(aViewRange
);
475 if (aVisibleRange
.isEmpty())
481 const basegfx::B2DHomMatrix
aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix(
482 -aVisibleRange
.getMinX(), -aVisibleRange
.getMinY()));
483 geometry::ViewInformation2D
aViewInformation2D(getViewInformation2D());
484 aViewInformation2D
.setViewTransformation(aEmbedTransform
485 * getViewInformation2D().getViewTransformation());
486 // draw mask to temporary surface
487 cairo_surface_t
* pMask
= cairo_surface_create_similar_image(pTarget
, CAIRO_FORMAT_ARGB32
,
488 ceil(aVisibleRange
.getWidth()),
489 ceil(aVisibleRange
.getHeight()));
490 CairoPixelProcessor2D
aMaskRenderer(aViewInformation2D
, pMask
);
491 aMaskRenderer
.process(rTransCandidate
.getTransparence());
493 // convert mask to something cairo can use
494 LuminanceToAlpha(pMask
);
496 // draw content to temporary surface
497 cairo_surface_t
* pContent
= cairo_surface_create_similar(
498 pTarget
, cairo_surface_get_content(pTarget
), ceil(aVisibleRange
.getWidth()),
499 ceil(aVisibleRange
.getHeight()));
500 CairoPixelProcessor2D
aContent(aViewInformation2D
, pContent
);
501 aContent
.process(rTransCandidate
.getChildren());
503 // munge the temporary surfaces to our target surface
504 cairo_set_source_surface(mpRT
, pContent
, aVisibleRange
.getMinX(), aVisibleRange
.getMinY());
505 cairo_mask_surface(mpRT
, pMask
, aVisibleRange
.getMinX(), aVisibleRange
.getMinY());
507 cairo_surface_destroy(pContent
);
508 cairo_surface_destroy(pMask
);
511 void CairoPixelProcessor2D::processMaskPrimitive2DPixel(
512 const primitive2d::MaskPrimitive2D
& rMaskCandidate
)
514 if (rMaskCandidate
.getChildren().empty())
517 basegfx::B2DPolyPolygon
aMask(rMaskCandidate
.getMask());
522 double clip_x1
, clip_x2
, clip_y1
, clip_y2
;
523 cairo_clip_extents(mpRT
, &clip_x1
, &clip_y1
, &clip_x2
, &clip_y2
);
525 basegfx::B2DRange
aMaskRange(aMask
.getB2DRange());
526 aMaskRange
.transform(getViewInformation2D().getObjectToViewTransformation());
527 const basegfx::B2DRange
aViewRange(basegfx::B2DPoint(clip_x1
, clip_y1
),
528 basegfx::B2DPoint(clip_x2
, clip_y2
));
530 if (!aViewRange
.overlaps(aMaskRange
))
535 cairo_matrix_t aMatrix
;
536 const basegfx::B2DHomMatrix
& rObjectToView(
537 getViewInformation2D().getObjectToViewTransformation());
538 cairo_matrix_init(&aMatrix
, rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
539 rObjectToView
.d(), rObjectToView
.e(), rObjectToView
.f());
541 // set linear transformation
542 cairo_set_matrix(mpRT
, &aMatrix
);
545 for (const auto& rPolygon
: aMask
)
546 addB2DPolygonToPathGeometry(mpRT
, rPolygon
, &getViewInformation2D());
551 process(rMaskCandidate
.getChildren());
556 void CairoPixelProcessor2D::processPointArrayPrimitive2D(
557 const primitive2d::PointArrayPrimitive2D
& rPointArrayCandidate
)
559 const std::vector
<basegfx::B2DPoint
>& rPositions(rPointArrayCandidate
.getPositions());
560 if (rPositions
.empty())
563 const basegfx::BColor
aPointColor(
564 maBColorModifierStack
.getModifiedColor(rPointArrayCandidate
.getRGBColor()));
565 cairo_set_source_rgb(mpRT
, aPointColor
.getRed(), aPointColor
.getGreen(), aPointColor
.getBlue());
567 // To really paint a single pixel I found nothing better than
568 // switch off AA and draw a pixel-aligned rectangle
569 const cairo_antialias_t
eOldAAMode(cairo_get_antialias(mpRT
));
570 cairo_set_antialias(mpRT
, CAIRO_ANTIALIAS_NONE
);
572 for (auto const& pos
: rPositions
)
574 const basegfx::B2DPoint
aDiscretePos(getViewInformation2D().getObjectToViewTransformation()
576 const double fX(ceil(aDiscretePos
.getX()));
577 const double fY(ceil(aDiscretePos
.getY()));
579 cairo_rectangle(mpRT
, fX
, fY
, 1, 1);
583 cairo_set_antialias(mpRT
, eOldAAMode
);
586 void CairoPixelProcessor2D::processModifiedColorPrimitive2D(
587 const primitive2d::ModifiedColorPrimitive2D
& rModifiedCandidate
)
589 if (!rModifiedCandidate
.getChildren().empty())
591 maBColorModifierStack
.push(rModifiedCandidate
.getColorModifier());
592 process(rModifiedCandidate
.getChildren());
593 maBColorModifierStack
.pop();
597 void CairoPixelProcessor2D::processTransformPrimitive2D(
598 const primitive2d::TransformPrimitive2D
& rTransformCandidate
)
600 // remember current transformation and ViewInformation
601 const geometry::ViewInformation2D
aLastViewInformation2D(getViewInformation2D());
603 // create new transformations for local ViewInformation2D
604 geometry::ViewInformation2D
aViewInformation2D(getViewInformation2D());
605 aViewInformation2D
.setObjectTransformation(getViewInformation2D().getObjectTransformation()
606 * rTransformCandidate
.getTransformation());
607 updateViewInformation(aViewInformation2D
);
610 process(rTransformCandidate
.getChildren());
612 // restore transformations
613 updateViewInformation(aLastViewInformation2D
);
616 void CairoPixelProcessor2D::processPolygonStrokePrimitive2D(
617 const primitive2d::PolygonStrokePrimitive2D
& rPolygonStrokeCandidate
)
619 const basegfx::B2DPolygon
& rPolygon(rPolygonStrokeCandidate
.getB2DPolygon());
620 const attribute::LineAttribute
& rLineAttribute(rPolygonStrokeCandidate
.getLineAttribute());
622 if (!rPolygon
.count() || rLineAttribute
.getWidth() < 0.0)
628 // get some values early that might be used for decisions
629 const bool bHairline(0.0 == rLineAttribute
.getWidth());
630 const basegfx::B2DHomMatrix
& rObjectToView(
631 getViewInformation2D().getObjectToViewTransformation());
632 const double fDiscreteLineWidth(
635 : (rObjectToView
* basegfx::B2DVector(rLineAttribute
.getWidth(), 0.0)).getLength());
637 // Here for every combination which the system-specific implementation is not
638 // capable of visualizing, use the (for decomposable Primitives always possible)
639 // fallback to the decomposition.
640 if (basegfx::B2DLineJoin::NONE
== rLineAttribute
.getLineJoin() && fDiscreteLineWidth
> 1.5)
642 // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem
643 // knows that (so far), so fallback to decomposition. This is only needed if
644 // LineJoin will be used, so also check for discrete LineWidth before falling back
645 process(rPolygonStrokeCandidate
);
649 // This is a method every system-specific implementation of a decomposable Primitive
650 // can use to allow simple optical control of paint implementation:
651 // Create a copy, e.g. change color to 'red' as here and paint before the system
652 // paints it using the decomposition. That way you can - if active - directly
653 // optically compare if the system-specific solution is geometrically identical to
654 // the decomposition (which defines our interpretation that we need to visualize).
655 // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case
656 // we create a half-transparent paint to better support visual control
657 static bool bRenderDecomposeForCompareInRed(false);
659 if (bRenderDecomposeForCompareInRed
)
661 const attribute::LineAttribute
aRed(
662 basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute
.getWidth(), rLineAttribute
.getLineJoin(),
663 rLineAttribute
.getLineCap(), rLineAttribute
.getMiterMinimumAngle());
664 rtl::Reference
<primitive2d::PolygonStrokePrimitive2D
> xCopy(
665 new primitive2d::PolygonStrokePrimitive2D(
666 rPolygonStrokeCandidate
.getB2DPolygon(), aRed
,
667 rPolygonStrokeCandidate
.getStrokeAttribute()));
673 cairo_matrix_t aMatrix
;
674 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
675 cairo_matrix_init(&aMatrix
, rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
676 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
677 rObjectToView
.f() + fAAOffset
);
679 // set linear transformation
680 cairo_set_matrix(mpRT
, &aMatrix
);
682 // setup line attributes
683 cairo_line_join_t eCairoLineJoin
= CAIRO_LINE_JOIN_MITER
;
684 switch (rLineAttribute
.getLineJoin())
686 case basegfx::B2DLineJoin::Bevel
:
687 eCairoLineJoin
= CAIRO_LINE_JOIN_BEVEL
;
689 case basegfx::B2DLineJoin::Round
:
690 eCairoLineJoin
= CAIRO_LINE_JOIN_ROUND
;
692 case basegfx::B2DLineJoin::NONE
:
693 case basegfx::B2DLineJoin::Miter
:
694 eCairoLineJoin
= CAIRO_LINE_JOIN_MITER
;
698 // convert miter minimum angle to miter limit
700 = 1.0 / sin(std::max(rLineAttribute
.getMiterMinimumAngle(), 0.01 * M_PI
) / 2.0);
702 // setup cap attribute
703 cairo_line_cap_t
eCairoLineCap(CAIRO_LINE_CAP_BUTT
);
705 switch (rLineAttribute
.getLineCap())
707 default: // css::drawing::LineCap_BUTT:
709 eCairoLineCap
= CAIRO_LINE_CAP_BUTT
;
712 case css::drawing::LineCap_ROUND
:
714 eCairoLineCap
= CAIRO_LINE_CAP_ROUND
;
717 case css::drawing::LineCap_SQUARE
:
719 eCairoLineCap
= CAIRO_LINE_CAP_SQUARE
;
724 basegfx::BColor
aLineColor(maBColorModifierStack
.getModifiedColor(rLineAttribute
.getColor()));
725 if (bRenderDecomposeForCompareInRed
)
726 aLineColor
.setRed(0.5);
728 cairo_set_source_rgb(mpRT
, aLineColor
.getRed(), aLineColor
.getGreen(), aLineColor
.getBlue());
730 cairo_set_line_join(mpRT
, eCairoLineJoin
);
731 cairo_set_line_cap(mpRT
, eCairoLineCap
);
733 // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D
734 cairo_set_line_width(mpRT
, bHairline
? 1.44 : fDiscreteLineWidth
);
735 cairo_set_miter_limit(mpRT
, fMiterLimit
);
737 const attribute::StrokeAttribute
& rStrokeAttribute(
738 rPolygonStrokeCandidate
.getStrokeAttribute());
739 const bool bDashUsed(!rStrokeAttribute
.isDefault()
740 && !rStrokeAttribute
.getDotDashArray().empty()
741 && 0.0 < rStrokeAttribute
.getFullDotDashLen());
744 const std::vector
<double>& rStroke
= rStrokeAttribute
.getDotDashArray();
745 cairo_set_dash(mpRT
, rStroke
.data(), rStroke
.size(), 0.0);
748 addB2DPolygonToPathGeometry(mpRT
, rPolygon
, &getViewInformation2D());
755 void CairoPixelProcessor2D::processLineRectanglePrimitive2D(
756 const primitive2d::LineRectanglePrimitive2D
& rLineRectanglePrimitive2D
)
758 if (rLineRectanglePrimitive2D
.getB2DRange().isEmpty())
766 cairo_matrix_t aMatrix
;
767 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
768 const basegfx::B2DHomMatrix
& rObjectToView(
769 getViewInformation2D().getObjectToViewTransformation());
770 cairo_matrix_init(&aMatrix
, rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
771 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
772 rObjectToView
.f() + fAAOffset
);
774 // set linear transformation
775 cairo_set_matrix(mpRT
, &aMatrix
);
777 const basegfx::BColor
aHairlineColor(
778 maBColorModifierStack
.getModifiedColor(rLineRectanglePrimitive2D
.getBColor()));
779 cairo_set_source_rgb(mpRT
, aHairlineColor
.getRed(), aHairlineColor
.getGreen(),
780 aHairlineColor
.getBlue());
782 const double fDiscreteLineWidth((getViewInformation2D().getInverseObjectToViewTransformation()
783 * basegfx::B2DVector(1.44, 0.0))
785 cairo_set_line_width(mpRT
, fDiscreteLineWidth
);
787 const basegfx::B2DRange
& rRange(rLineRectanglePrimitive2D
.getB2DRange());
788 cairo_rectangle(mpRT
, rRange
.getMinX(), rRange
.getMinY(), rRange
.getWidth(),
795 void CairoPixelProcessor2D::processFilledRectanglePrimitive2D(
796 const primitive2d::FilledRectanglePrimitive2D
& rFilledRectanglePrimitive2D
)
798 if (rFilledRectanglePrimitive2D
.getB2DRange().isEmpty())
806 cairo_matrix_t aMatrix
;
807 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
808 const basegfx::B2DHomMatrix
& rObjectToView(
809 getViewInformation2D().getObjectToViewTransformation());
810 cairo_matrix_init(&aMatrix
, rObjectToView
.a(), rObjectToView
.b(), rObjectToView
.c(),
811 rObjectToView
.d(), rObjectToView
.e() + fAAOffset
,
812 rObjectToView
.f() + fAAOffset
);
814 // set linear transformation
815 cairo_set_matrix(mpRT
, &aMatrix
);
817 const basegfx::BColor
aFillColor(
818 maBColorModifierStack
.getModifiedColor(rFilledRectanglePrimitive2D
.getBColor()));
819 cairo_set_source_rgb(mpRT
, aFillColor
.getRed(), aFillColor
.getGreen(), aFillColor
.getBlue());
821 const basegfx::B2DRange
& rRange(rFilledRectanglePrimitive2D
.getB2DRange());
822 cairo_rectangle(mpRT
, rRange
.getMinX(), rRange
.getMinY(), rRange
.getWidth(),
829 void CairoPixelProcessor2D::processSingleLinePrimitive2D(
830 const primitive2d::SingleLinePrimitive2D
& rSingleLinePrimitive2D
)
834 const basegfx::BColor
aLineColor(
835 maBColorModifierStack
.getModifiedColor(rSingleLinePrimitive2D
.getBColor()));
836 cairo_set_source_rgb(mpRT
, aLineColor
.getRed(), aLineColor
.getGreen(), aLineColor
.getBlue());
838 const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
839 const basegfx::B2DHomMatrix
& rObjectToView(
840 getViewInformation2D().getObjectToViewTransformation());
841 const basegfx::B2DPoint
aStart(rObjectToView
* rSingleLinePrimitive2D
.getStart());
842 const basegfx::B2DPoint
aEnd(rObjectToView
* rSingleLinePrimitive2D
.getEnd());
844 cairo_set_line_width(mpRT
, 1.44f
);
846 cairo_move_to(mpRT
, aStart
.getX() + fAAOffset
, aStart
.getY() + fAAOffset
);
847 cairo_line_to(mpRT
, aEnd
.getX() + fAAOffset
, aEnd
.getY() + fAAOffset
);
853 void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D
& rCandidate
)
855 switch (rCandidate
.getPrimitive2DID())
857 // geometry that *has* to be processed
858 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D
:
860 processBitmapPrimitive2D(
861 static_cast<const primitive2d::BitmapPrimitive2D
&>(rCandidate
));
864 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D
:
866 processPointArrayPrimitive2D(
867 static_cast<const primitive2d::PointArrayPrimitive2D
&>(rCandidate
));
870 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
:
872 processPolygonHairlinePrimitive2D(
873 static_cast<const primitive2d::PolygonHairlinePrimitive2D
&>(rCandidate
));
876 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
:
878 processPolyPolygonColorPrimitive2D(
879 static_cast<const primitive2d::PolyPolygonColorPrimitive2D
&>(rCandidate
));
882 // embedding/groups that *have* to be processed
883 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D
:
885 processTransparencePrimitive2D(
886 static_cast<const primitive2d::TransparencePrimitive2D
&>(rCandidate
));
889 case PRIMITIVE2D_ID_INVERTPRIMITIVE2D
:
891 // TODO: fallback is at VclPixelProcessor2D::processInvertPrimitive2D, so
892 // not in reach. Ignore for now.
893 // processInvertPrimitive2D(rCandidate);
896 case PRIMITIVE2D_ID_MASKPRIMITIVE2D
:
898 processMaskPrimitive2DPixel(
899 static_cast<const primitive2d::MaskPrimitive2D
&>(rCandidate
));
902 case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D
:
904 processModifiedColorPrimitive2D(
905 static_cast<const primitive2d::ModifiedColorPrimitive2D
&>(rCandidate
));
908 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D
:
910 processTransformPrimitive2D(
911 static_cast<const primitive2d::TransformPrimitive2D
&>(rCandidate
));
915 // geometry that *may* be processed due to being able to do it better
916 // then using the decomposition
917 case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D
:
919 processUnifiedTransparencePrimitive2D(
920 static_cast<const primitive2d::UnifiedTransparencePrimitive2D
&>(rCandidate
));
923 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D
:
925 processMarkerArrayPrimitive2D(
926 static_cast<const primitive2d::MarkerArrayPrimitive2D
&>(rCandidate
));
929 case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D
:
931 processBackgroundColorPrimitive2D(
932 static_cast<const primitive2d::BackgroundColorPrimitive2D
&>(rCandidate
));
936 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
:
938 processPolygonStrokePrimitive2D(
939 static_cast<const primitive2d::PolygonStrokePrimitive2D
&>(rCandidate
));
942 case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D
:
944 processLineRectanglePrimitive2D(
945 static_cast<const primitive2d::LineRectanglePrimitive2D
&>(rCandidate
));
948 case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D
:
950 processFilledRectanglePrimitive2D(
951 static_cast<const primitive2d::FilledRectanglePrimitive2D
&>(rCandidate
));
954 case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D
:
956 processSingleLinePrimitive2D(
957 static_cast<const primitive2d::SingleLinePrimitive2D
&>(rCandidate
));
961 // continue with decompose
964 SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
965 rCandidate
.getPrimitive2DID()));
966 // process recursively
973 } // end of namespace
975 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */