Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/vcl/source/graphic/GraphicObject2.cxx
Line
Count
Source (jump to first uncovered line)
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
22
#include <tools/gen.hxx>
23
#include <vcl/outdev.hxx>
24
#include <vcl/alpha.hxx>
25
#include <vcl/virdev.hxx>
26
#include <vcl/GraphicObject.hxx>
27
#include <memory>
28
29
struct ImplTileInfo
30
{
31
0
    ImplTileInfo() : nTilesEmptyX(0), nTilesEmptyY(0) {}
32
33
    Point aTileTopLeft;     // top, left position of the rendered tile
34
    Point aNextTileTopLeft; // top, left position for next recursion
35
                            // level's tile
36
    Size  aTileSizePixel;   // size of the generated tile (might
37
                            // differ from
38
                            // aNextTileTopLeft-aTileTopLeft, because
39
                            // this is nExponent*prevTileSize. The
40
                            // generated tile is always nExponent
41
                            // times the previous tile, such that it
42
                            // can be used in the next stage. The
43
                            // required area coverage is often
44
                            // less. The extraneous area covered is
45
                            // later overwritten by the next stage)
46
    int   nTilesEmptyX;     // number of original tiles empty right of
47
                            // this tile. This counts from
48
                            // aNextTileTopLeft, i.e. the additional
49
                            // area covered by aTileSizePixel is not
50
                            // considered here. This is for
51
                            // unification purposes, as the iterative
52
                            // calculation of the next level's empty
53
                            // tiles has to be based on this value.
54
    int   nTilesEmptyY;     // as above, for Y
55
};
56
57
58
bool GraphicObject::ImplRenderTempTile( VirtualDevice& rVDev,
59
                                        int nNumTilesX, int nNumTilesY,
60
                                        const Size& rTileSizePixel,
61
                                        const GraphicAttr* pAttr )
62
0
{
63
    // how many tiles to generate per recursion step
64
0
    const int nExponent = 2;
65
66
    // determine MSB factor
67
0
    int nMSBFactor( 1 );
68
0
    while( nNumTilesX / nMSBFactor != 0 ||
69
0
           nNumTilesY / nMSBFactor != 0 )
70
0
    {
71
0
        nMSBFactor *= nExponent;
72
0
    }
73
74
    // one less
75
0
    if(nMSBFactor > 1)
76
0
    {
77
0
        nMSBFactor /= nExponent;
78
0
    }
79
0
    ImplTileInfo aTileInfo;
80
81
    // #105229# Switch off mapping (converting to logic and back to
82
    // pixel might cause roundoff errors)
83
0
    bool bOldMap( rVDev.IsMapModeEnabled() );
84
0
    rVDev.EnableMapMode( false );
85
86
0
    bool bRet( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor, nNumTilesX, nNumTilesY,
87
0
                                        nNumTilesX, nNumTilesY, rTileSizePixel, pAttr, aTileInfo ) );
88
89
0
    rVDev.EnableMapMode( bOldMap );
90
91
0
    return bRet;
92
0
}
93
94
// define for debug drawings
95
//#define DBG_TEST
96
97
// see header comment. this works similar to base conversion of a
98
// number, i.e. if the exponent is 10, then the number for every tile
99
// size is given by the decimal place of the corresponding decimal
100
// representation.
101
bool GraphicObject::ImplRenderTileRecursive( VirtualDevice& rVDev, int nExponent, int nMSBFactor,
102
                                             int nNumOrigTilesX, int nNumOrigTilesY,
103
                                             int nRemainderTilesX, int nRemainderTilesY,
104
                                             const Size& rTileSizePixel, const GraphicAttr* pAttr,
105
                                             ImplTileInfo& rTileInfo )
106
0
{
107
    // gets loaded with our tile bitmap
108
0
    std::unique_ptr<GraphicObject> xTmpGraphic;
109
0
    GraphicObject* pTileGraphic;
110
111
    // stores a flag that renders the zero'th tile position
112
    // (i.e. (0,0)+rCurrPos) only if we're at the bottom of the
113
    // recursion stack. All other position already have that tile
114
    // rendered, because the lower levels painted their generated tile
115
    // there.
116
0
    bool bNoFirstTileDraw( false );
117
118
    // what's left when we're done with our tile size
119
0
    const int nNewRemainderX( nRemainderTilesX % nMSBFactor );
120
0
    const int nNewRemainderY( nRemainderTilesY % nMSBFactor );
121
122
    // gets filled out from the recursive call with info of what's
123
    // been generated
124
0
    ImplTileInfo aTileInfo;
125
126
    // check for recursion's end condition: LSB place reached?
127
0
    if( nMSBFactor == 1 )
128
0
    {
129
0
        pTileGraphic = this;
130
131
        // set initial tile size -> orig size
132
0
        aTileInfo.aTileSizePixel = rTileSizePixel;
133
0
        aTileInfo.nTilesEmptyX = nNumOrigTilesX;
134
0
        aTileInfo.nTilesEmptyY = nNumOrigTilesY;
135
0
    }
136
0
    else if( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor/nExponent,
137
0
                                      nNumOrigTilesX, nNumOrigTilesY,
138
0
                                      nNewRemainderX, nNewRemainderY,
139
0
                                      rTileSizePixel, pAttr, aTileInfo ) )
140
0
    {
141
        // extract generated tile -> see comment on the first loop below
142
0
        BitmapEx aTileBitmap( rVDev.GetBitmap( aTileInfo.aTileTopLeft, aTileInfo.aTileSizePixel ) );
143
144
0
        xTmpGraphic.reset(new GraphicObject(std::move(aTileBitmap)));
145
0
        pTileGraphic = xTmpGraphic.get();
146
147
        // fill stripes left over from upstream levels:
148
149
        //  x0000
150
        //  0
151
        //  0
152
        //  0
153
        //  0
154
155
        // where x denotes the place filled by our recursive predecessors
156
157
        // check whether we have to fill stripes here. Although not
158
        // obvious, there is one case where we can skip this step: if
159
        // the previous recursion level (the one who filled our
160
        // aTileInfo) had zero area to fill, then there are no white
161
        // stripes left, naturally. This happens if the digit
162
        // associated to that level has a zero, and can be checked via
163
        // aTileTopLeft==aNextTileTopLeft.
164
0
        if( aTileInfo.aTileTopLeft != aTileInfo.aNextTileTopLeft )
165
0
        {
166
            // now fill one row from aTileInfo.aNextTileTopLeft.X() all
167
            // the way to the right
168
            // current output position while drawing
169
0
            Point aCurrPos(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y());
170
0
            for (int nX=0; nX < aTileInfo.nTilesEmptyX; nX += nMSBFactor)
171
0
            {
172
0
                if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
173
0
                    return false;
174
175
0
                aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() );
176
0
            }
177
178
#ifdef DBG_TEST
179
//          rVDev.SetFillCOL_WHITE );
180
            rVDev.SetFillColor();
181
            rVDev.SetLineColor( Color( 255 * nExponent / nMSBFactor, 255 - 255 * nExponent / nMSBFactor, 128 - 255 * nExponent / nMSBFactor ) );
182
            rVDev.DrawEllipse( tools::Rectangle(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y(),
183
                                         aTileInfo.aNextTileTopLeft.X() - 1 + (aTileInfo.nTilesEmptyX/nMSBFactor)*aTileInfo.aTileSizePixel.Width(),
184
                                         aTileInfo.aTileTopLeft.Y() + aTileInfo.aTileSizePixel.Height() - 1) );
185
#endif
186
187
            // now fill one column from aTileInfo.aNextTileTopLeft.Y() all
188
            // the way to the bottom
189
0
            aCurrPos.setX( aTileInfo.aTileTopLeft.X() );
190
0
            aCurrPos.setY( aTileInfo.aNextTileTopLeft.Y() );
191
0
            for (int nY=0; nY < aTileInfo.nTilesEmptyY; nY += nMSBFactor)
192
0
            {
193
0
                if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
194
0
                    return false;
195
196
0
                aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() );
197
0
            }
198
199
#ifdef DBG_TEST
200
            rVDev.DrawEllipse( tools::Rectangle(aTileInfo.aTileTopLeft.X(), aTileInfo.aNextTileTopLeft.Y(),
201
                                         aTileInfo.aTileTopLeft.X() + aTileInfo.aTileSizePixel.Width() - 1,
202
                                         aTileInfo.aNextTileTopLeft.Y() - 1 + (aTileInfo.nTilesEmptyY/nMSBFactor)*aTileInfo.aTileSizePixel.Height()) );
203
#endif
204
0
        }
205
0
        else
206
0
        {
207
            // Thought that aTileInfo.aNextTileTopLeft tile has always
208
            // been drawn already, but that's wrong: typically,
209
            // _parts_ of that tile have been drawn, since the
210
            // previous level generated the tile there. But when
211
            // aTileInfo.aNextTileTopLeft!=aTileInfo.aTileTopLeft, the
212
            // difference between these two values is missing in the
213
            // lower right corner of this first tile. So, can do that
214
            // only here.
215
0
            bNoFirstTileDraw = true;
216
0
        }
217
0
    }
218
0
    else
219
0
    {
220
0
        return false;
221
0
    }
222
223
    // calc number of original tiles in our drawing area without
224
    // remainder
225
0
    nRemainderTilesX -= nNewRemainderX;
226
0
    nRemainderTilesY -= nNewRemainderY;
227
228
    // fill tile info for calling method
229
0
    rTileInfo.aTileTopLeft     = aTileInfo.aNextTileTopLeft;
230
0
    rTileInfo.aNextTileTopLeft = Point( rTileInfo.aTileTopLeft.X() + rTileSizePixel.Width()*nRemainderTilesX,
231
0
                                        rTileInfo.aTileTopLeft.Y() + rTileSizePixel.Height()*nRemainderTilesY );
232
0
    rTileInfo.aTileSizePixel   = Size( rTileSizePixel.Width()*nMSBFactor*nExponent,
233
0
                                       rTileSizePixel.Height()*nMSBFactor*nExponent );
234
0
    rTileInfo.nTilesEmptyX     = aTileInfo.nTilesEmptyX - nRemainderTilesX;
235
0
    rTileInfo.nTilesEmptyY     = aTileInfo.nTilesEmptyY - nRemainderTilesY;
236
237
    // init output position
238
0
    Point aCurrPos = aTileInfo.aNextTileTopLeft;
239
240
    // fill our drawing area. Fill possibly more, to create the next
241
    // bigger tile size -> see bitmap extraction above. This does no
242
    // harm, since everything right or below our actual area is
243
    // overdrawn by our caller. Just in case we're in the last level,
244
    // we don't draw beyond the right or bottom border.
245
0
    for (int nY=0; nY < aTileInfo.nTilesEmptyY && nY < nExponent*nMSBFactor; nY += nMSBFactor)
246
0
    {
247
0
        aCurrPos.setX( aTileInfo.aNextTileTopLeft.X() );
248
249
0
        for (int nX=0; nX < aTileInfo.nTilesEmptyX && nX < nExponent*nMSBFactor; nX += nMSBFactor)
250
0
        {
251
0
            if( bNoFirstTileDraw )
252
0
                bNoFirstTileDraw = false; // don't draw first tile position
253
0
            else if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
254
0
                return false;
255
256
0
            aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() );
257
0
        }
258
259
0
        aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() );
260
0
    }
261
262
#ifdef DBG_TEST
263
//  rVDev.SetFillCOL_WHITE );
264
    rVDev.SetFillColor();
265
    rVDev.SetLineColor( Color( 255 * nExponent / nMSBFactor, 255 - 255 * nExponent / nMSBFactor, 128 - 255 * nExponent / nMSBFactor ) );
266
    rVDev.DrawRect( tools::Rectangle((rTileInfo.aTileTopLeft.X())*rTileSizePixel.Width(),
267
                              (rTileInfo.aTileTopLeft.Y())*rTileSizePixel.Height(),
268
                              (rTileInfo.aNextTileTopLeft.X())*rTileSizePixel.Width()-1,
269
                              (rTileInfo.aNextTileTopLeft.Y())*rTileSizePixel.Height()-1) );
270
#endif
271
272
0
    return true;
273
0
}
274
275
bool GraphicObject::ImplDrawTiled(OutputDevice& rOut, const tools::Rectangle& rArea, const Size& rSizePixel,
276
                                  const Size& rOffset, const GraphicAttr* pAttr, int nTileCacheSize1D)
277
0
{
278
0
    const MapMode   aOutMapMode(rOut.GetMapMode());
279
0
    const MapMode   aMapMode( aOutMapMode.GetMapUnit(), Point(), aOutMapMode.GetScaleX(), aOutMapMode.GetScaleY() );
280
0
    bool            bRet( false );
281
282
    // #i42643# Casting to Int64, to avoid integer overflow for
283
    // huge-DPI output devices
284
0
    if( GetGraphic().GetType() == GraphicType::Bitmap &&
285
0
        static_cast<sal_Int64>(rSizePixel.Width()) * rSizePixel.Height() <
286
0
        static_cast<sal_Int64>(nTileCacheSize1D)*nTileCacheSize1D )
287
0
    {
288
        // First combine very small bitmaps into a larger tile
289
290
291
0
        ScopedVclPtrInstance< VirtualDevice > aVDev;
292
0
        const int       nNumTilesInCacheX( (nTileCacheSize1D + rSizePixel.Width()-1) / rSizePixel.Width() );
293
0
        const int       nNumTilesInCacheY( (nTileCacheSize1D + rSizePixel.Height()-1) / rSizePixel.Height() );
294
295
0
        aVDev->SetOutputSizePixel( Size( nNumTilesInCacheX*rSizePixel.Width(),
296
0
                                        nNumTilesInCacheY*rSizePixel.Height() ) );
297
0
        aVDev->SetMapMode( aMapMode );
298
299
        // draw bitmap content
300
0
        if( ImplRenderTempTile( *aVDev, nNumTilesInCacheX,
301
0
                                nNumTilesInCacheY, rSizePixel, pAttr ) )
302
0
        {
303
0
            BitmapEx aTileBitmap( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) );
304
305
            // draw alpha content, if any
306
0
            if( IsTransparent() )
307
0
            {
308
0
                GraphicObject aAlphaGraphic;
309
310
0
                if( GetGraphic().IsAlpha() )
311
0
                    aAlphaGraphic.SetGraphic(BitmapEx(GetGraphic().GetBitmapEx().GetAlphaMask().GetBitmap()));
312
0
                else
313
0
                    aAlphaGraphic.SetGraphic(BitmapEx(Bitmap()));
314
315
0
                if( aAlphaGraphic.ImplRenderTempTile( *aVDev, nNumTilesInCacheX,
316
0
                                                      nNumTilesInCacheY, rSizePixel, pAttr ) )
317
0
                {
318
                    // Combine bitmap and alpha/mask
319
0
                    if( GetGraphic().IsAlpha() )
320
0
                        aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(),
321
0
                                                AlphaMask( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) ) );
322
0
                    else
323
0
                        aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(),
324
0
                                                aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ).CreateAlphaMask( COL_WHITE ) );
325
0
                }
326
0
            }
327
328
            // paint generated tile
329
0
            GraphicObject aTmpGraphic( aTileBitmap );
330
0
            bRet = aTmpGraphic.ImplDrawTiled(rOut, rArea,
331
0
                                             aTileBitmap.GetSizePixel(),
332
0
                                             rOffset, pAttr, nTileCacheSize1D);
333
0
        }
334
0
    }
335
0
    else
336
0
    {
337
0
        const Size      aOutOffset( rOut.LogicToPixel( rOffset, aOutMapMode ) );
338
0
        const tools::Rectangle aOutArea( rOut.LogicToPixel( rArea, aOutMapMode ) );
339
340
        // number of invisible (because out-of-area) tiles
341
0
        int nInvisibleTilesX;
342
0
        int nInvisibleTilesY;
343
344
        // round towards -infty for negative offset
345
0
        if( aOutOffset.Width() < 0 )
346
0
            nInvisibleTilesX = (aOutOffset.Width() - rSizePixel.Width() + 1) / rSizePixel.Width();
347
0
        else
348
0
            nInvisibleTilesX = aOutOffset.Width() / rSizePixel.Width();
349
350
        // round towards -infty for negative offset
351
0
        if( aOutOffset.Height() < 0 )
352
0
            nInvisibleTilesY = (aOutOffset.Height() - rSizePixel.Height() + 1) / rSizePixel.Height();
353
0
        else
354
0
            nInvisibleTilesY = aOutOffset.Height() / rSizePixel.Height();
355
356
        // origin from where to 'virtually' start drawing in pixel
357
0
        const Point aOutOrigin( rOut.LogicToPixel( Point( rArea.Left() - rOffset.Width(),
358
0
                                                           rArea.Top() - rOffset.Height() ) ) );
359
        // position in pixel from where to really start output
360
0
        const Point aOutStart( aOutOrigin.X() + nInvisibleTilesX*rSizePixel.Width(),
361
0
                               aOutOrigin.Y() + nInvisibleTilesY*rSizePixel.Height() );
362
363
0
        rOut.Push( vcl::PushFlags::CLIPREGION );
364
0
        rOut.IntersectClipRegion( rArea );
365
366
        // Paint all tiles
367
0
        auto nOutAreaWidth = aOutArea.GetWidth();
368
0
        auto nOutAreaHeight = aOutArea.GetHeight();
369
0
        assert(nOutAreaWidth >= 0 && nOutAreaHeight >= 0 && "coverity 2023.12.2");
370
0
        bRet = ImplDrawTiled(rOut, aOutStart,
371
0
                             (nOutAreaWidth + aOutArea.Left() - aOutStart.X() + rSizePixel.Width() - 1) / rSizePixel.Width(),
372
0
                             (nOutAreaHeight + aOutArea.Top() - aOutStart.Y() + rSizePixel.Height() - 1) / rSizePixel.Height(),
373
0
                             rSizePixel, pAttr);
374
375
0
        rOut.Pop();
376
0
    }
377
378
0
    return bRet;
379
0
}
380
381
bool GraphicObject::ImplDrawTiled( OutputDevice& rOut, const Point& rPosPixel,
382
                                   int nNumTilesX, int nNumTilesY,
383
                                   const Size& rTileSizePixel, const GraphicAttr* pAttr ) const
384
0
{
385
0
    Point   aCurrPos( rPosPixel );
386
0
    Size    aTileSizeLogic( rOut.PixelToLogic( rTileSizePixel ) );
387
0
    int     nX, nY;
388
389
    // #107607# Use logical coordinates for metafile playing, too
390
0
    bool    bDrawInPixel( rOut.GetConnectMetaFile() == nullptr && GraphicType::Bitmap == GetType() );
391
0
    bool    bRet = false;
392
393
    // #105229# Switch off mapping (converting to logic and back to
394
    // pixel might cause roundoff errors)
395
0
    bool bOldMap( rOut.IsMapModeEnabled() );
396
397
0
    if( bDrawInPixel )
398
0
        rOut.EnableMapMode( false );
399
400
0
    for( nY=0; nY < nNumTilesY; ++nY )
401
0
    {
402
0
        aCurrPos.setX( rPosPixel.X() );
403
404
0
        for( nX=0; nX < nNumTilesX; ++nX )
405
0
        {
406
            // #105229# work with pixel coordinates here, mapping is disabled!
407
            // #104004# don't disable mapping for metafile recordings
408
            // #108412# don't quit the loop if one draw fails
409
410
            // update return value. This method should return true, if
411
            // at least one of the looped Draws succeeded.
412
0
            bRet |= Draw(rOut,
413
0
                         bDrawInPixel ? aCurrPos : rOut.PixelToLogic(aCurrPos),
414
0
                         bDrawInPixel ? rTileSizePixel : aTileSizeLogic,
415
0
                         pAttr);
416
417
0
            aCurrPos.AdjustX(rTileSizePixel.Width() );
418
0
        }
419
420
0
        aCurrPos.AdjustY(rTileSizePixel.Height() );
421
0
    }
422
423
0
    if( bDrawInPixel )
424
0
        rOut.EnableMapMode( bOldMap );
425
426
0
    return bRet;
427
0
}
428
429
void GraphicObject::ImplTransformBitmap( BitmapEx&          rBmpEx,
430
                                         const GraphicAttr& rAttr,
431
                                         const Size&        rCropLeftTop,
432
                                         const Size&        rCropRightBottom,
433
                                         const tools::Rectangle&   rCropRect,
434
                                         const Size&        rDstSize,
435
                                         bool               bEnlarge ) const
436
0
{
437
    // #107947# Extracted from svdograf.cxx
438
439
    // #104115# Crop the bitmap
440
0
    if( rAttr.IsCropped() )
441
0
    {
442
0
        rBmpEx.Crop( rCropRect );
443
444
        // #104115# Negative crop sizes mean: enlarge bitmap and pad
445
0
        if( bEnlarge && (
446
0
            rCropLeftTop.Width() < 0 ||
447
0
            rCropLeftTop.Height() < 0 ||
448
0
            rCropRightBottom.Width() < 0 ||
449
0
            rCropRightBottom.Height() < 0 ) )
450
0
        {
451
0
            Size aBmpSize( rBmpEx.GetSizePixel() );
452
0
            sal_Int32 nPadLeft( rCropLeftTop.Width() < 0 ? -rCropLeftTop.Width() : 0 );
453
0
            sal_Int32 nPadTop( rCropLeftTop.Height() < 0 ? -rCropLeftTop.Height() : 0 );
454
0
            sal_Int32 nPadTotalWidth( aBmpSize.Width() + nPadLeft + (rCropRightBottom.Width() < 0 ? -rCropRightBottom.Width() : 0) );
455
0
            sal_Int32 nPadTotalHeight( aBmpSize.Height() + nPadTop + (rCropRightBottom.Height() < 0 ? -rCropRightBottom.Height() : 0) );
456
457
0
            BitmapEx aBmpEx2;
458
459
0
            if( rBmpEx.IsAlpha() )
460
0
            {
461
0
                aBmpEx2 = rBmpEx;
462
0
            }
463
0
            else
464
0
            {
465
                // #104115# Generate mask bitmap and init to zero
466
0
                AlphaMask aMask(aBmpSize);
467
0
                aMask.Erase(255);
468
469
                // #104115# Always generate transparent bitmap, we need the border transparent
470
0
                aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), aMask );
471
472
                // #104115# Add opaque mask to source bitmap, otherwise the destination remains transparent
473
0
                rBmpEx = aBmpEx2;
474
0
            }
475
476
0
            aBmpEx2.Scale(Size(nPadTotalWidth, nPadTotalHeight));
477
0
            aBmpEx2.Erase( Color(ColorAlpha,0,0,0,0) );
478
0
            aBmpEx2.CopyPixel( tools::Rectangle( Point(nPadLeft, nPadTop), aBmpSize ), tools::Rectangle( Point(0, 0), aBmpSize ), rBmpEx );
479
0
            rBmpEx = aBmpEx2;
480
0
        }
481
0
    }
482
483
0
    const Size  aSizePixel( rBmpEx.GetSizePixel() );
484
485
0
    if( rAttr.GetRotation() == 0_deg10 || IsAnimated() )
486
0
        return;
487
488
0
    if( !(aSizePixel.Width() && aSizePixel.Height() && rDstSize.Width() && rDstSize.Height()) )
489
0
        return;
490
491
0
    double fSrcWH = static_cast<double>(aSizePixel.Width()) / aSizePixel.Height();
492
0
    double fDstWH = static_cast<double>(rDstSize.Width()) / rDstSize.Height();
493
0
    double fScaleX = 1.0, fScaleY = 1.0;
494
495
    // always choose scaling to shrink bitmap
496
0
    if( fSrcWH < fDstWH )
497
0
        fScaleY = aSizePixel.Width() / ( fDstWH * aSizePixel.Height() );
498
0
    else
499
0
        fScaleX = fDstWH * aSizePixel.Height() / aSizePixel.Width();
500
501
0
    rBmpEx.Scale( fScaleX, fScaleY );
502
0
}
503
504
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */