/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_TILE_WIDTH 1024 #define MAX_TILE_HEIGHT 1024 namespace { /** * Perform a safe approximation of a polygon from double-precision * coordinates to integer coordinates, to ensure that it has at least 2 * pixels in both X and Y directions. */ tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly ) { basegfx::B2DRange aRange = rPoly.getB2DRange(); double fW = aRange.getWidth(), fH = aRange.getHeight(); if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0)) { // This polygon not empty but is too small to display. Approximate it // with a rectangle large enough to be displayed. double nX = aRange.getMinX(), nY = aRange.getMinY(); double nW = std::max(1.0, rtl::math::round(fW)); double nH = std::max(1.0, rtl::math::round(fH)); tools::Polygon aTarget; aTarget.Insert(0, Point(nX, nY)); aTarget.Insert(1, Point(nX+nW, nY)); aTarget.Insert(2, Point(nX+nW, nY+nH)); aTarget.Insert(3, Point(nX, nY+nH)); aTarget.Insert(4, Point(nX, nY)); return aTarget; } return tools::Polygon(rPoly); } tools::PolyPolygon toPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly ) { tools::PolyPolygon aTarget; for (auto const& rB2DPolygon : rPolyPoly) aTarget.Insert(toPolygon(rB2DPolygon)); return aTarget; } } // Caution: This method is nearly the same as // void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly ) // so when changes are made here do not forget to make changes there, too void OutputDevice::DrawTransparent( const basegfx::B2DHomMatrix& rObjectTransform, const basegfx::B2DPolyPolygon& rB2DPolyPoly, double fTransparency) { assert(!is_double_buffered_window()); // AW: Do NOT paint empty PolyPolygons if(!rB2DPolyPoly.count()) return; // we need a graphics if( !mpGraphics && !AcquireGraphics() ) return; assert(mpGraphics); if( mbInitClipRegion ) InitClipRegion(); if( mbOutputClipped ) return; if( mbInitLineColor ) InitLineColor(); if( mbInitFillColor ) InitFillColor(); if(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && (RasterOp::OverPaint == GetRasterOp()) ) { // b2dpolygon support not implemented yet on non-UNX platforms basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly); // ensure it is closed if(!aB2DPolyPolygon.isClosed()) { // maybe assert, prevents buffering due to making a copy aB2DPolyPolygon.setClosed( true ); } // create ObjectToDevice transformation const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform); // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied // alpha... but that requires using premultiplied alpha also for already drawn data const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency; bool bDrawnOk(true); if( IsFillColor() ) { bDrawnOk = mpGraphics->DrawPolyPolygon( aFullTransform, aB2DPolyPolygon, fAdjustedTransparency, *this); } if( bDrawnOk && IsLineColor() ) { const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) { mpGraphics->DrawPolyLine( aFullTransform, rPolygon, fAdjustedTransparency, 0.0, // tdf#124848 hairline nullptr, // MM01 basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default bPixelSnapHairline, *this ); } } if( bDrawnOk ) { if( mpMetaFile ) { // tdf#119843 need transformed Polygon here basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly); aB2DPolyPoly.transform(rObjectTransform); mpMetaFile->AddAction( new MetaTransparentAction( tools::PolyPolygon(aB2DPolyPoly), static_cast< sal_uInt16 >(fTransparency * 100.0))); } if (mpAlphaVDev) mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency); return; } } // fallback to old polygon drawing if needed // tdf#119843 need transformed Polygon here basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly); aB2DPolyPoly.transform(rObjectTransform); DrawTransparent( toPolyPolygon(aB2DPolyPoly), static_cast(fTransparency * 100.0)); } bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent ) { assert(!is_double_buffered_window()); bool bDrawn = false; if (mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) #if defined UNX && ! defined MACOSX && ! defined IOS && GetBitCount() > 8 #endif #ifdef _WIN32 // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting && !rPolyPoly.IsRect() #endif ) { // prepare the graphics device if( mbInitClipRegion ) InitClipRegion(); if( mbOutputClipped ) return true; if( mbInitLineColor ) InitLineColor(); if( mbInitFillColor ) InitFillColor(); // get the polygon in device coordinates basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon()); const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); const double fTransparency = 0.01 * nTransparencePercent; if( mbFillColor ) { // #i121591# // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices // should be used when printing. Normally this is avoided by the printer being // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary // to figure out a way of moving this code to its own function that is // overridden by the Print class, which will mean we deliberately override the // functionality and we use the fallback some lines below (which is not very good, // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and // correct the wrong mapping (see there for details) bDrawn = mpGraphics->DrawPolyPolygon( aTransform, aB2DPolyPolygon, fTransparency, *this); } if( mbLineColor ) { // disable the fill color for now mpGraphics->SetFillColor(); // draw the border line const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) { bDrawn = mpGraphics->DrawPolyLine( aTransform, rPolygon, fTransparency, 0.0, // tdf#124848 hairline nullptr, // MM01 basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default bPixelSnapHairline, *this ); } // prepare to restore the fill color mbInitFillColor = mbFillColor; } } return bDrawn; } void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent ) { // #110958# Disable alpha VDev, we perform the necessary VirtualDevice* pOldAlphaVDev = mpAlphaVDev; // operation explicitly further below. if( mpAlphaVDev ) mpAlphaVDev = nullptr; GDIMetaFile* pOldMetaFile = mpMetaFile; mpMetaFile = nullptr; tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) ); tools::Rectangle aPolyRect( aPolyPoly.GetBoundRect() ); tools::Rectangle aDstRect( Point(), GetOutputSizePixel() ); aDstRect.Intersection( aPolyRect ); ClipToPaintRegion( aDstRect ); if( !aDstRect.IsEmpty() ) { bool bDrawn = false; // #i66849# Added fast path for exactly rectangular // polygons // #i83087# Naturally, system alpha blending cannot // work with separate alpha VDev if( !mpAlphaVDev && aPolyPoly.IsRect() ) { // setup Graphics only here (other cases delegate // to basic OutDev methods) if ( mbInitClipRegion ) InitClipRegion(); if ( mbInitLineColor ) InitLineColor(); if ( mbInitFillColor ) InitFillColor(); tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() ); tools::Rectangle aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect ) ); if( !mbOutputClipped ) { bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(), // #i98405# use methods with small g, else one pixel too much will be painted. // This is because the source is a polygon which when painted would not paint // the rightmost and lowest pixel line(s), so use one pixel less for the // rectangle, too. aPixelRect.getOpenWidth(), aPixelRect.getOpenHeight(), sal::static_int_cast(nTransparencePercent), *this ); } else { bDrawn = true; } } if( !bDrawn ) { ScopedVclPtrInstance< VirtualDevice > aVDev(*this); const Size aDstSz( aDstRect.GetSize() ); const sal_uInt8 cTrans = static_cast(MinMax( FRound( nTransparencePercent * 2.55 ), 0, 255 )); if( aDstRect.Left() || aDstRect.Top() ) aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() ); if( aVDev->SetOutputSizePixel( aDstSz ) ) { const bool bOldMap = mbMap; EnableMapMode( false ); aVDev->SetLineColor( COL_BLACK ); aVDev->SetFillColor( COL_BLACK ); aVDev->DrawPolyPolygon( aPolyPoly ); Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) ); Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) ); // #107766# check for non-empty bitmaps before accessing them if( !aPaint.IsEmpty() && !aPolyMask.IsEmpty() ) { BitmapScopedWriteAccess pW(aPaint); Bitmap::ScopedReadAccess pR(aPolyMask); if( pW && pR ) { BitmapColor aPixCol; const BitmapColor aFillCol( GetFillColor() ); const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) ); const tools::Long nWidth = pW->Width(); const tools::Long nHeight = pW->Height(); const tools::Long nR = aFillCol.GetRed(); const tools::Long nG = aFillCol.GetGreen(); const tools::Long nB = aFillCol.GetBlue(); tools::Long nX, nY; if (vcl::isPalettePixelFormat(aPaint.getPixelFormat())) { const BitmapPalette& rPal = pW->GetPalette(); const sal_uInt16 nCount = rPal.GetEntryCount(); std::unique_ptr xMap(new sal_uInt8[ nCount * sizeof( BitmapColor )]); BitmapColor* pMap = reinterpret_cast(xMap.get()); for( sal_uInt16 i = 0; i < nCount; i++ ) { BitmapColor aCol( rPal[ i ] ); aCol.Merge( aFillCol, cTrans ); pMap[ i ] = BitmapColor( static_cast(rPal.GetBestIndex( aCol )) ); } if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal && pW->GetScanlineFormat() == ScanlineFormat::N8BitPal ) { const sal_uInt8 cBlack = aBlack.GetIndex(); for( nY = 0; nY < nHeight; nY++ ) { Scanline pWScan = pW->GetScanline( nY ); Scanline pRScan = pR->GetScanline( nY ); sal_uInt8 cBit = 128; for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan++ ) { if( !cBit ) { cBit = 128; pRScan += 1; } if( ( *pRScan & cBit ) == cBlack ) { *pWScan = pMap[ *pWScan ].GetIndex(); } } } } else { for( nY = 0; nY < nHeight; nY++ ) { Scanline pScanline = pW->GetScanline(nY); Scanline pScanlineRead = pR->GetScanline(nY); for( nX = 0; nX < nWidth; nX++ ) { if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack ) { pW->SetPixelOnData( pScanline, nX, pMap[ pW->GetIndexFromData( pScanline, nX ) ] ); } } } } } else { if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal && pW->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr ) { const sal_uInt8 cBlack = aBlack.GetIndex(); for( nY = 0; nY < nHeight; nY++ ) { Scanline pWScan = pW->GetScanline( nY ); Scanline pRScan = pR->GetScanline( nY ); sal_uInt8 cBit = 128; for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan += 3 ) { if( !cBit ) { cBit = 128; pRScan += 1; } if( ( *pRScan & cBit ) == cBlack ) { pWScan[ 0 ] = color::ColorChannelMerge( pWScan[ 0 ], nB, cTrans ); pWScan[ 1 ] = color::ColorChannelMerge( pWScan[ 1 ], nG, cTrans ); pWScan[ 2 ] = color::ColorChannelMerge( pWScan[ 2 ], nR, cTrans ); } } } } else { for( nY = 0; nY < nHeight; nY++ ) { Scanline pScanline = pW->GetScanline(nY); Scanline pScanlineRead = pR->GetScanline(nY); for( nX = 0; nX < nWidth; nX++ ) { if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack ) { aPixCol = pW->GetColor( nY, nX ); aPixCol.Merge(aFillCol, cTrans); pW->SetPixelOnData(pScanline, nX, aPixCol); } } } } } } pR.reset(); pW.reset(); DrawBitmap( aDstRect.TopLeft(), aPaint ); EnableMapMode( bOldMap ); if( mbLineColor ) { Push( vcl::PushFlags::FILLCOLOR ); SetFillColor(); DrawPolyPolygon( rPolyPoly ); Pop(); } } } else { DrawPolyPolygon( rPolyPoly ); } } } mpMetaFile = pOldMetaFile; // #110958# Restore disabled alpha VDev mpAlphaVDev = pOldAlphaVDev; } void OutputDevice::DrawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent ) { assert(!is_double_buffered_window()); // short circuit for drawing an opaque polygon if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) ) { DrawPolyPolygon( rPolyPoly ); return; } // short circuit for drawing an invisible polygon if( (!mbFillColor && !mbLineColor) || (nTransparencePercent >= 100) ) return; // tdf#84294: do not record it in metafile // handle metafile recording if( mpMetaFile ) mpMetaFile->AddAction( new MetaTransparentAction( rPolyPoly, nTransparencePercent ) ); bool bDrawn = !IsDeviceOutputNecessary() || ImplIsRecordLayout(); if( bDrawn ) return; // get the device graphics as drawing target if( !mpGraphics && !AcquireGraphics() ) return; assert(mpGraphics); // try hard to draw it directly, because the emulation layers are slower bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent ); if (!bDrawn) EmulateDrawTransparent( rPolyPoly, nTransparencePercent ); // #110958# Apply alpha value also to VDev alpha channel if( mpAlphaVDev ) { const Color aFillCol( mpAlphaVDev->GetFillColor() ); mpAlphaVDev->SetFillColor( Color(sal::static_int_cast(255*nTransparencePercent/100), sal::static_int_cast(255*nTransparencePercent/100), sal::static_int_cast(255*nTransparencePercent/100)) ); mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent ); mpAlphaVDev->SetFillColor( aFillCol ); } } void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, const Size& rSize, const Gradient& rTransparenceGradient ) { assert(!is_double_buffered_window()); const Color aBlack( COL_BLACK ); if( mpMetaFile ) { // missing here is to map the data using the DeviceTransformation mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) ); } if ( !IsDeviceOutputNecessary() ) return; if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) || ( mnDrawMode & DrawModeFlags::NoTransparency ) ) { const_cast(rMtf).WindStart(); const_cast(rMtf).Play(*this, rPos, rSize); const_cast(rMtf).WindStart(); } else { GDIMetaFile* pOldMetaFile = mpMetaFile; tools::Rectangle aOutRect( LogicToPixel( rPos ), LogicToPixel( rSize ) ); Point aPoint; tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() ); mpMetaFile = nullptr; aDstRect.Intersection( aOutRect ); ClipToPaintRegion( aDstRect ); if( !aDstRect.IsEmpty() ) { // Create transparent buffer ScopedVclPtrInstance xVDev(DeviceFormat::WITH_ALPHA); xVDev->mnDPIX = mnDPIX; xVDev->mnDPIY = mnDPIY; if( xVDev->SetOutputSizePixel( aDstRect.GetSize() ) ) { if(GetAntialiasing() != AntialiasingFlags::NONE) { // #i102109# // For MetaFile replay (see task) it may now be necessary to take // into account that the content is AntiAlialiased and needs to be masked // like that. Instead of masking, i will use a copy-modify-paste cycle // here (as i already use in the VclPrimiziveRenderer with success) xVDev->SetAntialiasing(GetAntialiasing()); // create MapMode for buffer (offset needed) and set MapMode aMap(GetMapMode()); const Point aOutPos(PixelToLogic(aDstRect.TopLeft())); aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y())); xVDev->SetMapMode(aMap); // copy MapMode state and disable for target const bool bOrigMapModeEnabled(IsMapModeEnabled()); EnableMapMode(false); // copy MapMode state and disable for buffer const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled()); xVDev->EnableMapMode(false); // copy content from original to buffer xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source *this); // draw MetaFile to buffer xVDev->EnableMapMode(bBufferMapModeEnabled); const_cast(rMtf).WindStart(); const_cast(rMtf).Play(*xVDev, rPos, rSize); const_cast(rMtf).WindStart(); // get content bitmap from buffer xVDev->EnableMapMode(false); const Bitmap aPaint(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel())); // create alpha mask from gradient and get as Bitmap xVDev->EnableMapMode(bBufferMapModeEnabled); xVDev->SetDrawMode(DrawModeFlags::GrayGradient); xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient); xVDev->SetDrawMode(DrawModeFlags::Default); xVDev->EnableMapMode(false); const AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel())); xVDev.disposeAndClear(); // draw masked content to target and restore MapMode DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint, aAlpha)); EnableMapMode(bOrigMapModeEnabled); } else { MapMode aMap( GetMapMode() ); Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) ); const bool bOldMap = mbMap; aMap.SetOrigin( Point( -aOutPos.X(), -aOutPos.Y() ) ); xVDev->SetMapMode( aMap ); const bool bVDevOldMap = xVDev->IsMapModeEnabled(); // create paint bitmap const_cast(rMtf).WindStart(); const_cast(rMtf).Play(*xVDev, rPos, rSize); const_cast(rMtf).WindStart(); xVDev->EnableMapMode( false ); BitmapEx aPaint = xVDev->GetBitmapEx(Point(), xVDev->GetOutputSizePixel()); xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here! // create alpha mask from gradient xVDev->SetDrawMode( DrawModeFlags::GrayGradient ); xVDev->DrawGradient( tools::Rectangle( rPos, rSize ), rTransparenceGradient ); xVDev->SetDrawMode( DrawModeFlags::Default ); xVDev->EnableMapMode( false ); AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel())); aAlpha.BlendWith(aPaint.GetAlphaMask()); xVDev.disposeAndClear(); EnableMapMode( false ); DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha)); EnableMapMode( bOldMap ); } } } mpMetaFile = pOldMetaFile; } } typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile namespace { // List of (intersecting) actions, plus overall bounds struct ConnectedComponents { ConnectedComponents() : aComponentList(), aBounds(), aBgColor(COL_WHITE), bIsSpecial(false), bIsFullyTransparent(false) {} ::std::list< Component > aComponentList; tools::Rectangle aBounds; Color aBgColor; bool bIsSpecial; bool bIsFullyTransparent; }; } namespace { /** Determines whether the action can handle transparency correctly (i.e. when painted on white background, does the action still look correct)? */ bool DoesActionHandleTransparency( const MetaAction& rAct ) { // MetaActionType::FLOATTRANSPARENT can contain a whole metafile, // which is to be rendered with the given transparent gradient. We // currently cannot emulate transparent painting on a white // background reliably. // the remainder can handle printing itself correctly on a uniform // white background. switch( rAct.GetType() ) { case MetaActionType::Transparent: case MetaActionType::BMPEX: case MetaActionType::BMPEXSCALE: case MetaActionType::BMPEXSCALEPART: return true; default: return false; } } bool doesRectCoverWithUniformColor( tools::Rectangle const & rPrevRect, tools::Rectangle const & rCurrRect, OutputDevice const & rMapModeVDev) { // shape needs to fully cover previous content, and have uniform // color return (rMapModeVDev.LogicToPixel(rCurrRect).Contains(rPrevRect) && rMapModeVDev.IsFillColor()); } /** Check whether rCurrRect rectangle fully covers io_rPrevRect - if yes, return true and update o_rBgColor */ bool checkRect( tools::Rectangle& io_rPrevRect, Color& o_rBgColor, const tools::Rectangle& rCurrRect, OutputDevice const & rMapModeVDev ) { bool bRet = doesRectCoverWithUniformColor(io_rPrevRect, rCurrRect, rMapModeVDev); if( bRet ) { io_rPrevRect = rCurrRect; o_rBgColor = rMapModeVDev.GetFillColor(); } return bRet; } /** #107169# Convert BitmapEx to Bitmap with appropriately blended color. Convert MetaTransparentAction to plain polygon, appropriately colored @param o_rMtf Add converted actions to this metafile */ void ImplConvertTransparentAction( GDIMetaFile& o_rMtf, const MetaAction& rAct, const OutputDevice& rStateOutDev, Color aBgColor ) { if (rAct.GetType() == MetaActionType::Transparent) { const MetaTransparentAction* pTransAct = static_cast(&rAct); sal_uInt16 nTransparency( pTransAct->GetTransparence() ); // #i10613# Respect transparency for draw color if (nTransparency) { o_rMtf.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR)); // assume white background for alpha blending Color aLineColor(rStateOutDev.GetLineColor()); aLineColor.SetRed(static_cast((255*nTransparency + (100 - nTransparency) * aLineColor.GetRed()) / 100)); aLineColor.SetGreen(static_cast((255*nTransparency + (100 - nTransparency) * aLineColor.GetGreen()) / 100)); aLineColor.SetBlue(static_cast((255*nTransparency + (100 - nTransparency) * aLineColor.GetBlue()) / 100)); o_rMtf.AddAction(new MetaLineColorAction(aLineColor, true)); Color aFillColor(rStateOutDev.GetFillColor()); aFillColor.SetRed(static_cast((255*nTransparency + (100 - nTransparency)*aFillColor.GetRed()) / 100)); aFillColor.SetGreen(static_cast((255*nTransparency + (100 - nTransparency)*aFillColor.GetGreen()) / 100)); aFillColor.SetBlue(static_cast((255*nTransparency + (100 - nTransparency)*aFillColor.GetBlue()) / 100)); o_rMtf.AddAction(new MetaFillColorAction(aFillColor, true)); } o_rMtf.AddAction(new MetaPolyPolygonAction(pTransAct->GetPolyPolygon())); if(nTransparency) o_rMtf.AddAction(new MetaPopAction()); } else { BitmapEx aBmpEx; switch (rAct.GetType()) { case MetaActionType::BMPEX: aBmpEx = static_cast(rAct).GetBitmapEx(); break; case MetaActionType::BMPEXSCALE: aBmpEx = static_cast(rAct).GetBitmapEx(); break; case MetaActionType::BMPEXSCALEPART: aBmpEx = static_cast(rAct).GetBitmapEx(); break; case MetaActionType::Transparent: default: OSL_FAIL("Printer::GetPreparedMetafile impossible state reached"); break; } Bitmap aBmp(aBmpEx.GetBitmap()); if (aBmpEx.IsAlpha()) { // blend with alpha channel aBmp.Convert(BmpConversion::N24Bit); aBmp.Blend(aBmpEx.GetAlphaMask(), aBgColor); } // add corresponding action switch (rAct.GetType()) { case MetaActionType::BMPEX: o_rMtf.AddAction(new MetaBmpAction( static_cast(rAct).GetPoint(), aBmp)); break; case MetaActionType::BMPEXSCALE: o_rMtf.AddAction(new MetaBmpScaleAction( static_cast(rAct).GetPoint(), static_cast(rAct).GetSize(), aBmp)); break; case MetaActionType::BMPEXSCALEPART: o_rMtf.AddAction(new MetaBmpScalePartAction( static_cast(rAct).GetDestPoint(), static_cast(rAct).GetDestSize(), static_cast(rAct).GetSrcPoint(), static_cast(rAct).GetSrcSize(), aBmp)); break; default: OSL_FAIL("Unexpected case"); break; } } } // #i10613# Extracted from ImplCheckRect::ImplCreate // Returns true, if given action creates visible (i.e. non-transparent) output bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut ) { const bool bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().IsFullyTransparent() ); const bool bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().IsFullyTransparent() ); bool bRet( false ); switch( rAct.GetType() ) { case MetaActionType::POINT: if( !bLineTransparency ) bRet = true; break; case MetaActionType::LINE: if( !bLineTransparency ) bRet = true; break; case MetaActionType::RECT: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::ROUNDRECT: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::ELLIPSE: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::ARC: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::PIE: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::CHORD: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::POLYLINE: if( !bLineTransparency ) bRet = true; break; case MetaActionType::POLYGON: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::POLYPOLYGON: if( !bLineTransparency || !bFillTransparency ) bRet = true; break; case MetaActionType::TEXT: { const MetaTextAction& rTextAct = static_cast(rAct); const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); if (!aString.isEmpty()) bRet = true; } break; case MetaActionType::TEXTARRAY: { const MetaTextArrayAction& rTextAct = static_cast(rAct); const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); if (!aString.isEmpty()) bRet = true; } break; case MetaActionType::PIXEL: case MetaActionType::BMP: case MetaActionType::BMPSCALE: case MetaActionType::BMPSCALEPART: case MetaActionType::BMPEX: case MetaActionType::BMPEXSCALE: case MetaActionType::BMPEXSCALEPART: case MetaActionType::MASK: case MetaActionType::MASKSCALE: case MetaActionType::MASKSCALEPART: case MetaActionType::GRADIENT: case MetaActionType::GRADIENTEX: case MetaActionType::HATCH: case MetaActionType::WALLPAPER: case MetaActionType::Transparent: case MetaActionType::FLOATTRANSPARENT: case MetaActionType::EPS: case MetaActionType::TEXTRECT: case MetaActionType::STRETCHTEXT: case MetaActionType::TEXTLINE: // all other actions: generate non-transparent output bRet = true; break; default: break; } return bRet; } // #i10613# Extracted from ImplCheckRect::ImplCreate tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut ) { tools::Rectangle aActionBounds; switch( rAct.GetType() ) { case MetaActionType::PIXEL: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), Size( 1, 1 ) ); break; case MetaActionType::POINT: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), Size( 1, 1 ) ); break; case MetaActionType::LINE: { const MetaLineAction& rMetaLineAction = static_cast(rAct); aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() ); aActionBounds.Normalize(); const tools::Long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth()); if(nLineWidth) { const tools::Long nHalfLineWidth((nLineWidth + 1) / 2); aActionBounds.AdjustLeft( -nHalfLineWidth ); aActionBounds.AdjustTop( -nHalfLineWidth ); aActionBounds.AdjustRight(nHalfLineWidth ); aActionBounds.AdjustBottom(nHalfLineWidth ); } break; } case MetaActionType::RECT: aActionBounds = static_cast(rAct).GetRect(); break; case MetaActionType::ROUNDRECT: aActionBounds = tools::Polygon( static_cast(rAct).GetRect(), static_cast(rAct).GetHorzRound(), static_cast(rAct).GetVertRound() ).GetBoundRect(); break; case MetaActionType::ELLIPSE: { const tools::Rectangle& rRect = static_cast(rAct).GetRect(); aActionBounds = tools::Polygon( rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1 ).GetBoundRect(); break; } case MetaActionType::ARC: aActionBounds = tools::Polygon( static_cast(rAct).GetRect(), static_cast(rAct).GetStartPoint(), static_cast(rAct).GetEndPoint(), PolyStyle::Arc ).GetBoundRect(); break; case MetaActionType::PIE: aActionBounds = tools::Polygon( static_cast(rAct).GetRect(), static_cast(rAct).GetStartPoint(), static_cast(rAct).GetEndPoint(), PolyStyle::Pie ).GetBoundRect(); break; case MetaActionType::CHORD: aActionBounds = tools::Polygon( static_cast(rAct).GetRect(), static_cast(rAct).GetStartPoint(), static_cast(rAct).GetEndPoint(), PolyStyle::Chord ).GetBoundRect(); break; case MetaActionType::POLYLINE: { const MetaPolyLineAction& rMetaPolyLineAction = static_cast(rAct); aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect(); const tools::Long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth()); if(nLineWidth) { const tools::Long nHalfLineWidth((nLineWidth + 1) / 2); aActionBounds.AdjustLeft( -nHalfLineWidth ); aActionBounds.AdjustTop( -nHalfLineWidth ); aActionBounds.AdjustRight(nHalfLineWidth ); aActionBounds.AdjustBottom(nHalfLineWidth ); } break; } case MetaActionType::POLYGON: aActionBounds = static_cast(rAct).GetPolygon().GetBoundRect(); break; case MetaActionType::POLYPOLYGON: aActionBounds = static_cast(rAct).GetPolyPolygon().GetBoundRect(); break; case MetaActionType::BMP: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), rOut.PixelToLogic( static_cast(rAct).GetBitmap().GetSizePixel() ) ); break; case MetaActionType::BMPSCALE: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), static_cast(rAct).GetSize() ); break; case MetaActionType::BMPSCALEPART: aActionBounds = tools::Rectangle( static_cast(rAct).GetDestPoint(), static_cast(rAct).GetDestSize() ); break; case MetaActionType::BMPEX: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), rOut.PixelToLogic( static_cast(rAct).GetBitmapEx().GetSizePixel() ) ); break; case MetaActionType::BMPEXSCALE: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), static_cast(rAct).GetSize() ); break; case MetaActionType::BMPEXSCALEPART: aActionBounds = tools::Rectangle( static_cast(rAct).GetDestPoint(), static_cast(rAct).GetDestSize() ); break; case MetaActionType::MASK: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), rOut.PixelToLogic( static_cast(rAct).GetBitmap().GetSizePixel() ) ); break; case MetaActionType::MASKSCALE: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), static_cast(rAct).GetSize() ); break; case MetaActionType::MASKSCALEPART: aActionBounds = tools::Rectangle( static_cast(rAct).GetDestPoint(), static_cast(rAct).GetDestSize() ); break; case MetaActionType::GRADIENT: aActionBounds = static_cast(rAct).GetRect(); break; case MetaActionType::GRADIENTEX: aActionBounds = static_cast(rAct).GetPolyPolygon().GetBoundRect(); break; case MetaActionType::HATCH: aActionBounds = static_cast(rAct).GetPolyPolygon().GetBoundRect(); break; case MetaActionType::WALLPAPER: aActionBounds = static_cast(rAct).GetRect(); break; case MetaActionType::Transparent: aActionBounds = static_cast(rAct).GetPolyPolygon().GetBoundRect(); break; case MetaActionType::FLOATTRANSPARENT: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), static_cast(rAct).GetSize() ); break; case MetaActionType::EPS: aActionBounds = tools::Rectangle( static_cast(rAct).GetPoint(), static_cast(rAct).GetSize() ); break; case MetaActionType::TEXT: { const MetaTextAction& rTextAct = static_cast(rAct); const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); if (!aString.isEmpty()) { const Point aPtLog( rTextAct.GetPoint() ); // #105987# Use API method instead of Impl* methods // #107490# Set base parameter equal to index parameter rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetIndex(), rTextAct.GetLen() ); aActionBounds.Move( aPtLog.X(), aPtLog.Y() ); } } break; case MetaActionType::TEXTARRAY: { const MetaTextArrayAction& rTextAct = static_cast(rAct); const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); if( !aString.isEmpty() ) { // #105987# ImplLayout takes everything in logical coordinates std::unique_ptr pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen(), rTextAct.GetPoint(), 0, rTextAct.GetDXArray(), rTextAct.GetKashidaArray() ); if( pSalLayout ) { tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) ); aActionBounds = rOut.PixelToLogic( aBoundRect ); } } } break; case MetaActionType::TEXTRECT: aActionBounds = static_cast(rAct).GetRect(); break; case MetaActionType::STRETCHTEXT: { const MetaStretchTextAction& rTextAct = static_cast(rAct); const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); // #i16195# Literate copy from TextArray action, the // semantics for the ImplLayout call are copied from the // OutDev::DrawStretchText() code. Unfortunately, also in // this case, public outdev methods such as GetTextWidth() // don't provide enough info. if( !aString.isEmpty() ) { // #105987# ImplLayout takes everything in logical coordinates std::unique_ptr pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen(), rTextAct.GetPoint(), rTextAct.GetWidth() ); if( pSalLayout ) { tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) ); aActionBounds = rOut.PixelToLogic( aBoundRect ); } } } break; case MetaActionType::TEXTLINE: OSL_FAIL("MetaActionType::TEXTLINE not supported"); break; default: break; } if( !aActionBounds.IsEmpty() ) { // fdo#40421 limit current action's output to clipped area if( rOut.IsClipRegion() ) return rOut.LogicToPixel( rOut.GetClipRegion().GetBoundRect().Intersection( aActionBounds ) ); else return rOut.LogicToPixel( aActionBounds ); } else return tools::Rectangle(); } } // end anon namespace // TODO: this massive function operates on metafiles, so eventually it should probably // be shifted to the GDIMetaFile class bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf, tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY, bool bReduceTransparency, bool bTransparencyAutoMode, bool bDownsampleBitmaps, const Color& rBackground ) { MetaAction* pCurrAct; bool bTransparent( false ); rOutMtf.Clear(); if(!bReduceTransparency || bTransparencyAutoMode) bTransparent = rInMtf.HasTransparentActions(); // #i10613# Determine set of connected components containing transparent objects. These are // then processed as bitmaps, the original actions are removed from the metafile. if( !bTransparent ) { // nothing transparent -> just copy rOutMtf = rInMtf; } else { // #i10613# // This works as follows: we want a number of distinct sets of // connected components, where each set contains metafile // actions that are intersecting (note: there are possibly // more actions contained as are directly intersecting, // because we can only produce rectangular bitmaps later // on. Thus, each set of connected components is the smallest // enclosing, axis-aligned rectangle that completely bounds a // number of intersecting metafile actions, plus any action // that would otherwise be cut in two). Therefore, we // iteratively add metafile actions from the original metafile // to this connected components list (aCCList), by checking // each element's bounding box against intersection with the // metaaction at hand. // All those intersecting elements are removed from aCCList // and collected in a temporary list (aCCMergeList). After all // elements have been checked, the aCCMergeList elements are // merged with the metaaction at hand into one resulting // connected component, with one big bounding box, and // inserted into aCCList again. // The time complexity of this algorithm is O(n^3), where n is // the number of metafile actions, and it finds all distinct // regions of rectangle-bounded connected components. This // algorithm was designed by AF. // STAGE 1: Detect background // Receives uniform background content, and is _not_ merged // nor checked for intersection against other aCCList elements ConnectedComponents aBackgroundComponent; // Read the configuration value of minimal object area where transparency will be removed double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0; SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl", "Value of ReduceTransparencyMinArea config option is too high"); SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl", "Value of ReduceTransparencyMinArea config option is too low"); fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0); // create an OutputDevice to record mapmode changes and the like ScopedVclPtrInstance< VirtualDevice > aMapModeVDev; aMapModeVDev->mnDPIX = mnDPIX; aMapModeVDev->mnDPIY = mnDPIY; aMapModeVDev->EnableOutput(false); // weed out page-filling background objects (if they are // uniformly coloured). Keeping them outside the other // connected components often prevents whole-page bitmap // generation. bool bStillBackground=true; // true until first non-bg action int nActionNum = 0, nLastBgAction = -1; pCurrAct=const_cast(rInMtf).FirstAction(); if( rBackground != COL_TRANSPARENT ) { aBackgroundComponent.aBgColor = rBackground; aBackgroundComponent.aBounds = GetBackgroundComponentBounds(); } while( pCurrAct && bStillBackground ) { switch( pCurrAct->GetType() ) { case MetaActionType::RECT: { if( !checkRect( aBackgroundComponent.aBounds, aBackgroundComponent.aBgColor, static_cast(pCurrAct)->GetRect(), *aMapModeVDev) ) bStillBackground=false; // incomplete occlusion of background else nLastBgAction=nActionNum; // this _is_ background break; } case MetaActionType::POLYGON: { const tools::Polygon aPoly( static_cast(pCurrAct)->GetPolygon()); if( !basegfx::utils::isRectangle( aPoly.getB2DPolygon()) || !checkRect( aBackgroundComponent.aBounds, aBackgroundComponent.aBgColor, aPoly.GetBoundRect(), *aMapModeVDev) ) bStillBackground=false; // incomplete occlusion of background else nLastBgAction=nActionNum; // this _is_ background break; } case MetaActionType::POLYPOLYGON: { const tools::PolyPolygon aPoly( static_cast(pCurrAct)->GetPolyPolygon()); if( aPoly.Count() != 1 || !basegfx::utils::isRectangle( aPoly[0].getB2DPolygon()) || !checkRect( aBackgroundComponent.aBounds, aBackgroundComponent.aBgColor, aPoly.GetBoundRect(), *aMapModeVDev) ) bStillBackground=false; // incomplete occlusion of background else nLastBgAction=nActionNum; // this _is_ background break; } case MetaActionType::WALLPAPER: { if( !checkRect( aBackgroundComponent.aBounds, aBackgroundComponent.aBgColor, static_cast(pCurrAct)->GetRect(), *aMapModeVDev) ) bStillBackground=false; // incomplete occlusion of background else nLastBgAction=nActionNum; // this _is_ background break; } default: { if( ImplIsNotTransparent( *pCurrAct, *aMapModeVDev ) ) bStillBackground=false; // non-transparent action, possibly // not uniform else // extend current bounds (next uniform action // needs to fully cover this area) aBackgroundComponent.aBounds.Union( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) ); break; } } // execute action to get correct MapModes etc. pCurrAct->Execute( aMapModeVDev.get() ); pCurrAct=const_cast(rInMtf).NextAction(); ++nActionNum; } if (nLastBgAction != -1) { size_t nActionSize = rInMtf.GetActionSize(); // tdf#134736 move nLastBgAction to also include any trailing pops for (size_t nPostLastBgAction = nLastBgAction + 1; nPostLastBgAction < nActionSize; ++nPostLastBgAction) { if (rInMtf.GetAction(nPostLastBgAction)->GetType() != MetaActionType::POP) break; nLastBgAction = nPostLastBgAction; } } aMapModeVDev->ClearStack(); // clean up aMapModeVDev // fast-forward until one after the last background action // (need to reconstruct map mode vdev state) nActionNum=0; pCurrAct=const_cast(rInMtf).FirstAction(); while( pCurrAct && nActionNum<=nLastBgAction ) { // up to and including last ink-generating background // action go to background component aBackgroundComponent.aComponentList.emplace_back( pCurrAct, nActionNum ); // execute action to get correct MapModes etc. pCurrAct->Execute( aMapModeVDev.get() ); pCurrAct=const_cast(rInMtf).NextAction(); ++nActionNum; } // STAGE 2: Generate connected components list ::std::vector aCCList; // contains distinct sets of connected components as elements. // iterate over all actions (start where background action // search left off) for( ; pCurrAct; pCurrAct=const_cast(rInMtf).NextAction(), ++nActionNum ) { // execute action to get correct MapModes etc. pCurrAct->Execute( aMapModeVDev.get() ); // cache bounds of current action const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) ); // accumulate collected bounds here, initialize with current action tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty // for non-output-generating actions bool bTreatSpecial( false ); ConnectedComponents aTotalComponents; // STAGE 2.1: Search for intersecting cc entries // if aBBCurrAct is empty, it will intersect with no // aCCList member. Thus, we can save the check. // Furthermore, this ensures that non-output-generating // actions get their own aCCList entry, which is necessary // when copying them to the output metafile (see stage 4 // below). // #107169# Wholly transparent objects need // not be considered for connected components, // too. Just put each of them into a separate // component. aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev); if( !aBBCurrAct.IsEmpty() && !aTotalComponents.bIsFullyTransparent ) { if( !aBackgroundComponent.aComponentList.empty() && !aBackgroundComponent.aBounds.Contains(aTotalBounds) ) { // it seems the background is not large enough. to // be on the safe side, combine with this component. aTotalBounds.Union( aBackgroundComponent.aBounds ); // extract all aCurr actions to aTotalComponents aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(), aBackgroundComponent.aComponentList ); if( aBackgroundComponent.bIsSpecial ) bTreatSpecial = true; } bool bSomeComponentsChanged; // now, this is unfortunate: since changing anyone of // the aCCList elements (e.g. by merging or addition // of an action) might generate new intersection with // other aCCList elements, have to repeat the whole // element scanning, until nothing changes anymore. // Thus, this loop here makes us O(n^3) in the worst // case. do { // only loop here if 'intersects' branch below was hit bSomeComponentsChanged = false; // iterate over all current members of aCCList for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); ) { // first check if current element's bounds are // empty. This ensures that empty actions are not // merged into one component, as a matter of fact, // they have no position. // #107169# Wholly transparent objects need // not be considered for connected components, // too. Just put each of them into a separate // component. if( !aCurrCC->aBounds.IsEmpty() && !aCurrCC->bIsFullyTransparent && aCurrCC->aBounds.Overlaps( aTotalBounds ) ) { // union the intersecting aCCList element into aTotalComponents // calc union bounding box aTotalBounds.Union( aCurrCC->aBounds ); // extract all aCurr actions to aTotalComponents aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(), aCurrCC->aComponentList ); if( aCurrCC->bIsSpecial ) bTreatSpecial = true; // remove and delete aCurrCC element from list (we've now merged its content) aCurrCC = aCCList.erase( aCurrCC ); // at least one component changed, need to rescan everything bSomeComponentsChanged = true; } else { ++aCurrCC; } } } while( bSomeComponentsChanged ); } // STAGE 2.2: Determine special state for cc element // now test whether the whole connected component must be // treated specially (i.e. rendered as a bitmap): if the // added action is the very first action, or all actions // before it are completely transparent, the connected // component need not be treated specially, not even if // the added action contains transparency. This is because // painting of transparent objects on _white background_ // works without alpha compositing (you just calculate the // color). Note that for the test "all objects before me // are transparent" no sorting is necessary, since the // added metaaction pCurrAct is always in the order the // metafile is painted. Generally, the order of the // metaactions in the ConnectedComponents are not // guaranteed to be the same as in the metafile. if( bTreatSpecial ) { // prev component(s) special -> this one, too aTotalComponents.bIsSpecial = true; } else if(!pCurrAct->IsTransparent()) { // added action and none of prev components special -> // this one normal, too aTotalComponents.bIsSpecial = false; } else { // added action is special and none of prev components // special -> do the detailed tests // can the action handle transparency correctly // (i.e. when painted on white background, does the // action still look correct)? if( !DoesActionHandleTransparency( *pCurrAct ) ) { // no, action cannot handle its transparency on // a printer device, render to bitmap aTotalComponents.bIsSpecial = true; } else { // yes, action can handle its transparency, so // check whether we're on white background if( aTotalComponents.aComponentList.empty() ) { // nothing between pCurrAct and page // background -> don't be special aTotalComponents.bIsSpecial = false; } else { // #107169# Fixes above now ensure that _no_ // object in the list is fully transparent. Thus, // if the component list is not empty above, we // must assume that we have to treat this // component special. // there are non-transparent objects between // pCurrAct and the empty sheet of paper -> be // special, then aTotalComponents.bIsSpecial = true; } } } // STAGE 2.3: Add newly generated CC list element // set new bounds and add action to list aTotalComponents.aBounds = aTotalBounds; aTotalComponents.aComponentList.emplace_back( pCurrAct, nActionNum ); // add aTotalComponents as a new entry to aCCList aCCList.push_back( aTotalComponents ); SAL_WARN_IF( aTotalComponents.aComponentList.empty(), "vcl", "Printer::GetPreparedMetaFile empty component" ); SAL_WARN_IF( aTotalComponents.aBounds.IsEmpty() && (aTotalComponents.aComponentList.size() != 1), "vcl", "Printer::GetPreparedMetaFile non-output generating actions must be solitary"); SAL_WARN_IF( aTotalComponents.bIsFullyTransparent && (aTotalComponents.aComponentList.size() != 1), "vcl", "Printer::GetPreparedMetaFile fully transparent actions must be solitary"); } // well now, we've got the list of disjunct connected // components. Now we've got to create a map, which contains // the corresponding aCCList element for every // metaaction. Later on, we always process the complete // metafile for each bitmap to be generated, but switch on // output only for actions contained in the then current // aCCList element. This ensures correct mapmode and attribute // settings for all cases. // maps mtf actions to CC list entries ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionSize() ); // iterate over all aCCList members and their contained metaactions for (auto const& currentItem : aCCList) { for (auto const& currentAction : currentItem.aComponentList) { // set pointer to aCCList element for corresponding index aCCList_MemberMap[ currentAction.second ] = ¤tItem; } } // STAGE 3.1: Output background mtf actions (if there are any) for (auto & component : aBackgroundComponent.aComponentList) { // simply add this action (above, we inserted the actions // starting at index 0 up to and including nLastBgAction) rOutMtf.AddAction( component.first ); } // STAGE 3.2: Generate banded bitmaps for special regions Point aPageOffset; Size aTmpSize( GetOutputSizePixel() ); if( meOutDevType == OUTDEV_PDF ) { auto pPdfWriter = static_cast(this); aTmpSize = LogicToPixel(pPdfWriter->getCurPageSize(), MapMode(MapUnit::MapPoint)); // also add error code to PDFWriter pPdfWriter->insertError(vcl::PDFWriter::Warning_Transparency_Converted); } else if( meOutDevType == OUTDEV_PRINTER ) { Printer* pThis = dynamic_cast(this); assert(pThis); aPageOffset = pThis->GetPageOffsetPixel(); aPageOffset = Point( 0, 0 ) - aPageOffset; aTmpSize = pThis->GetPaperSizePixel(); } const tools::Rectangle aOutputRect( aPageOffset, aTmpSize ); bool bTiling = dynamic_cast(this) != nullptr; // iterate over all aCCList members and generate bitmaps for the special ones for (auto & currentItem : aCCList) { if( currentItem.bIsSpecial ) { tools::Rectangle aBoundRect( currentItem.aBounds ); aBoundRect.Intersection( aOutputRect ); const double fBmpArea( static_cast(aBoundRect.GetWidth()) * aBoundRect.GetHeight() ); const double fOutArea( static_cast(aOutputRect.GetWidth()) * aOutputRect.GetHeight() ); // check if output doesn't exceed given size if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( fReduceTransparencyMinArea * fOutArea ) ) ) { // output normally. Therefore, we simply clear the // special attribute, as everything non-special is // copied to rOutMtf further below. currentItem.bIsSpecial = false; } else { // create new bitmap action first if( aBoundRect.GetWidth() && aBoundRect.GetHeight() ) { Point aDstPtPix( aBoundRect.TopLeft() ); Size aDstSzPix; ScopedVclPtrInstance aMapVDev; // here, we record only mapmode information aMapVDev->EnableOutput(false); ScopedVclPtrInstance aPaintVDev; // into this one, we render. aPaintVDev->SetBackground( aBackgroundComponent.aBgColor ); rOutMtf.AddAction( new MetaPushAction( vcl::PushFlags::MAPMODE ) ); rOutMtf.AddAction( new MetaMapModeAction() ); aPaintVDev->SetDrawMode( GetDrawMode() ); while( aDstPtPix.Y() <= aBoundRect.Bottom() ) { aDstPtPix.setX( aBoundRect.Left() ); aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize(); if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1 ) > aBoundRect.Bottom() ) aDstSzPix.setHeight( aBoundRect.Bottom() - aDstPtPix.Y() + 1 ); while( aDstPtPix.X() <= aBoundRect.Right() ) { if( ( aDstPtPix.X() + aDstSzPix.Width() - 1 ) > aBoundRect.Right() ) aDstSzPix.setWidth( aBoundRect.Right() - aDstPtPix.X() + 1 ); if( !tools::Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() && aPaintVDev->SetOutputSizePixel( aDstSzPix ) ) { aPaintVDev->Push(); aMapVDev->Push(); aMapVDev->mnDPIX = aPaintVDev->mnDPIX = mnDPIX; aMapVDev->mnDPIY = aPaintVDev->mnDPIY = mnDPIY; aPaintVDev->EnableOutput(false); // iterate over all actions for( pCurrAct=const_cast(rInMtf).FirstAction(), nActionNum=0; pCurrAct; pCurrAct=const_cast(rInMtf).NextAction(), ++nActionNum ) { // enable output only for // actions that are members of // the current aCCList element // (currentItem) if( aCCList_MemberMap[nActionNum] == ¤tItem ) aPaintVDev->EnableOutput(); // but process every action const MetaActionType nType( pCurrAct->GetType() ); if( MetaActionType::MAPMODE == nType ) { pCurrAct->Execute( aMapVDev.get() ); MapMode aMtfMap( aMapVDev->GetMapMode() ); const Point aNewOrg( aMapVDev->PixelToLogic( aDstPtPix ) ); aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) ); aPaintVDev->SetMapMode( aMtfMap ); } else if( ( MetaActionType::PUSH == nType ) || MetaActionType::POP == nType ) { pCurrAct->Execute( aMapVDev.get() ); pCurrAct->Execute( aPaintVDev.get() ); } else if( MetaActionType::GRADIENT == nType ) { MetaGradientAction* pGradientAction = static_cast(pCurrAct); Printer* pPrinter = dynamic_cast< Printer* >(this); if( pPrinter ) pPrinter->DrawGradientEx( aPaintVDev.get(), pGradientAction->GetRect(), pGradientAction->GetGradient() ); else DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() ); } else { pCurrAct->Execute( aPaintVDev.get() ); } Application::Reschedule( true ); } const bool bOldMap = mbMap; mbMap = aPaintVDev->mbMap = false; Bitmap aBandBmp( aPaintVDev->GetBitmap( Point(), aDstSzPix ) ); // scale down bitmap, if requested if( bDownsampleBitmaps ) aBandBmp = vcl::bitmap::GetDownsampledBitmap(PixelToLogic(LogicToPixel(aDstSzPix), MapMode(MapUnit::MapTwip)), Point(), aBandBmp.GetSizePixel(), aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY); rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN" ) ); rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) ); rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END" ) ); aPaintVDev->mbMap = true; mbMap = bOldMap; aMapVDev->Pop(); aPaintVDev->Pop(); } // overlapping bands to avoid missing lines (e.g. PostScript) aDstPtPix.AdjustX(aDstSzPix.Width() ); } // overlapping bands to avoid missing lines (e.g. PostScript) aDstPtPix.AdjustY(aDstSzPix.Height() ); } rOutMtf.AddAction( new MetaPopAction() ); } } } } aMapModeVDev->ClearStack(); // clean up aMapModeVDev // STAGE 4: Copy actions to output metafile // iterate over all actions and duplicate the ones not in a // special aCCList member into rOutMtf for( pCurrAct=const_cast(rInMtf).FirstAction(), nActionNum=0; pCurrAct; pCurrAct=const_cast(rInMtf).NextAction(), ++nActionNum ) { const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum]; // NOTE: This relies on the fact that map-mode or draw // mode changing actions are solitary aCCList elements and // have empty bounding boxes, see comment on stage 2.1 // above if( pCurrAssociatedComponent && (pCurrAssociatedComponent->aBounds.IsEmpty() || !pCurrAssociatedComponent->bIsSpecial) ) { // #107169# Treat transparent bitmaps special, if they // are the first (or sole) action in their bounds // list. Note that we previously ensured that no // fully-transparent objects are before us here. if( DoesActionHandleTransparency( *pCurrAct ) && pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct ) { // convert actions, where masked-out parts are of // given background color ImplConvertTransparentAction(rOutMtf, *pCurrAct, *aMapModeVDev, aBackgroundComponent.aBgColor); } else { // simply add this action rOutMtf.AddAction( pCurrAct ); } pCurrAct->Execute(aMapModeVDev.get()); } } rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() ); rOutMtf.SetPrefSize( rInMtf.GetPrefSize() ); #if OSL_DEBUG_LEVEL > 1 // iterate over all aCCList members and generate rectangles for the bounding boxes rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) ); for(auto const& aCurr:aCCList) { if( aCurr.bIsSpecial ) rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) ); else rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) ); rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) ); } #endif } return bTransparent; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */