bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / headless / CairoCommon.cxx
blobffcd50014e48f066a1c8a382734acba66c830249
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <headless/BitmapHelper.hxx>
21 #include <headless/CairoCommon.hxx>
22 #include <dlfcn.h>
23 #include <vcl/BitmapTools.hxx>
24 #include <SalGradient.hxx>
25 #include <svdata.hxx>
26 #include <tools/helpers.hxx>
27 #include <basegfx/utils/canvastools.hxx>
28 #include <basegfx/matrix/b2dhommatrixtools.hxx>
29 #include <basegfx/polygon/b2dpolypolygontools.hxx>
30 #include <basegfx/polygon/b2dpolygontools.hxx>
31 #include <unotools/configmgr.hxx>
32 #include <sal/log.hxx>
33 #include <osl/module.h>
35 #if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 12, 0)
36 #error "require at least cairo 1.12.0"
37 #endif
39 void dl_cairo_surface_set_device_scale(cairo_surface_t* surface, double x_scale, double y_scale)
41 #if !HAVE_DLAPI
42 cairo_surface_set_device_scale(surface, x_scale, y_scale);
43 #else
44 static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double, double)>(
45 osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_set_device_scale"));
46 if (func)
47 func(surface, x_scale, y_scale);
48 #endif
51 void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, double* x_scale, double* y_scale)
53 #if !HAVE_DLAPI
54 cairo_surface_get_device_scale(surface, x_scale, y_scale);
55 #else
56 static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double*, double*)>(
57 osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_get_device_scale"));
58 if (func)
59 func(surface, x_scale, y_scale);
60 else
62 if (x_scale)
63 *x_scale = 1.0;
64 if (y_scale)
65 *y_scale = 1.0;
67 #endif
70 basegfx::B2DRange getFillDamage(cairo_t* cr)
72 double x1, y1, x2, y2;
74 // this is faster than cairo_fill_extents, at the cost of some overdraw
75 cairo_path_extents(cr, &x1, &y1, &x2, &y2);
77 // support B2DRange::isEmpty()
78 if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
80 return basegfx::B2DRange(x1, y1, x2, y2);
83 return basegfx::B2DRange();
86 basegfx::B2DRange getClipBox(cairo_t* cr)
88 double x1, y1, x2, y2;
90 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
92 // support B2DRange::isEmpty()
93 if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
95 return basegfx::B2DRange(x1, y1, x2, y2);
98 return basegfx::B2DRange();
101 basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
103 basegfx::B2DRange aDamageRect(getFillDamage(cr));
104 aDamageRect.intersect(getClipBox(cr));
105 return aDamageRect;
108 basegfx::B2DRange getStrokeDamage(cairo_t* cr)
110 double x1, y1, x2, y2;
112 // less accurate, but much faster
113 cairo_path_extents(cr, &x1, &y1, &x2, &y2);
115 // support B2DRange::isEmpty()
116 if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
118 return basegfx::B2DRange(x1, y1, x2, y2);
121 return basegfx::B2DRange();
124 basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
126 basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
127 aDamageRect.intersect(getClipBox(cr));
128 return aDamageRect;
131 // Remove bClosePath: Checked that the already used mechanism for Win using
132 // Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace
133 // this.
134 // For PixelSnap we need the ObjectToDevice transformation here now. This is a
135 // special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in
136 // DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we
137 // need the ObjectToDevice transformation *without* that offset here to do the
138 // same. The LineDraw-Offset will be applied by the callers using a linear
139 // transformation for Cairo now
140 // For support of PixelSnapHairline we also need the ObjectToDevice transformation
141 // and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g.
142 // for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!)
143 // tdf#129845 add reply value to allow counting a point/byte/size measurement to
144 // be included
145 size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
146 const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap,
147 bool bPixelSnapHairline)
149 // short circuit if there is nothing to do
150 const sal_uInt32 nPointCount(rPolygon.count());
151 size_t nSizeMeasure(0);
153 if (0 == nPointCount)
155 return nSizeMeasure;
158 const bool bHasCurves(rPolygon.areControlPointsUsed());
159 const bool bClosePath(rPolygon.isClosed());
160 const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
161 basegfx::B2DHomMatrix aObjectToDeviceInv;
162 basegfx::B2DPoint aLast;
163 PixelSnapper aSnapper;
165 for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
167 int nClosedIdx = nPointIdx;
168 if (nPointIdx >= nPointCount)
170 // prepare to close last curve segment if needed
171 if (bClosePath && (nPointIdx == nPointCount))
173 nClosedIdx = 0;
175 else
177 break;
181 basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx));
183 if (bPixelSnap)
185 // snap device coordinates to full pixels
186 if (bObjectToDeviceUsed)
188 // go to DeviceCoordinates
189 aPoint *= rObjectToDevice;
192 // snap by rounding
193 aPoint.setX(basegfx::fround(aPoint.getX()));
194 aPoint.setY(basegfx::fround(aPoint.getY()));
196 if (bObjectToDeviceUsed)
198 if (aObjectToDeviceInv.isIdentity())
200 aObjectToDeviceInv = rObjectToDevice;
201 aObjectToDeviceInv.invert();
204 // go back to ObjectCoordinates
205 aPoint *= aObjectToDeviceInv;
209 if (bPixelSnapHairline)
211 // snap horizontal and vertical lines (mainly used in Chart for
212 // 'nicer' AAing)
213 aPoint = aSnapper.snap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
216 if (!nPointIdx)
218 // first point => just move there
219 cairo_move_to(cr, aPoint.getX(), aPoint.getY());
220 aLast = aPoint;
221 continue;
224 bool bPendingCurve(false);
226 if (bHasCurves)
228 bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
229 bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
232 if (!bPendingCurve) // line segment
234 cairo_line_to(cr, aPoint.getX(), aPoint.getY());
235 nSizeMeasure++;
237 else // cubic bezier segment
239 basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
240 basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
242 // tdf#99165 if the control points are 'empty', create the mathematical
243 // correct replacement ones to avoid problems with the graphical sub-system
244 // tdf#101026 The 1st attempt to create a mathematically correct replacement control
245 // vector was wrong. Best alternative is one as close as possible which means short.
246 if (aCP1.equal(aLast))
248 aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
251 if (aCP2.equal(aPoint))
253 aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
256 cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
257 aPoint.getY());
258 // take some bigger measure for curve segments - too expensive to subdivide
259 // here and that precision not needed, but four (2 points, 2 control-points)
260 // would be a too low weight
261 nSizeMeasure += 10;
264 aLast = aPoint;
267 if (bClosePath)
269 cairo_close_path(cr);
272 return nSizeMeasure;
275 basegfx::B2DPoint PixelSnapper::snap(const basegfx::B2DPolygon& rPolygon,
276 const basegfx::B2DHomMatrix& rObjectToDevice,
277 basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex)
279 const sal_uInt32 nCount(rPolygon.count());
281 // get the data
282 if (nIndex == 0)
284 // if it's the first time, we need to calculate everything
285 maPrevPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount);
286 maCurrPoint = rObjectToDevice * rPolygon.getB2DPoint(nIndex);
287 maPrevTuple = basegfx::fround(maPrevPoint);
288 maCurrTuple = basegfx::fround(maCurrPoint);
290 else
292 // but for all other times, we can re-use the previous iteration computations
293 maPrevPoint = maCurrPoint;
294 maPrevTuple = maCurrTuple;
295 maCurrPoint = maNextPoint;
296 maCurrTuple = maNextTuple;
298 maNextPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount);
299 maNextTuple = basegfx::fround(maNextPoint);
301 // get the states
302 const bool bPrevVertical(maPrevTuple.getX() == maCurrTuple.getX());
303 const bool bNextVertical(maNextTuple.getX() == maCurrTuple.getX());
304 const bool bPrevHorizontal(maPrevTuple.getY() == maCurrTuple.getY());
305 const bool bNextHorizontal(maNextTuple.getY() == maCurrTuple.getY());
306 const bool bSnapX(bPrevVertical || bNextVertical);
307 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
309 if (bSnapX || bSnapY)
311 basegfx::B2DPoint aSnappedPoint(bSnapX ? maCurrTuple.getX() : maCurrPoint.getX(),
312 bSnapY ? maCurrTuple.getY() : maCurrPoint.getY());
314 if (rObjectToDeviceInv.isIdentity())
316 rObjectToDeviceInv = rObjectToDevice;
317 rObjectToDeviceInv.invert();
320 aSnappedPoint *= rObjectToDeviceInv;
322 return aSnappedPoint;
325 return rPolygon.getB2DPoint(nIndex);
328 SystemDependentData_CairoPath::SystemDependentData_CairoPath(size_t nSizeMeasure, cairo_t* cr,
329 bool bNoJoin, bool bAntiAlias,
330 const std::vector<double>* pStroke)
331 : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
332 , mpCairoPath(nullptr)
333 , mbNoJoin(bNoJoin)
334 , mbAntiAlias(bAntiAlias)
336 static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
338 // tdf#129845 only create a copy of the path when nSizeMeasure is
339 // bigger than some decent threshold
340 if (!bFuzzing && nSizeMeasure > 50)
342 mpCairoPath = cairo_copy_path(cr);
344 if (nullptr != pStroke)
346 maStroke = *pStroke;
351 SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
353 if (nullptr != mpCairoPath)
355 cairo_path_destroy(mpCairoPath);
356 mpCairoPath = nullptr;
360 sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
362 // tdf#129845 by using the default return value of zero when no path
363 // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds
364 // will do the right thing and not buffer this entry at all
365 sal_Int64 nRetval(0);
367 if (nullptr != mpCairoPath)
369 // per node
370 // - num_data incarnations of
371 // - sizeof(cairo_path_data_t) which is a union of defines and point data
372 // thus may 2 x sizeof(double)
373 nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
376 return nRetval;
379 void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon,
380 const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap)
382 // try to access buffered data
383 std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
384 rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPath>());
386 if (pSystemDependentData_CairoPath)
388 // re-use data
389 cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
391 else
393 // create data
394 size_t nSizeMeasure(0);
396 for (const auto& rPoly : rPolyPolygon)
398 // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE'
399 // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset
400 nSizeMeasure += AddPolygonToPath(cr, rPoly, rObjectToDevice, bPixelSnap, false);
403 // copy and add to buffering mechanism
404 // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon
405 pSystemDependentData_CairoPath
406 = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
407 nSizeMeasure, cr, false, false, nullptr);
411 cairo_user_data_key_t* CairoCommon::getDamageKey()
413 static cairo_user_data_key_t aDamageKey;
414 return &aDamageKey;
417 sal_uInt16 CairoCommon::GetBitCount() const
419 if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_ALPHA)
420 return 1;
421 return 32;
424 cairo_t* CairoCommon::getCairoContext(bool bXorModeAllowed, bool bAntiAlias) const
426 cairo_t* cr;
427 if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
428 cr = createTmpCompatibleCairoContext();
429 else
430 cr = cairo_create(m_pSurface);
431 cairo_set_line_width(cr, 1);
432 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
433 cairo_set_antialias(cr, bAntiAlias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
434 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
436 // ensure no linear transformation and no PathInfo in local cairo_path_t
437 cairo_identity_matrix(cr);
438 cairo_new_path(cr);
440 return cr;
443 void CairoCommon::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed,
444 const basegfx::B2DRange& rExtents) const
446 const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
448 if (rExtents.isEmpty())
450 //nothing changed, return early
451 if (bXoring)
453 cairo_surface_t* surface = cairo_get_target(cr);
454 cairo_surface_destroy(surface);
456 cairo_destroy(cr);
457 return;
460 basegfx::B2IRange aIntExtents(basegfx::unotools::b2ISurroundingRangeFromB2DRange(rExtents));
461 sal_Int32 nExtentsLeft(aIntExtents.getMinX()), nExtentsTop(aIntExtents.getMinY());
462 sal_Int32 nExtentsRight(aIntExtents.getMaxX()), nExtentsBottom(aIntExtents.getMaxY());
463 sal_Int32 nWidth = m_aFrameSize.getX();
464 sal_Int32 nHeight = m_aFrameSize.getY();
465 nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
466 nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
467 nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
468 nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
470 cairo_surface_t* surface = cairo_get_target(cr);
471 cairo_surface_flush(surface);
473 //For the most part we avoid the use of XOR these days, but there
474 //are some edge cases where legacy stuff still supports it, so
475 //emulate it (slowly) here.
476 if (bXoring)
477 doXorOnRelease(nExtentsLeft, nExtentsTop, nExtentsRight, nExtentsBottom, surface, nWidth);
479 cairo_destroy(cr); // unref
481 DamageHandler* pDamage
482 = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
484 if (pDamage)
486 pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
487 nExtentsBottom - nExtentsTop);
491 void CairoCommon::doXorOnRelease(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
492 sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom,
493 cairo_surface_t* const surface, sal_Int32 nWidth) const
495 //For the most part we avoid the use of XOR these days, but there
496 //are some edge cases where legacy stuff still supports it, so
497 //emulate it (slowly) here.
498 cairo_surface_t* target_surface = m_pSurface;
499 if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
501 //in the unlikely case we can't use m_pSurface directly, copy contents
502 //to another temp image surface
503 if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
504 target_surface = cairo_surface_map_to_image(target_surface, nullptr);
505 else
507 // for gen, which is CAIRO_FORMAT_RGB24/CAIRO_CONTENT_COLOR I'm getting
508 // visual corruption in vcldemo with cairo_surface_map_to_image
509 cairo_t* copycr = createTmpCompatibleCairoContext();
510 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
511 nExtentsBottom - nExtentsTop);
512 cairo_set_source_surface(copycr, m_pSurface, 0, 0);
513 cairo_fill(copycr);
514 target_surface = cairo_get_target(copycr);
515 cairo_destroy(copycr);
519 cairo_surface_flush(target_surface);
520 unsigned char* target_surface_data = cairo_image_surface_get_data(target_surface);
521 unsigned char* xor_surface_data = cairo_image_surface_get_data(surface);
523 cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
524 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
525 sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
526 sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
527 sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
528 sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
529 sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
531 // Handle headless size forced to (1,1) by SvpSalFrame::GetSurfaceFrameSize().
532 int target_surface_width = cairo_image_surface_get_width(target_surface);
533 if (nUnscaledExtentsLeft > target_surface_width)
534 nUnscaledExtentsLeft = target_surface_width;
535 if (nUnscaledExtentsRight > target_surface_width)
536 nUnscaledExtentsRight = target_surface_width;
537 int target_surface_height = cairo_image_surface_get_height(target_surface);
538 if (nUnscaledExtentsTop > target_surface_height)
539 nUnscaledExtentsTop = target_surface_height;
540 if (nUnscaledExtentsBottom > target_surface_height)
541 nUnscaledExtentsBottom = target_surface_height;
543 #if !ENABLE_WASM_STRIP_PREMULTIPLY
544 vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
545 vcl::bitmap::lookup_table const& premultiply_table = vcl::bitmap::get_premultiply_table();
546 #endif
547 for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
549 unsigned char* true_row = target_surface_data + (nStride * y);
550 unsigned char* xor_row = xor_surface_data + (nStride * y);
551 unsigned char* true_data = true_row + (nUnscaledExtentsLeft * 4);
552 unsigned char* xor_data = xor_row + (nUnscaledExtentsLeft * 4);
553 for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
555 sal_uInt8 a = true_data[SVP_CAIRO_ALPHA];
556 sal_uInt8 xor_a = xor_data[SVP_CAIRO_ALPHA];
557 #if ENABLE_WASM_STRIP_PREMULTIPLY
558 sal_uInt8 b = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_BLUE])
559 ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_BLUE]);
560 sal_uInt8 g = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_GREEN])
561 ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_GREEN]);
562 sal_uInt8 r = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_RED])
563 ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_RED]);
564 true_data[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(a, b);
565 true_data[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(a, g);
566 true_data[SVP_CAIRO_RED] = vcl::bitmap::premultiply(a, r);
567 #else
568 sal_uInt8 b = unpremultiply_table[a][true_data[SVP_CAIRO_BLUE]]
569 ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_BLUE]];
570 sal_uInt8 g = unpremultiply_table[a][true_data[SVP_CAIRO_GREEN]]
571 ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_GREEN]];
572 sal_uInt8 r = unpremultiply_table[a][true_data[SVP_CAIRO_RED]]
573 ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_RED]];
574 true_data[SVP_CAIRO_BLUE] = premultiply_table[a][b];
575 true_data[SVP_CAIRO_GREEN] = premultiply_table[a][g];
576 true_data[SVP_CAIRO_RED] = premultiply_table[a][r];
577 #endif
578 true_data += 4;
579 xor_data += 4;
582 cairo_surface_mark_dirty(target_surface);
584 if (target_surface != m_pSurface)
586 if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
587 cairo_surface_unmap_image(m_pSurface, target_surface);
588 else
590 cairo_t* copycr = cairo_create(m_pSurface);
591 //copy contents back from image surface
592 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
593 nExtentsBottom - nExtentsTop);
594 cairo_set_source_surface(copycr, target_surface, 0, 0);
595 cairo_fill(copycr);
596 cairo_destroy(copycr);
597 cairo_surface_destroy(target_surface);
601 cairo_surface_destroy(surface);
604 cairo_t* CairoCommon::createTmpCompatibleCairoContext() const
606 cairo_surface_t* target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32,
607 m_aFrameSize.getX() * m_fScale,
608 m_aFrameSize.getY() * m_fScale);
610 dl_cairo_surface_set_device_scale(target, m_fScale, m_fScale);
612 return cairo_create(target);
615 void CairoCommon::applyColor(cairo_t* cr, Color aColor, double fTransparency)
617 if (cairo_surface_get_content(cairo_get_target(cr)) != CAIRO_CONTENT_ALPHA)
619 cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0,
620 aColor.GetBlue() / 255.0, 1.0 - fTransparency);
622 else
624 double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
625 cairo_set_source_rgba(cr, 1, 1, 1, fSet);
626 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
630 void CairoCommon::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
632 RectangleVector aRectangles;
633 if (!rClipRegion.IsEmpty())
635 rClipRegion.GetRegionRectangles(aRectangles);
637 if (!aRectangles.empty())
639 bool bEmpty = true;
640 for (auto const& rectangle : aRectangles)
642 if (rectangle.GetWidth() <= 0 || rectangle.GetHeight() <= 0)
644 SAL_WARN("vcl.gdi", "bad clip rect of: " << rectangle);
645 continue;
647 cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(),
648 rectangle.GetHeight());
649 bEmpty = false;
651 if (!bEmpty)
652 cairo_clip(cr);
656 void CairoCommon::clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, m_aClipRegion); }
658 void CairoCommon::SetXORMode(bool bSet, bool /*bInvertOnly*/)
660 m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
663 void CairoCommon::SetROPLineColor(SalROPColor nROPColor)
665 switch (nROPColor)
667 case SalROPColor::N0:
668 m_oLineColor = Color(0, 0, 0);
669 break;
670 case SalROPColor::N1:
671 m_oLineColor = Color(0xff, 0xff, 0xff);
672 break;
673 case SalROPColor::Invert:
674 m_oLineColor = Color(0xff, 0xff, 0xff);
675 break;
679 void CairoCommon::SetROPFillColor(SalROPColor nROPColor)
681 switch (nROPColor)
683 case SalROPColor::N0:
684 m_oFillColor = Color(0, 0, 0);
685 break;
686 case SalROPColor::N1:
687 m_oFillColor = Color(0xff, 0xff, 0xff);
688 break;
689 case SalROPColor::Invert:
690 m_oFillColor = Color(0xff, 0xff, 0xff);
691 break;
695 void CairoCommon::drawPixel(const std::optional<Color>& rLineColor, tools::Long nX, tools::Long nY,
696 bool bAntiAlias)
698 if (!rLineColor)
699 return;
701 cairo_t* cr = getCairoContext(true, bAntiAlias);
702 clipRegion(cr);
704 cairo_rectangle(cr, nX, nY, 1, 1);
705 CairoCommon::applyColor(cr, *rLineColor, 0.0);
706 cairo_fill(cr);
708 basegfx::B2DRange extents = getClippedFillDamage(cr);
709 releaseCairoContext(cr, true, extents);
712 Color CairoCommon::getPixel(cairo_surface_t* pSurface, tools::Long nX, tools::Long nY)
714 cairo_surface_t* target
715 = cairo_surface_create_similar_image(pSurface, CAIRO_FORMAT_ARGB32, 1, 1);
717 cairo_t* cr = cairo_create(target);
719 cairo_rectangle(cr, 0, 0, 1, 1);
720 cairo_set_source_surface(cr, pSurface, -nX, -nY);
721 cairo_paint(cr);
722 cairo_destroy(cr);
724 cairo_surface_flush(target);
725 #if !ENABLE_WASM_STRIP_PREMULTIPLY
726 vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
727 #endif
728 unsigned char* data = cairo_image_surface_get_data(target);
729 sal_uInt8 a = data[SVP_CAIRO_ALPHA];
730 #if ENABLE_WASM_STRIP_PREMULTIPLY
731 sal_uInt8 b = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_BLUE]);
732 sal_uInt8 g = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_GREEN]);
733 sal_uInt8 r = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_RED]);
734 #else
735 sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
736 sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
737 sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
738 #endif
739 Color aColor(ColorAlpha, a, r, g, b);
740 cairo_surface_destroy(target);
742 return aColor;
745 void CairoCommon::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2,
746 bool bAntiAlias)
748 cairo_t* cr = getCairoContext(false, bAntiAlias);
749 clipRegion(cr);
751 basegfx::B2DPolygon aPoly;
753 // PixelOffset used: To not mix with possible PixelSnap, cannot do
754 // directly on coordinates as tried before - despite being already 'snapped'
755 // due to being integer. If it would be directly added here, it would be
756 // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset
757 aPoly.append(basegfx::B2DPoint(nX1, nY1));
758 aPoly.append(basegfx::B2DPoint(nX2, nY2));
760 // PixelOffset used: Set PixelOffset as linear transformation
761 cairo_matrix_t aMatrix;
762 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
763 cairo_set_matrix(cr, &aMatrix);
765 AddPolygonToPath(cr, aPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false);
767 CairoCommon::applyColor(cr, *m_oLineColor);
769 basegfx::B2DRange extents = getClippedStrokeDamage(cr);
770 extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
772 cairo_stroke(cr);
774 releaseCairoContext(cr, false, extents);
777 void CairoCommon::drawRect(double nX, double nY, double nWidth, double nHeight, bool bAntiAlias)
779 // fast path for the common case of simply creating a solid block of color
780 if (m_oFillColor && m_oLineColor && m_oFillColor == m_oLineColor)
782 double fTransparency = 0;
783 // don't bother trying to draw stuff which is effectively invisible
784 if (nWidth < 0.1 || nHeight < 0.1)
785 return;
787 cairo_t* cr = getCairoContext(true, bAntiAlias);
788 clipRegion(cr);
790 bool bPixelSnap = !bAntiAlias;
791 if (bPixelSnap)
793 // snap by rounding
794 nX = basegfx::fround(nX);
795 nY = basegfx::fround(nY);
796 nWidth = basegfx::fround(nWidth);
797 nHeight = basegfx::fround(nHeight);
799 cairo_rectangle(cr, nX, nY, nWidth, nHeight);
801 CairoCommon::applyColor(cr, *m_oFillColor, fTransparency);
802 // Get FillDamage
803 basegfx::B2DRange extents = getClippedFillDamage(cr);
805 cairo_fill(cr);
807 releaseCairoContext(cr, true, extents);
809 return;
811 // because of the -1 hack we have to do fill and draw separately
812 std::optional<Color> aOrigFillColor = m_oFillColor;
813 std::optional<Color> aOrigLineColor = m_oLineColor;
814 m_oFillColor = std::nullopt;
815 m_oLineColor = std::nullopt;
817 if (aOrigFillColor)
819 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
820 basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
822 m_oFillColor = aOrigFillColor;
823 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0, bAntiAlias);
824 m_oFillColor = std::nullopt;
827 if (aOrigLineColor)
829 // need -1 hack to exclude the bottom and right edges to act like wingdi "Rectangle"
830 // function which is what this was probably the ultimate origin of this behavior
831 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
832 basegfx::B2DRectangle(nX, nY, nX + nWidth - 1, nY + nHeight - 1));
834 m_oLineColor = aOrigLineColor;
835 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0, bAntiAlias);
836 m_oLineColor = std::nullopt;
839 m_oFillColor = aOrigFillColor;
840 m_oLineColor = aOrigLineColor;
843 void CairoCommon::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
845 basegfx::B2DPolygon aPoly;
846 aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
847 for (sal_uInt32 i = 1; i < nPoints; ++i)
848 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
850 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPoly), 0.0, bAntiAlias);
853 void CairoCommon::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts,
854 const Point** pPtAry, bool bAntiAlias)
856 basegfx::B2DPolyPolygon aPolyPoly;
857 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
859 sal_uInt32 nPoints = pPointCounts[nPolygon];
860 if (nPoints)
862 const Point* pPoints = pPtAry[nPolygon];
863 basegfx::B2DPolygon aPoly;
864 aPoly.append(basegfx::B2DPoint(pPoints->getX(), pPoints->getY()), nPoints);
865 for (sal_uInt32 i = 1; i < nPoints; ++i)
866 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPoints[i].getX(), pPoints[i].getY()));
868 aPolyPoly.append(aPoly);
872 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPoly, 0.0, bAntiAlias);
875 bool CairoCommon::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
876 const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency,
877 bool bAntiAlias)
879 const bool bHasFill(m_oFillColor.has_value());
880 const bool bHasLine(m_oLineColor.has_value());
882 if (0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0
883 || fTransparency >= 1.0)
885 return true;
888 if (!bHasLine)
890 // don't bother trying to draw stuff which is effectively invisible, speeds up
891 // drawing some complex drawings. This optimisation is not valid when we do
892 // the pixel offset thing (i.e. bHasLine)
893 basegfx::B2DRange aPolygonRange = rPolyPolygon.getB2DRange();
894 aPolygonRange.transform(rObjectToDevice);
895 if (aPolygonRange.getWidth() < 0.1 || aPolygonRange.getHeight() < 0.1)
896 return true;
899 cairo_t* cr = getCairoContext(true, bAntiAlias);
900 if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
902 SAL_WARN("vcl.gdi",
903 "cannot render to surface: " << cairo_status_to_string(cairo_status(cr)));
904 releaseCairoContext(cr, true, basegfx::B2DRange());
905 return true;
907 clipRegion(cr);
909 // Set full (Object-to-Device) transformation - if used
910 if (!rObjectToDevice.isIdentity())
912 cairo_matrix_t aMatrix;
914 cairo_matrix_init(&aMatrix, rObjectToDevice.get(0, 0), rObjectToDevice.get(1, 0),
915 rObjectToDevice.get(0, 1), rObjectToDevice.get(1, 1),
916 rObjectToDevice.get(0, 2), rObjectToDevice.get(1, 2));
917 cairo_set_matrix(cr, &aMatrix);
920 // To make releaseCairoContext work, use empty extents
921 basegfx::B2DRange extents;
923 if (bHasFill)
925 add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
927 CairoCommon::applyColor(cr, *m_oFillColor, fTransparency);
928 // Get FillDamage (will be extended for LineDamage below)
929 extents = getClippedFillDamage(cr);
931 cairo_fill(cr);
934 if (bHasLine)
936 // PixelOffset used: Set PixelOffset as linear transformation
937 cairo_matrix_t aMatrix;
938 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
939 cairo_set_matrix(cr, &aMatrix);
941 add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
943 CairoCommon::applyColor(cr, *m_oLineColor, fTransparency);
945 // expand with possible StrokeDamage
946 basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
947 stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
948 extents.expand(stroke_extents);
950 cairo_stroke(cr);
953 // if transformation has been applied, transform also extents (ranges)
954 // of damage so they can be correctly redrawn
955 extents.transform(rObjectToDevice);
956 releaseCairoContext(cr, true, extents);
958 return true;
961 void CairoCommon::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
963 basegfx::B2DPolygon aPoly;
964 aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
965 for (sal_uInt32 i = 1; i < nPoints; ++i)
966 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
967 aPoly.setClosed(false);
969 drawPolyLine(basegfx::B2DHomMatrix(), aPoly, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
970 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false, bAntiAlias);
973 bool CairoCommon::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
974 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
975 double fLineWidth, const std::vector<double>* pStroke,
976 basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
977 double fMiterMinimumAngle, bool bPixelSnapHairline, bool bAntiAlias)
979 // short circuit if there is nothing to do
980 if (0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
982 return true;
985 static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
986 if (bFuzzing)
988 const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyLine));
989 if (aRange.getMaxX() - aRange.getMinX() > 0x10000000
990 || aRange.getMaxY() - aRange.getMinY() > 0x10000000)
992 SAL_WARN("vcl.gdi", "drawPolyLine, skipping suspicious range of: "
993 << aRange << " for fuzzing performance");
994 return true;
998 cairo_t* cr = getCairoContext(false, bAntiAlias);
999 clipRegion(cr);
1001 // need to check/handle LineWidth when ObjectToDevice transformation is used
1002 const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
1004 // tdf#124848 calculate-back logical LineWidth for a hairline
1005 // since this implementation hands over the transformation to
1006 // the graphic sub-system
1007 if (fLineWidth == 0)
1009 fLineWidth = 1.0;
1011 if (!bObjectToDeviceIsIdentity)
1013 basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
1014 aObjectToDeviceInv.invert();
1015 fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
1019 // PixelOffset used: Need to reflect in linear transformation
1020 cairo_matrix_t aMatrix;
1021 basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
1023 if (bObjectToDeviceIsIdentity)
1025 // Set PixelOffset as requested
1026 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1028 else
1030 // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into
1031 // account: Multiply from left to act in DeviceCoordinates
1032 aDamageMatrix = aDamageMatrix * rObjectToDevice;
1033 cairo_matrix_init(&aMatrix, aDamageMatrix.get(0, 0), aDamageMatrix.get(1, 0),
1034 aDamageMatrix.get(0, 1), aDamageMatrix.get(1, 1), aDamageMatrix.get(0, 2),
1035 aDamageMatrix.get(1, 2));
1038 // set linear transformation
1039 cairo_set_matrix(cr, &aMatrix);
1041 // setup line attributes
1042 cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
1043 switch (eLineJoin)
1045 case basegfx::B2DLineJoin::Bevel:
1046 eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
1047 break;
1048 case basegfx::B2DLineJoin::Round:
1049 eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
1050 break;
1051 case basegfx::B2DLineJoin::NONE:
1052 case basegfx::B2DLineJoin::Miter:
1053 eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
1054 break;
1057 // convert miter minimum angle to miter limit
1058 double fMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0);
1060 // setup cap attribute
1061 cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
1063 switch (eLineCap)
1065 default: // css::drawing::LineCap_BUTT:
1067 eCairoLineCap = CAIRO_LINE_CAP_BUTT;
1068 break;
1070 case css::drawing::LineCap_ROUND:
1072 eCairoLineCap = CAIRO_LINE_CAP_ROUND;
1073 break;
1075 case css::drawing::LineCap_SQUARE:
1077 eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
1078 break;
1082 cairo_set_source_rgba(cr, m_oLineColor->GetRed() / 255.0, m_oLineColor->GetGreen() / 255.0,
1083 m_oLineColor->GetBlue() / 255.0, 1.0 - fTransparency);
1085 cairo_set_line_join(cr, eCairoLineJoin);
1086 cairo_set_line_cap(cr, eCairoLineCap);
1088 constexpr int MaxNormalLineWidth = 64;
1089 if (fLineWidth > MaxNormalLineWidth)
1091 const double fLineWidthPixel
1092 = bObjectToDeviceIsIdentity
1093 ? fLineWidth
1094 : (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1095 if (fLineWidthPixel > MaxNormalLineWidth)
1097 SAL_WARN("vcl.gdi", "drawPolyLine, suspicious input line width of: "
1098 << fLineWidth << ", will be " << fLineWidthPixel
1099 << " pixels thick");
1100 if (bFuzzing)
1102 basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
1103 aObjectToDeviceInv.invert();
1104 fLineWidth
1105 = (aObjectToDeviceInv * basegfx::B2DVector(MaxNormalLineWidth, 0)).getLength();
1106 fLineWidth = std::min(fLineWidth, 2048.0);
1110 cairo_set_line_width(cr, fLineWidth);
1111 cairo_set_miter_limit(cr, fMiterLimit);
1113 // try to access buffered data
1114 std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
1115 rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
1117 // MM01 need to do line dashing as fallback stuff here now
1118 const double fDotDashLength(
1119 nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
1120 const bool bStrokeUsed(0.0 != fDotDashLength);
1121 assert(!bStrokeUsed || (bStrokeUsed && pStroke));
1123 // MM01 decide if to stroke directly
1124 static const bool bDoDirectCairoStroke(true);
1126 // MM01 activate to stroke directly
1127 if (bDoDirectCairoStroke && bStrokeUsed)
1129 cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0);
1132 if (!bDoDirectCairoStroke && pSystemDependentData_CairoPath)
1134 // MM01 - check on stroke change. Used against not used, or if both used,
1135 // equal or different?
1136 const bool bStrokeWasUsed(!pSystemDependentData_CairoPath->getStroke().empty());
1138 if (bStrokeWasUsed != bStrokeUsed
1139 || (bStrokeUsed && *pStroke != pSystemDependentData_CairoPath->getStroke()))
1141 // data invalid, forget
1142 pSystemDependentData_CairoPath.reset();
1146 // check for basegfx::B2DLineJoin::NONE to react accordingly
1147 const bool bNoJoin(
1148 (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(fLineWidth, 0.0)));
1150 if (pSystemDependentData_CairoPath)
1152 // check data validity
1153 if (nullptr == pSystemDependentData_CairoPath->getCairoPath()
1154 || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
1155 || pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias
1156 || bPixelSnapHairline /*tdf#124700*/)
1158 // data invalid, forget
1159 pSystemDependentData_CairoPath.reset();
1163 if (pSystemDependentData_CairoPath)
1165 // re-use data
1166 cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
1168 else
1170 // create data
1171 size_t nSizeMeasure(0);
1173 // MM01 need to do line dashing as fallback stuff here now
1174 basegfx::B2DPolyPolygon aPolyPolygonLine;
1176 if (!bDoDirectCairoStroke && bStrokeUsed)
1178 // apply LineStyle
1179 basegfx::utils::applyLineDashing(rPolyLine, // source
1180 *pStroke, // pattern
1181 &aPolyPolygonLine, // target for lines
1182 nullptr, // target for gaps
1183 fDotDashLength); // full length if available
1185 else
1187 // no line dashing or direct stroke, just copy
1188 aPolyPolygonLine.append(rPolyLine);
1191 // MM01 checked/verified for Cairo
1192 for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
1194 const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
1196 if (!bNoJoin)
1198 // PixelOffset now reflected in linear transformation used
1199 nSizeMeasure
1200 += AddPolygonToPath(cr, aPolyLine,
1201 rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
1202 !bAntiAlias, bPixelSnapHairline);
1204 else
1206 const sal_uInt32 nPointCount(aPolyLine.count());
1207 const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1);
1208 basegfx::B2DPolygon aEdge;
1210 aEdge.append(aPolyLine.getB2DPoint(0));
1211 aEdge.append(basegfx::B2DPoint(0.0, 0.0));
1213 for (sal_uInt32 i(0); i < nEdgeCount; i++)
1215 const sal_uInt32 nNextIndex((i + 1) % nPointCount);
1216 aEdge.setB2DPoint(1, aPolyLine.getB2DPoint(nNextIndex));
1217 aEdge.setNextControlPoint(0, aPolyLine.getNextControlPoint(i));
1218 aEdge.setPrevControlPoint(1, aPolyLine.getPrevControlPoint(nNextIndex));
1220 // PixelOffset now reflected in linear transformation used
1221 nSizeMeasure += AddPolygonToPath(
1222 cr, aEdge,
1223 rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
1224 !bAntiAlias, bPixelSnapHairline);
1226 // prepare next step
1227 aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
1232 // copy and add to buffering mechanism
1233 if (!bPixelSnapHairline /*tdf#124700*/)
1235 pSystemDependentData_CairoPath
1236 = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
1237 nSizeMeasure, cr, bNoJoin, bAntiAlias, pStroke);
1241 // extract extents
1242 basegfx::B2DRange extents = getClippedStrokeDamage(cr);
1243 // transform also extents (ranges) of damage so they can be correctly redrawn
1244 extents.transform(aDamageMatrix);
1246 // draw and consume
1247 cairo_stroke(cr);
1249 releaseCairoContext(cr, false, extents);
1251 return true;
1254 bool CairoCommon::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
1255 tools::Long nHeight, sal_uInt8 nTransparency, bool bAntiAlias)
1257 const bool bHasFill(m_oFillColor.has_value());
1258 const bool bHasLine(m_oLineColor.has_value());
1260 if (!bHasFill && !bHasLine)
1261 return true;
1263 cairo_t* cr = getCairoContext(false, bAntiAlias);
1264 clipRegion(cr);
1266 const double fTransparency = nTransparency * (1.0 / 100);
1268 // To make releaseCairoContext work, use empty extents
1269 basegfx::B2DRange extents;
1271 if (bHasFill)
1273 cairo_rectangle(cr, nX, nY, nWidth, nHeight);
1275 applyColor(cr, *m_oFillColor, fTransparency);
1277 // set FillDamage
1278 extents = getClippedFillDamage(cr);
1280 cairo_fill(cr);
1283 if (bHasLine)
1285 // PixelOffset used: Set PixelOffset as linear transformation
1286 // Note: Was missing here - probably not by purpose (?)
1287 cairo_matrix_t aMatrix;
1288 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1289 cairo_set_matrix(cr, &aMatrix);
1291 cairo_rectangle(cr, nX, nY, nWidth, nHeight);
1293 applyColor(cr, *m_oLineColor, fTransparency);
1295 // expand with possible StrokeDamage
1296 basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
1297 stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
1298 extents.expand(stroke_extents);
1300 cairo_stroke(cr);
1303 releaseCairoContext(cr, false, extents);
1305 return true;
1308 bool CairoCommon::drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient,
1309 bool bAntiAlias)
1311 if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
1312 && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL)
1313 return false; // unsupported
1314 if (rGradient.GetSteps() != 0)
1315 return false; // We can't tell cairo how many colors to use in the gradient.
1317 cairo_t* cr = getCairoContext(true, bAntiAlias);
1318 clipRegion(cr);
1320 tools::Rectangle aInputRect(rPolyPolygon.GetBoundRect());
1321 if (rPolyPolygon.IsRect())
1323 // Rect->Polygon conversion loses the right and bottom edge, fix that.
1324 aInputRect.AdjustRight(1);
1325 aInputRect.AdjustBottom(1);
1326 basegfx::B2DHomMatrix rObjectToDevice;
1327 AddPolygonToPath(cr, tools::Polygon(aInputRect).getB2DPolygon(), rObjectToDevice,
1328 !bAntiAlias, false);
1330 else
1332 basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPolygon.getB2DPolyPolygon());
1333 for (auto const& rPolygon : std::as_const(aB2DPolyPolygon))
1335 basegfx::B2DHomMatrix rObjectToDevice;
1336 AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false);
1340 Gradient aGradient(rGradient);
1342 tools::Rectangle aBoundRect;
1343 Point aCenter;
1345 aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
1346 aGradient.GetBoundRect(aInputRect, aBoundRect, aCenter);
1347 Color aStartColor = aGradient.GetStartColor();
1348 Color aEndColor = aGradient.GetEndColor();
1350 cairo_pattern_t* pattern;
1351 if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR)
1353 tools::Polygon aPoly(aBoundRect);
1354 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
1355 pattern
1356 = cairo_pattern_create_linear(aPoly[0].X(), aPoly[0].Y(), aPoly[1].X(), aPoly[1].Y());
1358 else
1360 double radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
1361 // Move the center a bit to the top-left (the default VCL algorithm is a bit off-center that way,
1362 // cairo is the opposite way).
1363 pattern = cairo_pattern_create_radial(aCenter.X() - 0.5, aCenter.Y() - 0.5, 0,
1364 aCenter.X() - 0.5, aCenter.Y() - 0.5, radius);
1365 std::swap(aStartColor, aEndColor);
1368 cairo_pattern_add_color_stop_rgba(
1369 pattern, aGradient.GetBorder() / 100.0,
1370 aStartColor.GetRed() * aGradient.GetStartIntensity() / 25500.0,
1371 aStartColor.GetGreen() * aGradient.GetStartIntensity() / 25500.0,
1372 aStartColor.GetBlue() * aGradient.GetStartIntensity() / 25500.0, 1.0);
1374 cairo_pattern_add_color_stop_rgba(
1375 pattern, 1.0, aEndColor.GetRed() * aGradient.GetEndIntensity() / 25500.0,
1376 aEndColor.GetGreen() * aGradient.GetEndIntensity() / 25500.0,
1377 aEndColor.GetBlue() * aGradient.GetEndIntensity() / 25500.0, 1.0);
1379 cairo_set_source(cr, pattern);
1380 cairo_pattern_destroy(pattern);
1382 basegfx::B2DRange extents = getClippedFillDamage(cr);
1383 cairo_fill_preserve(cr);
1385 releaseCairoContext(cr, true, extents);
1387 return true;
1390 bool CairoCommon::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
1391 SalGradient const& rGradient, bool bAntiAlias)
1393 cairo_t* cr = getCairoContext(true, bAntiAlias);
1395 basegfx::B2DHomMatrix rObjectToDevice;
1397 for (auto const& rPolygon : rPolyPolygon)
1398 AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false);
1400 cairo_pattern_t* pattern
1401 = cairo_pattern_create_linear(rGradient.maPoint1.getX(), rGradient.maPoint1.getY(),
1402 rGradient.maPoint2.getX(), rGradient.maPoint2.getY());
1404 for (SalGradientStop const& rStop : rGradient.maStops)
1406 double r = rStop.maColor.GetRed() / 255.0;
1407 double g = rStop.maColor.GetGreen() / 255.0;
1408 double b = rStop.maColor.GetBlue() / 255.0;
1409 double a = rStop.maColor.GetAlpha() / 255.0;
1410 double offset = rStop.mfOffset;
1412 cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a);
1414 cairo_set_source(cr, pattern);
1415 cairo_pattern_destroy(pattern);
1417 basegfx::B2DRange extents = getClippedFillDamage(cr);
1419 cairo_fill_preserve(cr);
1421 releaseCairoContext(cr, true, extents);
1423 return true;
1426 namespace
1428 basegfx::B2DRange renderWithOperator(cairo_t* cr, const SalTwoRect& rTR, cairo_surface_t* source,
1429 cairo_operator_t eOperator = CAIRO_OPERATOR_SOURCE)
1431 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1433 basegfx::B2DRange extents = getClippedFillDamage(cr);
1435 cairo_clip(cr);
1437 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1438 if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0)
1440 double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
1441 double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
1442 cairo_scale(cr, fXScale, fYScale);
1445 cairo_save(cr);
1446 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
1448 if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
1450 //tdf#133716 borders of upscaled images should not be blurred
1451 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1452 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
1455 cairo_set_operator(cr, eOperator);
1456 cairo_paint(cr);
1457 cairo_restore(cr);
1459 return extents;
1462 } // end anonymous ns
1464 basegfx::B2DRange CairoCommon::renderSource(cairo_t* cr, const SalTwoRect& rTR,
1465 cairo_surface_t* source)
1467 return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE);
1470 void CairoCommon::copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source,
1471 cairo_operator_t eOp, bool bAntiAlias)
1473 cairo_t* cr = getCairoContext(false, bAntiAlias);
1474 clipRegion(cr);
1476 basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp);
1478 releaseCairoContext(cr, false, extents);
1481 void CairoCommon::copySource(const SalTwoRect& rTR, cairo_surface_t* source, bool bAntiAlias)
1483 copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
1486 void CairoCommon::copyBitsCairo(const SalTwoRect& rTR, cairo_surface_t* pSourceSurface,
1487 bool bAntiAlias)
1489 SalTwoRect aTR(rTR);
1491 cairo_surface_t* pCopy = nullptr;
1493 if (pSourceSurface == getSurface())
1495 //self copy is a problem, so dup source in that case
1496 pCopy
1497 = cairo_surface_create_similar(pSourceSurface, cairo_surface_get_content(getSurface()),
1498 aTR.mnSrcWidth * m_fScale, aTR.mnSrcHeight * m_fScale);
1499 dl_cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
1500 cairo_t* cr = cairo_create(pCopy);
1501 cairo_set_source_surface(cr, pSourceSurface, -aTR.mnSrcX, -aTR.mnSrcY);
1502 cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
1503 cairo_fill(cr);
1504 cairo_destroy(cr);
1506 pSourceSurface = pCopy;
1508 aTR.mnSrcX = 0;
1509 aTR.mnSrcY = 0;
1512 copySource(aTR, pSourceSurface, bAntiAlias);
1514 if (pCopy)
1515 cairo_surface_destroy(pCopy);
1518 namespace
1520 cairo_pattern_t* create_stipple()
1522 static unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
1523 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF };
1524 cairo_surface_t* surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_A8, 4, 4, 4);
1525 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
1526 cairo_surface_destroy(surface);
1527 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
1528 cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
1529 return pattern;
1531 } // end anonymous ns
1533 void CairoCommon::invert(const basegfx::B2DPolygon& rPoly, SalInvert nFlags, bool bAntiAlias)
1535 cairo_t* cr = getCairoContext(false, bAntiAlias);
1536 clipRegion(cr);
1538 // To make releaseCairoContext work, use empty extents
1539 basegfx::B2DRange extents;
1541 AddPolygonToPath(cr, rPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false);
1543 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1545 cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
1547 if (nFlags & SalInvert::TrackFrame)
1549 cairo_set_line_width(cr, 2.0);
1550 const double dashLengths[2] = { 4.0, 4.0 };
1551 cairo_set_dash(cr, dashLengths, 2, 0);
1553 extents = getClippedStrokeDamage(cr);
1554 //see tdf#106577 under wayland, some pixel droppings seen, maybe we're
1555 //out by one somewhere, or cairo_stroke_extents is confused by
1556 //dashes/line width
1557 if (!extents.isEmpty())
1559 extents.grow(1);
1562 cairo_stroke(cr);
1564 else
1566 extents = getClippedFillDamage(cr);
1568 cairo_clip(cr);
1570 if (nFlags & SalInvert::N50)
1572 cairo_pattern_t* pattern = create_stipple();
1573 cairo_surface_t* surface = cairo_surface_create_similar(
1574 m_pSurface, cairo_surface_get_content(m_pSurface), extents.getWidth() * m_fScale,
1575 extents.getHeight() * m_fScale);
1577 dl_cairo_surface_set_device_scale(surface, m_fScale, m_fScale);
1578 cairo_t* stipple_cr = cairo_create(surface);
1579 cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0);
1580 cairo_mask(stipple_cr, pattern);
1581 cairo_pattern_destroy(pattern);
1582 cairo_destroy(stipple_cr);
1583 cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY());
1584 cairo_surface_destroy(surface);
1586 else
1588 cairo_paint(cr);
1592 releaseCairoContext(cr, false, extents);
1595 void CairoCommon::invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
1596 SalInvert nFlags, bool bAntiAlias)
1598 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
1599 basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
1601 invert(aRect, nFlags, bAntiAlias);
1604 void CairoCommon::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, bool bAntiAlias)
1606 basegfx::B2DPolygon aPoly;
1607 aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
1608 for (sal_uInt32 i = 1; i < nPoints; ++i)
1609 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
1610 aPoly.setClosed(true);
1612 invert(aPoly, nFlags, bAntiAlias);
1615 void CairoCommon::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1616 bool bAntiAlias)
1618 // MM02 try to access buffered BitmapHelper
1619 std::shared_ptr<BitmapHelper> aSurface;
1620 tryToUseSourceBuffer(rSalBitmap, aSurface);
1621 cairo_surface_t* source = aSurface->getSurface(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1623 if (!source)
1625 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
1626 return;
1629 #if 0 // LO code is not yet bitmap32-ready.
1630 // if m_bSupportsBitmap32 becomes true for Svp revisit this
1631 copyWithOperator(rPosAry, source, CAIRO_OPERATOR_OVER, bAntiAlias);
1632 #else
1633 copyWithOperator(rPosAry, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
1634 #endif
1637 bool CairoCommon::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap,
1638 const SalBitmap& rAlphaBitmap, bool bAntiAlias)
1640 if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
1642 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: "
1643 << rAlphaBitmap.GetBitCount());
1644 return false;
1647 if (!rTR.mnSrcWidth || !rTR.mnSrcHeight)
1649 SAL_WARN("vcl.gdi", "not possible to stretch nothing");
1650 return true;
1653 // MM02 try to access buffered BitmapHelper
1654 std::shared_ptr<BitmapHelper> aSurface;
1655 tryToUseSourceBuffer(rSourceBitmap, aSurface);
1656 cairo_surface_t* source = aSurface->getSurface(rTR.mnDestWidth, rTR.mnDestHeight);
1658 if (!source)
1660 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
1661 return false;
1664 // MM02 try to access buffered MaskHelper
1665 std::shared_ptr<MaskHelper> aMask;
1666 tryToUseMaskBuffer(rAlphaBitmap, aMask);
1667 cairo_surface_t* mask = aMask->getSurface(rTR.mnDestWidth, rTR.mnDestHeight);
1669 if (!mask)
1671 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
1672 return false;
1675 cairo_t* cr = getCairoContext(false, bAntiAlias);
1676 if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
1678 SAL_WARN("vcl.gdi",
1679 "cannot render to surface: " << cairo_status_to_string(cairo_status(cr)));
1680 releaseCairoContext(cr, false, basegfx::B2DRange());
1681 return true;
1684 clipRegion(cr);
1686 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1688 basegfx::B2DRange extents = getClippedFillDamage(cr);
1690 cairo_clip(cr);
1692 cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask);
1693 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1694 double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
1695 double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
1696 cairo_scale(cr, fXScale, fYScale);
1697 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
1699 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1701 //tdf#133716 borders of upscaled images should not be blurred
1702 //tdf#114117 when stretching a single or multi pixel width/height source to fit an area
1703 //the image will be extended into that size.
1704 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
1705 cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_PAD);
1707 //this block is just "cairo_mask_surface", but we have to make it explicit
1708 //because of the cairo_pattern_set_filter etc we may want applied
1709 cairo_matrix_t matrix;
1710 cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY);
1711 cairo_pattern_set_matrix(maskpattern, &matrix);
1712 cairo_mask(cr, maskpattern);
1714 cairo_pattern_destroy(maskpattern);
1716 releaseCairoContext(cr, false, extents);
1718 return true;
1721 bool CairoCommon::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
1722 const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
1723 const SalBitmap* pAlphaBitmap, double fAlpha,
1724 bool bAntiAlias)
1726 if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1)
1728 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: "
1729 << pAlphaBitmap->GetBitCount());
1730 return false;
1733 if (fAlpha != 1.0)
1734 return false;
1736 // MM02 try to access buffered BitmapHelper
1737 std::shared_ptr<BitmapHelper> aSurface;
1738 tryToUseSourceBuffer(rSourceBitmap, aSurface);
1739 const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
1740 const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));
1741 cairo_surface_t* source(aSurface->getSurface(nDestWidth, nDestHeight));
1743 if (!source)
1745 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
1746 return false;
1749 // MM02 try to access buffered MaskHelper
1750 std::shared_ptr<MaskHelper> aMask;
1751 if (nullptr != pAlphaBitmap)
1753 tryToUseMaskBuffer(*pAlphaBitmap, aMask);
1756 // access cairo_surface_t from MaskHelper
1757 cairo_surface_t* mask(nullptr);
1758 if (aMask)
1760 mask = aMask->getSurface(nDestWidth, nDestHeight);
1763 if (nullptr != pAlphaBitmap && nullptr == mask)
1765 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
1766 return false;
1769 const Size aSize = rSourceBitmap.GetSize();
1770 cairo_t* cr = getCairoContext(false, bAntiAlias);
1771 clipRegion(cr);
1773 // setup the image transformation
1774 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
1775 const basegfx::B2DVector aXRel = rX - rNull;
1776 const basegfx::B2DVector aYRel = rY - rNull;
1777 cairo_matrix_t matrix;
1778 cairo_matrix_init(&matrix, aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(),
1779 aYRel.getX() / aSize.Height(), aYRel.getY() / aSize.Height(), rNull.getX(),
1780 rNull.getY());
1782 cairo_transform(cr, &matrix);
1784 cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height());
1785 basegfx::B2DRange extents = getClippedFillDamage(cr);
1786 cairo_clip(cr);
1788 cairo_set_source_surface(cr, source, 0, 0);
1789 if (mask)
1790 cairo_mask_surface(cr, mask, 0, 0);
1791 else
1792 cairo_paint(cr);
1794 releaseCairoContext(cr, false, extents);
1796 return true;
1799 void CairoCommon::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor,
1800 bool bAntiAlias)
1802 /** creates an image from the given rectangle, replacing all black pixels
1803 * with nMaskColor and make all other full transparent */
1804 // MM02 here decided *against* using buffered BitmapHelper
1805 // because the data gets somehow 'unmuliplied'. This may also be
1806 // done just once, but I am not sure if this is safe to do.
1807 // So for now dispense re-using data here.
1808 BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32
1809 if (!aSurface.getSurface())
1811 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");
1812 return;
1814 sal_Int32 nStride;
1815 unsigned char* mask_data = aSurface.getBits(nStride);
1816 #if !ENABLE_WASM_STRIP_PREMULTIPLY
1817 vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
1818 #endif
1819 for (tools::Long y = rTR.mnSrcY; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
1821 unsigned char* row = mask_data + (nStride * y);
1822 unsigned char* data = row + (rTR.mnSrcX * 4);
1823 for (tools::Long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
1825 sal_uInt8 a = data[SVP_CAIRO_ALPHA];
1826 #if ENABLE_WASM_STRIP_PREMULTIPLY
1827 sal_uInt8 b = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_BLUE]);
1828 sal_uInt8 g = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_GREEN]);
1829 sal_uInt8 r = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_RED]);
1830 #else
1831 sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
1832 sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
1833 sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
1834 #endif
1835 if (r == 0 && g == 0 && b == 0)
1837 data[0] = nMaskColor.GetBlue();
1838 data[1] = nMaskColor.GetGreen();
1839 data[2] = nMaskColor.GetRed();
1840 data[3] = 0xff;
1842 else
1844 data[0] = 0;
1845 data[1] = 0;
1846 data[2] = 0;
1847 data[3] = 0;
1849 data += 4;
1852 aSurface.mark_dirty();
1854 cairo_t* cr = getCairoContext(false, bAntiAlias);
1855 clipRegion(cr);
1857 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1859 basegfx::B2DRange extents = getClippedFillDamage(cr);
1861 cairo_clip(cr);
1863 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1864 double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
1865 double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
1866 cairo_scale(cr, fXScale, fYScale);
1867 cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY);
1869 if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
1871 //tdf#133716 borders of upscaled images should not be blurred
1872 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1873 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
1876 cairo_paint(cr);
1878 releaseCairoContext(cr, false, extents);
1881 std::shared_ptr<SalBitmap> CairoCommon::getBitmap(tools::Long nX, tools::Long nY,
1882 tools::Long nWidth, tools::Long nHeight)
1884 std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>();
1885 BitmapPalette aPal;
1886 vcl::PixelFormat ePixelFormat = vcl::PixelFormat::INVALID;
1887 assert(GetBitCount() != 1 && "not supported anymore");
1888 if (GetBitCount() == 1)
1890 ePixelFormat = vcl::PixelFormat::N8_BPP;
1891 aPal.SetEntryCount(2);
1892 aPal[0] = COL_BLACK;
1893 aPal[1] = COL_WHITE;
1895 else
1897 ePixelFormat = vcl::PixelFormat::N32_BPP;
1900 if (!pBitmap->Create(Size(nWidth, nHeight), ePixelFormat, aPal))
1902 SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
1903 return nullptr;
1906 cairo_surface_t* target = CairoCommon::createCairoSurface(pBitmap->GetBuffer());
1907 if (!target)
1909 SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create cairo surface");
1910 return nullptr;
1912 cairo_t* cr = cairo_create(target);
1914 SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight);
1915 CairoCommon::renderSource(cr, aTR, m_pSurface);
1917 cairo_destroy(cr);
1918 cairo_surface_destroy(target);
1920 Toggle1BitTransparency(*pBitmap->GetBuffer());
1922 return pBitmap;
1925 cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer)
1927 cairo_format_t nFormat;
1928 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
1929 assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 24 || rBuffer.mnBitCount == 1);
1930 #else
1931 assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 1);
1932 #endif
1934 if (rBuffer.mnBitCount == 32)
1935 nFormat = CAIRO_FORMAT_ARGB32;
1936 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
1937 else if (rBuffer.mnBitCount == 24)
1938 nFormat = CAIRO_FORMAT_RGB24_888;
1939 #endif
1940 else
1941 nFormat = CAIRO_FORMAT_A1;
1942 return nFormat;
1945 namespace
1947 bool isCairoCompatible(const BitmapBuffer* pBuffer)
1949 if (!pBuffer)
1950 return false;
1952 // We use Cairo that supports 24-bit RGB.
1953 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
1954 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24 && pBuffer->mnBitCount != 1)
1955 #else
1956 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 1)
1957 #endif
1958 return false;
1960 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1961 return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize);
1965 cairo_surface_t* CairoCommon::createCairoSurface(const BitmapBuffer* pBuffer)
1967 if (!isCairoCompatible(pBuffer))
1968 return nullptr;
1970 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1971 cairo_surface_t* target = cairo_image_surface_create_for_data(
1972 pBuffer->mpBits, nFormat, pBuffer->mnWidth, pBuffer->mnHeight, pBuffer->mnScanlineSize);
1973 if (cairo_surface_status(target) != CAIRO_STATUS_SUCCESS)
1975 cairo_surface_destroy(target);
1976 return nullptr;
1978 return target;
1981 bool CairoCommon::hasFastDrawTransformedBitmap() { return false; }
1983 bool CairoCommon::supportsOperation(OutDevSupportType eType)
1985 switch (eType)
1987 case OutDevSupportType::TransparentRect:
1988 case OutDevSupportType::B2DDraw:
1989 return true;
1991 return false;
1994 std::unique_ptr<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc)
1996 if (pSrc == nullptr)
1997 return nullptr;
1999 assert(pSrc->mnFormat == SVP_24BIT_FORMAT);
2000 const tools::Long nWidth = pSrc->mnWidth;
2001 const tools::Long nHeight = pSrc->mnHeight;
2002 std::unique_ptr<BitmapBuffer> pDst(new BitmapBuffer);
2003 pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown);
2004 pDst->mnWidth = nWidth;
2005 pDst->mnHeight = nHeight;
2006 pDst->mnBitCount = 32;
2007 pDst->maColorMask = pSrc->maColorMask;
2008 pDst->maPalette = pSrc->maPalette;
2010 tools::Long nScanlineBase;
2011 const bool bFail = o3tl::checked_multiply<tools::Long>(pDst->mnBitCount, nWidth, nScanlineBase);
2012 if (bFail)
2014 SAL_WARN("vcl.gdi", "checked multiply failed");
2015 pDst->mpBits = nullptr;
2016 return nullptr;
2019 pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
2020 if (pDst->mnScanlineSize < nScanlineBase / 8)
2022 SAL_WARN("vcl.gdi", "scanline calculation wraparound");
2023 pDst->mpBits = nullptr;
2024 return nullptr;
2029 pDst->mpBits = new sal_uInt8[pDst->mnScanlineSize * nHeight];
2031 catch (const std::bad_alloc&)
2033 // memory exception, clean up
2034 pDst->mpBits = nullptr;
2035 return nullptr;
2038 for (tools::Long y = 0; y < nHeight; ++y)
2040 sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
2041 sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
2042 for (tools::Long x = 0; x < nWidth; ++x)
2044 #if defined(ANDROID) && !HAVE_FEATURE_ANDROID_LOK
2045 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown)
2046 == ScanlineFormat::N32BitTcRgba,
2047 "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
2048 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown)
2049 == ScanlineFormat::N24BitTcRgb,
2050 "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
2051 pD[0] = pS[0];
2052 pD[1] = pS[1];
2053 pD[2] = pS[2];
2054 pD[3] = 0xff; // Alpha
2055 #elif defined OSL_BIGENDIAN
2056 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown)
2057 == ScanlineFormat::N32BitTcArgb,
2058 "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
2059 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown)
2060 == ScanlineFormat::N24BitTcRgb,
2061 "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
2062 pD[0] = 0xff; // Alpha
2063 pD[1] = pS[0];
2064 pD[2] = pS[1];
2065 pD[3] = pS[2];
2066 #else
2067 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown)
2068 == ScanlineFormat::N32BitTcBgra,
2069 "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
2070 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown)
2071 == ScanlineFormat::N24BitTcBgr,
2072 "Expected SVP_24BIT_FORMAT set to N24BitTcBgr");
2073 pD[0] = pS[0];
2074 pD[1] = pS[1];
2075 pD[2] = pS[2];
2076 pD[3] = 0xff; // Alpha
2077 #endif
2079 pS += 3;
2080 pD += 4;
2084 return pDst;
2087 void Toggle1BitTransparency(const BitmapBuffer& rBuf)
2089 assert(rBuf.maPalette.GetBestIndex(BitmapColor(COL_BLACK)) == 0);
2090 // TODO: make upper layers use standard alpha
2091 if (getCairoFormat(rBuf) == CAIRO_FORMAT_A1)
2093 const int nImageSize = rBuf.mnHeight * rBuf.mnScanlineSize;
2094 unsigned char* pDst = rBuf.mpBits;
2095 for (int i = nImageSize; --i >= 0; ++pDst)
2096 *pDst = ~*pDst;
2100 namespace
2102 // check for env var that decides for using downscale pattern
2103 const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
2104 bool bDisableDownScale(nullptr != pDisableDownScale);
2107 cairo_surface_t* SurfaceHelper::implCreateOrReuseDownscale(unsigned long nTargetWidth,
2108 unsigned long nTargetHeight)
2110 const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
2111 const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
2113 // zoomed in, need to stretch at paint, no pre-scale useful
2114 if (nTargetWidth >= nSourceWidth || nTargetHeight >= nSourceHeight)
2116 return pSurface;
2119 // calculate downscale factor
2120 unsigned long nWFactor(1);
2121 unsigned long nW((nSourceWidth + 1) / 2);
2122 unsigned long nHFactor(1);
2123 unsigned long nH((nSourceHeight + 1) / 2);
2125 while (nW > nTargetWidth && nW > 1)
2127 nW = (nW + 1) / 2;
2128 nWFactor *= 2;
2131 while (nH > nTargetHeight && nH > 1)
2133 nH = (nH + 1) / 2;
2134 nHFactor *= 2;
2137 if (1 == nWFactor && 1 == nHFactor)
2139 // original size *is* best binary size, use it
2140 return pSurface;
2143 // go up one scale again - look for no change
2144 nW = (1 == nWFactor) ? nTargetWidth : nW * 2;
2145 nH = (1 == nHFactor) ? nTargetHeight : nH * 2;
2147 // check if we have a downscaled version of required size
2148 // bail out if the multiplication for the key would overflow
2149 if (nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32)
2150 return pSurface;
2151 const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + nH);
2152 auto isHit(maDownscaled.find(key));
2154 if (isHit != maDownscaled.end())
2156 return isHit->second;
2159 // create new surface in the targeted size
2160 cairo_surface_t* pSurfaceTarget
2161 = cairo_surface_create_similar(pSurface, cairo_surface_get_content(pSurface), nW, nH);
2163 // made a version to scale self first that worked well, but would've
2164 // been hard to support CAIRO_FORMAT_A1 including bit shifting, so
2165 // I decided to go with cairo itself - use CAIRO_FILTER_FAST or
2166 // CAIRO_FILTER_GOOD though. Please modify as needed for
2167 // performance/quality
2168 cairo_t* cr = cairo_create(pSurfaceTarget);
2169 const double fScaleX(static_cast<double>(nW) / static_cast<double>(nSourceWidth));
2170 const double fScaleY(static_cast<double>(nH) / static_cast<double>(nSourceHeight));
2171 cairo_scale(cr, fScaleX, fScaleY);
2172 cairo_set_source_surface(cr, pSurface, 0.0, 0.0);
2173 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
2174 cairo_paint(cr);
2175 cairo_destroy(cr);
2177 // need to set device_scale for downscale surfaces to get
2178 // them handled correctly
2179 cairo_surface_set_device_scale(pSurfaceTarget, fScaleX, fScaleY);
2181 // add entry to cached entries
2182 maDownscaled[key] = pSurfaceTarget;
2184 return pSurfaceTarget;
2187 bool SurfaceHelper::isTrivial() const
2189 constexpr unsigned long nMinimalSquareSizeToBuffer(64 * 64);
2190 const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
2191 const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
2193 return nSourceWidth * nSourceHeight < nMinimalSquareSizeToBuffer;
2196 SurfaceHelper::SurfaceHelper()
2197 : pSurface(nullptr)
2201 SurfaceHelper::~SurfaceHelper()
2203 cairo_surface_destroy(pSurface);
2204 for (auto& candidate : maDownscaled)
2206 cairo_surface_destroy(candidate.second);
2210 cairo_surface_t* SurfaceHelper::getSurface(unsigned long nTargetWidth,
2211 unsigned long nTargetHeight) const
2213 if (bDisableDownScale || 0 == nTargetWidth || 0 == nTargetHeight || !pSurface || isTrivial())
2215 // caller asks for original or disabled or trivial (smaller then a minimal square size)
2216 // also excludes zero cases for width/height after this point if need to prescale
2217 return pSurface;
2220 return const_cast<SurfaceHelper*>(this)->implCreateOrReuseDownscale(nTargetWidth,
2221 nTargetHeight);
2224 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */