Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / drawinglayer / source / processor2d / cairopixelprocessor2d.cxx
blob53261229a0d7fdb0d0fe60bba46cc4f97623af2a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
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/.
8 */
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;
43 namespace
45 basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
46 const drawinglayer::geometry::ViewInformation2D& rViewInformation,
47 sal_uInt32 nIndex)
49 const sal_uInt32 nCount(rPolygon.count());
51 // get the data
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)));
62 // get the states
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);
70 if (bSnapX || bSnapY)
72 basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
73 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
75 aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation();
77 return aSnappedPoint;
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))
101 nClosedIdx = 0;
103 else
105 break;
109 const basegfx::B2DPoint aPoint(nullptr == pViewInformation
110 ? rPolygon.getB2DPoint(nClosedIdx)
111 : impPixelSnap(rPolygon, *pViewInformation, nClosedIdx));
113 if (!nPointIdx)
115 // first point => just move there
116 cairo_move_to(cr, aPoint.getX(), aPoint.getY());
117 aLast = aPoint;
118 continue;
121 bool bPendingCurve(false);
123 if (bHasCurves)
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(),
153 aPoint.getY());
156 aLast = aPoint;
159 if (bClosePath)
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());
174 if (bAlpha)
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;
195 pPixelData += 4;
199 else
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;
215 pPixelData += 4;
220 return aData;
224 namespace drawinglayer::processor2d
226 CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation)
227 : BaseProcessor2D(rViewInformation)
228 , maBColorModifierStack()
229 , mpRT(nullptr)
233 CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
234 cairo_surface_t* pTarget)
235 : BaseProcessor2D(rViewInformation)
236 , maBColorModifierStack()
237 , mpRT(nullptr)
239 if (pTarget)
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()
250 if (mpRT)
251 cairo_destroy(mpRT);
254 void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
255 const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
257 const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
259 if (!rPolygon.count())
260 return;
262 cairo_save(mpRT);
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());
290 cairo_stroke(mpRT);
292 cairo_restore(mpRT);
295 void CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D(
296 const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
298 const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
299 const sal_uInt32 nCount(rPolyPolygon.count());
301 if (!nCount)
302 return;
304 cairo_save(mpRT);
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());
324 cairo_fill(mpRT);
326 cairo_restore(mpRT);
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
346 return;
350 BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
352 if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty())
354 return;
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),
374 aModifiedColor));
375 processPolyPolygonColorPrimitive2D(*xTemp);
376 return;
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()));
387 cairo_save(mpRT);
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);
400 cairo_clip(mpRT);
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);
410 cairo_paint(mpRT);
412 cairo_surface_destroy(pBitmapSurface);
414 cairo_restore(mpRT);
417 namespace
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;
444 pMaskPixelData += 4;
448 cairo_surface_mark_dirty(pMask);
452 void CairoPixelProcessor2D::processTransparencePrimitive2D(
453 const primitive2d::TransparencePrimitive2D& rTransCandidate)
455 if (rTransCandidate.getChildren().empty())
456 return;
458 if (rTransCandidate.getTransparence().empty())
459 return;
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())
477 // not visible, done
478 return;
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())
515 return;
517 basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
519 if (!aMask.count())
520 return;
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))
531 return;
533 cairo_save(mpRT);
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);
544 // put mask as path
545 for (const auto& rPolygon : aMask)
546 addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
548 // clip to this mask
549 cairo_clip(mpRT);
551 process(rMaskCandidate.getChildren());
553 cairo_restore(mpRT);
556 void CairoPixelProcessor2D::processPointArrayPrimitive2D(
557 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
559 const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions());
560 if (rPositions.empty())
561 return;
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()
575 * pos);
576 const double fX(ceil(aDiscretePos.getX()));
577 const double fY(ceil(aDiscretePos.getY()));
579 cairo_rectangle(mpRT, fX, fY, 1, 1);
580 cairo_fill(mpRT);
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);
609 // process content
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)
624 // no geometry, done
625 return;
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(
633 bHairline
634 ? 1.0
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);
646 return;
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()));
668 process(*xCopy);
671 cairo_save(mpRT);
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;
688 break;
689 case basegfx::B2DLineJoin::Round:
690 eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
691 break;
692 case basegfx::B2DLineJoin::NONE:
693 case basegfx::B2DLineJoin::Miter:
694 eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
695 break;
698 // convert miter minimum angle to miter limit
699 double fMiterLimit
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;
710 break;
712 case css::drawing::LineCap_ROUND:
714 eCairoLineCap = CAIRO_LINE_CAP_ROUND;
715 break;
717 case css::drawing::LineCap_SQUARE:
719 eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
720 break;
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());
742 if (bDashUsed)
744 const std::vector<double>& rStroke = rStrokeAttribute.getDotDashArray();
745 cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0);
748 addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
750 cairo_stroke(mpRT);
752 cairo_restore(mpRT);
755 void CairoPixelProcessor2D::processLineRectanglePrimitive2D(
756 const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D)
758 if (rLineRectanglePrimitive2D.getB2DRange().isEmpty())
760 // no geometry, done
761 return;
764 cairo_save(mpRT);
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))
784 .getLength());
785 cairo_set_line_width(mpRT, fDiscreteLineWidth);
787 const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange());
788 cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(),
789 rRange.getHeight());
790 cairo_stroke(mpRT);
792 cairo_restore(mpRT);
795 void CairoPixelProcessor2D::processFilledRectanglePrimitive2D(
796 const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
798 if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
800 // no geometry, done
801 return;
804 cairo_save(mpRT);
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(),
823 rRange.getHeight());
824 cairo_fill(mpRT);
826 cairo_restore(mpRT);
829 void CairoPixelProcessor2D::processSingleLinePrimitive2D(
830 const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D)
832 cairo_save(mpRT);
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);
848 cairo_stroke(mpRT);
850 cairo_restore(mpRT);
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));
862 break;
864 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
866 processPointArrayPrimitive2D(
867 static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
868 break;
870 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
872 processPolygonHairlinePrimitive2D(
873 static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
874 break;
876 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
878 processPolyPolygonColorPrimitive2D(
879 static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
880 break;
882 // embedding/groups that *have* to be processed
883 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
885 processTransparencePrimitive2D(
886 static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
887 break;
889 case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
891 // TODO: fallback is at VclPixelProcessor2D::processInvertPrimitive2D, so
892 // not in reach. Ignore for now.
893 // processInvertPrimitive2D(rCandidate);
894 break;
896 case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
898 processMaskPrimitive2DPixel(
899 static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
900 break;
902 case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
904 processModifiedColorPrimitive2D(
905 static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
906 break;
908 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
910 processTransformPrimitive2D(
911 static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
912 break;
914 #if 0
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));
921 break;
923 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
925 processMarkerArrayPrimitive2D(
926 static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
927 break;
929 case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
931 processBackgroundColorPrimitive2D(
932 static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
933 break;
935 #endif
936 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
938 processPolygonStrokePrimitive2D(
939 static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
940 break;
942 case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D:
944 processLineRectanglePrimitive2D(
945 static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate));
946 break;
948 case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D:
950 processFilledRectanglePrimitive2D(
951 static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate));
952 break;
954 case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D:
956 processSingleLinePrimitive2D(
957 static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate));
958 break;
961 // continue with decompose
962 default:
964 SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
965 rCandidate.getPrimitive2DID()));
966 // process recursively
967 process(rCandidate);
968 break;
973 } // end of namespace
975 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */