Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/processor2d/vclhelperbufferdevice.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 <sal/config.h>
21
#include <sal/log.hxx>
22
23
#include <algorithm>
24
#include <map>
25
#include <vector>
26
27
#include "vclhelperbufferdevice.hxx"
28
#include <basegfx/range/b2drange.hxx>
29
#include <vcl/alpha.hxx>
30
#include <vcl/bitmap.hxx>
31
#include <basegfx/matrix/b2dhommatrix.hxx>
32
#include <basegfx/matrix/b2dhommatrixtools.hxx>
33
#include <vcl/timer.hxx>
34
#include <tools/lazydelete.hxx>
35
#include <vcl/dibtools.hxx>
36
#include <vcl/skia/SkiaHelper.hxx>
37
#include <mutex>
38
39
#ifdef DBG_UTIL
40
#include <o3tl/environment.hxx>
41
#include <tools/stream.hxx>
42
#endif
43
44
// #define SPEED_COMPARE
45
#ifdef SPEED_COMPARE
46
#include <tools/time.hxx>
47
#endif
48
49
// buffered VDev usage
50
namespace
51
{
52
class VDevBuffer : public Timer
53
{
54
private:
55
    struct Entry
56
    {
57
        VclPtr<VirtualDevice> buf;
58
        Entry(const VclPtr<VirtualDevice>& vdev)
59
0
            : buf(vdev)
60
0
        {
61
0
        }
62
    };
63
64
    std::mutex m_aMutex;
65
66
    // available buffers
67
    std::vector<Entry> maFreeBuffers;
68
69
    // allocated/used buffers (remembered to allow deleting them in destructor)
70
    std::vector<Entry> maUsedBuffers;
71
72
    // remember what outputdevice was the template passed to VirtualDevice::Create
73
    // so we can test if that OutputDevice was disposed before reusing a
74
    // virtualdevice because that isn't safe to do at least for Gtk2
75
    std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates;
76
77
    static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size);
78
79
public:
80
    VDevBuffer();
81
    virtual ~VDevBuffer() override;
82
83
    VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel);
84
    void free(VirtualDevice& rDevice);
85
86
    // Timer virtuals
87
    virtual void Invoke() override;
88
};
89
90
VDevBuffer::VDevBuffer()
91
0
    : Timer("drawinglayer::VDevBuffer via Invoke()")
92
0
{
93
0
    SetTimeout(10L * 1000L); // ten seconds
94
0
}
95
96
VDevBuffer::~VDevBuffer()
97
0
{
98
0
    std::unique_lock aGuard(m_aMutex);
99
0
    Stop();
100
101
0
    while (!maFreeBuffers.empty())
102
0
    {
103
0
        maFreeBuffers.back().buf.disposeAndClear();
104
0
        maFreeBuffers.pop_back();
105
0
    }
106
107
0
    while (!maUsedBuffers.empty())
108
0
    {
109
0
        maUsedBuffers.back().buf.disposeAndClear();
110
0
        maUsedBuffers.pop_back();
111
0
    }
112
0
}
113
114
bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel)
115
0
{
116
0
    if (device->GetOutputWidthPixel() >= rSizePixel.getWidth()
117
0
        && device->GetOutputHeightPixel() >= rSizePixel.getHeight())
118
0
    {
119
0
        bool requireSmall = false;
120
0
#if defined(UNX)
121
        // HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface().
122
        // Make sure to not reuse a larger device when a small one should be preferred.
123
0
        if (device->GetRenderBackendName() == "svp")
124
0
            requireSmall = true;
125
0
#endif
126
        // The same for Skia, see renderMethodToUseForSize().
127
0
        if (SkiaHelper::isVCLSkiaEnabled())
128
0
            requireSmall = true;
129
0
        if (requireSmall)
130
0
        {
131
0
            if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32
132
0
                && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32))
133
0
            {
134
0
                return false;
135
0
            }
136
0
        }
137
0
        return true;
138
0
    }
139
0
    return false;
140
0
}
141
142
VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel)
143
0
{
144
0
    std::unique_lock aGuard(m_aMutex);
145
0
    VclPtr<VirtualDevice> pRetval;
146
147
0
    sal_Int32 nBits = rOutDev.GetBitCount();
148
149
0
    bool bOkay(false);
150
0
    if (!maFreeBuffers.empty())
151
0
    {
152
0
        auto aFound(maFreeBuffers.end());
153
154
0
        for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a)
155
0
        {
156
0
            assert(a->buf && "Empty pointer in VDevBuffer (!)");
157
158
0
            if (nBits == a->buf->GetBitCount())
159
0
            {
160
                // candidate is valid due to bit depth
161
0
                if (aFound != maFreeBuffers.end())
162
0
                {
163
                    // already found
164
0
                    if (bOkay)
165
0
                    {
166
                        // found is valid
167
0
                        const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel);
168
169
0
                        if (bCandidateOkay)
170
0
                        {
171
                            // found and candidate are valid
172
0
                            const tools::Long aSquare(aFound->buf->GetOutputWidthPixel()
173
0
                                                      * aFound->buf->GetOutputHeightPixel());
174
0
                            const tools::Long aCandidateSquare(a->buf->GetOutputWidthPixel()
175
0
                                                               * a->buf->GetOutputHeightPixel());
176
177
0
                            if (aCandidateSquare < aSquare)
178
0
                            {
179
                                // candidate is valid and smaller, use it
180
0
                                aFound = a;
181
0
                            }
182
0
                        }
183
0
                        else
184
0
                        {
185
                            // found is valid, candidate is not. Keep found
186
0
                        }
187
0
                    }
188
0
                    else
189
0
                    {
190
                        // found is invalid, use candidate
191
0
                        aFound = a;
192
0
                        bOkay = isSizeSuitable(aFound->buf, rSizePixel);
193
0
                    }
194
0
                }
195
0
                else
196
0
                {
197
                    // none yet, use candidate
198
0
                    aFound = a;
199
0
                    bOkay = isSizeSuitable(aFound->buf, rSizePixel);
200
0
                }
201
0
            }
202
0
        }
203
204
0
        if (aFound != maFreeBuffers.end())
205
0
        {
206
0
            pRetval = aFound->buf;
207
0
            maFreeBuffers.erase(aFound);
208
0
        }
209
0
    }
210
211
0
    if (pRetval)
212
0
    {
213
        // found a suitable cached virtual device, but the
214
        // outputdevice it was based on has been disposed,
215
        // drop it and create a new one instead as reusing
216
        // such devices is unsafe under at least Gtk2
217
0
        if (maDeviceTemplates[pRetval]->isDisposed())
218
0
        {
219
0
            maDeviceTemplates.erase(pRetval);
220
0
            pRetval.disposeAndClear();
221
0
        }
222
0
        else
223
0
        {
224
0
            if (bOkay)
225
0
            {
226
0
                pRetval->Erase(pRetval->PixelToLogic(
227
0
                    tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())));
228
0
            }
229
0
            else
230
0
            {
231
0
                pRetval->SetOutputSizePixel(rSizePixel, true);
232
0
            }
233
0
        }
234
0
    }
235
236
    // no success yet, create new buffer
237
0
    if (!pRetval)
238
0
    {
239
0
        pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::WITHOUT_ALPHA);
240
0
        maDeviceTemplates[pRetval] = &rOutDev;
241
0
        pRetval->SetOutputSizePixel(rSizePixel, true);
242
0
    }
243
0
    else
244
0
    {
245
        // reused, reset some values
246
0
        pRetval->SetMapMode();
247
0
        pRetval->SetRasterOp(RasterOp::OverPaint);
248
0
    }
249
250
    // remember allocated buffer
251
0
    maUsedBuffers.emplace_back(pRetval);
252
253
0
    return pRetval;
254
0
}
255
256
void VDevBuffer::free(VirtualDevice& rDevice)
257
0
{
258
0
    std::unique_lock aGuard(m_aMutex);
259
0
    const auto aUsedFound
260
0
        = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(),
261
0
                       [&rDevice](const Entry& el) { return el.buf == &rDevice; });
262
0
    SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer",
263
0
                "OOps, non-registered buffer freed (!)");
264
0
    if (aUsedFound != maUsedBuffers.end())
265
0
    {
266
0
        maFreeBuffers.emplace_back(*aUsedFound);
267
0
        maUsedBuffers.erase(aUsedFound);
268
0
        SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer",
269
0
                    "excessive cached buffers, " << maFreeBuffers.size() << " entries!");
270
0
    }
271
0
    Start();
272
0
}
273
274
void VDevBuffer::Invoke()
275
0
{
276
0
    std::unique_lock aGuard(m_aMutex);
277
278
0
    while (!maFreeBuffers.empty())
279
0
    {
280
0
        auto aLastOne = maFreeBuffers.back();
281
0
        maDeviceTemplates.erase(aLastOne.buf);
282
0
        aLastOne.buf.disposeAndClear();
283
0
        maFreeBuffers.pop_back();
284
0
    }
285
0
}
286
287
#ifdef SPEED_COMPARE
288
void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel,
289
                    OutputDevice& rOutDev)
290
{
291
    const int nAvInd(500);
292
    static double fFactors[nAvInd];
293
    static int nIndex(nAvInd + 1);
294
    static int nRepeat(5);
295
    static int nWorseTotal(0);
296
    static int nBetterTotal(0);
297
    int a(0);
298
299
    const Size aSizePixel(rDestPixel.GetSize());
300
301
    // init statics
302
    if (nIndex > nAvInd)
303
    {
304
        for (a = 0; a < nAvInd; a++)
305
            fFactors[a] = 1.0;
306
        nIndex = 0;
307
    }
308
309
    // get start time
310
    const sal_uInt64 nTimeA(tools::Time::GetSystemTicks());
311
312
    // loop nRepeat times to get somewhat better timings, else
313
    // numbers are pretty small
314
    for (a = 0; a < nRepeat; a++)
315
    {
316
        // "Former" method using a temporary AlphaMask & DrawBitmap
317
        sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
318
        const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
319
        rOutDev.DrawBitmap(rDestPixel.TopLeft(), Bitmap(rContent, aAlphaMask));
320
    }
321
322
    // get intermediate time
323
    const sal_uInt64 nTimeB(tools::Time::GetSystemTicks());
324
325
    // loop nRepeat times
326
    for (a = 0; a < nRepeat; a++)
327
    {
328
        // New method using DrawTransformedBitmapEx & fTrans directly
329
        rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix(
330
                                            aSizePixel.Width(), aSizePixel.Height(),
331
                                            rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()),
332
                                        rContent, 1 - fTrans);
333
    }
334
335
    // get end time
336
    const sal_uInt64 nTimeC(tools::Time::GetSystemTicks());
337
338
    // calculate deltas
339
    const sal_uInt64 nTimeFormer(nTimeB - nTimeA);
340
    const sal_uInt64 nTimeNew(nTimeC - nTimeB);
341
342
    // compare & note down
343
    if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew)
344
    {
345
        if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500)
346
        {
347
            nRepeat += 1;
348
            SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat);
349
            return;
350
        }
351
352
        const double fNewFactor((double)nTimeFormer / nTimeNew);
353
        fFactors[nIndex % nAvInd] = fNewFactor;
354
        nIndex++;
355
        double fAverage(0.0);
356
        {
357
            for (a = 0; a < nAvInd; a++)
358
                fAverage += fFactors[a];
359
            fAverage /= nAvInd;
360
        }
361
        if (fNewFactor < 1.0)
362
            nWorseTotal++;
363
        else
364
            nBetterTotal++;
365
366
        char buf[300];
367
        sprintf(buf,
368
                "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, "
369
                "WorseTotal: %d, BetterTotal: %d)",
370
                nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER",
371
                fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal,
372
                nBetterTotal);
373
        SAL_INFO("drawinglayer.processor2d", buf);
374
    }
375
}
376
#endif
377
}
378
379
// support for rendering Bitmap contents
380
namespace drawinglayer
381
{
382
// static global VDev buffer for VclProcessor2D/VclPixelProcessor2D
383
VDevBuffer& getVDevBuffer()
384
0
{
385
    // secure global instance with Vcl's safe destroyer of external (seen by
386
    // library base) stuff, the remembered VDevs need to be deleted before
387
    // Vcl's deinit
388
0
    static tools::DeleteOnDeinit<VDevBuffer> aVDevBuffer{};
389
0
    return *aVDevBuffer.get();
390
0
}
391
392
impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const tools::Rectangle& rDestPixel)
393
0
    : mrOutDev(rOutDev)
394
0
    , mpContent(nullptr)
395
0
    , mpAlpha(nullptr)
396
0
    , maDestPixel(rDestPixel)
397
0
{
398
0
    maDestPixel.Intersection(tools::Rectangle{ Point{}, mrOutDev.GetOutputSizePixel() });
399
400
0
    if (!isVisible())
401
0
        return;
402
403
0
    mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize());
404
405
    // #i93485# assert when copying from window to VDev is used
406
0
    SAL_WARN_IF(
407
0
        mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer",
408
0
        "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)");
409
410
    // initialize buffer by blitting content of source to prepare for
411
    // transparence/ copying back
412
0
    const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled());
413
0
    mrOutDev.EnableMapMode(false);
414
0
    mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(),
415
0
                          maDestPixel.GetSize(), mrOutDev);
416
0
    mrOutDev.EnableMapMode(bWasEnabledSrc);
417
418
0
    MapMode aNewMapMode(mrOutDev.GetMapMode());
419
420
0
    const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft()));
421
0
    aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y()));
422
423
0
    mpContent->SetMapMode(aNewMapMode);
424
425
    // copy AA flag for new target
426
0
    mpContent->SetAntialiasing(mrOutDev.GetAntialiasing());
427
428
    // copy RasterOp (e.g. may be RasterOp::Xor on destination)
429
0
    mpContent->SetRasterOp(mrOutDev.GetRasterOp());
430
0
}
431
432
impBufferDevice::~impBufferDevice()
433
0
{
434
0
    if (mpContent)
435
0
    {
436
0
        getVDevBuffer().free(*mpContent);
437
0
    }
438
439
0
    if (mpAlpha)
440
0
    {
441
0
        getVDevBuffer().free(*mpAlpha);
442
0
    }
443
0
}
444
445
void impBufferDevice::paint(double fTrans)
446
0
{
447
0
    if (!isVisible())
448
0
        return;
449
450
0
    const Point aEmptyPoint;
451
0
    const Size aSizePixel(maDestPixel.GetSize());
452
0
    const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled());
453
454
0
    mrOutDev.EnableMapMode(false);
455
0
    mpContent->EnableMapMode(false);
456
457
#ifdef DBG_UTIL
458
    // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
459
    static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
460
    static const OUString sDumpPath(o3tl::getEnvironment(u"VCL_DUMP_BMP_PATH"_ustr));
461
462
    if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
463
    {
464
        SvFileStream aNew(sDumpPath + "content.bmp", StreamMode::WRITE | StreamMode::TRUNC);
465
        Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
466
        WriteDIB(aContent, aNew, false, true);
467
    }
468
#endif
469
470
    // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
471
0
    const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp());
472
0
    mrOutDev.SetRasterOp(RasterOp::OverPaint);
473
474
0
    if (mpAlpha)
475
0
    {
476
0
        mpAlpha->EnableMapMode(false);
477
0
        AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel));
478
0
        aAlphaMask.Invert(); // convert transparency to alpha
479
480
#ifdef DBG_UTIL
481
        if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
482
        {
483
            SvFileStream aNew(sDumpPath + "transparence.bmp",
484
                              StreamMode::WRITE | StreamMode::TRUNC);
485
            WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
486
        }
487
#endif
488
489
0
        Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
490
0
        mrOutDev.DrawBitmap(maDestPixel.TopLeft(), Bitmap(aContent, aAlphaMask));
491
0
    }
492
0
    else if (0.0 != fTrans)
493
0
    {
494
0
        const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
495
496
#ifdef SPEED_COMPARE
497
        static bool bCompareFormerAndNewTimings(true);
498
499
        if (bCompareFormerAndNewTimings)
500
        {
501
            doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev);
502
        }
503
        else
504
#endif
505
        // Note: this extra scope is needed due to 'clang plugin indentation'. It complains
506
        //       that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'.
507
        //       That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this.
508
0
        {
509
            // For the case we have a unified transparency value there is a former
510
            // and new method to paint that which can be used. To decide on measurements,
511
            // I added 'doSpeedCompare' above which can be activated by defining
512
            // SPEED_COMPARE at the top of this file.
513
            // I added the used Testdoc: blurplay3.odg as
514
            //     https://bugs.documentfoundation.org/attachment.cgi?id=182463
515
            // I did measure on
516
            //
517
            // Linux Dbg:
518
            // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934)
519
            //
520
            // Linux Pro:
521
            // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337)
522
            //
523
            // Win Dbg:
524
            // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428)
525
            //
526
            // Win Pro:
527
            // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909)
528
            //
529
            // Note: I am aware that the Dbg are of limited usefulness, but include them here
530
            // for reference.
531
            //
532
            // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New
533
            // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the
534
            // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will
535
            // use Former for now.
536
            //
537
            // To easily allow to change this (maybe system-dependent) I add a static switch here,
538
            // also for eventually experimenting (hint: can be changed in the debugger).
539
0
            static bool bUseNew(false);
540
541
0
            if (bUseNew)
542
0
            {
543
                // New method using DrawTransformedBitmapEx & fTrans directly
544
0
                mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix(
545
0
                                                     aSizePixel.Width(), aSizePixel.Height(),
546
0
                                                     maDestPixel.TopLeft().X(),
547
0
                                                     maDestPixel.TopLeft().Y()),
548
0
                                                 aContent, 1 - fTrans);
549
0
            }
550
0
            else
551
0
            {
552
                // "Former" method using a temporary AlphaMask & DrawBitmap
553
0
                sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
554
0
                const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
555
0
                mrOutDev.DrawBitmap(maDestPixel.TopLeft(), Bitmap(aContent, aAlphaMask));
556
0
            }
557
0
        }
558
0
    }
559
0
    else
560
0
    {
561
0
        mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent);
562
0
    }
563
564
0
    mrOutDev.SetRasterOp(aOrigRasterOp);
565
0
    mrOutDev.EnableMapMode(bWasEnabledDst);
566
0
}
567
568
VirtualDevice& impBufferDevice::getContent()
569
0
{
570
0
    SAL_WARN_IF(!mpContent, "drawinglayer",
571
0
                "impBufferDevice: No content, check isVisible() before accessing (!)");
572
0
    return *mpContent;
573
0
}
574
575
VirtualDevice& impBufferDevice::getTransparence()
576
0
{
577
0
    SAL_WARN_IF(!mpContent, "drawinglayer",
578
0
                "impBufferDevice: No content, check isVisible() before accessing (!)");
579
0
    if (!mpAlpha)
580
0
    {
581
0
        mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize());
582
0
        mpAlpha->SetMapMode(mpContent->GetMapMode());
583
584
        // copy AA flag for new target; masking needs to be smooth
585
0
        mpAlpha->SetAntialiasing(mpContent->GetAntialiasing());
586
0
    }
587
588
0
    return *mpAlpha;
589
0
}
590
} // end of namespace drawinglayer
591
592
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */