/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ #include #include "gdimtftools.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; // free support functions // ====================== namespace slideshow::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. static bool hasUnsupportedActions( const GDIMetaFile& rMtf ) { // search metafile for RasterOp action MetaAction* pCurrAct; // TODO(Q3): avoid const-cast for( pCurrAct = const_cast(rMtf).FirstAction(); pCurrAct; pCurrAct = const_cast(rMtf).NextAction() ) { switch( pCurrAct->GetType() ) { case MetaActionType::RASTEROP: // overpaint is okay - that's the default, anyway if( RasterOp::OverPaint == static_cast(pCurrAct)->GetRasterOp() ) { break; } [[fallthrough]]; case MetaActionType::MOVECLIPREGION: case MetaActionType::REFPOINT: case MetaActionType::WALLPAPER: return true; // at least one unsupported // action encountered default: break; } } return false; // no unsupported action found } namespace { typedef ::cppu::WeakComponentImplHelper< graphic::XGraphicRenderer > DummyRenderer_Base; class DummyRenderer: public cppu::BaseMutex, public DummyRenderer_Base { public: DummyRenderer() : DummyRenderer_Base( m_aMutex ), mxGraphic() { } //--- XGraphicRenderer ----------------------------------- virtual void SAL_CALL render( const uno::Reference< graphic::XGraphic >& rGraphic ) override { ::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-rendered bitmap. */ GDIMetaFileSharedPtr getMtf( bool bForeignSource ) const { ::osl::MutexGuard aGuard( m_aMutex ); Graphic aGraphic( mxGraphic ); if( aGraphic.GetType() == GraphicType::Bitmap || (bForeignSource && hasUnsupportedActions(aGraphic.GetGDIMetaFile()) ) ) { // wrap bitmap into GDIMetafile GDIMetaFileSharedPtr xMtf = std::make_shared(); ::BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); xMtf->AddAction( new MetaBmpExAction( Point(), aBmpEx ) ); xMtf->SetPrefSize( aBmpEx.GetPrefSize() ); xMtf->SetPrefMapMode( aBmpEx.GetPrefMapMode() ); return xMtf; } return std::make_shared(aGraphic.GetGDIMetaFile()); } private: uno::Reference< graphic::XGraphic > mxGraphic; }; } // anon namespace // Quick'n'dirty way: tunnel Graphic (only works for // in-process slideshow, of course) GDIMetaFileSharedPtr getMetaFile( const uno::Reference< lang::XComponent >& xSource, const uno::Reference< drawing::XDrawPage >& xContainingPage, int mtfLoadFlags, const uno::Reference< uno::XComponentContext >& rxContext ) { if (!rxContext.is()) { SAL_WARN("slideshow.opengl", "getMetaFile(): Invalid context" ); return GDIMetaFileSharedPtr(); } // 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. rtl::Reference xRenderer( new DummyRenderer() ); // creating the graphic exporter uno::Reference< drawing::XGraphicExportFilter > xExporter = drawing::GraphicExportFilter::create(rxContext); uno::Sequence< beans::PropertyValue > aFilterData{ comphelper::makePropertyValue(u"ScrollText"_ustr, ((mtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != 0)), comphelper::makePropertyValue(u"ExportOnlyBackground"_ustr, ((mtfLoadFlags & MTF_LOAD_BACKGROUND_ONLY) != 0)), comphelper::makePropertyValue(u"Version"_ustr, static_cast( SOFFICE_FILEFORMAT_50 )), comphelper::makePropertyValue( u"CurrentPage"_ustr, uno::Reference< uno::XInterface >( xContainingPage, uno::UNO_QUERY_THROW )) }; uno::Sequence< beans::PropertyValue > aProps{ comphelper::makePropertyValue(u"FilterName"_ustr, u"SVM"_ustr), comphelper::makePropertyValue(u"GraphicRenderer"_ustr, uno::Reference< graphic::XGraphicRenderer >(xRenderer)), comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }; xExporter->setSourceDocument( xSource ); if( !xExporter->filter( aProps ) ) return GDIMetaFileSharedPtr(); GDIMetaFileSharedPtr xMtf = xRenderer->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 xMtf; } sal_Int32 getNextActionOffset( MetaAction * pCurrAct ) { // Special handling for actions that represent // more than one indexable action // =========================================== switch (pCurrAct->GetType()) { case MetaActionType::TEXT: { MetaTextAction * pAct = static_cast(pCurrAct); sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); return nLen; } case MetaActionType::TEXTARRAY: { MetaTextArrayAction * pAct = static_cast(pCurrAct); sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); return nLen; } case MetaActionType::STRETCHTEXT: { MetaStretchTextAction * pAct = static_cast(pCurrAct); sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); return nLen; } case MetaActionType::FLOATTRANSPARENT: { MetaFloatTransparentAction * pAct = static_cast(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().GetActionSize(); } default: return 1; } } bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames, sal_uInt32& o_rLoopCount, std::shared_ptr pGraphic, ScopedVclPtrInstance& pVDev, ScopedVclPtrInstance& pVDevMask, sal_uInt16& mnLoadedFrames, sal_uInt16 nFramesToLoad ) { bool bFirstRun = mnLoadedFrames == 0; if (bFirstRun) o_rFrames.clear(); if( !pGraphic->IsAnimated() ) return false; // some loop invariants ::Animation aAnimation( pGraphic->GetAnimation() ); const Point aEmptyPoint; const Size aAnimSize( aAnimation.GetDisplaySizePixel() ); if (bFirstRun) { // 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) pVDev->SetOutputSizePixel(aAnimSize); pVDev->EnableMapMode(false); // setup mask VDev (alpha VDev is currently rather slow) pVDevMask->SetOutputSizePixel(aAnimSize); pVDevMask->EnableMapMode(false); // tdf#156630 make erase calls fill with transparency pVDev->SetBackground(Wallpaper(COL_BLACK)); pVDevMask->SetBackground(Wallpaper(COL_ALPHA_TRANSPARENT)); o_rLoopCount = aAnimation.GetLoopCount(); } sal_uInt16 nCount = aAnimation.Count(); if (!bFirstRun && mnLoadedFrames + nFramesToLoad < nCount) nCount = mnLoadedFrames + nFramesToLoad; for (sal_uInt16 i = mnLoadedFrames; i < nCount; ++i) { const AnimationFrame& rAnimationFrame( aAnimation.Get(i) ); bool bCalculateNow = !bFirstRun || i < nFramesToLoad; if (bCalculateNow) { switch (rAnimationFrame.meDisposal) { case Disposal::Not: { pVDev->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx); AlphaMask aMask = rAnimationFrame.maBitmapEx.GetAlphaMask(); if (aMask.IsEmpty()) { const tools::Rectangle aRect(aEmptyPoint, pVDevMask->GetOutputSizePixel()); const Wallpaper aWallpaper(COL_BLACK); pVDevMask->DrawWallpaper(aRect, aWallpaper); } else { BitmapEx aTmpMask(aMask.GetBitmap(), aMask); pVDevMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aTmpMask); } break; } case Disposal::Back: { // #i70772# react on no mask const AlphaMask aMask(rAnimationFrame.maBitmapEx.GetAlphaMask()); const Bitmap& rContent(rAnimationFrame.maBitmapEx.GetBitmap()); pVDevMask->Erase(); pVDev->DrawBitmap(rAnimationFrame.maPositionPixel, rContent); if (aMask.IsEmpty()) { const tools::Rectangle aRect(rAnimationFrame.maPositionPixel, rContent.GetSizePixel()); pVDevMask->SetFillColor(COL_BLACK); pVDevMask->SetLineColor(); pVDevMask->DrawRect(aRect); } else { pVDevMask->DrawBitmap(rAnimationFrame.maPositionPixel, aMask.GetBitmap()); } break; } case Disposal::Previous: { pVDev->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx); pVDevMask->DrawBitmap(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx.GetAlphaMask().GetBitmap()); break; } } } // extract current aVDev content into a new animation // frame GDIMetaFileSharedPtr pMtf; if (bFirstRun) { pMtf = std::make_shared(); } else { pMtf = o_rFrames[i].mpMtf; } bool useAlphaMask = false; #if defined(MACOSX) || defined(IOS) useAlphaMask = true; #else // GetBitmap()-> AlphaMask is optimized with SkiaSalBitmap::InterpretAs8Bit(), 1bpp mask is not. if( SkiaHelper::isVCLSkiaEnabled()) useAlphaMask = true; #endif if (bCalculateNow) { if( useAlphaMask ) { AlphaMask aAlphaMask(pVDevMask->GetBitmap(aEmptyPoint, aAnimSize)); pMtf->AddAction( new MetaBmpExAction( aEmptyPoint, BitmapEx( pVDev->GetBitmap( aEmptyPoint, aAnimSize ), aAlphaMask))); } else { Bitmap aAlphaMask = pVDevMask->GetBitmap(aEmptyPoint, aAnimSize); aAlphaMask.Invert(); // convert from transparency to alpha pMtf->AddAction( new MetaBmpExAction( aEmptyPoint, BitmapEx( pVDev->GetBitmap( aEmptyPoint, aAnimSize ), aAlphaMask))); } mnLoadedFrames = i+1; } if (bFirstRun) { // 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); // Take care of special value for MultiPage TIFFs. ATM these shall just // show their first page for _quite_ some time. sal_Int32 nWaitTime100thSeconds(rAnimationFrame.mnWait); 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.emplace_back(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 != nullptr; pCurrAct = rMtf->NextAction() ) { if (pCurrAct->GetType() == MetaActionType::COMMENT) { MetaCommentAction * pAct = static_cast(pCurrAct); // skip comment if not a special XTEXT... comment if( pAct->GetComment().matchIgnoreAsciiCase( "XTEXT" ) ) { if (pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_SCROLLRECT")) { o_rScrollRect = vcl::unotools::b2DRectangleFromRectangle( *reinterpret_cast( pAct->GetData() )); bScrollRectSet = true; } else if (pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTRECT") ) { o_rPaintRect = vcl::unotools::b2DRectangleFromRectangle( *reinterpret_cast( pAct->GetData() )); bPaintRectSet = true; } } } } return bScrollRectSet && bPaintRectSet; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */