| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_slideshow.hxx" |
| |
| // must be first |
| #include <canvas/debug.hxx> |
| #include <tools/diagnose_ex.h> |
| #include <gdimtftools.hxx> |
| |
| #include <com/sun/star/document/XExporter.hpp> |
| #include <com/sun/star/document/XFilter.hpp> |
| #include <com/sun/star/graphic/XGraphic.hpp> |
| #include <com/sun/star/graphic/XGraphicRenderer.hpp> |
| #include <com/sun/star/drawing/XShape.hpp> |
| |
| #include <cppuhelper/basemutex.hxx> |
| #include <cppuhelper/compbase1.hxx> |
| |
| #include <comphelper/uno3.hxx> |
| #include <cppuhelper/implbase1.hxx> |
| |
| #include <tools/stream.hxx> |
| #include <vcl/svapp.hxx> |
| #include <vcl/canvastools.hxx> |
| #include <vcl/metaact.hxx> |
| #include <vcl/virdev.hxx> |
| #include <vcl/gdimtf.hxx> |
| #include <vcl/metaact.hxx> |
| #include <vcl/animate.hxx> |
| #include <vcl/graph.hxx> |
| |
| #include <unotools/streamwrap.hxx> |
| |
| #include "tools.hxx" |
| |
| using namespace ::com::sun::star; |
| |
| |
| // free support functions |
| // ====================== |
| |
| namespace slideshow |
| { |
| namespace internal |
| { |
| // TODO(E2): Detect the case when svx/drawing layer is not |
| // in-process, or even not on the same machine, and |
| // fallback to metafile streaming! |
| |
| // For fixing #i48102#, have to be a _lot_ more selective |
| // on which metafiles to convert to bitmaps. The problem |
| // here is that we _always_ get the shape content as a |
| // metafile, even if we have a bitmap graphic shape. Thus, |
| // calling GetBitmapEx on such a Graphic (see below) will |
| // result in one poorly scaled bitmap into another, |
| // somewhat arbitrarily sized bitmap. |
| bool hasUnsupportedActions( const GDIMetaFile& rMtf ) |
| { |
| // search metafile for RasterOp action |
| MetaAction* pCurrAct; |
| |
| // TODO(Q3): avoid const-cast |
| for( pCurrAct = const_cast<GDIMetaFile&>(rMtf).FirstAction(); |
| pCurrAct; |
| pCurrAct = const_cast<GDIMetaFile&>(rMtf).NextAction() ) |
| { |
| switch( pCurrAct->GetType() ) |
| { |
| case META_RASTEROP_ACTION: |
| // overpaint is okay - that's the default, anyway |
| if( ROP_OVERPAINT == |
| static_cast<MetaRasterOpAction*>(pCurrAct)->GetRasterOp() ) |
| { |
| break; |
| } |
| // FALLTHROUGH intended |
| case META_MOVECLIPREGION_ACTION: |
| // FALLTHROUGH intended |
| case META_REFPOINT_ACTION: |
| // FALLTHROUGH intended |
| case META_WALLPAPER_ACTION: |
| return true; // at least one unsupported |
| // action encountered |
| } |
| } |
| |
| return false; // no unsupported action found |
| } |
| |
| namespace { |
| |
| typedef ::cppu::WeakComponentImplHelper1< graphic::XGraphicRenderer > DummyRenderer_Base; |
| |
| class DummyRenderer : |
| public DummyRenderer_Base, |
| public cppu::BaseMutex |
| { |
| public: |
| DummyRenderer() : |
| DummyRenderer_Base( m_aMutex ), |
| mxGraphic() |
| { |
| } |
| |
| //--- XGraphicRenderer ----------------------------------- |
| virtual void SAL_CALL render( const uno::Reference< graphic::XGraphic >& rGraphic ) throw (uno::RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| mxGraphic = rGraphic; |
| } |
| |
| /** Retrieve GDIMetaFile from renderer |
| |
| @param bForeignSource |
| When true, the source of the metafile might be a |
| foreign application. The metafile is checked |
| against unsupported content, and, if necessary, |
| returned as a pre-rendererd bitmap. |
| */ |
| GDIMetaFile getMtf( bool bForeignSource ) const |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| |
| Graphic aGraphic( mxGraphic ); |
| |
| if( aGraphic.GetType() == GRAPHIC_BITMAP || |
| (bForeignSource && |
| hasUnsupportedActions(aGraphic.GetGDIMetaFile()) ) ) |
| { |
| // wrap bitmap into GDIMetafile |
| GDIMetaFile aMtf; |
| ::Point aEmptyPoint; |
| |
| ::BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); |
| |
| aMtf.AddAction( new MetaBmpExAction( aEmptyPoint, |
| aBmpEx ) ); |
| aMtf.SetPrefSize( aBmpEx.GetPrefSize() ); |
| aMtf.SetPrefMapMode( aBmpEx.GetPrefMapMode() ); |
| |
| return aMtf; |
| } |
| else |
| { |
| return aGraphic.GetGDIMetaFile(); |
| } |
| } |
| |
| private: |
| uno::Reference< graphic::XGraphic > mxGraphic; |
| }; |
| |
| } // anon namespace |
| |
| // Quick'n'dirty way: tunnel Graphic (only works for |
| // in-process slideshow, of course) |
| bool getMetaFile( const uno::Reference< lang::XComponent >& xSource, |
| const uno::Reference< drawing::XDrawPage >& xContainingPage, |
| GDIMetaFile& rMtf, |
| int mtfLoadFlags, |
| const uno::Reference< uno::XComponentContext >& rxContext ) |
| { |
| ENSURE_OR_RETURN_FALSE( rxContext.is(), |
| "getMetaFile(): Invalid context" ); |
| |
| // create dummy XGraphicRenderer, which receives the |
| // generated XGraphic from the GraphicExporter |
| |
| // TODO(P3): Move creation of DummyRenderer out of the |
| // loop! Either by making it static, or transforming |
| // the whole thing here into a class. |
| DummyRenderer* pRenderer( new DummyRenderer() ); |
| uno::Reference< graphic::XGraphicRenderer > xRenderer( pRenderer ); |
| |
| // -> stuff that into UnoGraphicExporter. |
| uno::Reference<lang::XMultiComponentFactory> xFactory( |
| rxContext->getServiceManager() ); |
| |
| OSL_ENSURE( xFactory.is(), "### no UNO?!" ); |
| if( !xFactory.is() ) |
| return false; |
| |
| // creating the graphic exporter |
| uno::Reference< document::XExporter > xExporter( |
| xFactory->createInstanceWithContext( |
| OUSTR("com.sun.star.drawing.GraphicExportFilter"), |
| rxContext), |
| uno::UNO_QUERY ); |
| uno::Reference< document::XFilter > xFilter( xExporter, uno::UNO_QUERY ); |
| |
| OSL_ENSURE( xExporter.is() && xFilter.is(), "### no graphic exporter?!" ); |
| if( !xExporter.is() || !xFilter.is() ) |
| return false; |
| |
| uno::Sequence< beans::PropertyValue > aProps(3); |
| aProps[0].Name = OUSTR("FilterName"); |
| aProps[0].Value <<= OUSTR("SVM"); |
| |
| aProps[1].Name = OUSTR("GraphicRenderer"); |
| aProps[1].Value <<= xRenderer; |
| |
| uno::Sequence< beans::PropertyValue > aFilterData(5); |
| aFilterData[0].Name = OUSTR("VerboseComments"); |
| aFilterData[0].Value <<= ((mtfLoadFlags & MTF_LOAD_VERBOSE_COMMENTS) != 0); |
| |
| aFilterData[1].Name = OUSTR("ScrollText"); |
| aFilterData[1].Value <<= ((mtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != 0); |
| |
| aFilterData[2].Name = OUSTR("ExportOnlyBackground"); |
| aFilterData[2].Value <<= ((mtfLoadFlags & MTF_LOAD_BACKGROUND_ONLY) != 0); |
| |
| aFilterData[3].Name = OUSTR("Version"); |
| aFilterData[3].Value <<= static_cast<sal_Int32>( SOFFICE_FILEFORMAT_50 ); |
| |
| aFilterData[4].Name = OUSTR("CurrentPage"); |
| aFilterData[4].Value <<= uno::Reference< uno::XInterface >( xContainingPage, |
| uno::UNO_QUERY_THROW ); |
| |
| aProps[2].Name = OUSTR("FilterData"); |
| aProps[2].Value <<= aFilterData; |
| |
| xExporter->setSourceDocument( xSource ); |
| if( !xFilter->filter( aProps ) ) |
| return false; |
| |
| rMtf = pRenderer->getMtf( (mtfLoadFlags & MTF_LOAD_FOREIGN_SOURCE) != 0 ); |
| |
| // pRenderer is automatically destroyed when xRenderer |
| // goes out of scope |
| |
| // TODO(E3): Error handling. Exporter might have |
| // generated nothing, a bitmap, threw an exception, |
| // whatever. |
| return true; |
| } |
| |
| void removeTextActions( GDIMetaFile& rMtf ) |
| { |
| // search metafile for text output |
| MetaAction* pCurrAct; |
| |
| int nActionIndex(0); |
| pCurrAct = rMtf.FirstAction(); |
| while( pCurrAct ) |
| { |
| switch( pCurrAct->GetType() ) |
| { |
| case META_TEXTCOLOR_ACTION: |
| case META_TEXTFILLCOLOR_ACTION: |
| case META_TEXTLINECOLOR_ACTION: |
| case META_TEXTALIGN_ACTION: |
| case META_FONT_ACTION: |
| case META_LAYOUTMODE_ACTION: |
| case META_TEXT_ACTION: |
| case META_TEXTARRAY_ACTION: |
| case META_TEXTRECT_ACTION: |
| case META_STRETCHTEXT_ACTION: |
| case META_TEXTLINE_ACTION: |
| { |
| // remove every text-related actions |
| pCurrAct = rMtf.NextAction(); |
| |
| rMtf.RemoveAction( nActionIndex ); |
| break; |
| } |
| |
| default: |
| pCurrAct = rMtf.NextAction(); |
| ++nActionIndex; |
| break; |
| } |
| } |
| } |
| |
| sal_Int32 getNextActionOffset( MetaAction * pCurrAct ) |
| { |
| // Special handling for actions that represent |
| // more than one indexable action |
| // =========================================== |
| |
| switch (pCurrAct->GetType()) { |
| case META_TEXT_ACTION: { |
| MetaTextAction * pAct = static_cast<MetaTextAction *>(pCurrAct); |
| return (pAct->GetLen() == (sal_uInt16)STRING_LEN |
| ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen()); |
| } |
| case META_TEXTARRAY_ACTION: { |
| MetaTextArrayAction * pAct = |
| static_cast<MetaTextArrayAction *>(pCurrAct); |
| return (pAct->GetLen() == (sal_uInt16)STRING_LEN |
| ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen()); |
| } |
| case META_STRETCHTEXT_ACTION: { |
| MetaStretchTextAction * pAct = |
| static_cast<MetaStretchTextAction *>(pCurrAct); |
| return (pAct->GetLen() == (sal_uInt16)STRING_LEN |
| ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen()); |
| } |
| case META_FLOATTRANSPARENT_ACTION: { |
| MetaFloatTransparentAction * pAct = |
| static_cast<MetaFloatTransparentAction*>(pCurrAct); |
| // TODO(F2): Recurse into action metafile |
| // (though this is currently not used from the |
| // DrawingLayer - shape transparency gradients |
| // don't affect shape text) |
| return pAct->GetGDIMetaFile().GetActionCount(); |
| } |
| default: |
| return 1; |
| } |
| } |
| |
| bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames, |
| ::std::size_t& o_rLoopCount, |
| CycleMode& o_eCycleMode, |
| const Graphic& rGraphic ) |
| { |
| o_rFrames.clear(); |
| |
| if( !rGraphic.IsAnimated() ) |
| return false; |
| |
| // some loop invariants |
| Animation aAnimation( rGraphic.GetAnimation() ); |
| const Point aEmptyPoint; |
| const Size aAnimSize( aAnimation.GetDisplaySizePixel() ); |
| |
| // setup VDev, into which all bitmaps are painted (want to |
| // normalize animations to n bitmaps of same size. An Animation, |
| // though, can contain bitmaps of varying sizes and different |
| // update modes) |
| VirtualDevice aVDev; |
| aVDev.SetOutputSizePixel( aAnimSize ); |
| aVDev.EnableMapMode( sal_False ); |
| |
| // setup mask VDev (alpha VDev is currently rather slow) |
| VirtualDevice aVDevMask; |
| aVDevMask.SetOutputSizePixel( aAnimSize ); |
| aVDevMask.EnableMapMode( sal_False ); |
| |
| switch( aAnimation.GetCycleMode() ) |
| { |
| case CYCLE_NOT: |
| o_rLoopCount = 1; |
| o_eCycleMode = CYCLE_LOOP; |
| break; |
| |
| case CYCLE_FALLBACK: |
| // FALLTHROUGH intended |
| case CYCLE_NORMAL: |
| o_rLoopCount = aAnimation.GetLoopCount(); |
| o_eCycleMode = CYCLE_LOOP; |
| break; |
| |
| case CYCLE_REVERS: |
| // FALLTHROUGH intended |
| case CYCLE_REVERS_FALLBACK: |
| o_rLoopCount = aAnimation.GetLoopCount(); |
| o_eCycleMode = CYCLE_PINGPONGLOOP; |
| break; |
| |
| default: |
| ENSURE_OR_RETURN_FALSE(false, |
| "getAnimationFromGraphic(): Unexpected case" ); |
| break; |
| } |
| |
| for( sal_uInt16 i=0, nCount=aAnimation.Count(); i<nCount; ++i ) |
| { |
| const AnimationBitmap& rAnimBmp( aAnimation.Get(i) ); |
| switch(rAnimBmp.eDisposal) |
| { |
| case DISPOSE_NOT: |
| { |
| aVDev.DrawBitmapEx(rAnimBmp.aPosPix, |
| rAnimBmp.aBmpEx); |
| Bitmap aMask = rAnimBmp.aBmpEx.GetMask(); |
| |
| if( aMask.IsEmpty() ) |
| { |
| const Point aEmpty; |
| const Rectangle aRect(aEmptyPoint, |
| aVDevMask.GetOutputSizePixel()); |
| const Wallpaper aWallpaper(COL_BLACK); |
| aVDevMask.DrawWallpaper(aRect, |
| aWallpaper); |
| } |
| else |
| { |
| BitmapEx aTmpMask = BitmapEx(aMask, |
| aMask); |
| aVDevMask.DrawBitmapEx(rAnimBmp.aPosPix, |
| aTmpMask ); |
| } |
| break; |
| } |
| |
| case DISPOSE_BACK: |
| { |
| // #i70772# react on no mask |
| const Bitmap aMask(rAnimBmp.aBmpEx.GetMask()); |
| const Bitmap aContent(rAnimBmp.aBmpEx.GetBitmap()); |
| |
| aVDevMask.Erase(); |
| aVDev.DrawBitmap(rAnimBmp.aPosPix, aContent); |
| |
| if(aMask.IsEmpty()) |
| { |
| const Rectangle aRect(rAnimBmp.aPosPix, aContent.GetSizePixel()); |
| aVDevMask.SetFillColor(COL_BLACK); |
| aVDevMask.SetLineColor(); |
| aVDevMask.DrawRect(aRect); |
| } |
| else |
| { |
| aVDevMask.DrawBitmap(rAnimBmp.aPosPix, aMask); |
| } |
| break; |
| } |
| |
| case DISPOSE_FULL: |
| { |
| aVDev.DrawBitmapEx(rAnimBmp.aPosPix, |
| rAnimBmp.aBmpEx); |
| break; |
| } |
| |
| case DISPOSE_PREVIOUS : |
| { |
| aVDev.DrawBitmapEx(rAnimBmp.aPosPix, |
| rAnimBmp.aBmpEx); |
| aVDevMask.DrawBitmap(rAnimBmp.aPosPix, |
| rAnimBmp.aBmpEx.GetMask()); |
| break; |
| } |
| } |
| |
| // extract current aVDev content into a new animation |
| // frame |
| GDIMetaFileSharedPtr pMtf( new GDIMetaFile() ); |
| pMtf->AddAction( |
| new MetaBmpExAction( aEmptyPoint, |
| BitmapEx( |
| aVDev.GetBitmap( |
| aEmptyPoint, |
| aAnimSize ), |
| aVDevMask.GetBitmap( |
| aEmptyPoint, |
| aAnimSize )))); |
| |
| // setup mtf dimensions and pref map mode (for |
| // simplicity, keep it all in pixel. the metafile |
| // renderer scales it down to (1, 1) box anyway) |
| pMtf->SetPrefMapMode( MapMode() ); |
| pMtf->SetPrefSize( aAnimSize ); |
| |
| // #115934# |
| // Take care of special value for MultiPage TIFFs. ATM these shall just |
| // show their first page for _quite_ some time. |
| sal_Int32 nWaitTime100thSeconds( rAnimBmp.nWait ); |
| if( ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds ) |
| { |
| // ATM the huge value would block the timer, so use a long |
| // time to show first page (whole day) |
| nWaitTime100thSeconds = 100 * 60 * 60 * 24; |
| } |
| |
| // There are animated GIFs with no WaitTime set. Take 0.1 sec, the |
| // same duration that is used by the edit view. |
| if( nWaitTime100thSeconds == 0 ) |
| nWaitTime100thSeconds = 10; |
| |
| o_rFrames.push_back( MtfAnimationFrame( pMtf, |
| nWaitTime100thSeconds / 100.0 ) ); |
| } |
| |
| return !o_rFrames.empty(); |
| } |
| |
| bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle& o_rScrollRect, |
| ::basegfx::B2DRectangle& o_rPaintRect, |
| const GDIMetaFileSharedPtr& rMtf ) |
| { |
| // extract bounds: scroll rect, paint rect |
| bool bScrollRectSet(false); |
| bool bPaintRectSet(false); |
| |
| for ( MetaAction * pCurrAct = rMtf->FirstAction(); |
| pCurrAct != 0; pCurrAct = rMtf->NextAction() ) |
| { |
| if (pCurrAct->GetType() == META_COMMENT_ACTION) |
| { |
| MetaCommentAction * pAct = |
| static_cast<MetaCommentAction *>(pCurrAct); |
| // skip comment if not a special XTEXT comment |
| if (pAct->GetComment().CompareIgnoreCaseToAscii( |
| RTL_CONSTASCII_STRINGPARAM("XTEXT") ) == COMPARE_EQUAL) |
| { |
| if (pAct->GetComment().CompareIgnoreCaseToAscii( |
| "XTEXT_SCROLLRECT" ) == COMPARE_EQUAL) { |
| o_rScrollRect = ::vcl::unotools::b2DRectangleFromRectangle( |
| *reinterpret_cast<Rectangle const *>( |
| pAct->GetData() ) ); |
| |
| bScrollRectSet = true; |
| } |
| else if (pAct->GetComment().CompareIgnoreCaseToAscii( |
| "XTEXT_PAINTRECT" ) == COMPARE_EQUAL) { |
| o_rPaintRect = ::vcl::unotools::b2DRectangleFromRectangle( |
| *reinterpret_cast<Rectangle const *>( |
| pAct->GetData() ) ); |
| |
| bPaintRectSet = true; |
| } |
| } |
| } |
| } |
| |
| return bScrollRectSet && bPaintRectSet; |
| } |
| |
| } |
| } |
| |