diff options
Diffstat (limited to 'vcl/source/outdev/transparent.cxx')
-rw-r--r-- | vcl/source/outdev/transparent.cxx | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx new file mode 100644 index 000000000..56e4f2d03 --- /dev/null +++ b/vcl/source/outdev/transparent.cxx @@ -0,0 +1,859 @@ +/* -*- 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 <cassert> + +#include <sal/types.h> +#include <tools/helpers.hxx> +#include <rtl/math.hxx> + +#include <memory> + +#include <vcl/bitmapaccess.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <outdata.hxx> +#include <salgdi.hxx> +#include <bitmapwriteaccess.hxx> + +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<double>(1.0, rtl::math::round(fW)); + double nH = std::max<double>(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; + } +} + +Color OutputDevice::ImplDrawModeToColor( const Color& rColor ) const +{ + Color aColor( rColor ); + DrawModeFlags nDrawMode = GetDrawMode(); + + if( nDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine | + DrawModeFlags::GrayLine | + DrawModeFlags::SettingsLine ) ) + { + if( !ImplIsColorTransparent( aColor ) ) + { + if( nDrawMode & DrawModeFlags::BlackLine ) + { + aColor = COL_BLACK; + } + else if( nDrawMode & DrawModeFlags::WhiteLine ) + { + aColor = COL_WHITE; + } + else if( nDrawMode & DrawModeFlags::GrayLine ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if( nDrawMode & DrawModeFlags::SettingsLine ) + { + aColor = GetSettings().GetStyleSettings().GetFontColor(); + } + } + } + return aColor; +} + +void OutputDevice::ImplPrintTransparent( const Bitmap& rBmp, const Bitmap& rMask, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + Point aDestPt( LogicToPixel( rDestPt ) ); + Size aDestSz( LogicToPixel( rDestSize ) ); + tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel ); + + aSrcRect.Justify(); + + if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() ) + return; + + Bitmap aPaint( rBmp ), aMask( rMask ); + BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE; + + if( aMask.GetBitCount() > 1 ) + aMask.Convert( BmpConversion::N1BitThreshold ); + + // mirrored horizontically + if( aDestSz.Width() < 0 ) + { + aDestSz.setWidth( -aDestSz.Width() ); + aDestPt.AdjustX( -( aDestSz.Width() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Horizontal; + } + + // mirrored vertically + if( aDestSz.Height() < 0 ) + { + aDestSz.setHeight( -aDestSz.Height() ); + aDestPt.AdjustY( -( aDestSz.Height() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Vertical; + } + + // source cropped? + if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) ) + { + aPaint.Crop( aSrcRect ); + aMask.Crop( aSrcRect ); + } + + // destination mirrored + if( nMirrFlags != BmpMirrorFlags::NONE ) + { + aPaint.Mirror( nMirrFlags ); + aMask.Mirror( nMirrFlags ); + } + + // we always want to have a mask + if( aMask.IsEmpty() ) + { + aMask = Bitmap( aSrcRect.GetSize(), 1 ); + aMask.Erase( COL_BLACK ); + } + + // do painting + const long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight(); + long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight; + std::unique_ptr<long[]> pMapX(new long[ nSrcWidth + 1 ]); + std::unique_ptr<long[]> pMapY(new long[ nSrcHeight + 1 ]); + const bool bOldMap = mbMap; + + mbMap = false; + + // create forward mapping tables + for( nX = 0; nX <= nSrcWidth; nX++ ) + pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth ); + + for( nY = 0; nY <= nSrcHeight; nY++ ) + pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight ); + + // walk through all rectangles of mask + const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel()))); + RectangleVector aRectangles; + aWorkRgn.GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]); + const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1 + pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y + Bitmap aBandBmp(aPaint); + + aBandBmp.Crop(rectangle); + DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp); + } + + mbMap = bOldMap; + +} + +// 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; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + 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 + // aplha... 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 : 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<sal_uInt16>(fTransparency * 100.0)); +} + +void OutputDevice::DrawInvisiblePolygon( const tools::PolyPolygon& rPolyPoly ) +{ + assert(!is_double_buffered_window()); + + // short circuit if the polygon border is invisible too + if( !mbLineColor ) + return; + + // we assume that the border is NOT to be drawn transparently??? + Push( PushFlags::FILLCOLOR ); + SetFillColor(); + DrawPolyPolygon( rPolyPoly ); + Pop(); +} + +bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + assert(!is_double_buffered_window()); + + bool bDrawn = false; + + // debug helper: + static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA"); + + if( !pDisableNative && + 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 false; + + 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 : 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; + + // debug helper: + static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA" ); + + // #i66849# Added fast path for exactly rectangular + // polygons + // #i83087# Naturally, system alpha blending cannot + // work with separate alpha VDev + if( !mpAlphaVDev && !pDisableNative && 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.getWidth(), aPixelRect.getHeight(), + sal::static_int_cast<sal_uInt8>(nTransparencePercent), + this ); + } + else + { + bDrawn = true; + } + } + + if( !bDrawn ) + { + ScopedVclPtrInstance< VirtualDevice > aVDev(*this, DeviceFormat::BITMASK); + const Size aDstSz( aDstRect.GetSize() ); + const sal_uInt8 cTrans = static_cast<sal_uInt8>(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 && !!aPolyMask ) + { + BitmapScopedWriteAccess pW(aPaint); + Bitmap::ScopedReadAccess pR(aPolyMask); + + if( pW && pR ) + { + BitmapColor aPixCol; + const BitmapColor aFillCol( GetFillColor() ); + const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) ); + const long nWidth = pW->Width(); + const long nHeight = pW->Height(); + const long nR = aFillCol.GetRed(); + const long nG = aFillCol.GetGreen(); + const long nB = aFillCol.GetBlue(); + long nX, nY; + + if( aPaint.GetBitCount() <= 8 ) + { + const BitmapPalette& rPal = pW->GetPalette(); + const sal_uInt16 nCount = rPal.GetEntryCount(); + BitmapColor* pMap = reinterpret_cast<BitmapColor*>(new sal_uInt8[ nCount * sizeof( BitmapColor ) ]); + + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + BitmapColor aCol( rPal[ i ] ); + aCol.Merge( aFillCol, cTrans ); + pMap[ i ] = BitmapColor( static_cast<sal_uInt8>(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 ) ] ); + } + } + } + } + delete[] reinterpret_cast<sal_uInt8*>(pMap); + } + 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 ] = ColorChannelMerge( pWScan[ 0 ], nB, cTrans ); + pWScan[ 1 ] = ColorChannelMerge( pWScan[ 1 ], nG, cTrans ); + pWScan[ 2 ] = 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( 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 || (nTransparencePercent >= 100) ) + { + DrawInvisiblePolygon( rPolyPoly ); + 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; + + // 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<sal_uInt8>(255*nTransparencePercent/100), + sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100), + sal::static_int_cast<sal_uInt8>(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<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play( this, rPos, rSize ); + const_cast<GDIMetaFile&>(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() ) + { + ScopedVclPtrInstance< VirtualDevice > xVDev; + + 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<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play(xVDev.get(), rPos, rSize); + const_cast<GDIMetaFile&>(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 + { + Bitmap aPaint, aMask; + AlphaMask aAlpha; + 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<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play( xVDev.get(), rPos, rSize ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + xVDev->EnableMapMode( false ); + aPaint = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() ); + xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here! + + // create mask bitmap + xVDev->SetLineColor( COL_BLACK ); + xVDev->SetFillColor( COL_BLACK ); + xVDev->DrawRect( tools::Rectangle( xVDev->PixelToLogic( Point() ), xVDev->GetOutputSize() ) ); + xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText | + DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play( xVDev.get(), rPos, rSize ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + xVDev->EnableMapMode( false ); + aMask = xVDev->GetBitmap( 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 ); + xVDev->DrawMask( Point(), xVDev->GetOutputSizePixel(), aMask, COL_WHITE ); + + aAlpha = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() ); + + xVDev.disposeAndClear(); + + EnableMapMode( false ); + DrawBitmapEx( aDstRect.TopLeft(), BitmapEx( aPaint, aAlpha ) ); + EnableMapMode( bOldMap ); + } + } + } + + mpMetaFile = pOldMetaFile; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |