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