Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/canvas/source/tools/canvascustomspritehelper.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
22
#include <com/sun/star/rendering/XPolyPolygon2D.hpp>
23
#include <com/sun/star/geometry/RealSize2D.hpp>
24
#include <com/sun/star/rendering/XBitmap.hpp>
25
#include <com/sun/star/geometry/IntegerSize2D.hpp>
26
#include <basegfx/matrix/b2dhommatrix.hxx>
27
#include <basegfx/point/b2dpoint.hxx>
28
#include <basegfx/polygon/b2dpolygontools.hxx>
29
#include <basegfx/polygon/b2dpolypolygontools.hxx>
30
#include <basegfx/utils/canvastools.hxx>
31
#include <basegfx/vector/b2dsize.hxx>
32
#include <rtl/math.hxx>
33
#include <comphelper/diagnose_ex.hxx>
34
35
#include <base/canvascustomspritehelper.hxx>
36
#include <canvas/canvastools.hxx>
37
38
using namespace ::com::sun::star;
39
40
41
namespace canvas
42
{
43
    bool CanvasCustomSpriteHelper::updateClipState( const Sprite::Reference& rSprite )
44
0
    {
45
0
        if( !mxClipPoly.is() )
46
0
        {
47
            // empty clip polygon -> everything is visible now
48
0
            maCurrClipBounds.reset();
49
0
            mbIsCurrClipRectangle = true;
50
0
        }
51
0
        else
52
0
        {
53
0
            const sal_Int32 nNumClipPolygons( mxClipPoly->getNumberOfPolygons() );
54
55
            // clip is not empty - determine actual update area
56
0
            ::basegfx::B2DPolyPolygon aClipPath(
57
0
                polyPolygonFromXPolyPolygon2D( mxClipPoly ) );
58
59
            // apply sprite transformation also to clip!
60
0
            aClipPath.transform( maTransform );
61
62
            // clip which is about to be set, expressed as a
63
            // b2drectangle
64
0
            const ::basegfx::B2DRectangle aClipBounds( aClipPath.getB2DRange() );
65
66
0
            const ::basegfx::B2DRectangle aBounds( 0.0, 0.0,
67
0
                                                   maSize.getX(),
68
0
                                                   maSize.getY() );
69
70
            // rectangular area which is actually covered by the sprite.
71
            // coordinates are relative to the sprite origin.
72
0
            ::basegfx::B2DRectangle aSpriteRectPixel = ::canvastools::calcTransformedRectBounds(
73
0
                                                        aBounds,
74
0
                                                        maTransform );
75
76
            // aClipBoundsA = new clip bound rect, intersected
77
            // with sprite area
78
0
            ::basegfx::B2DRectangle aClipBoundsA(aClipBounds);
79
0
            aClipBoundsA.intersect( aSpriteRectPixel );
80
81
0
            if( nNumClipPolygons != 1 )
82
0
            {
83
                // clip cannot be a single rectangle -> cannot
84
                // optimize update
85
0
                mbIsCurrClipRectangle = false;
86
0
                maCurrClipBounds = aClipBoundsA;
87
0
            }
88
0
            else
89
0
            {
90
                // new clip could be a single rectangle - check
91
                // that now:
92
0
                const bool bNewClipIsRect(
93
0
                    ::basegfx::utils::isRectangle( aClipPath.getB2DPolygon(0) ) );
94
95
                // both new and old clip are truly rectangles
96
                // - can now take the optimized path
97
0
                const bool bUseOptimizedUpdate( bNewClipIsRect &&
98
0
                                                mbIsCurrClipRectangle );
99
100
0
                const ::basegfx::B2DRectangle aOldBounds( maCurrClipBounds );
101
102
                // store new current clip type
103
0
                maCurrClipBounds = aClipBoundsA;
104
0
                mbIsCurrClipRectangle = bNewClipIsRect;
105
106
0
                if( mbActive &&
107
0
                    bUseOptimizedUpdate  )
108
0
                {
109
                    // aClipBoundsB = maCurrClipBounds, i.e. last
110
                    // clip, intersected with sprite area
111
0
                    std::vector< ::basegfx::B2DRectangle > aClipDifferences;
112
113
                    // get all rectangles covered by exactly one
114
                    // of the polygons (aka XOR)
115
0
                    ::basegfx::computeSetDifference(aClipDifferences,
116
0
                                                    aClipBoundsA,
117
0
                                                    aOldBounds);
118
119
                    // aClipDifferences now contains the final
120
                    // update areas, coordinates are still relative
121
                    // to the sprite origin. before submitting
122
                    // this area to 'updateSprite()' we need to
123
                    // translate this area to the final position,
124
                    // coordinates need to be relative to the
125
                    // spritecanvas.
126
0
                    for( const auto& rClipDiff : aClipDifferences )
127
0
                    {
128
0
                        mpSpriteCanvas->updateSprite(
129
0
                            rSprite,
130
0
                            maPosition,
131
0
                            ::basegfx::B2DRectangle(
132
0
                                maPosition + rClipDiff.getMinimum(),
133
0
                                maPosition + rClipDiff.getMaximum() ) );
134
0
                    }
135
136
                    // update calls all done
137
0
                    return true;
138
0
                }
139
0
            }
140
0
        }
141
142
        // caller needs to perform update calls
143
0
        return false;
144
0
    }
145
146
    CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() :
147
0
        mfPriority(0.0),
148
0
        mfAlpha(0.0),
149
0
        mbActive(false),
150
0
        mbIsCurrClipRectangle(true),
151
0
        mbIsContentFullyOpaque( false ),
152
0
        mbTransformDirty( true )
153
0
    {
154
0
    }
155
156
    void CanvasCustomSpriteHelper::init( const geometry::RealSize2D&        rSpriteSize,
157
                                         const SpriteSurface::Reference&    rOwningSpriteCanvas )
158
0
    {
159
0
        ENSURE_OR_THROW( rOwningSpriteCanvas,
160
0
                          "CanvasCustomSpriteHelper::init(): Invalid owning sprite canvas" );
161
162
0
        mpSpriteCanvas = rOwningSpriteCanvas;
163
0
        maSize.setX( std::max( 1.0,
164
0
                                 ceil( rSpriteSize.Width ) ) ); // round up to nearest int,
165
                                                                 // enforce sprite to have at
166
                                                                 // least (1,1) pixel size
167
0
        maSize.setY( std::max( 1.0,
168
0
                                 ceil( rSpriteSize.Height ) ) );
169
0
    }
170
171
    void CanvasCustomSpriteHelper::disposing()
172
0
    {
173
0
        mpSpriteCanvas.clear();
174
0
    }
175
176
    void CanvasCustomSpriteHelper::clearingContent( const Sprite::Reference& /*rSprite*/ )
177
0
    {
178
        // about to clear content to fully transparent
179
0
        mbIsContentFullyOpaque = false;
180
0
    }
181
182
    void CanvasCustomSpriteHelper::checkDrawBitmap( const Sprite::Reference&                    rSprite,
183
                                                    const uno::Reference< rendering::XBitmap >& xBitmap,
184
                                                    const rendering::ViewState&                 viewState,
185
                                                    const rendering::RenderState&               renderState )
186
0
    {
187
        // check whether bitmap is non-alpha, and whether its
188
        // transformed size covers the whole sprite.
189
0
        if( xBitmap->hasAlpha() )
190
0
            return;
191
192
0
        const geometry::IntegerSize2D aInputSize(xBitmap->getSize());
193
0
        basegfx::B2DSize rOurSize(rSprite->getSizePixel().getX(), rSprite->getSizePixel().getY());
194
195
0
        ::basegfx::B2DHomMatrix aTransform;
196
0
        if( canvastools::isInside(
197
0
                ::basegfx::B2DRectangle( 0.0,0.0,
198
0
                                         rOurSize.getWidth(),
199
0
                                         rOurSize.getHeight() ),
200
0
                ::basegfx::B2DRectangle( 0.0,0.0,
201
0
                                         aInputSize.Width,
202
0
                                         aInputSize.Height ),
203
0
                ::canvastools::mergeViewAndRenderTransform(aTransform,
204
0
                                                             viewState,
205
0
                                                             renderState) ) )
206
0
        {
207
            // bitmap is opaque and will fully cover the sprite,
208
            // set flag appropriately
209
0
            mbIsContentFullyOpaque = true;
210
0
        }
211
0
    }
212
213
    void CanvasCustomSpriteHelper::setAlpha( const Sprite::Reference&   rSprite,
214
                                             double                     alpha )
215
0
    {
216
0
        if( !mpSpriteCanvas )
217
0
            return; // we're disposed
218
219
0
        if( alpha != mfAlpha )
220
0
        {
221
0
            mfAlpha = alpha;
222
223
0
            if( mbActive )
224
0
            {
225
0
                mpSpriteCanvas->updateSprite( rSprite,
226
0
                                              maPosition,
227
0
                                              getUpdateArea() );
228
0
            }
229
0
        }
230
0
    }
231
232
    void CanvasCustomSpriteHelper::move( const Sprite::Reference&       rSprite,
233
                                         const geometry::RealPoint2D&   aNewPos,
234
                                         const rendering::ViewState&    viewState,
235
                                         const rendering::RenderState&  renderState )
236
0
    {
237
0
        if( !mpSpriteCanvas )
238
0
            return; // we're disposed
239
240
0
        ::basegfx::B2DHomMatrix aTransform;
241
0
        ::canvastools::mergeViewAndRenderTransform(aTransform,
242
0
                                                     viewState,
243
0
                                                     renderState);
244
245
        // convert position to device pixel
246
0
        ::basegfx::B2DPoint aPoint(
247
0
            ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos) );
248
0
        aPoint *= aTransform;
249
250
0
        if( aPoint == maPosition )
251
0
            return;
252
253
0
        const ::basegfx::B2DRectangle aBounds
254
0
            = getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
255
0
                                                      maSize.getX(),
256
0
                                                      maSize.getY() ) );
257
258
0
        if( mbActive )
259
0
        {
260
0
            mpSpriteCanvas->moveSprite( rSprite,
261
0
                                        aBounds.getMinimum(),
262
0
                                        aBounds.getMinimum() - maPosition + aPoint,
263
0
                                        aBounds.getRange() );
264
0
        }
265
266
0
        maPosition = aPoint;
267
0
    }
268
269
    void CanvasCustomSpriteHelper::transform( const Sprite::Reference&          rSprite,
270
                                              const geometry::AffineMatrix2D&   aTransformation )
271
0
    {
272
0
        ::basegfx::B2DHomMatrix aMatrix;
273
0
        ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix,
274
0
                                                       aTransformation);
275
276
0
        if( maTransform == aMatrix )
277
0
            return;
278
279
        // retrieve bounds before and after transformation change.
280
0
        const ::basegfx::B2DRectangle aPrevBounds( getUpdateArea() );
281
282
0
        maTransform = aMatrix;
283
284
0
        if( !updateClipState( rSprite ) &&
285
0
            mbActive )
286
0
        {
287
0
            mpSpriteCanvas->updateSprite( rSprite,
288
0
                                          maPosition,
289
0
                                          aPrevBounds );
290
0
            mpSpriteCanvas->updateSprite( rSprite,
291
0
                                          maPosition,
292
0
                                          getUpdateArea() );
293
0
        }
294
295
0
        mbTransformDirty = true;
296
0
    }
297
298
    void CanvasCustomSpriteHelper::clip( const Sprite::Reference&                           rSprite,
299
                                         const uno::Reference< rendering::XPolyPolygon2D >& xClip )
300
0
    {
301
        // NULL xClip explicitly allowed here (to clear clipping)
302
303
        // retrieve bounds before and after clip change.
304
0
        const ::basegfx::B2DRectangle aPrevBounds( getUpdateArea() );
305
306
0
        mxClipPoly = xClip;
307
308
0
        if( !updateClipState( rSprite ) &&
309
0
            mbActive )
310
0
        {
311
0
            mpSpriteCanvas->updateSprite( rSprite,
312
0
                                          maPosition,
313
0
                                          aPrevBounds );
314
0
            mpSpriteCanvas->updateSprite( rSprite,
315
0
                                          maPosition,
316
0
                                          getUpdateArea() );
317
0
        }
318
0
    }
319
320
    void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference&    rSprite,
321
                                                double                      nPriority )
322
0
    {
323
0
        if( !mpSpriteCanvas )
324
0
            return; // we're disposed
325
326
0
        if( nPriority != mfPriority )
327
0
        {
328
0
            mfPriority = nPriority;
329
330
0
            if( mbActive )
331
0
            {
332
0
                mpSpriteCanvas->updateSprite( rSprite,
333
0
                                              maPosition,
334
0
                                              getUpdateArea() );
335
0
            }
336
0
        }
337
0
    }
338
339
    void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite )
340
0
    {
341
0
        if( !mpSpriteCanvas )
342
0
            return; // we're disposed
343
344
0
        if( mbActive )
345
0
            return;
346
347
0
        mpSpriteCanvas->showSprite( rSprite );
348
0
        mbActive = true;
349
350
        // TODO(P1): if clip is the NULL clip (nothing visible),
351
        // also save us the update call.
352
353
0
        if( mfAlpha != 0.0 )
354
0
        {
355
0
            mpSpriteCanvas->updateSprite( rSprite,
356
0
                                          maPosition,
357
0
                                          getUpdateArea() );
358
0
        }
359
0
    }
360
361
    void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite )
362
0
    {
363
0
        if( !mpSpriteCanvas )
364
0
            return; // we're disposed
365
366
0
        if( !mbActive )
367
0
            return;
368
369
0
        mpSpriteCanvas->hideSprite( rSprite );
370
0
        mbActive = false;
371
372
        // TODO(P1): if clip is the NULL clip (nothing visible),
373
        // also save us the update call.
374
375
0
        if( mfAlpha != 0.0 )
376
0
        {
377
0
            mpSpriteCanvas->updateSprite( rSprite,
378
0
                                          maPosition,
379
0
                                          getUpdateArea() );
380
0
        }
381
0
    }
382
383
    bool CanvasCustomSpriteHelper::isAreaUpdateOpaque( const ::basegfx::B2DRange& rUpdateArea ) const
384
0
    {
385
0
        if( !mbIsCurrClipRectangle ||
386
0
            !mbIsContentFullyOpaque ||
387
0
            !::rtl::math::approxEqual(mfAlpha, 1.0) )
388
0
        {
389
            // sprite either transparent, or clip rect does not
390
            // represent exact bounds -> update might not be fully
391
            // opaque
392
0
            return false;
393
0
        }
394
0
        else
395
0
        {
396
            // make sure sprite rect fully covers update area -
397
            // although the update area originates from the sprite,
398
            // it's by no means guaranteed that it's limited to this
399
            // sprite's update area - after all, other sprites might
400
            // have been merged, or this sprite is moving.
401
0
            return getUpdateArea().isInside( rUpdateArea );
402
0
        }
403
0
    }
404
405
    ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea( const ::basegfx::B2DRange& rBounds ) const
406
0
    {
407
        // Internal! Only call with locked object mutex!
408
0
        ::basegfx::B2DHomMatrix aTransform( maTransform );
409
0
        aTransform.translate( maPosition.getX(),
410
0
                              maPosition.getY() );
411
412
        // transform bounds at origin, as the sprite transformation is
413
        // formulated that way
414
0
        return ::canvastools::calcTransformedRectBounds(
415
0
                                                           rBounds,
416
0
                                                           aTransform );
417
0
    }
418
419
    ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea() const
420
0
    {
421
        // Internal! Only call with locked object mutex!
422
423
        // return effective sprite rect, i.e. take active clip into
424
        // account
425
0
        if( maCurrClipBounds.isEmpty() )
426
0
            return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
427
0
                                                           maSize.getX(),
428
0
                                                           maSize.getY() ) );
429
0
        else
430
0
            return ::basegfx::B2DRectangle(
431
0
                maPosition + maCurrClipBounds.getMinimum(),
432
0
                maPosition + maCurrClipBounds.getMaximum() );
433
0
    }
434
}
435
436
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */