1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
24 #include <tools/gen.hxx>
25 #include <tools/debug.hxx>
27 #include <canvas/canvastools.hxx>
29 #include <com/sun/star/rendering/XBitmap.hpp>
30 #include <com/sun/star/rendering/XCanvas.hpp>
32 #include <vcl/metaact.hxx>
33 #include <vcl/bitmapex.hxx>
34 #include <vcl/svapp.hxx>
35 #include <vcl/virdev.hxx>
36 #include <vcl/gdimtf.hxx>
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>
48 #include "transparencygroupaction.hxx"
49 #include <outdevstate.hxx>
50 #include "mtftools.hxx"
51 #include <cppcanvas/vclfactory.hxx>
53 #if OSL_DEBUG_LEVEL > 2
54 #include <vcl/canvastools.hxx>
57 using namespace ::com::sun::star
;
59 namespace cppcanvas::internal
61 // free support functions
62 // ======================
65 class TransparencyGroupAction
: public Action
68 /** Create new transparency group action.
71 Metafile that groups all actions to be rendered
75 VCL gradient, to be rendered into the action's alpha
79 Left, top edge of destination, in current state
83 Size of the transparency group object, in current
84 state coordinate system.
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
);
93 TransparencyGroupAction(const TransparencyGroupAction
&) = delete;
94 const TransparencyGroupAction
& operator=(const TransparencyGroupAction
&) = delete;
96 virtual bool render( const ::basegfx::B2DHomMatrix
& rTransformation
) const override
;
97 virtual bool renderSubset( const ::basegfx::B2DHomMatrix
& rTransformation
,
98 const Subset
& rSubset
) const override
;
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
;
104 virtual sal_Int32
getActionCount() const override
;
107 std::unique_ptr
< GDIMetaFile
> mpGroupMtf
;
108 std::optional
< Gradient
> mpAlphaGradient
;
110 const ::basegfx::B2DSize maDstSize
;
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
116 // transformation for
117 // mxBufferBitmap content
118 CanvasSharedPtr mpCanvas
;
119 rendering::RenderState maState
;
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.
127 void implSetupTransform( rendering::RenderState
& rRenderState
,
128 const ::basegfx::B2DPoint
& rDstPoint
)
130 ::basegfx::B2DHomMatrix aLocalTransformation
;
132 aLocalTransformation
.translate( rDstPoint
.getX(),
134 ::canvas::tools::appendToRenderState( rRenderState
,
135 aLocalTransformation
);
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 mpGroupMtf( std::move(rGroupMtf
) ),
145 mpAlphaGradient( std::move(rAlphaGradient
) ),
146 maDstSize(rDstSize
.getX(), rDstSize
.getY()),
149 tools::initRenderState(maState
,rState
);
150 implSetupTransform( maState
, rDstPoint
);
152 // correct clip (which is relative to original transform)
153 tools::modifyClip( maState
,
160 maLastSubset
.mnSubsetBegin
= 0;
161 maLastSubset
.mnSubsetEnd
= -1;
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
171 bool TransparencyGroupAction::renderSubset( const ::basegfx::B2DHomMatrix
& rTransformation
,
172 const Subset
& rSubset
) const
174 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction::renderSubset()" );
175 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction: 0x" << std::hex
<< this );
177 // determine overall transformation matrix (render, view,
178 // and passed transformation)
179 ::basegfx::B2DHomMatrix aTransform
;
180 ::canvas::tools::getRenderStateTransform( aTransform
, maState
);
181 aTransform
= rTransformation
* aTransform
;
183 ::basegfx::B2DHomMatrix aTotalTransform
;
184 ::canvas::tools::getViewStateTransform( aTotalTransform
, mpCanvas
->getViewState() );
185 aTotalTransform
= aTotalTransform
* aTransform
;
187 // since pure translational changes to the transformation
188 // does not matter, remove them before comparing
189 aTotalTransform
.set( 0, 2, 0.0 );
190 aTotalTransform
.set( 1, 2, 0.0 );
192 // determine total scaling factor of the
193 // transformation matrix - need to make the bitmap
195 ::basegfx::B2DTuple aScale
;
196 ::basegfx::B2DTuple aTranslate
;
199 if( !aTotalTransform
.decompose( aScale
,
204 SAL_WARN( "cppcanvas.emf", "TransparencyGroupAction::renderSubset(): non-decomposable transformation" );
208 ::Point aMtfOffsetPoint
;
210 // if there's no buffer bitmap, or as soon as the
211 // total transformation changes, we've got to
212 // re-render the bitmap
213 if( !mxBufferBitmap
.is() ||
214 aTotalTransform
!= maLastTransformation
||
215 rSubset
.mnSubsetBegin
!= maLastSubset
.mnSubsetBegin
||
216 rSubset
.mnSubsetEnd
!= maLastSubset
.mnSubsetEnd
)
218 DBG_TESTSOLARMUTEX();
220 // tdf#150610 fix broken rendering of text meta actions
221 // Even when drawing to a VirtualDevice where antialiasing
222 // is disabled, text will still be drawn with some
223 // antialiased pixels on HiDPI displays. So, expand the
224 // size of the VirtualDevice slightly to capture any of
225 // the pixels drawn past the edges of the destination
227 bool bHasTextActions
= false;
228 MetaAction
* pCurrAct
;
229 int nCurrActionIndex
;
230 for( nCurrActionIndex
=0, pCurrAct
=mpGroupMtf
->FirstAction();
231 pCurrAct
&& !bHasTextActions
;
232 ++nCurrActionIndex
, pCurrAct
= mpGroupMtf
->NextAction() )
234 switch( pCurrAct
->GetType() )
236 case MetaActionType::TEXT
:
237 case MetaActionType::TEXTARRAY
:
238 case MetaActionType::STRETCHTEXT
:
239 case MetaActionType::TEXTRECT
:
240 if( ( rSubset
.mnSubsetBegin
== 0 && rSubset
.mnSubsetEnd
== -1 ) || ( rSubset
.mnSubsetBegin
<= nCurrActionIndex
&& rSubset
.mnSubsetEnd
> nCurrActionIndex
) )
241 bHasTextActions
= true;
248 // output size of metafile
249 ::Size
aOutputSizePixel( ::basegfx::fround( aScale
.getX() * maDstSize
.getWidth() ),
250 ::basegfx::fround( aScale
.getY() * maDstSize
.getHeight() ) );
252 sal_Int32 nBitmapExtra
;
253 if ( bHasTextActions
)
256 aMtfOffsetPoint
= ::Point( nBitmapExtra
/ 2, nBitmapExtra
/ 2 );
260 // Related tdf#150610 assume antialiasing is enabled
261 // Although antialiasing is normally disabled in the
262 // VirtualDevice, lines in tdf#150610 will draw past
263 // the edge of the VirtualDevice when running a
264 // slideshow so always add an extra pixel on the
265 // right and bottom edges.
269 // pixel size of cache bitmap: round up to nearest int
270 ::Point aBitmapPoint
;
271 ::Size
aBitmapSizePixel( static_cast<sal_Int32
>( aScale
.getX() * maDstSize
.getWidth() ) + nBitmapExtra
,
272 static_cast<sal_Int32
>( aScale
.getY() * maDstSize
.getHeight() ) + nBitmapExtra
);
274 // render our content into an appropriately sized
275 // VirtualDevice with alpha channel
276 ScopedVclPtrInstance
<VirtualDevice
> aVDev(
277 *::Application::GetDefaultDevice(), DeviceFormat::WITH_ALPHA
);
278 aVDev
->SetOutputSizePixel( aBitmapSizePixel
, true, true );
281 if( rSubset
.mnSubsetBegin
!= 0 ||
282 rSubset
.mnSubsetEnd
!= -1 )
284 // true subset - extract referenced
285 // metaactions from mpGroupMtf
288 // extract subset actions
289 for( nCurrActionIndex
=0,
290 pCurrAct
=mpGroupMtf
->FirstAction();
292 ++nCurrActionIndex
, pCurrAct
= mpGroupMtf
->NextAction() )
294 switch( pCurrAct
->GetType() )
296 case MetaActionType::PUSH
:
297 case MetaActionType::POP
:
298 case MetaActionType::CLIPREGION
:
299 case MetaActionType::ISECTRECTCLIPREGION
:
300 case MetaActionType::ISECTREGIONCLIPREGION
:
301 case MetaActionType::MOVECLIPREGION
:
302 case MetaActionType::LINECOLOR
:
303 case MetaActionType::FILLCOLOR
:
304 case MetaActionType::TEXTCOLOR
:
305 case MetaActionType::TEXTFILLCOLOR
:
306 case MetaActionType::TEXTLINECOLOR
:
307 case MetaActionType::TEXTALIGN
:
308 case MetaActionType::FONT
:
309 case MetaActionType::RASTEROP
:
310 case MetaActionType::REFPOINT
:
311 case MetaActionType::LAYOUTMODE
:
312 // state-changing action - copy as-is
313 aMtf
.AddAction( pCurrAct
->Clone() );
316 case MetaActionType::GRADIENT
:
317 case MetaActionType::HATCH
:
318 case MetaActionType::EPS
:
319 case MetaActionType::COMMENT
:
320 case MetaActionType::POINT
:
321 case MetaActionType::PIXEL
:
322 case MetaActionType::LINE
:
323 case MetaActionType::RECT
:
324 case MetaActionType::ROUNDRECT
:
325 case MetaActionType::ELLIPSE
:
326 case MetaActionType::ARC
:
327 case MetaActionType::PIE
:
328 case MetaActionType::CHORD
:
329 case MetaActionType::POLYLINE
:
330 case MetaActionType::POLYGON
:
331 case MetaActionType::POLYPOLYGON
:
332 case MetaActionType::BMP
:
333 case MetaActionType::BMPSCALE
:
334 case MetaActionType::BMPSCALEPART
:
335 case MetaActionType::BMPEX
:
336 case MetaActionType::BMPEXSCALE
:
337 case MetaActionType::BMPEXSCALEPART
:
338 case MetaActionType::MASK
:
339 case MetaActionType::MASKSCALE
:
340 case MetaActionType::MASKSCALEPART
:
341 case MetaActionType::GRADIENTEX
:
342 case MetaActionType::WALLPAPER
:
343 case MetaActionType::Transparent
:
344 case MetaActionType::FLOATTRANSPARENT
:
345 case MetaActionType::TEXT
:
346 case MetaActionType::TEXTARRAY
:
347 case MetaActionType::TEXTLINE
:
348 case MetaActionType::TEXTRECT
:
349 case MetaActionType::STRETCHTEXT
:
350 // output-generating action - only
351 // copy, if we're within the
353 if( rSubset
.mnSubsetBegin
<= nCurrActionIndex
&&
354 rSubset
.mnSubsetEnd
> nCurrActionIndex
)
356 aMtf
.AddAction( pCurrAct
->Clone() );
361 SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
366 aVDev
->DrawTransparent( aMtf
,
375 // no subsetting - render whole mtf
376 aVDev
->DrawTransparent( *mpGroupMtf
,
385 // update buffered bitmap and transformation
386 BitmapSharedPtr
aBmp( VCLFactory::createBitmap(
390 aBitmapSizePixel
) ) );
391 mxBufferBitmap
= aBmp
->getUNOBitmap();
392 maLastTransformation
= aTotalTransform
;
393 maLastSubset
= rSubset
;
396 // determine target transformation (we can't simply pass
397 // aTotalTransform as assembled above, since we must take
398 // the canvas' view state as is, it might contain clipping
399 // (which, in turn, is relative to the view
402 // given that aTotalTransform is the identity
403 // transformation, we could simply render our bitmap
404 // as-is. Now, since the mxBufferBitmap content already
405 // accounts for scale changes in the overall
406 // transformation, we must factor this out
407 // before. Generally, the transformation matrix should be
408 // structured like this:
409 // Translation*Rotation*Shear*Scale. Thus, to neutralize
410 // the contained scaling, we've got to right-multiply with
412 ::basegfx::B2DHomMatrix aScaleCorrection
;
413 aScaleCorrection
.translate( -aMtfOffsetPoint
.X(), -aMtfOffsetPoint
.Y() );
414 aScaleCorrection
.scale( 1/aScale
.getX(), 1/aScale
.getY() );
415 aTransform
= aTransform
* aScaleCorrection
;
417 rendering::RenderState
aLocalState( maState
);
418 ::canvas::tools::setRenderStateTransform(aLocalState
, aTransform
);
420 if(aLocalState
.Clip
.is())
423 // Adjust renderstate clip to modified scale from above
424 ::basegfx::B2DPolyPolygon aClip
= ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(aLocalState
.Clip
);
425 aClip
.transform(basegfx::utils::createScaleB2DHomMatrix(aScale
));
426 aLocalState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(mpCanvas
->getUNOCanvas()->getDevice(), aClip
);
429 #if OSL_DEBUG_LEVEL > 2
430 aLocalState
.Clip
.clear();
431 aLocalState
.DeviceColor
=
432 vcl::unotools::colorToDoubleSequence(
433 ::Color( 0x80FF0000 ),
434 mpCanvas
->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
436 if( maState
.Clip
.is() )
437 mpCanvas
->getUNOCanvas()->fillPolyPolygon( maState
.Clip
,
438 mpCanvas
->getViewState(),
441 aLocalState
.DeviceColor
= maState
.DeviceColor
;
444 // no further alpha changes necessary -> draw directly
445 mpCanvas
->getUNOCanvas()->drawBitmap( mxBufferBitmap
,
446 mpCanvas
->getViewState(),
451 // TODO(P3): The whole float transparency handling is a mess,
452 // this should be refactored. What's more, the old idea of
453 // having only internal 'metaactions', and not the original
454 // GDIMetaFile now looks a lot less attractive. Try to move
455 // into the direction of having a direct GDIMetaFile2XCanvas
456 // renderer, and maybe a separate metafile XCanvas
458 bool TransparencyGroupAction::render( const ::basegfx::B2DHomMatrix
& rTransformation
) const
462 aSubset
.mnSubsetBegin
= 0;
463 aSubset
.mnSubsetEnd
= -1;
465 return renderSubset( rTransformation
, aSubset
);
468 ::basegfx::B2DRange
TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix
& rTransformation
) const
470 rendering::RenderState
aLocalState( maState
);
471 ::canvas::tools::prependToRenderState(aLocalState
, rTransformation
);
473 return tools::calcDevicePixelBounds(
474 ::basegfx::B2DRange( 0,0,
475 maDstSize
.getWidth(),
476 maDstSize
.getHeight() ),
477 mpCanvas
->getViewState(),
481 ::basegfx::B2DRange
TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix
& rTransformation
,
482 const Subset
& rSubset
) const
484 // TODO(F3): Currently, the bounds for
485 // TransparencyGroupAction subsets equal those of the
486 // full set, although this action is able to render
489 // polygon only contains a single action, empty bounds
490 // if subset requests different range
491 if( rSubset
.mnSubsetBegin
!= 0 ||
492 rSubset
.mnSubsetEnd
!= 1 )
493 return ::basegfx::B2DRange();
495 return getBounds( rTransformation
);
498 sal_Int32
TransparencyGroupAction::getActionCount() const
500 return mpGroupMtf
? mpGroupMtf
->GetActionSize() : 0;
505 std::shared_ptr
<Action
> TransparencyGroupActionFactory::createTransparencyGroupAction( std::unique_ptr
< GDIMetaFile
>&& rGroupMtf
,
506 std::optional
< Gradient
>&& rAlphaGradient
,
507 const ::basegfx::B2DPoint
& rDstPoint
,
508 const ::basegfx::B2DVector
& rDstSize
,
509 const CanvasSharedPtr
& rCanvas
,
510 const OutDevState
& rState
)
512 return std::make_shared
<TransparencyGroupAction
>(std::move(rGroupMtf
),
513 std::move(rAlphaGradient
),
522 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */