LibreOffice Module drawinglayer (master) 1
cairopixelprocessor2d.cxx
Go to the documentation of this file.
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.
4 *
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 */
9
10#include <sal/config.h>
11
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>
40
41using namespace com::sun::star;
42
43namespace
44{
45basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
46 const drawinglayer::geometry::ViewInformation2D& rViewInformation,
47 sal_uInt32 nIndex)
48{
49 const sal_uInt32 nCount(rPolygon.count());
50
51 // get the data
52 const basegfx::B2ITuple aPrevTuple(
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(
60 * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
61
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);
69
70 if (bSnapX || bSnapY)
71 {
72 basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
73 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
74
75 aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation();
76
77 return aSnappedPoint;
78 }
79
80 return rPolygon.getB2DPoint(nIndex);
81}
82
83void addB2DPolygonToPathGeometry(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
84 const drawinglayer::geometry::ViewInformation2D* pViewInformation)
85{
86 // short circuit if there is nothing to do
87 const sal_uInt32 nPointCount(rPolygon.count());
88
89 const bool bHasCurves(rPolygon.areControlPointsUsed());
90 const bool bClosePath(rPolygon.isClosed());
92
93 for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
94 {
95 int nClosedIdx = nPointIdx;
96 if (nPointIdx >= nPointCount)
97 {
98 // prepare to close last curve segment if needed
99 if (bClosePath && (nPointIdx == nPointCount))
100 {
101 nClosedIdx = 0;
102 }
103 else
104 {
105 break;
106 }
107 }
108
109 const basegfx::B2DPoint aPoint(nullptr == pViewInformation
110 ? rPolygon.getB2DPoint(nClosedIdx)
111 : impPixelSnap(rPolygon, *pViewInformation, nClosedIdx));
112
113 if (!nPointIdx)
114 {
115 // first point => just move there
116 cairo_move_to(cr, aPoint.getX(), aPoint.getY());
117 aLast = aPoint;
118 continue;
119 }
120
121 bool bPendingCurve(false);
122
123 if (bHasCurves)
124 {
125 bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
126 bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
127 }
128
129 if (!bPendingCurve) // line segment
130 {
131 cairo_line_to(cr, aPoint.getX(), aPoint.getY());
132 }
133 else // cubic bezier segment
134 {
135 basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
136 basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
137
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))
143 {
144 aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
145 }
146
147 if (aCP2.equal(aPoint))
148 {
149 aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
150 }
151
152 cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
153 aPoint.getY());
154 }
155
156 aLast = aPoint;
157 }
158
159 if (bClosePath)
160 {
161 cairo_close_path(cr);
162 }
163}
164
165// split alpha remains as a constant irritant
166std::vector<sal_uInt8> createBitmapData(const BitmapEx& rBitmapEx)
167{
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());
173
174 if (bAlpha)
175 {
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());
181
182 for (tools::Long y = 0; y < nHeight; ++y)
183 {
184 unsigned char* pPixelData = aData.data() + (nStride * y);
185 for (tools::Long x = 0; x < nWidth; ++x)
186 {
187 const BitmapColor aColor(pReadAccess->GetColor(y, x));
188 const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x));
189 const sal_uInt16 nAlpha(255 - aAlpha.GetRed());
190
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;
196 }
197 }
198 }
199 else
200 {
201 Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.GetBitmap()));
202 const tools::Long nHeight(pReadAccess->Height());
203 const tools::Long nWidth(pReadAccess->Width());
204
205 for (tools::Long y = 0; y < nHeight; ++y)
206 {
207 unsigned char* pPixelData = aData.data() + (nStride * y);
208 for (tools::Long x = 0; x < nWidth; ++x)
209 {
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;
216 }
217 }
218 }
219
220 return aData;
221}
222}
223
225{
227 : BaseProcessor2D(rViewInformation)
228 , maBColorModifierStack()
229 , mpRT(nullptr)
230{
231}
232
234 cairo_surface_t* pTarget)
235 : BaseProcessor2D(rViewInformation)
236 , maBColorModifierStack()
237 , mpRT(nullptr)
238{
239 if (pTarget)
240 {
241 cairo_t* pRT = cairo_create(pTarget);
242 cairo_set_antialias(pRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
243 : CAIRO_ANTIALIAS_NONE);
244 setRenderTarget(pRT);
245 }
246}
247
249{
250 if (mpRT)
251 cairo_destroy(mpRT);
252}
253
255 const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
256{
257 const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
258
259 if (!rPolygon.count())
260 return;
261
262 cairo_save(mpRT);
263
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);
271
272 // set linear transformation
273 cairo_set_matrix(mpRT, &aMatrix);
274
275 const basegfx::BColor aHairlineColor(
276 maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor()));
277 cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(),
278 aHairlineColor.getBlue());
279
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);
287
288 addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
289
290 cairo_stroke(mpRT);
291
292 cairo_restore(mpRT);
293}
294
296 const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
297{
298 const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
299 const sal_uInt32 nCount(rPolyPolygon.count());
300
301 if (!nCount)
302 return;
303
304 cairo_save(mpRT);
305
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);
313
314 // set linear transformation
315 cairo_set_matrix(mpRT, &aMatrix);
316
317 const basegfx::BColor aFillColor(
318 maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor()));
319 cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
320
321 for (const auto& rPolygon : rPolyPolygon)
322 addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
323
324 cairo_fill(mpRT);
325
326 cairo_restore(mpRT);
327}
328
330 const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
331{
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());
336
337 if (!rDiscreteViewPort.isEmpty())
338 {
339 basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
340
341 aUnitRange.transform(aLocalTransform);
342
343 if (!aUnitRange.overlaps(rDiscreteViewPort))
344 {
345 // content is outside discrete local ViewPort
346 return;
347 }
348 }
349
350 BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
351
352 if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty())
353 {
354 return;
355 }
356
358 {
359 aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
360
361 if (aBitmapEx.IsEmpty())
362 {
363 // color gets completely replaced, get it
364 const basegfx::BColor aModifiedColor(
366
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
371
374 aModifiedColor));
376 return;
377 }
378 }
379
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()));
386
387 cairo_save(mpRT);
388
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);
394
395 // set linear transformation
396 cairo_set_matrix(mpRT, &aMatrix);
397
398 // destinationRectangle is part of transformation above, so use UnitRange
399 cairo_rectangle(mpRT, 0, 0, 1, 1);
400 cairo_clip(mpRT);
401
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);
409
410 cairo_paint(mpRT);
411
412 cairo_surface_destroy(pBitmapSurface);
413
414 cairo_restore(mpRT);
415}
416
417namespace
418{
419// This bit-tweaking looping is unpleasant and unfortunate
421{
422 cairo_surface_flush(pMask);
423
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);
428
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)
434 {
435 unsigned char* pMaskPixelData = mask_surface_data + (nStride * y);
436 for (int x = 0; x < nWidth; ++x)
437 {
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;
445 }
446 }
447
448 cairo_surface_mark_dirty(pMask);
449}
450}
451
453 const primitive2d::TransparencePrimitive2D& rTransCandidate)
454{
455 if (rTransCandidate.getChildren().empty())
456 return;
457
458 if (rTransCandidate.getTransparence().empty())
459 return;
460
461 cairo_surface_t* pTarget = cairo_get_target(mpRT);
462
463 double clip_x1, clip_x2, clip_y1, clip_y2;
464 cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
465
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);
474
475 if (aVisibleRange.isEmpty())
476 {
477 // not visible, done
478 return;
479 }
480
482 -aVisibleRange.getMinX(), -aVisibleRange.getMinY()));
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());
492
493 // convert mask to something cairo can use
494 LuminanceToAlpha(pMask);
495
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());
502
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());
506
507 cairo_surface_destroy(pContent);
508 cairo_surface_destroy(pMask);
509}
510
512 const primitive2d::MaskPrimitive2D& rMaskCandidate)
513{
514 if (rMaskCandidate.getChildren().empty())
515 return;
516
517 basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
518
519 if (!aMask.count())
520 return;
521
522 double clip_x1, clip_x2, clip_y1, clip_y2;
523 cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
524
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));
529
530 if (!aViewRange.overlaps(aMaskRange))
531 return;
532
533 cairo_save(mpRT);
534
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());
540
541 // set linear transformation
542 cairo_set_matrix(mpRT, &aMatrix);
543
544 // put mask as path
545 for (const auto& rPolygon : aMask)
546 addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
547
548 // clip to this mask
549 cairo_clip(mpRT);
550
551 process(rMaskCandidate.getChildren());
552
553 cairo_restore(mpRT);
554}
555
557 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
558{
559 const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions());
560 if (rPositions.empty())
561 return;
562
563 const basegfx::BColor aPointColor(
564 maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
565 cairo_set_source_rgb(mpRT, aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue());
566
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);
571
572 for (auto const& pos : rPositions)
573 {
574 const basegfx::B2DPoint aDiscretePos(getViewInformation2D().getObjectToViewTransformation()
575 * pos);
576 const double fX(ceil(aDiscretePos.getX()));
577 const double fY(ceil(aDiscretePos.getY()));
578
579 cairo_rectangle(mpRT, fX, fY, 1, 1);
580 cairo_fill(mpRT);
581 }
582
583 cairo_set_antialias(mpRT, eOldAAMode);
584}
585
587 const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
588{
589 if (!rModifiedCandidate.getChildren().empty())
590 {
591 maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
592 process(rModifiedCandidate.getChildren());
594 }
595}
596
598 const primitive2d::TransformPrimitive2D& rTransformCandidate)
599{
600 // remember current transformation and ViewInformation
601 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
602
603 // create new transformations for local ViewInformation2D
605 aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
606 * rTransformCandidate.getTransformation());
607 updateViewInformation(aViewInformation2D);
608
609 // process content
610 process(rTransformCandidate.getChildren());
611
612 // restore transformations
613 updateViewInformation(aLastViewInformation2D);
614}
615
617 const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
618{
619 const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon());
620 const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute());
621
622 if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0)
623 {
624 // no geometry, done
625 return;
626 }
627
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());
636
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)
641 {
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;
647 }
648
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);
658
659 if (bRenderDecomposeForCompareInRed)
660 {
661 const attribute::LineAttribute aRed(
662 basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(),
663 rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle());
666 rPolygonStrokeCandidate.getB2DPolygon(), aRed,
667 rPolygonStrokeCandidate.getStrokeAttribute()));
668 process(*xCopy);
669 }
670
671 cairo_save(mpRT);
672
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);
678
679 // set linear transformation
680 cairo_set_matrix(mpRT, &aMatrix);
681
682 // setup line attributes
683 cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
684 switch (rLineAttribute.getLineJoin())
685 {
687 eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
688 break;
690 eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
691 break;
694 eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
695 break;
696 }
697
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);
701
702 // setup cap attribute
703 cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
704
705 switch (rLineAttribute.getLineCap())
706 {
707 default: // css::drawing::LineCap_BUTT:
708 {
709 eCairoLineCap = CAIRO_LINE_CAP_BUTT;
710 break;
711 }
712 case css::drawing::LineCap_ROUND:
713 {
714 eCairoLineCap = CAIRO_LINE_CAP_ROUND;
715 break;
716 }
717 case css::drawing::LineCap_SQUARE:
718 {
719 eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
720 break;
721 }
722 }
723
725 if (bRenderDecomposeForCompareInRed)
726 aLineColor.setRed(0.5);
727
728 cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
729
730 cairo_set_line_join(mpRT, eCairoLineJoin);
731 cairo_set_line_cap(mpRT, eCairoLineCap);
732
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);
736
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)
743 {
744 const std::vector<double>& rStroke = rStrokeAttribute.getDotDashArray();
745 cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0);
746 }
747
748 addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
749
750 cairo_stroke(mpRT);
751
752 cairo_restore(mpRT);
753}
754
756 const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D)
757{
758 if (rLineRectanglePrimitive2D.getB2DRange().isEmpty())
759 {
760 // no geometry, done
761 return;
762 }
763
764 cairo_save(mpRT);
765
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);
773
774 // set linear transformation
775 cairo_set_matrix(mpRT, &aMatrix);
776
777 const basegfx::BColor aHairlineColor(
778 maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor()));
779 cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(),
780 aHairlineColor.getBlue());
781
782 const double fDiscreteLineWidth((getViewInformation2D().getInverseObjectToViewTransformation()
783 * basegfx::B2DVector(1.44, 0.0))
784 .getLength());
785 cairo_set_line_width(mpRT, fDiscreteLineWidth);
786
787 const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange());
788 cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(),
789 rRange.getHeight());
790 cairo_stroke(mpRT);
791
792 cairo_restore(mpRT);
793}
794
796 const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
797{
798 if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
799 {
800 // no geometry, done
801 return;
802 }
803
804 cairo_save(mpRT);
805
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);
813
814 // set linear transformation
815 cairo_set_matrix(mpRT, &aMatrix);
816
817 const basegfx::BColor aFillColor(
818 maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor()));
819 cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
820
821 const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange());
822 cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(),
823 rRange.getHeight());
824 cairo_fill(mpRT);
825
826 cairo_restore(mpRT);
827}
828
830 const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D)
831{
832 cairo_save(mpRT);
833
834 const basegfx::BColor aLineColor(
835 maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor()));
836 cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
837
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());
843
844 cairo_set_line_width(mpRT, 1.44f);
845
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);
849
850 cairo_restore(mpRT);
851}
852
854{
855 switch (rCandidate.getPrimitive2DID())
856 {
857 // geometry that *has* to be processed
859 {
861 static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
862 break;
863 }
865 {
867 static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
868 break;
869 }
871 {
873 static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
874 break;
875 }
877 {
879 static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
880 break;
881 }
882 // embedding/groups that *have* to be processed
884 {
886 static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
887 break;
888 }
890 {
891 // TODO: fallback is at VclPixelProcessor2D::processInvertPrimitive2D, so
892 // not in reach. Ignore for now.
893 // processInvertPrimitive2D(rCandidate);
894 break;
895 }
897 {
899 static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
900 break;
901 }
903 {
905 static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
906 break;
907 }
909 {
911 static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
912 break;
913 }
914#if 0
915 // geometry that *may* be processed due to being able to do it better
916 // then using the decomposition
918 {
920 static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
921 break;
922 }
924 {
926 static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
927 break;
928 }
930 {
932 static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
933 break;
934 }
935#endif
937 {
939 static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
940 break;
941 }
943 {
945 static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate));
946 break;
947 }
949 {
951 static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate));
952 break;
953 }
955 {
957 static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate));
958 break;
959 }
960
961 // continue with decompose
962 default:
963 {
964 SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
965 rCandidate.getPrimitive2DID()));
966 // process recursively
967 process(rCandidate);
968 break;
969 }
970 }
971}
972
973} // end of namespace
974
975/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
struct _cairo_surface cairo_surface_t
struct _cairo cairo_t
#define SVP_CAIRO_ALPHA
#define SVP_CAIRO_RED
#define SVP_CAIRO_BLUE
#define SVP_CAIRO_GREEN
Bitmap const & GetBitmap() const
const AlphaMask & GetAlphaMask() const
bool IsAlpha() const
bool IsEmpty() const
Bitmap GetBitmap(Color aTransparentReplaceColor) const
BitmapEx ModifyBitmapEx(const basegfx::BColorModifierStack &rBColorModifierStack) const
const Size & GetSizePixel() const
bool IsEmpty() const
constexpr tools::Long Height() const
constexpr tools::Long Width() const
double c() const
double e() const
double a() const
double f() const
double d() const
double b() const
B2DRange getB2DRange() const
sal_uInt32 count() const
bool isPrevControlPointUsed(sal_uInt32 nIndex) const
bool isNextControlPointUsed(sal_uInt32 nIndex) const
bool isClosed() const
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
basegfx::B2DPoint getPrevControlPoint(sal_uInt32 nIndex) const
bool areControlPointsUsed() const
sal_uInt32 count() const
basegfx::B2DPoint getNextControlPoint(sal_uInt32 nIndex) const
BASEGFX_DLLPUBLIC void transform(const B2DHomMatrix &rMatrix)
void push(const BColorModifierSharedPtr &rNew)
::basegfx::BColor getModifiedColor(const ::basegfx::BColor &rSource) const
sal_uInt32 count() const
double getBlue() const
double getRed() const
double getGreen() const
void setRed(double fNew)
TYPE getWidth() const
TYPE getMinX() const
TYPE getMinY() const
void intersect(const Range2D &rRange)
bool isEmpty() const
TYPE getHeight() const
bool overlaps(const Range2D &rRange) const
bool equal(const Tuple2D< TYPE > &rTup) const
TYPE getX() const
TYPE getY() const
basegfx::B2DLineJoin getLineJoin() const
css::drawing::LineCap getLineCap() const
const basegfx::BColor & getColor() const
const ::std::vector< double > & getDotDashArray() const
const basegfx::B2DHomMatrix & getInverseObjectToViewTransformation() const
void setObjectTransformation(const basegfx::B2DHomMatrix &rNew)
void setViewTransformation(const basegfx::B2DHomMatrix &rNew)
bool getUseAntiAliasing() const
Determine if to use AntiAliasing on target pixel device, PropertyName is 'UseAntiAliasing'.
const basegfx::B2DHomMatrix & getObjectToViewTransformation() const
On-demand prepared Object to View transformation and its inverse for convenience.
virtual sal_uInt32 getPrimitive2DID() const =0
provide unique ID for fast identifying of known primitive implementations in renderers.
const basegfx::B2DHomMatrix & getTransform() const
const BitmapEx & getBitmap() const
data read access
const basegfx::B2DRange & getB2DRange() const
data read access
const Primitive2DContainer & getChildren() const
data read access
const basegfx::B2DRange & getB2DRange() const
data read access
const basegfx::B2DPolyPolygon & getMask() const
data read access
const basegfx::BColorModifierSharedPtr & getColorModifier() const
data read access
const std::vector< basegfx::B2DPoint > & getPositions() const
data read access
const basegfx::B2DPolyPolygon & getB2DPolyPolygon() const
data read access
const basegfx::B2DPolygon & getB2DPolygon() const
data read access
const attribute::StrokeAttribute & getStrokeAttribute() const
const basegfx::B2DPolygon & getB2DPolygon() const
data read access
const attribute::LineAttribute & getLineAttribute() const
basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D &aViewInformation) const
const basegfx::B2DPoint & getStart() const
data read access
const basegfx::B2DHomMatrix & getTransformation() const
data read access
const Primitive2DContainer & getTransparence() const
data read access
void process(const primitive2d::BasePrimitive2D &rCandidate)
void updateViewInformation(const geometry::ViewInformation2D &rViewInformation2D)
const geometry::ViewInformation2D & getViewInformation2D() const
data read access
void processBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D &rBitmapCandidate)
void processPointArrayPrimitive2D(const primitive2d::PointArrayPrimitive2D &rPointArrayCandidate)
void processUnifiedTransparencePrimitive2D(const primitive2d::UnifiedTransparencePrimitive2D &rTransCandidate)
void processLineRectanglePrimitive2D(const primitive2d::LineRectanglePrimitive2D &rLineRectanglePrimitive2D)
void processBackgroundColorPrimitive2D(const primitive2d::BackgroundColorPrimitive2D &rBackgroundColorCandidate)
void processPolygonStrokePrimitive2D(const primitive2d::PolygonStrokePrimitive2D &rPolygonStrokeCandidate)
void processPolyPolygonColorPrimitive2D(const primitive2d::PolyPolygonColorPrimitive2D &rPolyPolygonColorPrimitive2D)
void processPolygonHairlinePrimitive2D(const primitive2d::PolygonHairlinePrimitive2D &rPolygonHairlinePrimitive2D)
virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D &rCandidate) override
void processTransparencePrimitive2D(const primitive2d::TransparencePrimitive2D &rTransCandidate)
void processMarkerArrayPrimitive2D(const primitive2d::MarkerArrayPrimitive2D &rMarkerArrayCandidate)
void processSingleLinePrimitive2D(const primitive2d::SingleLinePrimitive2D &rSingleLinePrimitive2D)
void processFilledRectanglePrimitive2D(const primitive2d::FilledRectanglePrimitive2D &rFilledRectanglePrimitive2D)
void processMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D &rMaskCandidate)
void processTransformPrimitive2D(const primitive2d::TransformPrimitive2D &rTransformCandidate)
CairoPixelProcessor2D(const geometry::ViewInformation2D &rViewInformation)
void processModifiedColorPrimitive2D(const primitive2d::ModifiedColorPrimitive2D &rModifiedCandidate)
int nCount
#define PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D
#define PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D
#define PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
#define PRIMITIVE2D_ID_INVERTPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
#define PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D
#define PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D
#define PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D
#define PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D
#define PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D
#define PRIMITIVE2D_ID_MASKPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
#define PRIMITIVE2D_ID_BITMAPPRIMITIVE2D
#define PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D
#define PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D
float y
float x
#define SAL_INFO(area, stream)
constexpr OUStringLiteral aData
double getLength(const B2DPolygon &rCandidate)
B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY)
B2DPolygon const & createUnitPolygon()
B2IRange fround(const B2DRange &rRange)
OUString idToString(sal_uInt32 nId)
@ LuminanceToAlpha
long Long
sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
size_t pos