1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_slideshow.hxx" 26 27 // must be first 28 #include <canvas/debug.hxx> 29 #include <tools/diagnose_ex.h> 30 #include <gdimtftools.hxx> 31 32 #include <com/sun/star/document/XExporter.hpp> 33 #include <com/sun/star/document/XFilter.hpp> 34 #include <com/sun/star/graphic/XGraphic.hpp> 35 #include <com/sun/star/graphic/XGraphicRenderer.hpp> 36 #include <com/sun/star/drawing/XShape.hpp> 37 38 #include <cppuhelper/basemutex.hxx> 39 #include <cppuhelper/compbase1.hxx> 40 41 #include <comphelper/uno3.hxx> 42 #include <cppuhelper/implbase1.hxx> 43 44 #include <tools/stream.hxx> 45 #include <vcl/svapp.hxx> 46 #include <vcl/canvastools.hxx> 47 #include <vcl/metaact.hxx> 48 #include <vcl/virdev.hxx> 49 #include <vcl/gdimtf.hxx> 50 #include <vcl/metaact.hxx> 51 #include <vcl/animate.hxx> 52 #include <vcl/graph.hxx> 53 54 #include <unotools/streamwrap.hxx> 55 56 #include "tools.hxx" 57 58 using namespace ::com::sun::star; 59 60 61 // free support functions 62 // ====================== 63 64 namespace slideshow 65 { 66 namespace internal 67 { 68 // TODO(E2): Detect the case when svx/drawing layer is not 69 // in-process, or even not on the same machine, and 70 // fallback to metafile streaming! 71 72 // For fixing #i48102#, have to be a _lot_ more selective 73 // on which metafiles to convert to bitmaps. The problem 74 // here is that we _always_ get the shape content as a 75 // metafile, even if we have a bitmap graphic shape. Thus, 76 // calling GetBitmapEx on such a Graphic (see below) will 77 // result in one poorly scaled bitmap into another, 78 // somewhat arbitrarily sized bitmap. 79 bool hasUnsupportedActions( const GDIMetaFile& rMtf ) 80 { 81 // search metafile for RasterOp action 82 MetaAction* pCurrAct; 83 84 // TODO(Q3): avoid const-cast 85 for( pCurrAct = const_cast<GDIMetaFile&>(rMtf).FirstAction(); 86 pCurrAct; 87 pCurrAct = const_cast<GDIMetaFile&>(rMtf).NextAction() ) 88 { 89 switch( pCurrAct->GetType() ) 90 { 91 case META_RASTEROP_ACTION: 92 // overpaint is okay - that's the default, anyway 93 if( ROP_OVERPAINT == 94 static_cast<MetaRasterOpAction*>(pCurrAct)->GetRasterOp() ) 95 { 96 break; 97 } 98 // FALLTHROUGH intended 99 case META_MOVECLIPREGION_ACTION: 100 // FALLTHROUGH intended 101 case META_REFPOINT_ACTION: 102 // FALLTHROUGH intended 103 case META_WALLPAPER_ACTION: 104 return true; // at least one unsupported 105 // action encountered 106 } 107 } 108 109 return false; // no unsupported action found 110 } 111 112 namespace { 113 114 typedef ::cppu::WeakComponentImplHelper1< graphic::XGraphicRenderer > DummyRenderer_Base; 115 116 class DummyRenderer : 117 public DummyRenderer_Base, 118 public cppu::BaseMutex 119 { 120 public: 121 DummyRenderer() : 122 DummyRenderer_Base( m_aMutex ), 123 mxGraphic() 124 { 125 } 126 127 //--- XGraphicRenderer ----------------------------------- 128 virtual void SAL_CALL render( const uno::Reference< graphic::XGraphic >& rGraphic ) throw (uno::RuntimeException) 129 { 130 ::osl::MutexGuard aGuard( m_aMutex ); 131 mxGraphic = rGraphic; 132 } 133 134 /** Retrieve GDIMetaFile from renderer 135 136 @param bForeignSource 137 When true, the source of the metafile might be a 138 foreign application. The metafile is checked 139 against unsupported content, and, if necessary, 140 returned as a pre-rendererd bitmap. 141 */ 142 GDIMetaFile getMtf( bool bForeignSource ) const 143 { 144 ::osl::MutexGuard aGuard( m_aMutex ); 145 146 Graphic aGraphic( mxGraphic ); 147 148 if( aGraphic.GetType() == GRAPHIC_BITMAP || 149 (bForeignSource && 150 hasUnsupportedActions(aGraphic.GetGDIMetaFile()) ) ) 151 { 152 // wrap bitmap into GDIMetafile 153 GDIMetaFile aMtf; 154 ::Point aEmptyPoint; 155 156 ::BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); 157 158 aMtf.AddAction( new MetaBmpExAction( aEmptyPoint, 159 aBmpEx ) ); 160 aMtf.SetPrefSize( aBmpEx.GetPrefSize() ); 161 aMtf.SetPrefMapMode( aBmpEx.GetPrefMapMode() ); 162 163 return aMtf; 164 } 165 else 166 { 167 return aGraphic.GetGDIMetaFile(); 168 } 169 } 170 171 private: 172 uno::Reference< graphic::XGraphic > mxGraphic; 173 }; 174 175 } // anon namespace 176 177 // Quick'n'dirty way: tunnel Graphic (only works for 178 // in-process slideshow, of course) 179 bool getMetaFile( const uno::Reference< lang::XComponent >& xSource, 180 const uno::Reference< drawing::XDrawPage >& xContainingPage, 181 GDIMetaFile& rMtf, 182 int mtfLoadFlags, 183 const uno::Reference< uno::XComponentContext >& rxContext ) 184 { 185 ENSURE_OR_RETURN_FALSE( rxContext.is(), 186 "getMetaFile(): Invalid context" ); 187 188 // create dummy XGraphicRenderer, which receives the 189 // generated XGraphic from the GraphicExporter 190 191 // TODO(P3): Move creation of DummyRenderer out of the 192 // loop! Either by making it static, or transforming 193 // the whole thing here into a class. 194 DummyRenderer* pRenderer( new DummyRenderer() ); 195 uno::Reference< graphic::XGraphicRenderer > xRenderer( pRenderer ); 196 197 // -> stuff that into UnoGraphicExporter. 198 uno::Reference<lang::XMultiComponentFactory> xFactory( 199 rxContext->getServiceManager() ); 200 201 OSL_ENSURE( xFactory.is(), "### no UNO?!" ); 202 if( !xFactory.is() ) 203 return false; 204 205 // creating the graphic exporter 206 uno::Reference< document::XExporter > xExporter( 207 xFactory->createInstanceWithContext( 208 OUSTR("com.sun.star.drawing.GraphicExportFilter"), 209 rxContext), 210 uno::UNO_QUERY ); 211 uno::Reference< document::XFilter > xFilter( xExporter, uno::UNO_QUERY ); 212 213 OSL_ENSURE( xExporter.is() && xFilter.is(), "### no graphic exporter?!" ); 214 if( !xExporter.is() || !xFilter.is() ) 215 return false; 216 217 uno::Sequence< beans::PropertyValue > aProps(3); 218 aProps[0].Name = OUSTR("FilterName"); 219 aProps[0].Value <<= OUSTR("SVM"); 220 221 aProps[1].Name = OUSTR("GraphicRenderer"); 222 aProps[1].Value <<= xRenderer; 223 224 uno::Sequence< beans::PropertyValue > aFilterData(5); 225 aFilterData[0].Name = OUSTR("VerboseComments"); 226 aFilterData[0].Value <<= ((mtfLoadFlags & MTF_LOAD_VERBOSE_COMMENTS) != 0); 227 228 aFilterData[1].Name = OUSTR("ScrollText"); 229 aFilterData[1].Value <<= ((mtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != 0); 230 231 aFilterData[2].Name = OUSTR("ExportOnlyBackground"); 232 aFilterData[2].Value <<= ((mtfLoadFlags & MTF_LOAD_BACKGROUND_ONLY) != 0); 233 234 aFilterData[3].Name = OUSTR("Version"); 235 aFilterData[3].Value <<= static_cast<sal_Int32>( SOFFICE_FILEFORMAT_50 ); 236 237 aFilterData[4].Name = OUSTR("CurrentPage"); 238 aFilterData[4].Value <<= uno::Reference< uno::XInterface >( xContainingPage, 239 uno::UNO_QUERY_THROW ); 240 241 aProps[2].Name = OUSTR("FilterData"); 242 aProps[2].Value <<= aFilterData; 243 244 xExporter->setSourceDocument( xSource ); 245 if( !xFilter->filter( aProps ) ) 246 return false; 247 248 rMtf = pRenderer->getMtf( (mtfLoadFlags & MTF_LOAD_FOREIGN_SOURCE) != 0 ); 249 250 // pRenderer is automatically destroyed when xRenderer 251 // goes out of scope 252 253 // TODO(E3): Error handling. Exporter might have 254 // generated nothing, a bitmap, threw an exception, 255 // whatever. 256 return true; 257 } 258 259 void removeTextActions( GDIMetaFile& rMtf ) 260 { 261 // search metafile for text output 262 MetaAction* pCurrAct; 263 264 int nActionIndex(0); 265 pCurrAct = rMtf.FirstAction(); 266 while( pCurrAct ) 267 { 268 switch( pCurrAct->GetType() ) 269 { 270 case META_TEXTCOLOR_ACTION: 271 case META_TEXTFILLCOLOR_ACTION: 272 case META_TEXTLINECOLOR_ACTION: 273 case META_TEXTALIGN_ACTION: 274 case META_FONT_ACTION: 275 case META_LAYOUTMODE_ACTION: 276 case META_TEXT_ACTION: 277 case META_TEXTARRAY_ACTION: 278 case META_TEXTRECT_ACTION: 279 case META_STRETCHTEXT_ACTION: 280 case META_TEXTLINE_ACTION: 281 { 282 // remove every text-related actions 283 pCurrAct = rMtf.NextAction(); 284 285 rMtf.RemoveAction( nActionIndex ); 286 break; 287 } 288 289 default: 290 pCurrAct = rMtf.NextAction(); 291 ++nActionIndex; 292 break; 293 } 294 } 295 } 296 297 sal_Int32 getNextActionOffset( MetaAction * pCurrAct ) 298 { 299 // Special handling for actions that represent 300 // more than one indexable action 301 // =========================================== 302 303 switch (pCurrAct->GetType()) { 304 case META_TEXT_ACTION: { 305 MetaTextAction * pAct = static_cast<MetaTextAction *>(pCurrAct); 306 return (pAct->GetLen() == (sal_uInt16)STRING_LEN 307 ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen()); 308 } 309 case META_TEXTARRAY_ACTION: { 310 MetaTextArrayAction * pAct = 311 static_cast<MetaTextArrayAction *>(pCurrAct); 312 return (pAct->GetLen() == (sal_uInt16)STRING_LEN 313 ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen()); 314 } 315 case META_STRETCHTEXT_ACTION: { 316 MetaStretchTextAction * pAct = 317 static_cast<MetaStretchTextAction *>(pCurrAct); 318 return (pAct->GetLen() == (sal_uInt16)STRING_LEN 319 ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen()); 320 } 321 case META_FLOATTRANSPARENT_ACTION: { 322 MetaFloatTransparentAction * pAct = 323 static_cast<MetaFloatTransparentAction*>(pCurrAct); 324 // TODO(F2): Recurse into action metafile 325 // (though this is currently not used from the 326 // DrawingLayer - shape transparency gradients 327 // don't affect shape text) 328 return pAct->GetGDIMetaFile().GetActionCount(); 329 } 330 default: 331 return 1; 332 } 333 } 334 335 bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames, 336 ::std::size_t& o_rLoopCount, 337 CycleMode& o_eCycleMode, 338 const Graphic& rGraphic ) 339 { 340 o_rFrames.clear(); 341 342 if( !rGraphic.IsAnimated() ) 343 return false; 344 345 // some loop invariants 346 Animation aAnimation( rGraphic.GetAnimation() ); 347 const Point aEmptyPoint; 348 const Size aAnimSize( aAnimation.GetDisplaySizePixel() ); 349 350 // setup VDev, into which all bitmaps are painted (want to 351 // normalize animations to n bitmaps of same size. An Animation, 352 // though, can contain bitmaps of varying sizes and different 353 // update modes) 354 VirtualDevice aVDev; 355 aVDev.SetOutputSizePixel( aAnimSize ); 356 aVDev.EnableMapMode( sal_False ); 357 358 // setup mask VDev (alpha VDev is currently rather slow) 359 VirtualDevice aVDevMask; 360 aVDevMask.SetOutputSizePixel( aAnimSize ); 361 aVDevMask.EnableMapMode( sal_False ); 362 363 switch( aAnimation.GetCycleMode() ) 364 { 365 case CYCLE_NOT: 366 o_rLoopCount = 1; 367 o_eCycleMode = CYCLE_LOOP; 368 break; 369 370 case CYCLE_FALLBACK: 371 // FALLTHROUGH intended 372 case CYCLE_NORMAL: 373 o_rLoopCount = aAnimation.GetLoopCount(); 374 o_eCycleMode = CYCLE_LOOP; 375 break; 376 377 case CYCLE_REVERS: 378 // FALLTHROUGH intended 379 case CYCLE_REVERS_FALLBACK: 380 o_rLoopCount = aAnimation.GetLoopCount(); 381 o_eCycleMode = CYCLE_PINGPONGLOOP; 382 break; 383 384 default: 385 ENSURE_OR_RETURN_FALSE(false, 386 "getAnimationFromGraphic(): Unexpected case" ); 387 break; 388 } 389 390 for( sal_uInt16 i=0, nCount=aAnimation.Count(); i<nCount; ++i ) 391 { 392 const AnimationBitmap& rAnimBmp( aAnimation.Get(i) ); 393 switch(rAnimBmp.eDisposal) 394 { 395 case DISPOSE_NOT: 396 { 397 aVDev.DrawBitmapEx(rAnimBmp.aPosPix, 398 rAnimBmp.aBmpEx); 399 Bitmap aMask = rAnimBmp.aBmpEx.GetMask(); 400 401 if( aMask.IsEmpty() ) 402 { 403 const Point aEmpty; 404 const Rectangle aRect(aEmptyPoint, 405 aVDevMask.GetOutputSizePixel()); 406 const Wallpaper aWallpaper(COL_BLACK); 407 aVDevMask.DrawWallpaper(aRect, 408 aWallpaper); 409 } 410 else 411 { 412 BitmapEx aTmpMask = BitmapEx(aMask, 413 aMask); 414 aVDevMask.DrawBitmapEx(rAnimBmp.aPosPix, 415 aTmpMask ); 416 } 417 break; 418 } 419 420 case DISPOSE_BACK: 421 { 422 // #i70772# react on no mask 423 const Bitmap aMask(rAnimBmp.aBmpEx.GetMask()); 424 const Bitmap aContent(rAnimBmp.aBmpEx.GetBitmap()); 425 426 aVDevMask.Erase(); 427 aVDev.DrawBitmap(rAnimBmp.aPosPix, aContent); 428 429 if(aMask.IsEmpty()) 430 { 431 const Rectangle aRect(rAnimBmp.aPosPix, aContent.GetSizePixel()); 432 aVDevMask.SetFillColor(COL_BLACK); 433 aVDevMask.SetLineColor(); 434 aVDevMask.DrawRect(aRect); 435 } 436 else 437 { 438 aVDevMask.DrawBitmap(rAnimBmp.aPosPix, aMask); 439 } 440 break; 441 } 442 443 case DISPOSE_FULL: 444 { 445 aVDev.DrawBitmapEx(rAnimBmp.aPosPix, 446 rAnimBmp.aBmpEx); 447 break; 448 } 449 450 case DISPOSE_PREVIOUS : 451 { 452 aVDev.DrawBitmapEx(rAnimBmp.aPosPix, 453 rAnimBmp.aBmpEx); 454 aVDevMask.DrawBitmap(rAnimBmp.aPosPix, 455 rAnimBmp.aBmpEx.GetMask()); 456 break; 457 } 458 } 459 460 // extract current aVDev content into a new animation 461 // frame 462 GDIMetaFileSharedPtr pMtf( new GDIMetaFile() ); 463 pMtf->AddAction( 464 new MetaBmpExAction( aEmptyPoint, 465 BitmapEx( 466 aVDev.GetBitmap( 467 aEmptyPoint, 468 aAnimSize ), 469 aVDevMask.GetBitmap( 470 aEmptyPoint, 471 aAnimSize )))); 472 473 // setup mtf dimensions and pref map mode (for 474 // simplicity, keep it all in pixel. the metafile 475 // renderer scales it down to (1, 1) box anyway) 476 pMtf->SetPrefMapMode( MapMode() ); 477 pMtf->SetPrefSize( aAnimSize ); 478 479 // #115934# 480 // Take care of special value for MultiPage TIFFs. ATM these shall just 481 // show their first page for _quite_ some time. 482 sal_Int32 nWaitTime100thSeconds( rAnimBmp.nWait ); 483 if( ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds ) 484 { 485 // ATM the huge value would block the timer, so use a long 486 // time to show first page (whole day) 487 nWaitTime100thSeconds = 100 * 60 * 60 * 24; 488 } 489 490 // There are animated GIFs with no WaitTime set. Take 0.1 sec, the 491 // same duration that is used by the edit view. 492 if( nWaitTime100thSeconds == 0 ) 493 nWaitTime100thSeconds = 10; 494 495 o_rFrames.push_back( MtfAnimationFrame( pMtf, 496 nWaitTime100thSeconds / 100.0 ) ); 497 } 498 499 return !o_rFrames.empty(); 500 } 501 502 bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle& o_rScrollRect, 503 ::basegfx::B2DRectangle& o_rPaintRect, 504 const GDIMetaFileSharedPtr& rMtf ) 505 { 506 // extract bounds: scroll rect, paint rect 507 bool bScrollRectSet(false); 508 bool bPaintRectSet(false); 509 510 for ( MetaAction * pCurrAct = rMtf->FirstAction(); 511 pCurrAct != 0; pCurrAct = rMtf->NextAction() ) 512 { 513 if (pCurrAct->GetType() == META_COMMENT_ACTION) 514 { 515 MetaCommentAction * pAct = 516 static_cast<MetaCommentAction *>(pCurrAct); 517 // skip comment if not a special XTEXT comment 518 if (pAct->GetComment().CompareIgnoreCaseToAscii( 519 RTL_CONSTASCII_STRINGPARAM("XTEXT") ) == COMPARE_EQUAL) 520 { 521 if (pAct->GetComment().CompareIgnoreCaseToAscii( 522 "XTEXT_SCROLLRECT" ) == COMPARE_EQUAL) { 523 o_rScrollRect = ::vcl::unotools::b2DRectangleFromRectangle( 524 *reinterpret_cast<Rectangle const *>( 525 pAct->GetData() ) ); 526 527 bScrollRectSet = true; 528 } 529 else if (pAct->GetComment().CompareIgnoreCaseToAscii( 530 "XTEXT_PAINTRECT" ) == COMPARE_EQUAL) { 531 o_rPaintRect = ::vcl::unotools::b2DRectangleFromRectangle( 532 *reinterpret_cast<Rectangle const *>( 533 pAct->GetData() ) ); 534 535 bPaintRectSet = true; 536 } 537 } 538 } 539 } 540 541 return bScrollRectSet && bPaintRectSet; 542 } 543 544 } 545 } 546 547