Bump version to 24.04.3.4
[LibreOffice.git] / cppcanvas / source / mtfrenderer / transparencygroupaction.cxx
blobd8bf50649b305155aa96b1ece29d3f48f90bda65
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <utility>
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>
55 #endif
57 using namespace ::com::sun::star;
59 namespace cppcanvas::internal
61 // free support functions
62 // ======================
63 namespace
65 class TransparencyGroupAction : public Action
67 public:
68 /** Create new transparency group action.
70 @param rGroupMtf
71 Metafile that groups all actions to be rendered
72 transparent.
74 @param rAlphaGradient
75 VCL gradient, to be rendered into the action's alpha
76 channel.
78 @param rDstPoint
79 Left, top edge of destination, in current state
80 coordinate system
82 @param rDstSize
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;
106 private:
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(),
133 rDstPoint.getY() );
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()),
147 mpCanvas( rCanvas )
149 tools::initRenderState(maState,rState);
150 implSetupTransform( maState, rDstPoint );
152 // correct clip (which is relative to original transform)
153 tools::modifyClip( maState,
154 rState,
155 rCanvas,
156 rDstPoint,
157 nullptr,
158 nullptr );
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
170 // implementation.
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
194 // large enough
195 ::basegfx::B2DTuple aScale;
196 ::basegfx::B2DTuple aTranslate;
197 double nRotate;
198 double nShearX;
199 if( !aTotalTransform.decompose( aScale,
200 aTranslate,
201 nRotate,
202 nShearX ) )
204 SAL_WARN( "cppcanvas.emf", "TransparencyGroupAction::renderSubset(): non-decomposable transformation" );
205 return false;
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
226 // bounds.
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;
242 break;
243 default:
244 break;
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 )
255 nBitmapExtra = 10;
256 aMtfOffsetPoint = ::Point( nBitmapExtra / 2, nBitmapExtra / 2 );
258 else
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.
266 nBitmapExtra = 1;
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 );
279 aVDev->SetMapMode();
281 if( rSubset.mnSubsetBegin != 0 ||
282 rSubset.mnSubsetEnd != -1 )
284 // true subset - extract referenced
285 // metaactions from mpGroupMtf
286 GDIMetaFile aMtf;
288 // extract subset actions
289 for( nCurrActionIndex=0,
290 pCurrAct=mpGroupMtf->FirstAction();
291 pCurrAct;
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() );
314 break;
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
352 // requested subset
353 if( rSubset.mnSubsetBegin <= nCurrActionIndex &&
354 rSubset.mnSubsetEnd > nCurrActionIndex )
356 aMtf.AddAction( pCurrAct->Clone() );
358 break;
360 default:
361 SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
362 break;
366 aVDev->DrawTransparent( aMtf,
367 aBitmapPoint,
368 aBitmapSizePixel,
369 aMtfOffsetPoint,
370 aOutputSizePixel,
371 *mpAlphaGradient );
373 else
375 // no subsetting - render whole mtf
376 aVDev->DrawTransparent( *mpGroupMtf,
377 aBitmapPoint,
378 aBitmapSizePixel,
379 aMtfOffsetPoint,
380 aOutputSizePixel,
381 *mpAlphaGradient );
385 // update buffered bitmap and transformation
386 BitmapSharedPtr aBmp( VCLFactory::createBitmap(
387 mpCanvas,
388 aVDev->GetBitmapEx(
389 aBitmapPoint,
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
400 // transformation))
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
411 // the inverse.
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())
422 // tdf#95709
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(),
439 aLocalState );
441 aLocalState.DeviceColor = maState.DeviceColor;
442 #endif
444 // no further alpha changes necessary -> draw directly
445 mpCanvas->getUNOCanvas()->drawBitmap( mxBufferBitmap,
446 mpCanvas->getViewState(),
447 aLocalState );
448 return true;
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
457 // implementation.
458 bool TransparencyGroupAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
460 Subset aSubset;
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(),
478 aLocalState );
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
487 // true subsets.
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),
514 rDstPoint,
515 rDstSize,
516 rCanvas,
517 rState );
522 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */