Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/headless/CairoCommon.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
 * This file incorporates work covered by the following license notice:
10
 *
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 .
18
 */
19
20
#include <vcl/gradient.hxx>
21
#include <vcl/svapp.hxx>
22
#include <headless/BitmapHelper.hxx>
23
#include <headless/CairoCommon.hxx>
24
#include <vcl/cairo.hxx>
25
#include <vcl/CairoFormats.hxx>
26
#include <vcl/BitmapTools.hxx>
27
#include <SalGradient.hxx>
28
#include <tools/helpers.hxx>
29
#include <basegfx/utils/canvastools.hxx>
30
#include <basegfx/matrix/b2dhommatrixtools.hxx>
31
#include <basegfx/polygon/b2dpolygontools.hxx>
32
#include <basegfx/range/b2irange.hxx>
33
#include <comphelper/configuration.hxx>
34
#include <sal/log.hxx>
35
#include <osl/module.h>
36
37
#include <com/sun/star/awt/GradientStyle.hpp>
38
39
#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 12, 0)
40
#error "require at least cairo 1.12.0"
41
#endif
42
43
void dl_cairo_surface_set_device_scale(cairo_surface_t* surface, double x_scale, double y_scale)
44
966k
{
45
966k
#if !HAVE_DLAPI || !defined(SYSTEM_CAIRO)
46
966k
    cairo_surface_set_device_scale(surface, x_scale, y_scale);
47
#else
48
    static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double, double)>(
49
        osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_set_device_scale"));
50
    if (func)
51
        func(surface, x_scale, y_scale);
52
#endif
53
966k
}
54
55
void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, double* x_scale, double* y_scale)
56
1.43M
{
57
1.43M
#if !HAVE_DLAPI || !defined(SYSTEM_CAIRO)
58
1.43M
    cairo_surface_get_device_scale(surface, x_scale, y_scale);
59
#else
60
    static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double*, double*)>(
61
        osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_get_device_scale"));
62
    if (func)
63
        func(surface, x_scale, y_scale);
64
    else
65
    {
66
        if (x_scale)
67
            *x_scale = 1.0;
68
        if (y_scale)
69
            *y_scale = 1.0;
70
    }
71
#endif
72
1.43M
}
73
74
basegfx::B2DRange getFillDamage(cairo_t* cr)
75
440k
{
76
440k
    double x1, y1, x2, y2;
77
78
    // this is faster than cairo_fill_extents, at the cost of some overdraw
79
440k
    cairo_path_extents(cr, &x1, &y1, &x2, &y2);
80
81
    // support B2DRange::isEmpty()
82
440k
    if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
83
25.8k
    {
84
25.8k
        return basegfx::B2DRange(x1, y1, x2, y2);
85
25.8k
    }
86
87
414k
    return basegfx::B2DRange();
88
440k
}
89
90
basegfx::B2DRange getClipBox(cairo_t* cr)
91
440k
{
92
440k
    double x1, y1, x2, y2;
93
94
440k
    cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
95
96
    // support B2DRange::isEmpty()
97
440k
    if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
98
440k
    {
99
440k
        return basegfx::B2DRange(x1, y1, x2, y2);
100
440k
    }
101
102
0
    return basegfx::B2DRange();
103
440k
}
104
105
basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
106
440k
{
107
440k
    basegfx::B2DRange aDamageRect(getFillDamage(cr));
108
440k
    aDamageRect.intersect(getClipBox(cr));
109
440k
    return aDamageRect;
110
440k
}
111
112
basegfx::B2DRange getStrokeDamage(cairo_t* cr)
113
0
{
114
0
    double x1, y1, x2, y2;
115
116
    // less accurate, but much faster
117
0
    cairo_path_extents(cr, &x1, &y1, &x2, &y2);
118
119
    // support B2DRange::isEmpty()
120
0
    if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
121
0
    {
122
0
        return basegfx::B2DRange(x1, y1, x2, y2);
123
0
    }
124
125
0
    return basegfx::B2DRange();
126
0
}
127
128
basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
129
0
{
130
0
    basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
131
0
    aDamageRect.intersect(getClipBox(cr));
132
0
    return aDamageRect;
133
0
}
134
135
// Remove bClosePath: Checked that the already used mechanism for Win using
136
// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace
137
// this.
138
// For PixelSnap we need the ObjectToDevice transformation here now. This is a
139
// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in
140
// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we
141
// need the ObjectToDevice transformation *without* that offset here to do the
142
// same. The LineDraw-Offset will be applied by the callers using a linear
143
// transformation for Cairo now
144
// For support of PixelSnapHairline we also need the ObjectToDevice transformation
145
// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g.
146
// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!)
147
// tdf#129845 add reply value to allow counting a point/byte/size measurement to
148
// be included
149
static size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
150
                               const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap,
151
                               bool bPixelSnapHairline)
152
0
{
153
    // short circuit if there is nothing to do
154
0
    const sal_uInt32 nPointCount(rPolygon.count());
155
0
    size_t nSizeMeasure(0);
156
157
0
    if (0 == nPointCount)
158
0
    {
159
0
        return nSizeMeasure;
160
0
    }
161
162
0
    const bool bHasCurves(rPolygon.areControlPointsUsed());
163
0
    const bool bClosePath(rPolygon.isClosed());
164
0
    const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
165
0
    basegfx::B2DHomMatrix aObjectToDeviceInv;
166
0
    basegfx::B2DPoint aLast;
167
0
    PixelSnapper aSnapper;
168
169
0
    for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
170
0
    {
171
0
        int nClosedIdx = nPointIdx;
172
0
        if (nPointIdx >= nPointCount)
173
0
        {
174
            // prepare to close last curve segment if needed
175
0
            if (bClosePath && (nPointIdx == nPointCount))
176
0
            {
177
0
                nClosedIdx = 0;
178
0
            }
179
0
            else
180
0
            {
181
0
                break;
182
0
            }
183
0
        }
184
185
0
        basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx));
186
187
0
        if (bPixelSnap)
188
0
        {
189
            // snap device coordinates to full pixels
190
0
            if (bObjectToDeviceUsed)
191
0
            {
192
                // go to DeviceCoordinates
193
0
                aPoint *= rObjectToDevice;
194
0
            }
195
196
            // snap by rounding
197
0
            aPoint.setX(basegfx::fround(aPoint.getX()));
198
0
            aPoint.setY(basegfx::fround(aPoint.getY()));
199
200
0
            if (bObjectToDeviceUsed)
201
0
            {
202
0
                if (aObjectToDeviceInv.isIdentity())
203
0
                {
204
0
                    aObjectToDeviceInv = rObjectToDevice;
205
0
                    aObjectToDeviceInv.invert();
206
0
                }
207
208
                // go back to ObjectCoordinates
209
0
                aPoint *= aObjectToDeviceInv;
210
0
            }
211
0
        }
212
213
0
        if (bPixelSnapHairline)
214
0
        {
215
            // snap horizontal and vertical lines (mainly used in Chart for
216
            // 'nicer' AAing)
217
0
            aPoint = aSnapper.snap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
218
0
        }
219
220
0
        if (!nPointIdx)
221
0
        {
222
            // first point => just move there
223
0
            cairo_move_to(cr, aPoint.getX(), aPoint.getY());
224
0
            aLast = aPoint;
225
0
            continue;
226
0
        }
227
228
0
        bool bPendingCurve(false);
229
230
0
        if (bHasCurves)
231
0
        {
232
0
            bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
233
0
            bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
234
0
        }
235
236
0
        if (!bPendingCurve) // line segment
237
0
        {
238
0
            cairo_line_to(cr, aPoint.getX(), aPoint.getY());
239
0
            nSizeMeasure++;
240
0
        }
241
0
        else // cubic bezier segment
242
0
        {
243
0
            basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
244
0
            basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
245
246
            // tdf#99165 if the control points are 'empty', create the mathematical
247
            // correct replacement ones to avoid problems with the graphical sub-system
248
            // tdf#101026 The 1st attempt to create a mathematically correct replacement control
249
            // vector was wrong. Best alternative is one as close as possible which means short.
250
0
            if (aCP1.equal(aLast))
251
0
            {
252
0
                aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
253
0
            }
254
255
0
            if (aCP2.equal(aPoint))
256
0
            {
257
0
                aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
258
0
            }
259
260
0
            cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
261
0
                           aPoint.getY());
262
            // take some bigger measure for curve segments - too expensive to subdivide
263
            // here and that precision not needed, but four (2 points, 2 control-points)
264
            // would be a too low weight
265
0
            nSizeMeasure += 10;
266
0
        }
267
268
0
        aLast = aPoint;
269
0
    }
270
271
0
    if (bClosePath)
272
0
    {
273
0
        cairo_close_path(cr);
274
0
    }
275
276
0
    return nSizeMeasure;
277
0
}
278
279
basegfx::B2DPoint PixelSnapper::snap(const basegfx::B2DPolygon& rPolygon,
280
                                     const basegfx::B2DHomMatrix& rObjectToDevice,
281
                                     basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex)
282
0
{
283
0
    const sal_uInt32 nCount(rPolygon.count());
284
285
    // get the data
286
0
    if (nIndex == 0)
287
0
    {
288
        // if it's the first time, we need to calculate everything
289
0
        maPrevPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount);
290
0
        maCurrPoint = rObjectToDevice * rPolygon.getB2DPoint(nIndex);
291
0
        maPrevTuple = basegfx::fround(maPrevPoint);
292
0
        maCurrTuple = basegfx::fround(maCurrPoint);
293
0
    }
294
0
    else
295
0
    {
296
        // but for all other times, we can re-use the previous iteration computations
297
0
        maPrevPoint = maCurrPoint;
298
0
        maPrevTuple = maCurrTuple;
299
0
        maCurrPoint = maNextPoint;
300
0
        maCurrTuple = maNextTuple;
301
0
    }
302
0
    maNextPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount);
303
0
    maNextTuple = basegfx::fround(maNextPoint);
304
305
    // get the states
306
0
    const bool bPrevVertical(maPrevTuple.getX() == maCurrTuple.getX());
307
0
    const bool bNextVertical(maNextTuple.getX() == maCurrTuple.getX());
308
0
    const bool bPrevHorizontal(maPrevTuple.getY() == maCurrTuple.getY());
309
0
    const bool bNextHorizontal(maNextTuple.getY() == maCurrTuple.getY());
310
0
    const bool bSnapX(bPrevVertical || bNextVertical);
311
0
    const bool bSnapY(bPrevHorizontal || bNextHorizontal);
312
313
0
    if (bSnapX || bSnapY)
314
0
    {
315
0
        basegfx::B2DPoint aSnappedPoint(bSnapX ? maCurrTuple.getX() : maCurrPoint.getX(),
316
0
                                        bSnapY ? maCurrTuple.getY() : maCurrPoint.getY());
317
318
0
        if (rObjectToDeviceInv.isIdentity())
319
0
        {
320
0
            rObjectToDeviceInv = rObjectToDevice;
321
0
            rObjectToDeviceInv.invert();
322
0
        }
323
324
0
        aSnappedPoint *= rObjectToDeviceInv;
325
326
0
        return aSnappedPoint;
327
0
    }
328
329
0
    return rPolygon.getB2DPoint(nIndex);
330
0
}
331
332
SystemDependentData_CairoPath::SystemDependentData_CairoPath(size_t nSizeMeasure, cairo_t* cr,
333
                                                             bool bNoJoin, bool bAntiAlias,
334
                                                             const std::vector<double>* pStroke)
335
0
    : basegfx::SystemDependentData(Application::GetSystemDependentDataManager(),
336
0
                                   basegfx::SDD_Type::SDDType_CairoPath)
337
0
    , mpCairoPath(nullptr)
338
0
    , mbNoJoin(bNoJoin)
339
0
    , mbAntiAlias(bAntiAlias)
340
0
{
341
0
    static const bool bFuzzing = comphelper::IsFuzzing();
342
343
    // tdf#129845 only create a copy of the path when nSizeMeasure is
344
    // bigger than some decent threshold
345
0
    if (!bFuzzing && nSizeMeasure > 50)
346
0
    {
347
0
        mpCairoPath = cairo_copy_path(cr);
348
349
0
        if (nullptr != pStroke)
350
0
        {
351
0
            maStroke = *pStroke;
352
0
        }
353
0
    }
354
0
}
355
356
SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
357
0
{
358
0
    if (nullptr != mpCairoPath)
359
0
    {
360
0
        cairo_path_destroy(mpCairoPath);
361
0
        mpCairoPath = nullptr;
362
0
    }
363
0
}
364
365
sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
366
0
{
367
    // tdf#129845 by using the default return value of zero when no path
368
    // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds
369
    // will do the right thing and not buffer this entry at all
370
0
    sal_Int64 nRetval(0);
371
372
0
    if (nullptr != mpCairoPath)
373
0
    {
374
        // per node
375
        // - num_data incarnations of
376
        // - sizeof(cairo_path_data_t) which is a union of defines and point data
377
        //   thus may 2 x sizeof(double)
378
0
        nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
379
0
    }
380
381
0
    return nRetval;
382
0
}
383
384
void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon,
385
                      const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap)
386
0
{
387
    // try to access buffered data
388
0
    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
389
0
        rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPath>(
390
0
            basegfx::SDD_Type::SDDType_CairoPath));
391
392
0
    if (pSystemDependentData_CairoPath)
393
0
    {
394
        // re-use data
395
0
        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
396
0
    }
397
0
    else
398
0
    {
399
        // create data
400
0
        size_t nSizeMeasure(0);
401
402
0
        for (const auto& rPoly : rPolyPolygon)
403
0
        {
404
            // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE'
405
            // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset
406
0
            nSizeMeasure += AddPolygonToPath(cr, rPoly, rObjectToDevice, bPixelSnap, false);
407
0
        }
408
409
        // copy and add to buffering mechanism
410
        // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon
411
0
        pSystemDependentData_CairoPath
412
0
            = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
413
0
                nSizeMeasure, cr, false, false, nullptr);
414
0
    }
415
0
}
416
417
cairo_user_data_key_t* CairoCommon::getDamageKey()
418
19.4k
{
419
19.4k
    static cairo_user_data_key_t aDamageKey;
420
19.4k
    return &aDamageKey;
421
19.4k
}
422
423
sal_uInt16 CairoCommon::GetBitCount() const
424
987k
{
425
987k
    if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_ALPHA)
426
0
        return 1;
427
987k
    return 32;
428
987k
}
429
430
cairo_t* CairoCommon::getCairoContext(bool bXorModeAllowed, bool bAntiAlias) const
431
433k
{
432
433k
    cairo_t* cr;
433
433k
    if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
434
0
        cr = createTmpCompatibleCairoContext();
435
433k
    else
436
433k
        cr = cairo_create(m_pSurface);
437
433k
    cairo_set_line_width(cr, 1);
438
433k
    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
439
433k
    cairo_set_antialias(cr, bAntiAlias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
440
433k
    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
441
442
    // ensure no linear transformation and no PathInfo in local cairo_path_t
443
433k
    cairo_identity_matrix(cr);
444
433k
    cairo_new_path(cr);
445
446
433k
    return cr;
447
433k
}
448
449
void CairoCommon::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed,
450
                                      const basegfx::B2DRange& rExtents) const
451
433k
{
452
433k
    const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
453
454
433k
    if (rExtents.isEmpty())
455
414k
    {
456
        //nothing changed, return early
457
414k
        if (bXoring)
458
0
        {
459
0
            cairo_surface_t* surface = cairo_get_target(cr);
460
0
            cairo_surface_destroy(surface);
461
0
        }
462
414k
        cairo_destroy(cr);
463
414k
        return;
464
414k
    }
465
466
19.4k
    basegfx::B2IRange aIntExtents(basegfx::unotools::b2ISurroundingRangeFromB2DRange(rExtents));
467
19.4k
    sal_Int32 nExtentsLeft(aIntExtents.getMinX()), nExtentsTop(aIntExtents.getMinY());
468
19.4k
    sal_Int32 nExtentsRight(aIntExtents.getMaxX()), nExtentsBottom(aIntExtents.getMaxY());
469
19.4k
    sal_Int32 nWidth = m_aFrameSize.getX();
470
19.4k
    sal_Int32 nHeight = m_aFrameSize.getY();
471
19.4k
    nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
472
19.4k
    nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
473
19.4k
    nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
474
19.4k
    nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
475
476
19.4k
    cairo_surface_t* surface = cairo_get_target(cr);
477
19.4k
    cairo_surface_flush(surface);
478
479
    //For the most part we avoid the use of XOR these days, but there
480
    //are some edge cases where legacy stuff still supports it, so
481
    //emulate it (slowly) here.
482
19.4k
    if (bXoring)
483
0
        doXorOnRelease(nExtentsLeft, nExtentsTop, nExtentsRight, nExtentsBottom, surface, nWidth);
484
485
19.4k
    cairo_destroy(cr); // unref
486
487
19.4k
    DamageHandler* pDamage
488
19.4k
        = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
489
490
19.4k
    if (pDamage)
491
0
    {
492
0
        pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
493
0
                         nExtentsBottom - nExtentsTop);
494
0
    }
495
19.4k
}
496
497
void CairoCommon::applyFullDamage() const
498
4
{
499
4
    if (nullptr == m_pSurface)
500
0
        return;
501
4
    DamageHandler* pDamage
502
4
        = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
503
4
    if (nullptr == pDamage)
504
4
        return;
505
0
    pDamage->damaged(pDamage->handle, 0, 0, m_aFrameSize.getX(), m_aFrameSize.getY());
506
0
}
507
508
void CairoCommon::doXorOnRelease(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
509
                                 sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom,
510
                                 cairo_surface_t* const surface, sal_Int32 nWidth) const
511
0
{
512
    //For the most part we avoid the use of XOR these days, but there
513
    //are some edge cases where legacy stuff still supports it, so
514
    //emulate it (slowly) here.
515
0
    cairo_surface_t* target_surface = m_pSurface;
516
0
    if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
517
0
    {
518
        //in the unlikely case we can't use m_pSurface directly, copy contents
519
        //to another temp image surface
520
0
        if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
521
0
            target_surface = cairo_surface_map_to_image(target_surface, nullptr);
522
0
        else
523
0
        {
524
            // for gen, which is CAIRO_FORMAT_RGB24/CAIRO_CONTENT_COLOR I'm getting
525
            // visual corruption in vcldemo with cairo_surface_map_to_image
526
0
            cairo_t* copycr = createTmpCompatibleCairoContext();
527
0
            cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
528
0
                            nExtentsBottom - nExtentsTop);
529
0
            cairo_set_source_surface(copycr, m_pSurface, 0, 0);
530
0
            cairo_fill(copycr);
531
0
            target_surface = cairo_get_target(copycr);
532
0
            cairo_destroy(copycr);
533
0
        }
534
0
    }
535
536
0
    cairo_surface_flush(target_surface);
537
0
    unsigned char* target_surface_data = cairo_image_surface_get_data(target_surface);
538
0
    unsigned char* xor_surface_data = cairo_image_surface_get_data(surface);
539
540
0
    cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
541
0
    assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
542
0
    sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
543
0
    sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
544
0
    sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
545
0
    sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
546
0
    sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
547
548
    // Handle headless size forced to (1,1) by SvpSalFrame::GetSurfaceFrameSize().
549
0
    int target_surface_width = cairo_image_surface_get_width(target_surface);
550
0
    if (nUnscaledExtentsLeft > target_surface_width)
551
0
        nUnscaledExtentsLeft = target_surface_width;
552
0
    if (nUnscaledExtentsRight > target_surface_width)
553
0
        nUnscaledExtentsRight = target_surface_width;
554
0
    int target_surface_height = cairo_image_surface_get_height(target_surface);
555
0
    if (nUnscaledExtentsTop > target_surface_height)
556
0
        nUnscaledExtentsTop = target_surface_height;
557
0
    if (nUnscaledExtentsBottom > target_surface_height)
558
0
        nUnscaledExtentsBottom = target_surface_height;
559
560
0
#if !ENABLE_WASM_STRIP_PREMULTIPLY
561
0
    vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
562
0
    vcl::bitmap::lookup_table const& premultiply_table = vcl::bitmap::get_premultiply_table();
563
0
#endif
564
0
    for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
565
0
    {
566
0
        unsigned char* true_row = target_surface_data + (nStride * y);
567
0
        unsigned char* xor_row = xor_surface_data + (nStride * y);
568
0
        unsigned char* true_data = true_row + (nUnscaledExtentsLeft * 4);
569
0
        unsigned char* xor_data = xor_row + (nUnscaledExtentsLeft * 4);
570
0
        for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
571
0
        {
572
0
            sal_uInt8 a = true_data[SVP_CAIRO_ALPHA];
573
0
            sal_uInt8 xor_a = xor_data[SVP_CAIRO_ALPHA];
574
#if ENABLE_WASM_STRIP_PREMULTIPLY
575
            sal_uInt8 b = vcl::bitmap::unpremultiply(true_data[SVP_CAIRO_BLUE], a)
576
                          ^ vcl::bitmap::unpremultiply(xor_data[SVP_CAIRO_BLUE], xor_a);
577
            sal_uInt8 g = vcl::bitmap::unpremultiply(true_data[SVP_CAIRO_GREEN], a)
578
                          ^ vcl::bitmap::unpremultiply(xor_data[SVP_CAIRO_GREEN], xor_a);
579
            sal_uInt8 r = vcl::bitmap::unpremultiply(true_data[SVP_CAIRO_RED], a)
580
                          ^ vcl::bitmap::unpremultiply(xor_data[SVP_CAIRO_RED], xor_a);
581
            true_data[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(b, a);
582
            true_data[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(g, a);
583
            true_data[SVP_CAIRO_RED] = vcl::bitmap::premultiply(r, a);
584
#else
585
0
            sal_uInt8 b = unpremultiply_table[a][true_data[SVP_CAIRO_BLUE]]
586
0
                          ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_BLUE]];
587
0
            sal_uInt8 g = unpremultiply_table[a][true_data[SVP_CAIRO_GREEN]]
588
0
                          ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_GREEN]];
589
0
            sal_uInt8 r = unpremultiply_table[a][true_data[SVP_CAIRO_RED]]
590
0
                          ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_RED]];
591
0
            true_data[SVP_CAIRO_BLUE] = premultiply_table[a][b];
592
0
            true_data[SVP_CAIRO_GREEN] = premultiply_table[a][g];
593
0
            true_data[SVP_CAIRO_RED] = premultiply_table[a][r];
594
0
#endif
595
0
            true_data += 4;
596
0
            xor_data += 4;
597
0
        }
598
0
    }
599
0
    cairo_surface_mark_dirty(target_surface);
600
601
0
    if (target_surface != m_pSurface)
602
0
    {
603
0
        if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
604
0
            cairo_surface_unmap_image(m_pSurface, target_surface);
605
0
        else
606
0
        {
607
0
            cairo_t* copycr = cairo_create(m_pSurface);
608
            //copy contents back from image surface
609
0
            cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
610
0
                            nExtentsBottom - nExtentsTop);
611
0
            cairo_set_source_surface(copycr, target_surface, 0, 0);
612
0
            cairo_fill(copycr);
613
0
            cairo_destroy(copycr);
614
0
            cairo_surface_destroy(target_surface);
615
0
        }
616
0
    }
617
618
0
    cairo_surface_destroy(surface);
619
0
}
620
621
cairo_t* CairoCommon::createTmpCompatibleCairoContext() const
622
0
{
623
0
    cairo_surface_t* target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32,
624
0
                                                                 m_aFrameSize.getX() * m_fScale,
625
0
                                                                 m_aFrameSize.getY() * m_fScale);
626
627
0
    dl_cairo_surface_set_device_scale(target, m_fScale, m_fScale);
628
629
0
    return cairo_create(target);
630
0
}
631
632
void CairoCommon::applyColor(cairo_t* cr, Color aColor, double fTransparency)
633
0
{
634
0
    if (cairo_surface_get_content(cairo_get_target(cr)) != CAIRO_CONTENT_ALPHA)
635
0
    {
636
0
        cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0,
637
0
                              aColor.GetBlue() / 255.0, 1.0 - fTransparency);
638
0
    }
639
0
    else
640
0
    {
641
0
        double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
642
0
        cairo_set_source_rgba(cr, 1, 1, 1, fSet);
643
0
        cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
644
0
    }
645
0
}
646
647
void CairoCommon::applyColor2(cairo_t* cr, Color aColor)
648
12.9k
{
649
12.9k
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
650
12.9k
    cairo_content_t eContentType = cairo_surface_get_content(cairo_get_target(cr));
651
12.9k
    if (eContentType == CAIRO_CONTENT_COLOR_ALPHA)
652
12.9k
    {
653
12.9k
        cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0,
654
12.9k
                              aColor.GetBlue() / 255.0, aColor.GetAlpha() / 255.0);
655
12.9k
    }
656
0
    else if (eContentType == CAIRO_CONTENT_COLOR)
657
0
    {
658
0
        cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0,
659
0
                              aColor.GetBlue() / 255.0, 1.0);
660
0
    }
661
0
    else // CAIRO_CONTENT_ALPHA
662
0
    {
663
0
        double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
664
0
        cairo_set_source_rgba(cr, 1, 1, 1, fSet);
665
0
    }
666
12.9k
}
667
668
void CairoCommon::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
669
433k
{
670
433k
    RectangleVector aRectangles;
671
433k
    if (!rClipRegion.IsEmpty())
672
433k
    {
673
433k
        rClipRegion.GetRegionRectangles(aRectangles);
674
433k
    }
675
433k
    if (!aRectangles.empty())
676
0
    {
677
0
        bool bEmpty = true;
678
0
        for (auto const& rectangle : aRectangles)
679
0
        {
680
0
            if (rectangle.GetWidth() <= 0 || rectangle.GetHeight() <= 0)
681
0
            {
682
0
                SAL_WARN("vcl.gdi", "bad clip rect of: " << rectangle);
683
0
                continue;
684
0
            }
685
0
            cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(),
686
0
                            rectangle.GetHeight());
687
0
            bEmpty = false;
688
0
        }
689
0
        if (!bEmpty)
690
0
            cairo_clip(cr);
691
0
    }
692
433k
}
693
694
433k
void CairoCommon::clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, m_aClipRegion); }
695
696
void CairoCommon::SetXORMode(bool bSet, bool /*bInvertOnly*/)
697
471k
{
698
471k
    m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
699
471k
}
700
701
void CairoCommon::SetROPLineColor(SalROPColor nROPColor)
702
0
{
703
0
    switch (nROPColor)
704
0
    {
705
0
        case SalROPColor::N0:
706
0
            m_oLineColor = Color(0, 0, 0);
707
0
            break;
708
0
        case SalROPColor::N1:
709
0
        case SalROPColor::Invert:
710
0
            m_oLineColor = Color(0xff, 0xff, 0xff);
711
0
            break;
712
0
    }
713
0
}
714
715
void CairoCommon::SetROPFillColor(SalROPColor nROPColor)
716
0
{
717
0
    switch (nROPColor)
718
0
    {
719
0
        case SalROPColor::N0:
720
0
            m_oFillColor = Color(0, 0, 0);
721
0
            break;
722
0
        case SalROPColor::N1:
723
0
        case SalROPColor::Invert:
724
0
            m_oFillColor = Color(0xff, 0xff, 0xff);
725
0
            break;
726
0
    }
727
0
}
728
729
void CairoCommon::drawPixel(const std::optional<Color>& rLineColor, tools::Long nX, tools::Long nY,
730
                            bool bAntiAlias)
731
414k
{
732
414k
    if (!rLineColor)
733
0
        return;
734
735
414k
    cairo_t* cr = getCairoContext(true, bAntiAlias);
736
414k
    clipRegion(cr);
737
738
414k
    cairo_rectangle(cr, nX, nY, 1, 1);
739
740
414k
    cairo_content_t eContentType = cairo_surface_get_content(cairo_get_target(cr));
741
414k
    if (eContentType == CAIRO_CONTENT_COLOR_ALPHA)
742
414k
    {
743
414k
        cairo_set_source_rgba(cr, rLineColor->GetRed() / 255.0, rLineColor->GetGreen() / 255.0,
744
414k
                              rLineColor->GetBlue() / 255.0, rLineColor->GetAlpha() / 255.0);
745
414k
    }
746
0
    else if (eContentType == CAIRO_CONTENT_COLOR)
747
0
    {
748
0
        cairo_set_source_rgba(cr, rLineColor->GetRed() / 255.0, rLineColor->GetGreen() / 255.0,
749
0
                              rLineColor->GetBlue() / 255.0, 1.0);
750
0
    }
751
0
    else // eContentType == CAIRO_CONTENT_ALPHA
752
0
    {
753
0
        double fSet = *rLineColor == COL_BLACK ? 1.0 : 0.0;
754
0
        cairo_set_source_rgba(cr, 1, 1, 1, fSet);
755
0
        cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
756
0
    }
757
758
414k
    cairo_fill(cr);
759
760
414k
    basegfx::B2DRange extents = getClippedFillDamage(cr);
761
414k
    releaseCairoContext(cr, true, extents);
762
414k
}
763
764
Color CairoCommon::getPixel(cairo_surface_t* pSurface, tools::Long nX, tools::Long nY)
765
427k
{
766
427k
    cairo_surface_t* target
767
427k
        = cairo_surface_create_similar_image(pSurface, CAIRO_FORMAT_ARGB32, 1, 1);
768
769
427k
    cairo_t* cr = cairo_create(target);
770
771
427k
    cairo_rectangle(cr, 0, 0, 1, 1);
772
427k
    cairo_set_source_surface(cr, pSurface, -nX, -nY);
773
427k
    cairo_paint(cr);
774
427k
    cairo_destroy(cr);
775
776
427k
    cairo_surface_flush(target);
777
427k
#if !ENABLE_WASM_STRIP_PREMULTIPLY
778
427k
    vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
779
427k
#endif
780
427k
    unsigned char* data = cairo_image_surface_get_data(target);
781
427k
    sal_uInt8 a = data[SVP_CAIRO_ALPHA];
782
#if ENABLE_WASM_STRIP_PREMULTIPLY
783
    sal_uInt8 b = vcl::bitmap::unpremultiply(data[SVP_CAIRO_BLUE], a);
784
    sal_uInt8 g = vcl::bitmap::unpremultiply(data[SVP_CAIRO_GREEN], a);
785
    sal_uInt8 r = vcl::bitmap::unpremultiply(data[SVP_CAIRO_RED], a);
786
#else
787
427k
    sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
788
427k
    sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
789
427k
    sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
790
427k
#endif
791
427k
    Color aColor(ColorAlpha, a, r, g, b);
792
427k
    cairo_surface_destroy(target);
793
794
427k
    return aColor;
795
427k
}
796
797
void CairoCommon::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2,
798
                           bool bAntiAlias)
799
0
{
800
0
    cairo_t* cr = getCairoContext(false, bAntiAlias);
801
0
    clipRegion(cr);
802
803
0
    basegfx::B2DPolygon aPoly;
804
805
    // PixelOffset used: To not mix with possible PixelSnap, cannot do
806
    // directly on coordinates as tried before - despite being already 'snapped'
807
    // due to being integer. If it would be directly added here, it would be
808
    // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset
809
0
    aPoly.append(basegfx::B2DPoint(nX1, nY1));
810
0
    aPoly.append(basegfx::B2DPoint(nX2, nY2));
811
812
    // PixelOffset used: Set PixelOffset as linear transformation
813
0
    cairo_matrix_t aMatrix;
814
0
    cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
815
0
    cairo_set_matrix(cr, &aMatrix);
816
817
0
    AddPolygonToPath(cr, aPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false);
818
819
0
    CairoCommon::applyColor(cr, *m_oLineColor);
820
821
0
    basegfx::B2DRange extents = getClippedStrokeDamage(cr);
822
0
    extents.translate(0.5, 0.5);
823
824
0
    cairo_stroke(cr);
825
826
0
    releaseCairoContext(cr, false, extents);
827
0
}
828
829
// true if we have a fill color and the line color is the same or non-existent
830
static bool onlyFillRect(const std::optional<Color>& rFillColor,
831
                         const std::optional<Color>& rLineColor)
832
12.9k
{
833
12.9k
    if (!rFillColor)
834
0
        return false;
835
12.9k
    if (!rLineColor)
836
12.9k
        return true;
837
0
    return *rFillColor == *rLineColor;
838
12.9k
}
839
840
void CairoCommon::drawRect(double nX, double nY, double nWidth, double nHeight, bool bAntiAlias)
841
12.9k
{
842
    // fast path for the common case of simply creating a solid block of color
843
12.9k
    if (onlyFillRect(m_oFillColor, m_oLineColor))
844
12.9k
    {
845
        // don't bother trying to draw stuff which is effectively invisible
846
12.9k
        if (nWidth < 0.1 || nHeight < 0.1)
847
0
            return;
848
849
12.9k
        cairo_t* cr = getCairoContext(true, bAntiAlias);
850
12.9k
        clipRegion(cr);
851
852
12.9k
        bool bPixelSnap = !bAntiAlias;
853
12.9k
        if (bPixelSnap)
854
12.9k
        {
855
            // snap by rounding
856
12.9k
            nX = basegfx::fround(nX);
857
12.9k
            nY = basegfx::fround(nY);
858
12.9k
            nWidth = basegfx::fround(nWidth);
859
12.9k
            nHeight = basegfx::fround(nHeight);
860
12.9k
        }
861
12.9k
        cairo_rectangle(cr, nX, nY, nWidth, nHeight);
862
863
12.9k
        CairoCommon::applyColor2(cr, *m_oFillColor);
864
        // Get FillDamage
865
12.9k
        basegfx::B2DRange extents = getClippedFillDamage(cr);
866
867
12.9k
        cairo_fill(cr);
868
869
12.9k
        releaseCairoContext(cr, true, extents);
870
871
12.9k
        return;
872
12.9k
    }
873
    // because of the -1 hack we have to do fill and draw separately
874
0
    std::optional<Color> aOrigFillColor = m_oFillColor;
875
0
    std::optional<Color> aOrigLineColor = m_oLineColor;
876
0
    m_oFillColor = std::nullopt;
877
0
    m_oLineColor = std::nullopt;
878
879
0
    if (aOrigFillColor)
880
0
    {
881
0
        basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
882
0
            basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
883
884
0
        m_oFillColor = aOrigFillColor;
885
0
        drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect),
886
0
                        (255 - aOrigFillColor->GetAlpha()) / 255.0, bAntiAlias);
887
0
        m_oFillColor = std::nullopt;
888
0
    }
889
890
0
    if (aOrigLineColor)
891
0
    {
892
        // need -1 hack to exclude the bottom and right edges to act like wingdi "Rectangle"
893
        // function which is what was probably the ultimate origin of this behavior
894
0
        basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
895
0
            basegfx::B2DRectangle(nX, nY, nX + nWidth - 1, nY + nHeight - 1));
896
897
0
        m_oLineColor = aOrigLineColor;
898
0
        drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect),
899
0
                        (255 - aOrigLineColor->GetAlpha()) / 255.0, bAntiAlias);
900
0
        m_oLineColor = std::nullopt;
901
0
    }
902
903
0
    m_oFillColor = aOrigFillColor;
904
0
    m_oLineColor = aOrigLineColor;
905
0
}
906
907
void CairoCommon::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
908
0
{
909
0
    basegfx::B2DPolygon aPoly;
910
0
    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
911
0
    for (sal_uInt32 i = 1; i < nPoints; ++i)
912
0
        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
913
914
0
    drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPoly), 0.0, bAntiAlias);
915
0
}
916
917
void CairoCommon::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts,
918
                                  const Point** pPtAry, bool bAntiAlias)
919
0
{
920
0
    basegfx::B2DPolyPolygon aPolyPoly;
921
0
    for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
922
0
    {
923
0
        sal_uInt32 nPoints = pPointCounts[nPolygon];
924
0
        if (nPoints)
925
0
        {
926
0
            const Point* pPoints = pPtAry[nPolygon];
927
0
            basegfx::B2DPolygon aPoly;
928
0
            aPoly.append(basegfx::B2DPoint(pPoints->getX(), pPoints->getY()), nPoints);
929
0
            for (sal_uInt32 i = 1; i < nPoints; ++i)
930
0
                aPoly.setB2DPoint(i, basegfx::B2DPoint(pPoints[i].getX(), pPoints[i].getY()));
931
932
0
            aPolyPoly.append(aPoly);
933
0
        }
934
0
    }
935
936
0
    drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPoly, 0.0, bAntiAlias);
937
0
}
938
939
void CairoCommon::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
940
                                  const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency,
941
                                  bool bAntiAlias)
942
0
{
943
0
    const bool bHasFill(m_oFillColor.has_value());
944
0
    const bool bHasLine(m_oLineColor.has_value());
945
946
0
    if (0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0
947
0
        || fTransparency >= 1.0)
948
0
    {
949
0
        return;
950
0
    }
951
952
0
    static const bool bFuzzing = comphelper::IsFuzzing();
953
0
    if (bFuzzing)
954
0
    {
955
0
        const basegfx::B2DRange aRange(rPolyPolygon.getB2DRange());
956
0
        if (aRange.getMinX() < -nCairoCoordinateMax || aRange.getMinY() < -nCairoCoordinateMax
957
0
            || aRange.getMaxX() > nCairoCoordinateMax || aRange.getMaxY() > nCairoCoordinateMax)
958
0
        {
959
0
            SAL_WARN("vcl.gdi", "drawPolyPolygon, skipping suspicious range of: "
960
0
                                    << aRange << " for fuzzing performance");
961
0
            return;
962
0
        }
963
0
    }
964
965
0
    if (!bHasLine)
966
0
    {
967
        // don't bother trying to draw stuff which is effectively invisible, speeds up
968
        // drawing some complex drawings. This optimisation is not valid when we do
969
        // the pixel offset thing (i.e. bHasLine)
970
0
        basegfx::B2DRange aPolygonRange = rPolyPolygon.getB2DRange();
971
0
        aPolygonRange.transform(rObjectToDevice);
972
0
        if (aPolygonRange.getWidth() < 0.1 || aPolygonRange.getHeight() < 0.1)
973
0
            return;
974
0
    }
975
976
0
    cairo_t* cr = getCairoContext(true, bAntiAlias);
977
0
    if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
978
0
    {
979
0
        SAL_WARN("vcl.gdi",
980
0
                 "cannot render to surface: " << cairo_status_to_string(cairo_status(cr)));
981
0
        releaseCairoContext(cr, true, basegfx::B2DRange());
982
0
        return;
983
0
    }
984
0
    clipRegion(cr);
985
986
    // Set full (Object-to-Device) transformation - if used
987
0
    if (!rObjectToDevice.isIdentity())
988
0
    {
989
0
        cairo_matrix_t aMatrix;
990
991
0
        cairo_matrix_init(&aMatrix, rObjectToDevice.get(0, 0), rObjectToDevice.get(1, 0),
992
0
                          rObjectToDevice.get(0, 1), rObjectToDevice.get(1, 1),
993
0
                          rObjectToDevice.get(0, 2), rObjectToDevice.get(1, 2));
994
0
        cairo_set_matrix(cr, &aMatrix);
995
0
    }
996
997
    // To make releaseCairoContext work, use empty extents
998
0
    basegfx::B2DRange extents;
999
1000
0
    if (bHasFill)
1001
0
    {
1002
0
        add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
1003
1004
0
        CairoCommon::applyColor(cr, *m_oFillColor, fTransparency);
1005
        // Get FillDamage (will be extended for LineDamage below)
1006
0
        extents = getClippedFillDamage(cr);
1007
1008
0
        cairo_fill(cr);
1009
0
    }
1010
1011
0
    if (bHasLine)
1012
0
    {
1013
        // PixelOffset used: Set PixelOffset as linear transformation
1014
0
        cairo_matrix_t aMatrix;
1015
0
        cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1016
0
        cairo_set_matrix(cr, &aMatrix);
1017
1018
0
        add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
1019
1020
0
        CairoCommon::applyColor(cr, *m_oLineColor, fTransparency);
1021
1022
        // expand with possible StrokeDamage
1023
0
        basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
1024
0
        stroke_extents.translate(0.5, 0.5);
1025
0
        extents.expand(stroke_extents);
1026
1027
0
        cairo_stroke(cr);
1028
0
    }
1029
1030
    // if transformation has been applied, transform also extents (ranges)
1031
    // of damage so they can be correctly redrawn
1032
0
    extents.transform(rObjectToDevice);
1033
0
    releaseCairoContext(cr, true, extents);
1034
0
}
1035
1036
void CairoCommon::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
1037
0
{
1038
0
    basegfx::B2DPolygon aPoly;
1039
0
    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
1040
0
    for (sal_uInt32 i = 1; i < nPoints; ++i)
1041
0
        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
1042
0
    aPoly.setClosed(false);
1043
1044
0
    drawPolyLine(basegfx::B2DHomMatrix(), aPoly, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
1045
0
                 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false, bAntiAlias);
1046
0
}
1047
1048
bool CairoCommon::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1049
                               const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1050
                               double fLineWidth, const std::vector<double>* pStroke,
1051
                               basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
1052
                               double fMiterMinimumAngle, bool bPixelSnapHairline, bool bAntiAlias)
1053
0
{
1054
    // short circuit if there is nothing to do
1055
0
    if (0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
1056
0
    {
1057
0
        return true;
1058
0
    }
1059
1060
0
    static const bool bFuzzing = comphelper::IsFuzzing();
1061
0
    if (bFuzzing)
1062
0
    {
1063
0
        const basegfx::B2DRange aRange(rPolyLine.getB2DRange());
1064
0
        if (aRange.getMinX() < -nCairoCoordinateMax || aRange.getMinY() < -nCairoCoordinateMax
1065
0
            || aRange.getMaxX() > nCairoCoordinateMax || aRange.getMaxY() > nCairoCoordinateMax)
1066
0
        {
1067
0
            SAL_WARN("vcl.gdi", "drawPolyLine, skipping suspicious range of: "
1068
0
                                    << aRange << " for fuzzing performance");
1069
0
            return true;
1070
0
        }
1071
0
    }
1072
1073
0
    cairo_t* cr = getCairoContext(false, bAntiAlias);
1074
0
    clipRegion(cr);
1075
1076
    // need to check/handle LineWidth when ObjectToDevice transformation is used
1077
0
    const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
1078
1079
    // tdf#124848 calculate-back logical LineWidth for a hairline
1080
    // since this implementation hands over the transformation to
1081
    // the graphic sub-system
1082
0
    if (fLineWidth == 0)
1083
0
    {
1084
0
        fLineWidth = 1.0;
1085
1086
0
        if (!bObjectToDeviceIsIdentity)
1087
0
        {
1088
0
            basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
1089
0
            aObjectToDeviceInv.invert();
1090
0
            fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
1091
0
        }
1092
0
    }
1093
1094
    // PixelOffset used: Need to reflect in linear transformation
1095
0
    cairo_matrix_t aMatrix;
1096
0
    basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
1097
1098
0
    if (bObjectToDeviceIsIdentity)
1099
0
    {
1100
        // Set PixelOffset as requested
1101
0
        cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1102
0
    }
1103
0
    else
1104
0
    {
1105
        // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into
1106
        // account: Multiply from left to act in DeviceCoordinates
1107
0
        aDamageMatrix = aDamageMatrix * rObjectToDevice;
1108
0
        cairo_matrix_init(&aMatrix, aDamageMatrix.get(0, 0), aDamageMatrix.get(1, 0),
1109
0
                          aDamageMatrix.get(0, 1), aDamageMatrix.get(1, 1), aDamageMatrix.get(0, 2),
1110
0
                          aDamageMatrix.get(1, 2));
1111
0
    }
1112
1113
    // set linear transformation
1114
0
    cairo_set_matrix(cr, &aMatrix);
1115
1116
    // setup line attributes
1117
0
    cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
1118
0
    switch (eLineJoin)
1119
0
    {
1120
0
        case basegfx::B2DLineJoin::Bevel:
1121
0
            eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
1122
0
            break;
1123
0
        case basegfx::B2DLineJoin::Round:
1124
0
            eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
1125
0
            break;
1126
0
        case basegfx::B2DLineJoin::NONE:
1127
0
        case basegfx::B2DLineJoin::Miter:
1128
0
            eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
1129
0
            break;
1130
0
    }
1131
1132
    // convert miter minimum angle to miter limit
1133
0
    double fMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0);
1134
1135
    // setup cap attribute
1136
0
    cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
1137
1138
0
    switch (eLineCap)
1139
0
    {
1140
0
        default: // css::drawing::LineCap_BUTT:
1141
0
        {
1142
0
            eCairoLineCap = CAIRO_LINE_CAP_BUTT;
1143
0
            break;
1144
0
        }
1145
0
        case css::drawing::LineCap_ROUND:
1146
0
        {
1147
0
            eCairoLineCap = CAIRO_LINE_CAP_ROUND;
1148
0
            break;
1149
0
        }
1150
0
        case css::drawing::LineCap_SQUARE:
1151
0
        {
1152
0
            eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
1153
0
            break;
1154
0
        }
1155
0
    }
1156
1157
0
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1158
0
    cairo_set_source_rgba(cr, m_oLineColor->GetRed() / 255.0, m_oLineColor->GetGreen() / 255.0,
1159
0
                          m_oLineColor->GetBlue() / 255.0, 1.0 - fTransparency);
1160
1161
0
    cairo_set_line_join(cr, eCairoLineJoin);
1162
0
    cairo_set_line_cap(cr, eCairoLineCap);
1163
1164
0
    constexpr int MaxNormalLineWidthPx = 64;
1165
0
    if (fLineWidth > MaxNormalLineWidthPx)
1166
0
    {
1167
0
        const double fLineWidthPixel
1168
0
            = bObjectToDeviceIsIdentity
1169
0
                  ? fLineWidth
1170
0
                  : (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1171
0
        constexpr double MaxLineWidth = 0x20000000;
1172
        // if the width is pixels is excessive, or if the actual number is huge, then
1173
        // when fuzzing drop it to something small
1174
0
        if (fLineWidthPixel > MaxNormalLineWidthPx || fLineWidth > MaxLineWidth)
1175
0
        {
1176
0
            SAL_WARN("vcl.gdi", "drawPolyLine, suspicious input line width of: "
1177
0
                                    << fLineWidth << ", will be " << fLineWidthPixel
1178
0
                                    << " pixels thick");
1179
0
            if (bFuzzing)
1180
0
            {
1181
0
                basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
1182
0
                aObjectToDeviceInv.invert();
1183
0
                fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(MaxNormalLineWidthPx, 0))
1184
0
                                 .getLength();
1185
0
                fLineWidth = std::min(fLineWidth, 2048.0);
1186
0
            }
1187
0
        }
1188
0
    }
1189
0
    cairo_set_line_width(cr, fLineWidth);
1190
0
    cairo_set_miter_limit(cr, fMiterLimit);
1191
1192
    // try to access buffered data
1193
0
    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
1194
0
        rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>(
1195
0
            basegfx::SDD_Type::SDDType_CairoPath));
1196
1197
    // MM01 need to do line dashing as fallback stuff here now
1198
0
    const double fDotDashLength(
1199
0
        nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
1200
0
    const bool bStrokeUsed(0.0 != fDotDashLength);
1201
0
    assert(!bStrokeUsed || (bStrokeUsed && pStroke));
1202
1203
    // MM01 activate to stroke directly
1204
0
    if (bStrokeUsed)
1205
0
    {
1206
0
        cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0);
1207
0
    }
1208
1209
    // check for basegfx::B2DLineJoin::NONE to react accordingly
1210
0
    const bool bNoJoin(basegfx::B2DLineJoin::NONE == eLineJoin && fLineWidth > 0.0
1211
0
                       && !basegfx::fTools::equalZero(fLineWidth));
1212
1213
0
    if (pSystemDependentData_CairoPath)
1214
0
    {
1215
0
        auto strokeEquals
1216
0
            = [](const std::vector<double>& rStroke1, const std::vector<double>* pStroke2) -> bool {
1217
0
            if (!pStroke2)
1218
0
                return rStroke1.empty();
1219
0
            return rStroke1 == *pStroke2;
1220
0
        };
1221
        // check data validity
1222
0
        if (nullptr == pSystemDependentData_CairoPath->getCairoPath()
1223
0
            || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
1224
0
            || pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias
1225
0
            || !strokeEquals(pSystemDependentData_CairoPath->getStroke(), pStroke)
1226
0
            || bPixelSnapHairline /*tdf#124700*/)
1227
0
        {
1228
            // data invalid, forget
1229
0
            pSystemDependentData_CairoPath.reset();
1230
0
        }
1231
0
    }
1232
1233
0
    if (pSystemDependentData_CairoPath)
1234
0
    {
1235
        // re-use data
1236
0
        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
1237
0
    }
1238
0
    else
1239
0
    {
1240
        // create data
1241
0
        size_t nSizeMeasure(0);
1242
1243
        // MM01 need to do line dashing as fallback stuff here now
1244
0
        basegfx::B2DPolyPolygon aPolyPolygonLine;
1245
1246
        // no line dashing or direct stroke, just copy
1247
0
        aPolyPolygonLine.append(rPolyLine);
1248
1249
        // MM01 checked/verified for Cairo
1250
0
        for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
1251
0
        {
1252
0
            const basegfx::B2DPolygon& aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
1253
1254
0
            if (!bNoJoin)
1255
0
            {
1256
                // PixelOffset now reflected in linear transformation used
1257
0
                nSizeMeasure
1258
0
                    += AddPolygonToPath(cr, aPolyLine,
1259
0
                                        rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
1260
0
                                        !bAntiAlias, bPixelSnapHairline);
1261
0
            }
1262
0
            else
1263
0
            {
1264
0
                const sal_uInt32 nPointCount(aPolyLine.count());
1265
0
                const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1);
1266
0
                basegfx::B2DPolygon aEdge;
1267
1268
0
                aEdge.append(aPolyLine.getB2DPoint(0));
1269
0
                aEdge.append(basegfx::B2DPoint(0.0, 0.0));
1270
1271
0
                for (sal_uInt32 i(0); i < nEdgeCount; i++)
1272
0
                {
1273
0
                    const sal_uInt32 nNextIndex((i + 1) % nPointCount);
1274
0
                    aEdge.setB2DPoint(1, aPolyLine.getB2DPoint(nNextIndex));
1275
0
                    aEdge.setNextControlPoint(0, aPolyLine.getNextControlPoint(i));
1276
0
                    aEdge.setPrevControlPoint(1, aPolyLine.getPrevControlPoint(nNextIndex));
1277
1278
                    // PixelOffset now reflected in linear transformation used
1279
0
                    nSizeMeasure += AddPolygonToPath(
1280
0
                        cr, aEdge,
1281
0
                        rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
1282
0
                        !bAntiAlias, bPixelSnapHairline);
1283
1284
                    // prepare next step
1285
0
                    aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
1286
0
                }
1287
0
            }
1288
0
        }
1289
1290
        // copy and add to buffering mechanism
1291
0
        if (!bPixelSnapHairline /*tdf#124700*/)
1292
0
        {
1293
0
            pSystemDependentData_CairoPath
1294
0
                = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
1295
0
                    nSizeMeasure, cr, bNoJoin, bAntiAlias, pStroke);
1296
0
        }
1297
0
    }
1298
1299
    // extract extents
1300
0
    basegfx::B2DRange extents = getClippedStrokeDamage(cr);
1301
    // transform also extents (ranges) of damage so they can be correctly redrawn
1302
0
    extents.transform(aDamageMatrix);
1303
1304
    // draw and consume
1305
0
    cairo_stroke(cr);
1306
1307
0
    releaseCairoContext(cr, false, extents);
1308
1309
0
    return true;
1310
0
}
1311
1312
bool CairoCommon::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
1313
                                tools::Long nHeight, sal_uInt8 nTransparency, bool bAntiAlias)
1314
0
{
1315
0
    const bool bHasFill(m_oFillColor.has_value());
1316
0
    const bool bHasLine(m_oLineColor.has_value());
1317
1318
0
    if (!bHasFill && !bHasLine)
1319
0
        return true;
1320
1321
0
    cairo_t* cr = getCairoContext(false, bAntiAlias);
1322
0
    clipRegion(cr);
1323
1324
0
    const double fTransparency = nTransparency * (1.0 / 100);
1325
1326
    // To make releaseCairoContext work, use empty extents
1327
0
    basegfx::B2DRange extents;
1328
1329
0
    if (bHasFill)
1330
0
    {
1331
0
        cairo_rectangle(cr, nX, nY, nWidth, nHeight);
1332
1333
0
        applyColor(cr, *m_oFillColor, fTransparency);
1334
1335
        // set FillDamage
1336
0
        extents = getClippedFillDamage(cr);
1337
1338
0
        cairo_fill(cr);
1339
0
    }
1340
1341
0
    if (bHasLine)
1342
0
    {
1343
        // PixelOffset used: Set PixelOffset as linear transformation
1344
        // Note: Was missing here - probably not by purpose (?)
1345
0
        cairo_matrix_t aMatrix;
1346
0
        cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1347
0
        cairo_set_matrix(cr, &aMatrix);
1348
1349
0
        cairo_rectangle(cr, nX, nY, nWidth, nHeight);
1350
1351
0
        applyColor(cr, *m_oLineColor, fTransparency);
1352
1353
        // expand with possible StrokeDamage
1354
0
        basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
1355
0
        stroke_extents.translate(0.5, 0.5);
1356
0
        extents.expand(stroke_extents);
1357
1358
0
        cairo_stroke(cr);
1359
0
    }
1360
1361
0
    releaseCairoContext(cr, false, extents);
1362
1363
0
    return true;
1364
0
}
1365
1366
bool CairoCommon::drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient,
1367
                               bool bAntiAlias)
1368
0
{
1369
0
    if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
1370
0
        && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL)
1371
0
        return false; // unsupported
1372
0
    if (rGradient.GetSteps() != 0)
1373
0
        return false; // We can't tell cairo how many colors to use in the gradient.
1374
1375
0
    cairo_t* cr = getCairoContext(true, bAntiAlias);
1376
0
    clipRegion(cr);
1377
1378
0
    tools::Rectangle aInputRect(rPolyPolygon.GetBoundRect());
1379
0
    if (rPolyPolygon.IsRect())
1380
0
    {
1381
        // Rect->Polygon conversion loses the right and bottom edge, fix that.
1382
0
        aInputRect.AdjustRight(1);
1383
0
        aInputRect.AdjustBottom(1);
1384
0
        basegfx::B2DHomMatrix rObjectToDevice;
1385
0
        AddPolygonToPath(cr, tools::Polygon(aInputRect).getB2DPolygon(), rObjectToDevice,
1386
0
                         !bAntiAlias, false);
1387
0
    }
1388
0
    else
1389
0
    {
1390
0
        basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPolygon.getB2DPolyPolygon());
1391
0
        for (auto const& rPolygon : std::as_const(aB2DPolyPolygon))
1392
0
        {
1393
0
            basegfx::B2DHomMatrix rObjectToDevice;
1394
0
            AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false);
1395
0
        }
1396
0
    }
1397
1398
0
    Gradient aGradient(rGradient);
1399
1400
0
    tools::Rectangle aBoundRect;
1401
0
    Point aCenter;
1402
1403
0
    aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
1404
0
    aGradient.GetBoundRect(aInputRect, aBoundRect, aCenter);
1405
0
    if (aBoundRect.IsEmpty())
1406
0
        SAL_WARN("vcl.gdi", "empty gradient bounding rectangle");
1407
0
    else
1408
0
    {
1409
0
        Color aStartColor = aGradient.GetStartColor();
1410
0
        Color aEndColor = aGradient.GetEndColor();
1411
1412
0
        cairo_pattern_t* pattern;
1413
0
        if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR)
1414
0
        {
1415
0
            tools::Polygon aPoly(aBoundRect);
1416
0
            aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
1417
0
            pattern = cairo_pattern_create_linear(aPoly[0].X(), aPoly[0].Y(), aPoly[1].X(),
1418
0
                                                  aPoly[1].Y());
1419
0
        }
1420
0
        else
1421
0
        {
1422
0
            double radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
1423
            // Move the center a bit to the top-left (the default VCL algorithm is a bit off-center that way,
1424
            // cairo is the opposite way).
1425
0
            pattern = cairo_pattern_create_radial(aCenter.X() - 0.5, aCenter.Y() - 0.5, 0,
1426
0
                                                  aCenter.X() - 0.5, aCenter.Y() - 0.5, radius);
1427
0
            std::swap(aStartColor, aEndColor);
1428
0
        }
1429
1430
0
        cairo_pattern_add_color_stop_rgba(
1431
0
            pattern, aGradient.GetBorder() / 100.0,
1432
0
            aStartColor.GetRed() * aGradient.GetStartIntensity() / 25500.0,
1433
0
            aStartColor.GetGreen() * aGradient.GetStartIntensity() / 25500.0,
1434
0
            aStartColor.GetBlue() * aGradient.GetStartIntensity() / 25500.0, 1.0);
1435
1436
0
        cairo_pattern_add_color_stop_rgba(
1437
0
            pattern, 1.0, aEndColor.GetRed() * aGradient.GetEndIntensity() / 25500.0,
1438
0
            aEndColor.GetGreen() * aGradient.GetEndIntensity() / 25500.0,
1439
0
            aEndColor.GetBlue() * aGradient.GetEndIntensity() / 25500.0, 1.0);
1440
1441
0
        cairo_set_source(cr, pattern);
1442
0
        cairo_pattern_destroy(pattern);
1443
0
    }
1444
1445
0
    basegfx::B2DRange extents = getClippedFillDamage(cr);
1446
0
    cairo_fill_preserve(cr);
1447
1448
0
    releaseCairoContext(cr, true, extents);
1449
1450
0
    return true;
1451
0
}
1452
1453
bool CairoCommon::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
1454
                                   SalGradient const& rGradient, bool bAntiAlias)
1455
0
{
1456
0
    cairo_t* cr = getCairoContext(true, bAntiAlias);
1457
1458
0
    basegfx::B2DHomMatrix rObjectToDevice;
1459
1460
0
    for (auto const& rPolygon : rPolyPolygon)
1461
0
        AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false);
1462
1463
0
    cairo_pattern_t* pattern
1464
0
        = cairo_pattern_create_linear(rGradient.maPoint1.getX(), rGradient.maPoint1.getY(),
1465
0
                                      rGradient.maPoint2.getX(), rGradient.maPoint2.getY());
1466
1467
0
    for (SalGradientStop const& rStop : rGradient.maStops)
1468
0
    {
1469
0
        double r = rStop.maColor.GetRed() / 255.0;
1470
0
        double g = rStop.maColor.GetGreen() / 255.0;
1471
0
        double b = rStop.maColor.GetBlue() / 255.0;
1472
0
        double a = rStop.maColor.GetAlpha() / 255.0;
1473
0
        double offset = rStop.mfOffset;
1474
1475
0
        cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a);
1476
0
    }
1477
0
    cairo_set_source(cr, pattern);
1478
0
    cairo_pattern_destroy(pattern);
1479
1480
0
    basegfx::B2DRange extents = getClippedFillDamage(cr);
1481
1482
0
    cairo_fill_preserve(cr);
1483
1484
0
    releaseCairoContext(cr, true, extents);
1485
1486
0
    return true;
1487
0
}
1488
1489
namespace
1490
{
1491
basegfx::B2DRange renderWithOperator(cairo_t* cr, const SalTwoRect& rTR, cairo_surface_t* source,
1492
                                     cairo_operator_t eOperator = CAIRO_OPERATOR_SOURCE)
1493
12.9k
{
1494
12.9k
    cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1495
1496
12.9k
    basegfx::B2DRange extents = getClippedFillDamage(cr);
1497
1498
12.9k
    cairo_clip(cr);
1499
1500
12.9k
    cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1501
12.9k
    if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0)
1502
12.9k
    {
1503
12.9k
        double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
1504
12.9k
        double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
1505
12.9k
        cairo_scale(cr, fXScale, fYScale);
1506
12.9k
    }
1507
1508
12.9k
    cairo_save(cr);
1509
12.9k
    cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
1510
1511
12.9k
    if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
1512
12.9k
    {
1513
        //tdf#133716 borders of upscaled images should not be blurred
1514
12.9k
        cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1515
12.9k
        cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
1516
12.9k
    }
1517
1518
12.9k
    cairo_set_operator(cr, eOperator);
1519
12.9k
    cairo_paint(cr);
1520
12.9k
    cairo_restore(cr);
1521
1522
12.9k
    return extents;
1523
12.9k
}
1524
1525
} // end anonymous ns
1526
1527
basegfx::B2DRange CairoCommon::renderSource(cairo_t* cr, const SalTwoRect& rTR,
1528
                                            cairo_surface_t* source)
1529
6.47k
{
1530
6.47k
    return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE);
1531
6.47k
}
1532
1533
void CairoCommon::copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source,
1534
                                   cairo_operator_t eOp, bool bAntiAlias)
1535
6.47k
{
1536
6.47k
    cairo_t* cr = getCairoContext(false, bAntiAlias);
1537
6.47k
    clipRegion(cr);
1538
1539
6.47k
    basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp);
1540
1541
6.47k
    releaseCairoContext(cr, false, extents);
1542
6.47k
}
1543
1544
void CairoCommon::copySource(const SalTwoRect& rTR, cairo_surface_t* source, bool bAntiAlias)
1545
0
{
1546
0
    copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
1547
0
}
1548
1549
void CairoCommon::copyBitsCairo(const SalTwoRect& rTR, cairo_surface_t* pSourceSurface,
1550
                                bool bAntiAlias)
1551
0
{
1552
0
    SalTwoRect aTR(rTR);
1553
1554
0
    cairo_surface_t* pCopy = nullptr;
1555
1556
0
    if (pSourceSurface == getSurface())
1557
0
    {
1558
        //self copy is a problem, so dup source in that case
1559
0
        pCopy
1560
0
            = cairo_surface_create_similar(pSourceSurface, cairo_surface_get_content(getSurface()),
1561
0
                                           aTR.mnSrcWidth * m_fScale, aTR.mnSrcHeight * m_fScale);
1562
0
        dl_cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
1563
0
        cairo_t* cr = cairo_create(pCopy);
1564
0
        cairo_set_source_surface(cr, pSourceSurface, -aTR.mnSrcX, -aTR.mnSrcY);
1565
0
        cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
1566
0
        cairo_fill(cr);
1567
0
        cairo_destroy(cr);
1568
1569
0
        pSourceSurface = pCopy;
1570
1571
0
        aTR.mnSrcX = 0;
1572
0
        aTR.mnSrcY = 0;
1573
0
    }
1574
1575
0
    copySource(aTR, pSourceSurface, bAntiAlias);
1576
1577
0
    if (pCopy)
1578
0
        cairo_surface_destroy(pCopy);
1579
0
}
1580
1581
namespace
1582
{
1583
cairo_pattern_t* create_stipple()
1584
0
{
1585
0
    static const unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
1586
0
                                            0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF };
1587
0
    cairo_surface_t* surface = cairo_image_surface_create_for_data(const_cast<unsigned char*>(data),
1588
0
                                                                   CAIRO_FORMAT_A8, 4, 4, 4);
1589
0
    cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
1590
0
    cairo_surface_destroy(surface);
1591
0
    cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
1592
0
    cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
1593
0
    return pattern;
1594
0
}
1595
} // end anonymous ns
1596
1597
void CairoCommon::invert(const basegfx::B2DPolygon& rPoly, SalInvert nFlags, bool bAntiAlias)
1598
0
{
1599
0
    cairo_t* cr = getCairoContext(false, bAntiAlias);
1600
0
    clipRegion(cr);
1601
1602
    // To make releaseCairoContext work, use empty extents
1603
0
    basegfx::B2DRange extents;
1604
1605
0
    AddPolygonToPath(cr, rPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false);
1606
1607
0
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1608
1609
0
    cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
1610
1611
0
    if (nFlags & SalInvert::TrackFrame)
1612
0
    {
1613
0
        cairo_set_line_width(cr, 2.0);
1614
0
        const double dashLengths[2] = { 4.0, 4.0 };
1615
0
        cairo_set_dash(cr, dashLengths, 2, 0);
1616
1617
0
        extents = getClippedStrokeDamage(cr);
1618
        //see tdf#106577 under wayland, some pixel droppings seen, maybe we're
1619
        //out by one somewhere, or cairo_stroke_extents is confused by
1620
        //dashes/line width
1621
0
        if (!extents.isEmpty())
1622
0
        {
1623
0
            extents.grow(1);
1624
0
        }
1625
1626
0
        cairo_stroke(cr);
1627
0
    }
1628
0
    else
1629
0
    {
1630
0
        extents = getClippedFillDamage(cr);
1631
1632
0
        cairo_clip(cr);
1633
1634
0
        if (nFlags & SalInvert::N50)
1635
0
        {
1636
0
            cairo_pattern_t* pattern = create_stipple();
1637
0
            cairo_surface_t* surface = cairo_surface_create_similar(
1638
0
                m_pSurface, cairo_surface_get_content(m_pSurface), extents.getWidth() * m_fScale,
1639
0
                extents.getHeight() * m_fScale);
1640
1641
0
            dl_cairo_surface_set_device_scale(surface, m_fScale, m_fScale);
1642
0
            cairo_t* stipple_cr = cairo_create(surface);
1643
0
            cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0);
1644
0
            cairo_mask(stipple_cr, pattern);
1645
0
            cairo_pattern_destroy(pattern);
1646
0
            cairo_destroy(stipple_cr);
1647
0
            cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY());
1648
0
            cairo_surface_destroy(surface);
1649
0
        }
1650
0
        else
1651
0
        {
1652
0
            cairo_paint(cr);
1653
0
        }
1654
0
    }
1655
1656
0
    releaseCairoContext(cr, false, extents);
1657
0
}
1658
1659
void CairoCommon::invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
1660
                         SalInvert nFlags, bool bAntiAlias)
1661
0
{
1662
0
    basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
1663
0
        basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
1664
1665
0
    invert(aRect, nFlags, bAntiAlias);
1666
0
}
1667
1668
void CairoCommon::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, bool bAntiAlias)
1669
0
{
1670
0
    basegfx::B2DPolygon aPoly;
1671
0
    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
1672
0
    for (sal_uInt32 i = 1; i < nPoints; ++i)
1673
0
        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
1674
0
    aPoly.setClosed(true);
1675
1676
0
    invert(aPoly, nFlags, bAntiAlias);
1677
0
}
1678
1679
void CairoCommon::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1680
                             bool bAntiAlias)
1681
6.47k
{
1682
    // MM02 try to access buffered BitmapHelper
1683
6.47k
    std::shared_ptr<BitmapHelper> aSurface;
1684
6.47k
    tryToUseSourceBuffer(rSalBitmap, aSurface);
1685
6.47k
    cairo_surface_t* source = aSurface->getSurface(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1686
1687
6.47k
    if (!source)
1688
0
    {
1689
0
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
1690
0
        return;
1691
0
    }
1692
1693
#if 0 // LO code is not yet bitmap32-ready.
1694
    // if m_bSupportsBitmap32 becomes true for Svp revisit this
1695
    copyWithOperator(rPosAry, source, CAIRO_OPERATOR_OVER, bAntiAlias);
1696
#else
1697
6.47k
    copyWithOperator(rPosAry, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
1698
6.47k
#endif
1699
6.47k
}
1700
1701
void CairoCommon::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1702
                                  bool bAntiAlias)
1703
0
{
1704
    // MM02 try to access buffered BitmapHelper
1705
0
    std::shared_ptr<BitmapHelper> aSurface;
1706
0
    tryToUseSourceBuffer(rSalBitmap, aSurface);
1707
0
    cairo_surface_t* source = aSurface->getSurface(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1708
1709
0
    if (!source)
1710
0
    {
1711
0
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
1712
0
        return;
1713
0
    }
1714
1715
0
    copyWithOperator(rPosAry, source, CAIRO_OPERATOR_OVER, bAntiAlias);
1716
0
}
1717
1718
bool CairoCommon::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
1719
                                        const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
1720
                                        double fAlpha, bool bAntiAlias)
1721
0
{
1722
0
    if (fAlpha != 1.0)
1723
0
        return false;
1724
1725
    // MM02 try to access buffered BitmapHelper
1726
0
    std::shared_ptr<BitmapHelper> aSurface;
1727
0
    tryToUseSourceBuffer(rSourceBitmap, aSurface);
1728
0
    const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
1729
0
    const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));
1730
0
    cairo_surface_t* source(aSurface->getSurface(nDestWidth, nDestHeight));
1731
1732
0
    if (!source)
1733
0
    {
1734
0
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
1735
0
        return false;
1736
0
    }
1737
1738
0
    const Size aSize = rSourceBitmap.GetSize();
1739
0
    cairo_t* cr = getCairoContext(false, bAntiAlias);
1740
0
    clipRegion(cr);
1741
1742
    // setup the image transformation
1743
    // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
1744
0
    const basegfx::B2DVector aXRel = rX - rNull;
1745
0
    const basegfx::B2DVector aYRel = rY - rNull;
1746
0
    cairo_matrix_t matrix;
1747
0
    cairo_matrix_init(&matrix, aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(),
1748
0
                      aYRel.getX() / aSize.Height(), aYRel.getY() / aSize.Height(), rNull.getX(),
1749
0
                      rNull.getY());
1750
1751
0
    cairo_transform(cr, &matrix);
1752
1753
0
    cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height());
1754
0
    basegfx::B2DRange extents = getClippedFillDamage(cr);
1755
0
    cairo_clip(cr);
1756
1757
0
    cairo_set_source_surface(cr, source, 0, 0);
1758
0
    cairo_paint(cr);
1759
1760
0
    releaseCairoContext(cr, false, extents);
1761
1762
0
    return true;
1763
0
}
1764
1765
void CairoCommon::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor,
1766
                           bool bAntiAlias)
1767
0
{
1768
    /** creates an image from the given rectangle, replacing all black pixels
1769
     *  with nMaskColor and making all others fully transparent */
1770
    // MM02 here decided *against* using buffered BitmapHelper
1771
    // because the data gets somehow 'unmultiplied'. This may also be
1772
    // done just once, but I am not sure if this is safe to do.
1773
    // So for now dispense re-using data here.
1774
0
    BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32
1775
0
    if (!aSurface.getSurface())
1776
0
    {
1777
0
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");
1778
0
        return;
1779
0
    }
1780
0
    sal_Int32 nStride;
1781
0
    unsigned char* mask_data = aSurface.getBits(nStride);
1782
0
    for (tools::Long y = rTR.mnSrcY; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
1783
0
    {
1784
0
        unsigned char* row = mask_data + (nStride * y);
1785
0
        unsigned char* data = row + (rTR.mnSrcX * 4);
1786
0
        for (tools::Long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
1787
0
        {
1788
            // Don't need to unpremultiply the data here, because we are checking for zero,
1789
            // and that is the same multiplied or un-multiplied.
1790
0
            if (data[SVP_CAIRO_RED] == 0 && data[SVP_CAIRO_GREEN] == 0 && data[SVP_CAIRO_BLUE] == 0)
1791
0
            {
1792
0
                data[SVP_CAIRO_RED] = nMaskColor.GetRed();
1793
0
                data[SVP_CAIRO_GREEN] = nMaskColor.GetGreen();
1794
0
                data[SVP_CAIRO_BLUE] = nMaskColor.GetBlue();
1795
0
                data[SVP_CAIRO_ALPHA] = 0xff;
1796
0
            }
1797
0
            else
1798
0
            {
1799
0
                data[0] = 0;
1800
0
                data[1] = 0;
1801
0
                data[2] = 0;
1802
0
                data[3] = 0;
1803
0
            }
1804
0
            data += 4;
1805
0
        }
1806
0
    }
1807
0
    aSurface.mark_dirty();
1808
1809
0
    cairo_t* cr = getCairoContext(false, bAntiAlias);
1810
0
    clipRegion(cr);
1811
1812
0
    cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1813
1814
0
    basegfx::B2DRange extents = getClippedFillDamage(cr);
1815
1816
0
    cairo_clip(cr);
1817
1818
0
    cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1819
0
    double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
1820
0
    double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
1821
0
    cairo_scale(cr, fXScale, fYScale);
1822
0
    cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY);
1823
1824
0
    if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
1825
0
    {
1826
        //tdf#133716 borders of upscaled images should not be blurred
1827
0
        cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1828
0
        cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
1829
0
    }
1830
1831
0
    cairo_paint(cr);
1832
1833
0
    releaseCairoContext(cr, false, extents);
1834
0
}
1835
1836
std::shared_ptr<SalBitmap> CairoCommon::getBitmap(tools::Long nX, tools::Long nY,
1837
                                                  tools::Long nWidth, tools::Long nHeight,
1838
                                                  bool bWithoutAlpha)
1839
6.47k
{
1840
6.47k
    std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>();
1841
6.47k
    BitmapPalette aPal;
1842
6.47k
    assert(GetBitCount() != 1 && "not supported anymore");
1843
6.47k
    vcl::PixelFormat ePixelFormat = vcl::PixelFormat::N32_BPP;
1844
1845
6.47k
    if (!pBitmap->ImplCreate(Size(nWidth, nHeight), ePixelFormat, aPal, false, bWithoutAlpha))
1846
0
    {
1847
0
        SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
1848
0
        return nullptr;
1849
0
    }
1850
1851
6.47k
    cairo_surface_t* target = CairoCommon::createCairoSurface(pBitmap->GetBuffer());
1852
6.47k
    if (!target)
1853
0
    {
1854
0
        SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create cairo surface");
1855
0
        return nullptr;
1856
0
    }
1857
6.47k
    cairo_t* cr = cairo_create(target);
1858
1859
6.47k
    SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight);
1860
6.47k
    CairoCommon::renderSource(cr, aTR, m_pSurface);
1861
1862
6.47k
    cairo_destroy(cr);
1863
6.47k
    cairo_surface_destroy(target);
1864
1865
6.47k
    return pBitmap;
1866
6.47k
}
1867
1868
cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer)
1869
25.8k
{
1870
25.8k
    cairo_format_t nFormat;
1871
25.8k
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
1872
25.8k
    assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 24);
1873
#else
1874
    assert(rBuffer.mnBitCount == 32);
1875
#endif
1876
1877
25.8k
    if (rBuffer.mnBitCount == 32)
1878
25.8k
        nFormat = CAIRO_FORMAT_ARGB32;
1879
0
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
1880
0
    else if (rBuffer.mnBitCount == 24)
1881
0
        nFormat = CAIRO_FORMAT_RGB24_888;
1882
0
#endif
1883
0
    else
1884
0
        nFormat = CAIRO_FORMAT_A1;
1885
25.8k
    return nFormat;
1886
25.8k
}
1887
1888
namespace
1889
{
1890
bool isCairoCompatible(const BitmapBuffer* pBuffer)
1891
12.9k
{
1892
12.9k
    if (!pBuffer)
1893
0
        return false;
1894
1895
        // We use Cairo that supports 24-bit RGB.
1896
12.9k
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
1897
12.9k
    if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24)
1898
#else
1899
    if (pBuffer->mnBitCount != 32)
1900
#endif
1901
0
        return false;
1902
1903
12.9k
    cairo_format_t nFormat = getCairoFormat(*pBuffer);
1904
12.9k
    return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize);
1905
12.9k
}
1906
}
1907
1908
cairo_surface_t* CairoCommon::createCairoSurface(const BitmapBuffer* pBuffer)
1909
12.9k
{
1910
12.9k
    if (!isCairoCompatible(pBuffer))
1911
0
        return nullptr;
1912
1913
12.9k
    cairo_format_t nFormat = getCairoFormat(*pBuffer);
1914
12.9k
    cairo_surface_t* target = cairo_image_surface_create_for_data(
1915
12.9k
        pBuffer->mpBits, nFormat, pBuffer->mnWidth, pBuffer->mnHeight, pBuffer->mnScanlineSize);
1916
12.9k
    if (cairo_surface_status(target) != CAIRO_STATUS_SUCCESS)
1917
0
    {
1918
0
        cairo_surface_destroy(target);
1919
0
        return nullptr;
1920
0
    }
1921
12.9k
    return target;
1922
12.9k
}
1923
1924
bool CairoCommon::supportsOperation(OutDevSupportType eType)
1925
0
{
1926
0
    switch (eType)
1927
0
    {
1928
0
        case OutDevSupportType::TransparentText:
1929
0
            return true;
1930
0
    }
1931
0
    return false;
1932
0
}
1933
1934
std::optional<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc)
1935
0
{
1936
0
    if (pSrc == nullptr)
1937
0
        return std::nullopt;
1938
1939
0
    assert(pSrc->meFormat == SVP_24BIT_FORMAT);
1940
0
    const tools::Long nWidth = pSrc->mnWidth;
1941
0
    const tools::Long nHeight = pSrc->mnHeight;
1942
0
    std::optional<BitmapBuffer> pDst(std::in_place);
1943
0
    pDst->meFormat = ScanlineFormat::N32BitTcArgb;
1944
0
    pDst->meDirection = ScanlineDirection::TopDown;
1945
0
    pDst->mnWidth = nWidth;
1946
0
    pDst->mnHeight = nHeight;
1947
0
    pDst->mnBitCount = 32;
1948
0
    pDst->maPalette = pSrc->maPalette;
1949
1950
0
    tools::Long nScanlineBase;
1951
0
    const bool bFail = o3tl::checked_multiply<tools::Long>(pDst->mnBitCount, nWidth, nScanlineBase);
1952
0
    if (bFail)
1953
0
    {
1954
0
        SAL_WARN("vcl.gdi", "checked multiply failed");
1955
0
        pDst->mpBits = nullptr;
1956
0
        return std::nullopt;
1957
0
    }
1958
1959
0
    pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
1960
0
    if (pDst->mnScanlineSize < nScanlineBase / 8)
1961
0
    {
1962
0
        SAL_WARN("vcl.gdi", "scanline calculation wraparound");
1963
0
        pDst->mpBits = nullptr;
1964
0
        return std::nullopt;
1965
0
    }
1966
1967
0
    try
1968
0
    {
1969
0
        pDst->mpBits = new sal_uInt8[pDst->mnScanlineSize * nHeight];
1970
0
    }
1971
0
    catch (const std::bad_alloc&)
1972
0
    {
1973
        // memory exception, clean up
1974
0
        pDst->mpBits = nullptr;
1975
0
        return std::nullopt;
1976
0
    }
1977
1978
0
    for (tools::Long y = 0; y < nHeight; ++y)
1979
0
    {
1980
0
        sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
1981
0
        sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
1982
0
        for (tools::Long x = 0; x < nWidth; ++x)
1983
0
        {
1984
#if ENABLE_CAIRO_RGBA
1985
            static_assert(SVP_CAIRO_FORMAT == ScanlineFormat::N32BitTcRgba,
1986
                          "Expected SVP_CAIRO_FORMAT set to N32BitTcRgba");
1987
            static_assert(SVP_24BIT_FORMAT == ScanlineFormat::N24BitTcRgb,
1988
                          "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
1989
            pD[0] = pS[0];
1990
            pD[1] = pS[1];
1991
            pD[2] = pS[2];
1992
            pD[3] = 0xff; // Alpha
1993
#elif defined OSL_BIGENDIAN
1994
            static_assert(SVP_CAIRO_FORMAT == ScanlineFormat::N32BitTcArgb,
1995
                          "Expected SVP_CAIRO_FORMAT set to N32BitTcArgb");
1996
            static_assert(SVP_24BIT_FORMAT == ScanlineFormat::N24BitTcRgb,
1997
                          "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
1998
            pD[0] = 0xff; // Alpha
1999
            pD[1] = pS[0];
2000
            pD[2] = pS[1];
2001
            pD[3] = pS[2];
2002
#else
2003
0
            static_assert(SVP_CAIRO_FORMAT == ScanlineFormat::N32BitTcBgra,
2004
0
                          "Expected SVP_CAIRO_FORMAT set to N32BitTcAgrx");
2005
0
            static_assert(SVP_24BIT_FORMAT == ScanlineFormat::N24BitTcBgr,
2006
0
                          "Expected SVP_24BIT_FORMAT set to N24BitTcBgr");
2007
0
            pD[0] = pS[0];
2008
0
            pD[1] = pS[1];
2009
0
            pD[2] = pS[2];
2010
0
            pD[3] = 0xff; // Alpha
2011
0
#endif
2012
2013
0
            pS += 3;
2014
0
            pD += 4;
2015
0
        }
2016
0
    }
2017
2018
0
    return pDst;
2019
0
}
2020
2021
namespace
2022
{
2023
// check for env var that decides for using downscale pattern
2024
const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
2025
bool bDisableDownScale(nullptr != pDisableDownScale);
2026
}
2027
2028
cairo_surface_t* SurfaceHelper::implCreateOrReuseDownscale(unsigned long nTargetWidth,
2029
                                                           unsigned long nTargetHeight)
2030
0
{
2031
0
    const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
2032
0
    const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
2033
2034
    // zoomed in, need to stretch at paint, no pre-scale useful
2035
0
    if (nTargetWidth >= nSourceWidth || nTargetHeight >= nSourceHeight)
2036
0
    {
2037
0
        return pSurface;
2038
0
    }
2039
2040
    // calculate downscale factor
2041
0
    unsigned long nWFactor(1);
2042
0
    unsigned long nW((nSourceWidth + 1) / 2);
2043
0
    unsigned long nHFactor(1);
2044
0
    unsigned long nH((nSourceHeight + 1) / 2);
2045
2046
0
    while (nW > nTargetWidth && nW > 1)
2047
0
    {
2048
0
        nW = (nW + 1) / 2;
2049
0
        nWFactor *= 2;
2050
0
    }
2051
2052
0
    while (nH > nTargetHeight && nH > 1)
2053
0
    {
2054
0
        nH = (nH + 1) / 2;
2055
0
        nHFactor *= 2;
2056
0
    }
2057
2058
0
    if (1 == nWFactor && 1 == nHFactor)
2059
0
    {
2060
        // original size *is* best binary size, use it
2061
0
        return pSurface;
2062
0
    }
2063
2064
    // go up one scale again - look for no change
2065
0
    nW = (1 == nWFactor) ? nTargetWidth : nW * 2;
2066
0
    nH = (1 == nHFactor) ? nTargetHeight : nH * 2;
2067
2068
    // check if we have a downscaled version of required size
2069
    // bail out if the multiplication for the key would overflow
2070
0
    if (nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32)
2071
0
        return pSurface;
2072
0
    const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + nH);
2073
0
    auto isHit(maDownscaled.find(key));
2074
2075
0
    if (isHit != maDownscaled.end())
2076
0
    {
2077
0
        return isHit->second;
2078
0
    }
2079
2080
    // create new surface in the targeted size
2081
0
    cairo_surface_t* pSurfaceTarget
2082
0
        = cairo_surface_create_similar(pSurface, cairo_surface_get_content(pSurface), nW, nH);
2083
2084
    // made a version to scale self first that worked well, but would've
2085
    // been hard to support CAIRO_FORMAT_A1 including bit shifting, so
2086
    // I decided to go with cairo itself - use CAIRO_FILTER_FAST or
2087
    // CAIRO_FILTER_GOOD though. Please modify as needed for
2088
    // performance/quality
2089
0
    cairo_t* cr = cairo_create(pSurfaceTarget);
2090
0
    const double fScaleX(static_cast<double>(nW) / static_cast<double>(nSourceWidth));
2091
0
    const double fScaleY(static_cast<double>(nH) / static_cast<double>(nSourceHeight));
2092
0
    cairo_scale(cr, fScaleX, fScaleY);
2093
0
    cairo_set_source_surface(cr, pSurface, 0.0, 0.0);
2094
0
    cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
2095
0
    cairo_paint(cr);
2096
0
    cairo_destroy(cr);
2097
2098
    // need to set device_scale for downscale surfaces to get
2099
    // them handled correctly
2100
0
    cairo_surface_set_device_scale(pSurfaceTarget, fScaleX, fScaleY);
2101
2102
    // add entry to cached entries
2103
0
    maDownscaled[key] = pSurfaceTarget;
2104
2105
0
    return pSurfaceTarget;
2106
0
}
2107
2108
bool SurfaceHelper::isTrivial() const
2109
6.47k
{
2110
6.47k
    constexpr unsigned long nMinimalSquareSizeToBuffer(64 * 64);
2111
6.47k
    const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
2112
6.47k
    const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
2113
2114
6.47k
    return nSourceWidth * nSourceHeight < nMinimalSquareSizeToBuffer;
2115
6.47k
}
2116
2117
SurfaceHelper::SurfaceHelper()
2118
6.47k
    : pSurface(nullptr)
2119
6.47k
{
2120
6.47k
}
2121
2122
SurfaceHelper::~SurfaceHelper()
2123
6.47k
{
2124
6.47k
    cairo_surface_destroy(pSurface);
2125
6.47k
    for (auto& candidate : maDownscaled)
2126
0
    {
2127
0
        cairo_surface_destroy(candidate.second);
2128
0
    }
2129
6.47k
}
2130
2131
cairo_surface_t* SurfaceHelper::getSurface(unsigned long nTargetWidth,
2132
                                           unsigned long nTargetHeight) const
2133
6.47k
{
2134
6.47k
    if (bDisableDownScale || 0 == nTargetWidth || 0 == nTargetHeight || !pSurface || isTrivial())
2135
6.47k
    {
2136
        // caller asks for original or disabled or trivial (smaller than a minimal square size)
2137
        // also excludes zero cases for width/height after this point if need to prescale
2138
6.47k
        return pSurface;
2139
6.47k
    }
2140
2141
0
    return const_cast<SurfaceHelper*>(this)->implCreateOrReuseDownscale(nTargetWidth,
2142
0
                                                                        nTargetHeight);
2143
6.47k
}
2144
2145
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */