Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/cppcanvas/source/mtfrenderer/transparencygroupaction.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 <utility>
23
24
#include <tools/gen.hxx>
25
#include <tools/debug.hxx>
26
27
#include <canvas/canvastools.hxx>
28
29
#include <com/sun/star/rendering/XBitmap.hpp>
30
#include <com/sun/star/rendering/XCanvas.hpp>
31
32
#include <vcl/metaact.hxx>
33
#include <vcl/metaactiontypes.hxx>
34
#include <vcl/svapp.hxx>
35
#include <vcl/virdev.hxx>
36
#include <vcl/gdimtf.hxx>
37
38
#include <basegfx/range/b2drange.hxx>
39
#include <basegfx/point/b2dpoint.hxx>
40
#include <basegfx/vector/b2dsize.hxx>
41
#include <basegfx/numeric/ftools.hxx>
42
#include <basegfx/matrix/b2dhommatrix.hxx>
43
#include <basegfx/tuple/b2dtuple.hxx>
44
#include <basegfx/utils/canvastools.hxx>
45
#include <basegfx/matrix/b2dhommatrixtools.hxx>
46
#include <sal/log.hxx>
47
48
#include "transparencygroupaction.hxx"
49
#include <outdevstate.hxx>
50
#include "mtftools.hxx"
51
#include <cppcanvas/vclfactory.hxx>
52
53
#if OSL_DEBUG_LEVEL > 2
54
#include <vcl/canvastools.hxx>
55
#endif
56
57
using namespace ::com::sun::star;
58
59
namespace cppcanvas::internal
60
{
61
        // free support functions
62
        // ======================
63
        namespace
64
        {
65
            class TransparencyGroupAction : public Action
66
            {
67
            public:
68
                /** Create new transparency group action.
69
70
                    @param rGroupMtf
71
                    Metafile that groups all actions to be rendered
72
                    transparent.
73
74
                    @param rAlphaGradient
75
                    VCL gradient, to be rendered into the action's alpha
76
                    channel.
77
78
                    @param rDstPoint
79
                    Left, top edge of destination, in current state
80
                    coordinate system
81
82
                    @param rDstSize
83
                    Size of the transparency group object, in current
84
                    state coordinate system.
85
                */
86
                TransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf,
87
                                         std::optional< Gradient >&&    rAlphaGradient,
88
                                         const ::basegfx::B2DPoint&     rDstPoint,
89
                                         const ::basegfx::B2DVector&    rDstSize,
90
                                         const CanvasSharedPtr&         rCanvas,
91
                                         const OutDevState&             rState );
92
93
                TransparencyGroupAction(const TransparencyGroupAction&) = delete;
94
                const TransparencyGroupAction& operator=(const TransparencyGroupAction&) = delete;
95
96
                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
97
                virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
98
                                           const Subset&                  rSubset ) const override;
99
100
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
101
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&   rTransformation,
102
                                                       const Subset&                    rSubset ) const override;
103
104
                virtual sal_Int32 getActionCount() const override;
105
106
            private:
107
                std::unique_ptr< GDIMetaFile >                      mpGroupMtf;
108
                std::optional< Gradient >                           mpAlphaGradient;
109
110
                const ::basegfx::B2DSize                            maDstSize;
111
112
                mutable uno::Reference< rendering::XBitmap >        mxBufferBitmap; // contains last rendered version
113
                mutable ::basegfx::B2DHomMatrix                     maLastTransformation; // contains last active transformation
114
                mutable Subset                                      maLastSubset; // contains last effective subset
115
116
                // transformation for
117
                // mxBufferBitmap content
118
                CanvasSharedPtr                                     mpCanvas;
119
                rendering::RenderState                              maState;
120
            };
121
122
123
            /** Setup transformation such that the next render call is
124
                moved rPoint away, and scaled according to the ratio
125
                given by src and dst size.
126
            */
127
            void implSetupTransform( rendering::RenderState&    rRenderState,
128
                                     const ::basegfx::B2DPoint& rDstPoint   )
129
0
            {
130
0
                ::basegfx::B2DHomMatrix aLocalTransformation;
131
132
0
                aLocalTransformation.translate( rDstPoint.getX(),
133
0
                                                rDstPoint.getY() );
134
0
                ::canvastools::appendToRenderState( rRenderState,
135
0
                                                      aLocalTransformation );
136
0
            }
137
138
            TransparencyGroupAction::TransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf,
139
                                                              std::optional< Gradient >&&   rAlphaGradient,
140
                                                              const ::basegfx::B2DPoint&    rDstPoint,
141
                                                              const ::basegfx::B2DVector&   rDstSize,
142
                                                              const CanvasSharedPtr&        rCanvas,
143
                                                              const OutDevState&            rState ) :
144
0
                mpGroupMtf( std::move(rGroupMtf) ),
145
0
                mpAlphaGradient( std::move(rAlphaGradient) ),
146
0
                maDstSize(rDstSize.getX(), rDstSize.getY()),
147
0
                mpCanvas( rCanvas )
148
0
            {
149
0
                cppcanvastools::initRenderState(maState,rState);
150
0
                implSetupTransform( maState, rDstPoint );
151
152
                // correct clip (which is relative to original transform)
153
0
                cppcanvastools::modifyClip( maState,
154
0
                                   rState,
155
0
                                   rCanvas,
156
0
                                   rDstPoint,
157
0
                                   nullptr,
158
0
                                   nullptr );
159
160
0
                maLastSubset.mnSubsetBegin = 0;
161
0
                maLastSubset.mnSubsetEnd = -1;
162
0
            }
163
164
            // TODO(P3): The whole float transparency handling is a mess,
165
            // this should be refactored. What's more, the old idea of
166
            // having only internal 'metaactions', and not the original
167
            // GDIMetaFile now looks a lot less attractive. Try to move
168
            // into the direction of having a direct GDIMetaFile2XCanvas
169
            // renderer, and maybe a separate metafile XCanvas
170
            // implementation.
171
            bool TransparencyGroupAction::renderSubset( const ::basegfx::B2DHomMatrix&    rTransformation,
172
                                                        const Subset&                     rSubset ) const
173
0
            {
174
0
                SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction::renderSubset()" );
175
0
                SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction: 0x" << std::hex << this );
176
177
                // determine overall transformation matrix (render, view,
178
                // and passed transformation)
179
0
                ::basegfx::B2DHomMatrix aTransform = ::canvastools::getRenderStateTransform( maState );
180
0
                aTransform = rTransformation * aTransform;
181
182
0
                ::basegfx::B2DHomMatrix aTotalTransform = ::canvastools::getViewStateTransform( mpCanvas->getViewState() );
183
0
                aTotalTransform = aTotalTransform * aTransform;
184
185
                // since pure translational changes to the transformation
186
                // does not matter, remove them before comparing
187
0
                aTotalTransform.set( 0, 2, 0.0 );
188
0
                aTotalTransform.set( 1, 2, 0.0 );
189
190
                // determine total scaling factor of the
191
                // transformation matrix - need to make the bitmap
192
                // large enough
193
0
                ::basegfx::B2DTuple aScale;
194
0
                ::basegfx::B2DTuple aTranslate;
195
0
                double              nRotate;
196
0
                double              nShearX;
197
0
                if( !aTotalTransform.decompose( aScale,
198
0
                                                aTranslate,
199
0
                                                nRotate,
200
0
                                                nShearX ) )
201
0
                {
202
0
                    SAL_WARN( "cppcanvas.emf", "TransparencyGroupAction::renderSubset(): non-decomposable transformation" );
203
0
                    return false;
204
0
                }
205
206
0
                ::Point aMtfOffsetPoint;
207
208
                // if there's no buffer bitmap, or as soon as the
209
                // total transformation changes, we've got to
210
                // re-render the bitmap
211
0
                if( !mxBufferBitmap.is() ||
212
0
                    aTotalTransform != maLastTransformation ||
213
0
                    rSubset.mnSubsetBegin != maLastSubset.mnSubsetBegin ||
214
0
                    rSubset.mnSubsetEnd != maLastSubset.mnSubsetEnd )
215
0
                {
216
0
                    DBG_TESTSOLARMUTEX();
217
218
                    // tdf#150610 fix broken rendering of text meta actions
219
                    // Even when drawing to a VirtualDevice where antialiasing
220
                    // is disabled, text will still be drawn with some
221
                    // antialiased pixels on HiDPI displays. So, expand the
222
                    // size of the VirtualDevice slightly to capture any of
223
                    // the pixels drawn past the edges of the destination
224
                    // bounds.
225
0
                    bool        bHasTextActions = false;
226
0
                    MetaAction* pCurrAct;
227
0
                    int         nCurrActionIndex;
228
0
                    for( nCurrActionIndex=0, pCurrAct=mpGroupMtf->FirstAction();
229
0
                         pCurrAct && !bHasTextActions;
230
0
                         ++nCurrActionIndex, pCurrAct = mpGroupMtf->NextAction() )
231
0
                    {
232
0
                        switch( pCurrAct->GetType() )
233
0
                        {
234
0
                            case MetaActionType::TEXT:
235
0
                            case MetaActionType::TEXTARRAY:
236
0
                            case MetaActionType::STRETCHTEXT:
237
0
                            case MetaActionType::TEXTRECT:
238
0
                                if( ( rSubset.mnSubsetBegin == 0 && rSubset.mnSubsetEnd == -1 ) || ( rSubset.mnSubsetBegin <= nCurrActionIndex && rSubset.mnSubsetEnd > nCurrActionIndex ) )
239
0
                                    bHasTextActions = true;
240
0
                                break;
241
0
                            default:
242
0
                                break;
243
0
                        }
244
0
                    }
245
246
                    // output size of metafile
247
0
                    ::Size aOutputSizePixel( ::basegfx::fround<::tools::Long>( aScale.getX() * maDstSize.getWidth() ),
248
0
                                             ::basegfx::fround<::tools::Long>( aScale.getY() * maDstSize.getHeight() ) );
249
250
0
                    sal_Int32 nBitmapExtra;
251
0
                    if ( bHasTextActions )
252
0
                    {
253
0
                        nBitmapExtra = 10;
254
0
                        aMtfOffsetPoint = ::Point( nBitmapExtra / 2, nBitmapExtra / 2 );
255
0
                    }
256
0
                    else
257
0
                    {
258
                        // Related tdf#150610 assume antialiasing is enabled
259
                        // Although antialiasing is normally disabled in the
260
                        // VirtualDevice, lines in tdf#150610 will draw past
261
                        // the edge of the VirtualDevice when running a
262
                        // slideshow so always add an extra pixel on the
263
                        // right and bottom edges.
264
0
                        nBitmapExtra = 1;
265
0
                    }
266
267
                    // pixel size of cache bitmap: round up to nearest int
268
0
                    ::Point aBitmapPoint;
269
0
                    ::Size aBitmapSizePixel( static_cast<sal_Int32>( aScale.getX() * maDstSize.getWidth() ) + nBitmapExtra,
270
0
                                             static_cast<sal_Int32>( aScale.getY() * maDstSize.getHeight() ) + nBitmapExtra );
271
272
                    // render our content into an appropriately sized
273
                    // VirtualDevice with alpha channel
274
0
                    ScopedVclPtrInstance<VirtualDevice> aVDev(
275
0
                        *::Application::GetDefaultDevice(), DeviceFormat::WITH_ALPHA );
276
0
                    aVDev->SetOutputSizePixel( aBitmapSizePixel, true, true );
277
0
                    aVDev->SetMapMode();
278
279
0
                    if( rSubset.mnSubsetBegin != 0 ||
280
0
                        rSubset.mnSubsetEnd != -1 )
281
0
                    {
282
                        // true subset - extract referenced
283
                        // metaactions from mpGroupMtf
284
0
                        GDIMetaFile aMtf;
285
286
                        // extract subset actions
287
0
                        for( nCurrActionIndex=0,
288
0
                                 pCurrAct=mpGroupMtf->FirstAction();
289
0
                             pCurrAct;
290
0
                             ++nCurrActionIndex, pCurrAct = mpGroupMtf->NextAction() )
291
0
                        {
292
0
                            switch( pCurrAct->GetType() )
293
0
                            {
294
0
                                case MetaActionType::PUSH:
295
0
                                case MetaActionType::POP:
296
0
                                case MetaActionType::CLIPREGION:
297
0
                                case MetaActionType::ISECTRECTCLIPREGION:
298
0
                                case MetaActionType::ISECTREGIONCLIPREGION:
299
0
                                case MetaActionType::MOVECLIPREGION:
300
0
                                case MetaActionType::LINECOLOR:
301
0
                                case MetaActionType::FILLCOLOR:
302
0
                                case MetaActionType::TEXTCOLOR:
303
0
                                case MetaActionType::TEXTFILLCOLOR:
304
0
                                case MetaActionType::TEXTLINECOLOR:
305
0
                                case MetaActionType::TEXTALIGN:
306
0
                                case MetaActionType::FONT:
307
0
                                case MetaActionType::RASTEROP:
308
0
                                case MetaActionType::REFPOINT:
309
0
                                case MetaActionType::LAYOUTMODE:
310
                                    // state-changing action - copy as-is
311
0
                                    aMtf.AddAction( pCurrAct->Clone() );
312
0
                                    break;
313
314
0
                                case MetaActionType::GRADIENT:
315
0
                                case MetaActionType::HATCH:
316
0
                                case MetaActionType::EPS:
317
0
                                case MetaActionType::COMMENT:
318
0
                                case MetaActionType::POINT:
319
0
                                case MetaActionType::PIXEL:
320
0
                                case MetaActionType::LINE:
321
0
                                case MetaActionType::RECT:
322
0
                                case MetaActionType::ROUNDRECT:
323
0
                                case MetaActionType::ELLIPSE:
324
0
                                case MetaActionType::ARC:
325
0
                                case MetaActionType::PIE:
326
0
                                case MetaActionType::CHORD:
327
0
                                case MetaActionType::POLYLINE:
328
0
                                case MetaActionType::POLYGON:
329
0
                                case MetaActionType::POLYPOLYGON:
330
0
                                case MetaActionType::BMP:
331
0
                                case MetaActionType::BMPSCALE:
332
0
                                case MetaActionType::BMPSCALEPART:
333
0
                                case MetaActionType::BMPEX:
334
0
                                case MetaActionType::BMPEXSCALE:
335
0
                                case MetaActionType::BMPEXSCALEPART:
336
0
                                case MetaActionType::MASK:
337
0
                                case MetaActionType::MASKSCALE:
338
0
                                case MetaActionType::MASKSCALEPART:
339
0
                                case MetaActionType::GRADIENTEX:
340
0
                                case MetaActionType::WALLPAPER:
341
0
                                case MetaActionType::Transparent:
342
0
                                case MetaActionType::FLOATTRANSPARENT:
343
0
                                case MetaActionType::TEXT:
344
0
                                case MetaActionType::TEXTARRAY:
345
0
                                case MetaActionType::TEXTLINE:
346
0
                                case MetaActionType::TEXTRECT:
347
0
                                case MetaActionType::STRETCHTEXT:
348
                                    // output-generating action - only
349
                                    // copy, if we're within the
350
                                    // requested subset
351
0
                                    if( rSubset.mnSubsetBegin <= nCurrActionIndex &&
352
0
                                        rSubset.mnSubsetEnd > nCurrActionIndex )
353
0
                                    {
354
0
                                        aMtf.AddAction( pCurrAct->Clone() );
355
0
                                    }
356
0
                                    break;
357
358
0
                                default:
359
0
                                    SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
360
0
                                    break;
361
0
                            }
362
0
                        }
363
364
0
                        aVDev->DrawTransparent( aMtf,
365
0
                                               aBitmapPoint,
366
0
                                               aBitmapSizePixel,
367
0
                                               aMtfOffsetPoint,
368
0
                                               aOutputSizePixel,
369
0
                                               *mpAlphaGradient );
370
0
                    }
371
0
                    else
372
0
                    {
373
                        // no subsetting - render whole mtf
374
0
                        aVDev->DrawTransparent( *mpGroupMtf,
375
0
                                               aBitmapPoint,
376
0
                                               aBitmapSizePixel,
377
0
                                               aMtfOffsetPoint,
378
0
                                               aOutputSizePixel,
379
0
                                               *mpAlphaGradient );
380
0
                    }
381
382
383
                    // update buffered bitmap and transformation
384
0
                    BitmapSharedPtr aBmp( VCLFactory::createBitmap(
385
0
                                              mpCanvas,
386
0
                                              aVDev->GetBitmap(
387
0
                                                  aBitmapPoint,
388
0
                                                  aBitmapSizePixel ) ) );
389
0
                    mxBufferBitmap = aBmp->getUNOBitmap();
390
0
                    maLastTransformation = aTotalTransform;
391
0
                    maLastSubset = rSubset;
392
0
                }
393
394
                // determine target transformation (we can't simply pass
395
                // aTotalTransform as assembled above, since we must take
396
                // the canvas' view state as is, it might contain clipping
397
                // (which, in turn, is relative to the view
398
                // transformation))
399
400
                // given that aTotalTransform is the identity
401
                // transformation, we could simply render our bitmap
402
                // as-is. Now, since the mxBufferBitmap content already
403
                // accounts for scale changes in the overall
404
                // transformation, we must factor this out
405
                // before. Generally, the transformation matrix should be
406
                // structured like this:
407
                // Translation*Rotation*Shear*Scale. Thus, to neutralize
408
                // the contained scaling, we've got to right-multiply with
409
                // the inverse.
410
0
                ::basegfx::B2DHomMatrix aScaleCorrection;
411
0
                aScaleCorrection.translate( -aMtfOffsetPoint.X(), -aMtfOffsetPoint.Y() );
412
0
                aScaleCorrection.scale( 1/aScale.getX(), 1/aScale.getY() );
413
0
                aTransform = aTransform * aScaleCorrection;
414
415
0
                rendering::RenderState aLocalState( maState );
416
0
                ::canvastools::setRenderStateTransform(aLocalState, aTransform);
417
418
0
                if(aLocalState.Clip.is())
419
0
                {
420
                    // tdf#95709
421
                    // Adjust renderstate clip to modified scale from above
422
0
                    ::basegfx::B2DPolyPolygon aClip = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(aLocalState.Clip);
423
0
                    aClip.transform(basegfx::utils::createScaleB2DHomMatrix(aScale));
424
0
                    aLocalState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(mpCanvas->getUNOCanvas()->getDevice(), aClip);
425
0
                }
426
427
#if OSL_DEBUG_LEVEL > 2
428
                aLocalState.Clip.clear();
429
                aLocalState.DeviceColor =
430
                    vcl::unotools::colorToDoubleSequence(
431
                        ::Color( 0x80FF0000 ),
432
                        mpCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
433
434
                if( maState.Clip.is() )
435
                    mpCanvas->getUNOCanvas()->fillPolyPolygon( maState.Clip,
436
                                                               mpCanvas->getViewState(),
437
                                                               aLocalState );
438
439
                aLocalState.DeviceColor = maState.DeviceColor;
440
#endif
441
442
                // no further alpha changes necessary -> draw directly
443
0
                mpCanvas->getUNOCanvas()->drawBitmap( mxBufferBitmap,
444
0
                                                      mpCanvas->getViewState(),
445
0
                                                      aLocalState );
446
0
                return true;
447
0
            }
448
449
            // TODO(P3): The whole float transparency handling is a mess,
450
            // this should be refactored. What's more, the old idea of
451
            // having only internal 'metaactions', and not the original
452
            // GDIMetaFile now looks a lot less attractive. Try to move
453
            // into the direction of having a direct GDIMetaFile2XCanvas
454
            // renderer, and maybe a separate metafile XCanvas
455
            // implementation.
456
            bool TransparencyGroupAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
457
0
            {
458
0
                Subset aSubset;
459
460
0
                aSubset.mnSubsetBegin = 0;
461
0
                aSubset.mnSubsetEnd   = -1;
462
463
0
                return renderSubset( rTransformation, aSubset );
464
0
            }
465
466
            ::basegfx::B2DRange TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix&  rTransformation ) const
467
0
            {
468
0
                rendering::RenderState aLocalState( maState );
469
0
                ::canvastools::prependToRenderState(aLocalState, rTransformation);
470
471
0
                return cppcanvastools::calcDevicePixelBounds(
472
0
                    ::basegfx::B2DRange( 0,0,
473
0
                                         maDstSize.getWidth(),
474
0
                                         maDstSize.getHeight() ),
475
0
                    mpCanvas->getViewState(),
476
0
                    aLocalState );
477
0
            }
478
479
            ::basegfx::B2DRange TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix&  rTransformation,
480
                                                                    const Subset&                   rSubset ) const
481
0
            {
482
                // TODO(F3): Currently, the bounds for
483
                // TransparencyGroupAction subsets equal those of the
484
                // full set, although this action is able to render
485
                // true subsets.
486
487
                // polygon only contains a single action, empty bounds
488
                // if subset requests different range
489
0
                if( rSubset.mnSubsetBegin != 0 ||
490
0
                    rSubset.mnSubsetEnd != 1 )
491
0
                    return ::basegfx::B2DRange();
492
493
0
                return getBounds( rTransformation );
494
0
            }
495
496
            sal_Int32 TransparencyGroupAction::getActionCount() const
497
0
            {
498
0
                return mpGroupMtf ? mpGroupMtf->GetActionSize() : 0;
499
0
            }
500
501
        }
502
503
        std::shared_ptr<Action> TransparencyGroupActionFactory::createTransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf,
504
                                                                                       std::optional< Gradient >&&  rAlphaGradient,
505
                                                                                       const ::basegfx::B2DPoint&   rDstPoint,
506
                                                                                       const ::basegfx::B2DVector&  rDstSize,
507
                                                                                       const CanvasSharedPtr&       rCanvas,
508
                                                                                       const OutDevState&           rState )
509
0
        {
510
0
            return std::make_shared<TransparencyGroupAction>(std::move(rGroupMtf),
511
0
                                                             std::move(rAlphaGradient),
512
0
                                                             rDstPoint,
513
0
                                                             rDstSize,
514
0
                                                             rCanvas,
515
0
                                                             rState );
516
0
        }
517
518
}
519
520
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */