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