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/bitmapex.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( MtfAutoPtr&&                   rGroupMtf,
87                                          GradientAutoPtr&&              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                 MtfAutoPtr                                          mpGroupMtf;
108                 GradientAutoPtr                                     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             */
implSetupTransform(rendering::RenderState & rRenderState,const::basegfx::B2DPoint & rDstPoint)127             void implSetupTransform( rendering::RenderState&    rRenderState,
128                                      const ::basegfx::B2DPoint& rDstPoint   )
129             {
130                 ::basegfx::B2DHomMatrix aLocalTransformation;
131 
132                 aLocalTransformation.translate( rDstPoint.getX(),
133                                                 rDstPoint.getY() );
134                 ::canvas::tools::appendToRenderState( rRenderState,
135                                                       aLocalTransformation );
136             }
137 
TransparencyGroupAction(MtfAutoPtr && rGroupMtf,GradientAutoPtr && rAlphaGradient,const::basegfx::B2DPoint & rDstPoint,const::basegfx::B2DVector & rDstSize,const CanvasSharedPtr & rCanvas,const OutDevState & rState)138             TransparencyGroupAction::TransparencyGroupAction( MtfAutoPtr&&                  rGroupMtf,
139                                                               GradientAutoPtr&&             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 ),
147                 mxBufferBitmap(),
148                 maLastTransformation(),
149                 mpCanvas( rCanvas ),
150                 maState()
151             {
152                 tools::initRenderState(maState,rState);
153                 implSetupTransform( maState, rDstPoint );
154 
155                 // correct clip (which is relative to original transform)
156                 tools::modifyClip( maState,
157                                    rState,
158                                    rCanvas,
159                                    rDstPoint,
160                                    nullptr,
161                                    nullptr );
162 
163                 maLastSubset.mnSubsetBegin = 0;
164                 maLastSubset.mnSubsetEnd = -1;
165             }
166 
167             // TODO(P3): The whole float transparency handling is a mess,
168             // this should be refactored. What's more, the old idea of
169             // having only internal 'metaactions', and not the original
170             // GDIMetaFile now looks a lot less attractive. Try to move
171             // into the direction of having a direct GDIMetaFile2XCanvas
172             // renderer, and maybe a separate metafile XCanvas
173             // implementation.
renderSubset(const::basegfx::B2DHomMatrix & rTransformation,const Subset & rSubset) const174             bool TransparencyGroupAction::renderSubset( const ::basegfx::B2DHomMatrix&    rTransformation,
175                                                         const Subset&                     rSubset ) const
176             {
177                 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction::renderSubset()" );
178                 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction: 0x" << std::hex << this );
179 
180                 // determine overall transformation matrix (render, view,
181                 // and passed transformation)
182                 ::basegfx::B2DHomMatrix aTransform;
183                 ::canvas::tools::getRenderStateTransform( aTransform, maState );
184                 aTransform = rTransformation * aTransform;
185 
186                 ::basegfx::B2DHomMatrix aTotalTransform;
187                 ::canvas::tools::getViewStateTransform( aTotalTransform, mpCanvas->getViewState() );
188                 aTotalTransform = aTotalTransform * aTransform;
189 
190                 // since pure translational changes to the transformation
191                 // does not matter, remove them before comparing
192                 aTotalTransform.set( 0, 2, 0.0 );
193                 aTotalTransform.set( 1, 2, 0.0 );
194 
195                 // determine total scaling factor of the
196                 // transformation matrix - need to make the bitmap
197                 // large enough
198                 ::basegfx::B2DTuple aScale;
199                 ::basegfx::B2DTuple aTranslate;
200                 double              nRotate;
201                 double              nShearX;
202                 if( !aTotalTransform.decompose( aScale,
203                                                 aTranslate,
204                                                 nRotate,
205                                                 nShearX ) )
206                 {
207                     SAL_WARN( "cppcanvas.emf", "TransparencyGroupAction::renderSubset(): non-decomposable transformation" );
208                     return false;
209                 }
210 
211                 // if there's no buffer bitmap, or as soon as the
212                 // total transformation changes, we've got to
213                 // re-render the bitmap
214                 if( !mxBufferBitmap.is() ||
215                     aTotalTransform != maLastTransformation ||
216                     rSubset.mnSubsetBegin != maLastSubset.mnSubsetBegin ||
217                     rSubset.mnSubsetEnd != maLastSubset.mnSubsetEnd )
218                 {
219                     DBG_TESTSOLARMUTEX();
220 
221                     // output size of metafile
222                     ::Size aOutputSizePixel( ::basegfx::fround( aScale.getX() * maDstSize.getX() ),
223                                              ::basegfx::fround( aScale.getY() * maDstSize.getY() ) );
224 
225                     // pixel size of cache bitmap: round up to nearest int
226                     ::Size aBitmapSizePixel( static_cast<sal_Int32>( aScale.getX() * maDstSize.getX() )+1,
227                                              static_cast<sal_Int32>( aScale.getY() * maDstSize.getY() )+1 );
228 
229                     ::Point aEmptyPoint;
230 
231                     // render our content into an appropriately sized
232                     // VirtualDevice with alpha channel
233                     ScopedVclPtrInstance<VirtualDevice> aVDev(
234                         *::Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::DEFAULT );
235                     aVDev->SetOutputSizePixel( aBitmapSizePixel );
236                     aVDev->SetMapMode();
237 
238                     if( rSubset.mnSubsetBegin != 0 ||
239                         rSubset.mnSubsetEnd != -1 )
240                     {
241                         // true subset - extract referenced
242                         // metaactions from mpGroupMtf
243                         GDIMetaFile aMtf;
244                         MetaAction* pCurrAct;
245                         int         nCurrActionIndex;
246 
247                         // extract subset actions
248                         for( nCurrActionIndex=0,
249                                  pCurrAct=mpGroupMtf->FirstAction();
250                              pCurrAct;
251                              ++nCurrActionIndex, pCurrAct = mpGroupMtf->NextAction() )
252                         {
253                             switch( pCurrAct->GetType() )
254                             {
255                                 case MetaActionType::PUSH:
256                                 case MetaActionType::POP:
257                                 case MetaActionType::CLIPREGION:
258                                 case MetaActionType::ISECTRECTCLIPREGION:
259                                 case MetaActionType::ISECTREGIONCLIPREGION:
260                                 case MetaActionType::MOVECLIPREGION:
261                                 case MetaActionType::LINECOLOR:
262                                 case MetaActionType::FILLCOLOR:
263                                 case MetaActionType::TEXTCOLOR:
264                                 case MetaActionType::TEXTFILLCOLOR:
265                                 case MetaActionType::TEXTLINECOLOR:
266                                 case MetaActionType::TEXTALIGN:
267                                 case MetaActionType::FONT:
268                                 case MetaActionType::RASTEROP:
269                                 case MetaActionType::REFPOINT:
270                                 case MetaActionType::LAYOUTMODE:
271                                     // state-changing action - copy as-is
272                                     aMtf.AddAction( pCurrAct->Clone() );
273                                     break;
274 
275                                 case MetaActionType::GRADIENT:
276                                 case MetaActionType::HATCH:
277                                 case MetaActionType::EPS:
278                                 case MetaActionType::COMMENT:
279                                 case MetaActionType::POINT:
280                                 case MetaActionType::PIXEL:
281                                 case MetaActionType::LINE:
282                                 case MetaActionType::RECT:
283                                 case MetaActionType::ROUNDRECT:
284                                 case MetaActionType::ELLIPSE:
285                                 case MetaActionType::ARC:
286                                 case MetaActionType::PIE:
287                                 case MetaActionType::CHORD:
288                                 case MetaActionType::POLYLINE:
289                                 case MetaActionType::POLYGON:
290                                 case MetaActionType::POLYPOLYGON:
291                                 case MetaActionType::BMP:
292                                 case MetaActionType::BMPSCALE:
293                                 case MetaActionType::BMPSCALEPART:
294                                 case MetaActionType::BMPEX:
295                                 case MetaActionType::BMPEXSCALE:
296                                 case MetaActionType::BMPEXSCALEPART:
297                                 case MetaActionType::MASK:
298                                 case MetaActionType::MASKSCALE:
299                                 case MetaActionType::MASKSCALEPART:
300                                 case MetaActionType::GRADIENTEX:
301                                 case MetaActionType::WALLPAPER:
302                                 case MetaActionType::Transparent:
303                                 case MetaActionType::FLOATTRANSPARENT:
304                                 case MetaActionType::TEXT:
305                                 case MetaActionType::TEXTARRAY:
306                                 case MetaActionType::TEXTLINE:
307                                 case MetaActionType::TEXTRECT:
308                                 case MetaActionType::STRETCHTEXT:
309                                     // output-generating action - only
310                                     // copy, if we're within the
311                                     // requested subset
312                                     if( rSubset.mnSubsetBegin <= nCurrActionIndex &&
313                                         rSubset.mnSubsetEnd > nCurrActionIndex )
314                                     {
315                                         aMtf.AddAction( pCurrAct->Clone() );
316                                     }
317                                     break;
318 
319                                 default:
320                                     SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
321                                     break;
322                             }
323                         }
324 
325                         aVDev->DrawTransparent( aMtf,
326                                                aEmptyPoint,
327                                                aOutputSizePixel,
328                                                *mpAlphaGradient );
329                     }
330                     else
331                     {
332                         // no subsetting - render whole mtf
333                         aVDev->DrawTransparent( *mpGroupMtf,
334                                                aEmptyPoint,
335                                                aOutputSizePixel,
336                                                *mpAlphaGradient );
337                     }
338 
339 
340                     // update buffered bitmap and transformation
341                     BitmapSharedPtr aBmp( VCLFactory::createBitmap(
342                                               mpCanvas,
343                                               aVDev->GetBitmapEx(
344                                                   aEmptyPoint,
345                                                   aBitmapSizePixel ) ) );
346                     mxBufferBitmap = aBmp->getUNOBitmap();
347                     maLastTransformation = aTotalTransform;
348                     maLastSubset = rSubset;
349                 }
350 
351                 // determine target transformation (we can't simply pass
352                 // aTotalTransform as assembled above, since we must take
353                 // the canvas' view state as is, it might contain clipping
354                 // (which, in turn, is relative to the view
355                 // transformation))
356 
357                 // given that aTotalTransform is the identity
358                 // transformation, we could simply render our bitmap
359                 // as-is. Now, since the mxBufferBitmap content already
360                 // accounts for scale changes in the overall
361                 // transformation, we must factor this out
362                 // before. Generally, the transformation matrix should be
363                 // structured like this:
364                 // Translation*Rotation*Shear*Scale. Thus, to neutralize
365                 // the contained scaling, we've got to right-multiply with
366                 // the inverse.
367                 ::basegfx::B2DHomMatrix aScaleCorrection;
368                 aScaleCorrection.scale( 1/aScale.getX(), 1/aScale.getY() );
369                 aTransform = aTransform * aScaleCorrection;
370 
371                 rendering::RenderState aLocalState( maState );
372                 ::canvas::tools::setRenderStateTransform(aLocalState, aTransform);
373 
374                 if(aLocalState.Clip.is())
375                 {
376                     // tdf#95709
377                     // Adjust renderstate clip to modified scale from above
378                     ::basegfx::B2DPolyPolygon aClip = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(aLocalState.Clip);
379                     aClip.transform(basegfx::utils::createScaleB2DHomMatrix(aScale));
380                     aLocalState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(mpCanvas->getUNOCanvas()->getDevice(), aClip);
381                 }
382 
383 #if OSL_DEBUG_LEVEL > 2
384                 aLocalState.Clip.clear();
385                 aLocalState.DeviceColor =
386                     vcl::unotools::colorToDoubleSequence(
387                         ::Color( 0x80FF0000 ),
388                         mpCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
389 
390                 if( maState.Clip.is() )
391                     mpCanvas->getUNOCanvas()->fillPolyPolygon( maState.Clip,
392                                                                mpCanvas->getViewState(),
393                                                                aLocalState );
394 
395                 aLocalState.DeviceColor = maState.DeviceColor;
396 #endif
397 
398                 // no further alpha changes necessary -> draw directly
399                 mpCanvas->getUNOCanvas()->drawBitmap( mxBufferBitmap,
400                                                       mpCanvas->getViewState(),
401                                                       aLocalState );
402                 return true;
403             }
404 
405             // TODO(P3): The whole float transparency handling is a mess,
406             // this should be refactored. What's more, the old idea of
407             // having only internal 'metaactions', and not the original
408             // GDIMetaFile now looks a lot less attractive. Try to move
409             // into the direction of having a direct GDIMetaFile2XCanvas
410             // renderer, and maybe a separate metafile XCanvas
411             // implementation.
render(const::basegfx::B2DHomMatrix & rTransformation) const412             bool TransparencyGroupAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
413             {
414                 Subset aSubset;
415 
416                 aSubset.mnSubsetBegin = 0;
417                 aSubset.mnSubsetEnd   = -1;
418 
419                 return renderSubset( rTransformation, aSubset );
420             }
421 
getBounds(const::basegfx::B2DHomMatrix & rTransformation) const422             ::basegfx::B2DRange TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix&  rTransformation ) const
423             {
424                 rendering::RenderState aLocalState( maState );
425                 ::canvas::tools::prependToRenderState(aLocalState, rTransformation);
426 
427                 return tools::calcDevicePixelBounds(
428                     ::basegfx::B2DRange( 0,0,
429                                          maDstSize.getX(),
430                                          maDstSize.getY() ),
431                     mpCanvas->getViewState(),
432                     aLocalState );
433             }
434 
getBounds(const::basegfx::B2DHomMatrix & rTransformation,const Subset & rSubset) const435             ::basegfx::B2DRange TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix&  rTransformation,
436                                                                     const Subset&                   rSubset ) const
437             {
438                 // TODO(F3): Currently, the bounds for
439                 // TransparencyGroupAction subsets equal those of the
440                 // full set, although this action is able to render
441                 // true subsets.
442 
443                 // polygon only contains a single action, empty bounds
444                 // if subset requests different range
445                 if( rSubset.mnSubsetBegin != 0 ||
446                     rSubset.mnSubsetEnd != 1 )
447                     return ::basegfx::B2DRange();
448 
449                 return getBounds( rTransformation );
450             }
451 
getActionCount() const452             sal_Int32 TransparencyGroupAction::getActionCount() const
453             {
454                 return mpGroupMtf ? mpGroupMtf->GetActionSize() : 0;
455             }
456 
457         }
458 
createTransparencyGroupAction(MtfAutoPtr && rGroupMtf,GradientAutoPtr && rAlphaGradient,const::basegfx::B2DPoint & rDstPoint,const::basegfx::B2DVector & rDstSize,const CanvasSharedPtr & rCanvas,const OutDevState & rState)459         std::shared_ptr<Action> TransparencyGroupActionFactory::createTransparencyGroupAction( MtfAutoPtr&&                 rGroupMtf,
460                                                                                        GradientAutoPtr&&            rAlphaGradient,
461                                                                                        const ::basegfx::B2DPoint&   rDstPoint,
462                                                                                        const ::basegfx::B2DVector&  rDstSize,
463                                                                                        const CanvasSharedPtr&       rCanvas,
464                                                                                        const OutDevState&           rState )
465         {
466             return std::make_shared<TransparencyGroupAction>(std::move(rGroupMtf),
467                                                              std::move(rAlphaGradient),
468                                                              rDstPoint,
469                                                              rDstSize,
470                                                              rCanvas,
471                                                              rState );
472         }
473 
474 }
475 
476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
477