diff options
Diffstat (limited to 'vcl/source/outdev/bitmap.cxx')
-rw-r--r-- | vcl/source/outdev/bitmap.cxx | 1788 |
1 files changed, 1788 insertions, 0 deletions
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx new file mode 100644 index 000000000..dcb9a6b1d --- /dev/null +++ b/vcl/source/outdev/bitmap.cxx @@ -0,0 +1,1788 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapFilterStackBlur.hxx> +#include <vcl/bitmapaccess.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <config_features.h> +#if HAVE_FEATURE_OPENGL +#include <vcl/opengl/OpenGLHelper.hxx> +#endif +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> +#include <vcl/image.hxx> +#include <vcl/BitmapMonochromeFilter.hxx> + +#include <bmpfast.hxx> +#include <salgdi.hxx> +#include <salbmp.hxx> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <memory> +#include <comphelper/lok.hxx> +#include <bitmapwriteaccess.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/helpers.hxx> +#include <tools/debug.hxx> +#include <rtl/math.hxx> + +#include <vcl/dibtools.hxx> +#include <tools/stream.hxx> + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap ) +{ + assert(!is_double_buffered_window()); + + const Size aSizePix( rBitmap.GetSizePixel() ); + DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP ); +} + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap ) +{ + assert(!is_double_buffered_window()); + + DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE ); +} + + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap, const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + Bitmap aBmp( rBitmap ); + + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | + DrawModeFlags::GrayBitmap ) ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) ) + { + sal_uInt8 cCmpVal; + + if ( mnDrawMode & DrawModeFlags::BlackBitmap ) + cCmpVal = 0; + else + cCmpVal = 255; + + Color aCol( cCmpVal, cCmpVal, cCmpVal ); + Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + SetLineColor( aCol ); + SetFillColor( aCol ); + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + Pop(); + return; + } + else if( !!aBmp ) + { + if ( mnDrawMode & DrawModeFlags::GrayBitmap ) + aBmp.Convert( BmpConversion::N8BitGreys ); + } + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::BMP: + mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) ); + break; + + case MetaActionType::BMPSCALE: + mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) ); + break; + + case MetaActionType::BMPSCALEPART: + mpMetaFile->AddAction( new MetaBmpScalePartAction( + rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if( !aBmp.IsEmpty() ) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() ); + + if ( nMirrFlags != BmpMirrorFlags::NONE ) + aBmp.Mirror( nMirrFlags ); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + if ( nAction == MetaActionType::BMPSCALE ) + ScaleBitmap (aBmp, aPosAry); + + mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), this ); + } + } + } + + if( mpAlphaVDev ) + { + // #i32109#: Make bitmap area opaque + mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) ); + } +} + +Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const Bitmap& rBmp, long nMaxBmpDPIX, long nMaxBmpDPIY ) +{ + Bitmap aBmp( rBmp ); + + if( !aBmp.IsEmpty() ) + { + const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() ); + tools::Rectangle aSrcRect( rSrcPt, rSrcSz ); + + // do cropping if necessary + if( aSrcRect.Intersection( aBmpRect ) != aBmpRect ) + { + if( !aSrcRect.IsEmpty() ) + aBmp.Crop( aSrcRect ); + else + aBmp.SetEmpty(); + } + + if( !aBmp.IsEmpty() ) + { + // do downsampling if necessary + Size aDstSizeTwip( PixelToLogic(LogicToPixel(rDstSz), MapMode(MapUnit::MapTwip)) ); + + // #103209# Normalize size (mirroring has to happen outside of this method) + aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) ); + + const Size aBmpSize( aBmp.GetSizePixel() ); + const double fBmpPixelX = aBmpSize.Width(); + const double fBmpPixelY = aBmpSize.Height(); + const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0; + const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0; + + // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance) + if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) || + ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) && + ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) ) + { + // do scaling + Size aNewBmpSize; + const double fBmpWH = fBmpPixelX / fBmpPixelY; + const double fMaxWH = fMaxPixelX / fMaxPixelY; + + if( fBmpWH < fMaxWH ) + { + aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) ); + aNewBmpSize.setHeight( FRound( fMaxPixelY ) ); + } + else if( fBmpWH > 0.0 ) + { + aNewBmpSize.setWidth( FRound( fMaxPixelX ) ); + aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) ); + } + + if( aNewBmpSize.Width() && aNewBmpSize.Height() ) + aBmp.Scale( aNewBmpSize ); + else + aBmp.SetEmpty(); + } + } + } + + return aBmp; +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, + const BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( TransparentType::NONE == rBitmapEx.GetTransparentType() ) + { + DrawBitmap( rDestPt, rBitmapEx.GetBitmap() ); + } + else + { + const Size aSizePix( rBitmapEx.GetSizePixel() ); + DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX ); + } +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( TransparentType::NONE == rBitmapEx.GetTransparentType() ) + { + DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() ); + } + else + { + DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE ); + } +} + + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const BitmapEx& rBitmapEx, const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( TransparentType::NONE == rBitmapEx.GetTransparentType() ) + { + DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() ); + } + else + { + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + BitmapEx aBmpEx( rBitmapEx ); + + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | + DrawModeFlags::GrayBitmap ) ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) ) + { + Bitmap aColorBmp( aBmpEx.GetSizePixel(), 1 ); + sal_uInt8 cCmpVal; + + if ( mnDrawMode & DrawModeFlags::BlackBitmap ) + cCmpVal = 0; + else + cCmpVal = 255; + + aColorBmp.Erase( Color( cCmpVal, cCmpVal, cCmpVal ) ); + + if( aBmpEx.IsAlpha() ) + { + // Create one-bit mask out of alpha channel, by + // thresholding it at alpha=0.5. As + // DRAWMODE_BLACK/WHITEBITMAP requires monochrome + // output, having alpha-induced grey levels is not + // acceptable. + BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap()); + BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129)); + aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap()); + } + else + { + aBmpEx = BitmapEx( aColorBmp, aBmpEx.GetMask() ); + } + } + else if( !!aBmpEx ) + { + if ( mnDrawMode & DrawModeFlags::GrayBitmap ) + aBmpEx.Convert( BmpConversion::N8BitGreys ); + } + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::BMPEX: + mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) ); + break; + + case MetaActionType::BMPEXSCALE: + mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) ); + break; + + case MetaActionType::BMPEXSCALEPART: + mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize, + rSrcPtPixel, rSrcSizePixel, aBmpEx ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + DrawDeviceBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx ); + } +} + +Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const +{ + Bitmap aBmp; + long nX = ImplLogicXToDevicePixel( rSrcPt.X() ); + long nY = ImplLogicYToDevicePixel( rSrcPt.Y() ); + long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() ); + long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() ); + + if ( mpGraphics || AcquireGraphics() ) + { + if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY)) + { + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) ); + bool bClipped = false; + + // X-Coordinate outside of draw area? + if ( nX < mnOutOffX ) + { + nWidth -= ( mnOutOffX - nX ); + nX = mnOutOffX; + bClipped = true; + } + + // Y-Coordinate outside of draw area? + if ( nY < mnOutOffY ) + { + nHeight -= ( mnOutOffY - nY ); + nY = mnOutOffY; + bClipped = true; + } + + // Width outside of draw area? + if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) ) + { + nWidth = mnOutOffX + mnOutWidth - nX; + bClipped = true; + } + + // Height outside of draw area? + if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) ) + { + nHeight = mnOutOffY + mnOutHeight - nY; + bClipped = true; + } + + if ( bClipped ) + { + // If the visible part has been clipped, we have to create a + // Bitmap with the correct size in which we copy the clipped + // Bitmap to the correct position. + ScopedVclPtrInstance< VirtualDevice > aVDev( *this ); + + if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) ) + { + if ( aVDev->mpGraphics || aVDev->AcquireGraphics() ) + { + if ( (nWidth > 0) && (nHeight > 0) ) + { + SalTwoRect aPosAry(nX, nY, nWidth, nHeight, + (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L, + (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L, + nWidth, nHeight); + aVDev->mpGraphics->CopyBits( aPosAry, mpGraphics, this, this ); + } + else + { + OSL_ENSURE(false, "CopyBits with zero or negative width or height"); + } + + aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() ); + } + else + bClipped = false; + } + else + bClipped = false; + } + + if ( !bClipped ) + { + std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, this ); + + if( pSalBmp ) + { + aBmp.ImplSetSalBitmap(pSalBmp); + } + } + } + } + + return aBmp; +} + +BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const +{ + + // #110958# Extract alpha value from VDev, if any + if( mpAlphaVDev ) + { + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) ); + + // ensure 8 bit alpha + if( aAlphaBitmap.GetBitCount() > 8 ) + aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion ); + + return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) ); + } + else + return BitmapEx(GetBitmap( rSrcPt, rSize )); +} + +void OutputDevice::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if (rBitmapEx.IsAlpha()) + { + DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel); + } + else if (!!rBitmapEx) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel()); + + if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight) + { + + if (nMirrFlags != BmpMirrorFlags::NONE) + rBitmapEx.Mirror(nMirrFlags); + + const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get(); + std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.ImplGetMaskSalBitmap(); + + if (xMaskBmp) + { + bool bTryDirectPaint(pSalSrcBmp); + + if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this)) + { + // tried to paint as alpha directly. If this worked, we are done (except + // alpha, see below) + } + else + { + // #4919452# reduce operation area to bounds of + // cliprect. since masked transparency involves + // creation of a large vdev and copying the screen + // content into that (slooow read from framebuffer), + // that should considerably increase performance for + // large bitmaps and small clippings. + + // Note that this optimization is a workaround for a + // Writer peculiarity, namely, to decompose background + // graphics into myriads of disjunct, tiny + // rectangles. That otherwise kills us here, since for + // transparent output, SAL always prepares the whole + // bitmap, if aPosAry contains the whole bitmap (and + // it's _not_ to blame for that). + + // Note the call to ImplPixelToDevicePixel(), since + // aPosAry already contains the mnOutOff-offsets, they + // also have to be applied to the region + tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() ); + + // TODO: Also respect scaling (that's a bit tricky, + // since the source points have to move fractional + // amounts (which is not possible, thus has to be + // emulated by increases copy area) + // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth ); + // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight ); + + // for now, only identity scales allowed + if (!aClipRegionBounds.IsEmpty() && + aPosAry.mnDestWidth == aPosAry.mnSrcWidth && + aPosAry.mnDestHeight == aPosAry.mnSrcHeight) + { + // now intersect dest rect with clip region + aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX, + aPosAry.mnDestY, + aPosAry.mnDestX + aPosAry.mnDestWidth - 1, + aPosAry.mnDestY + aPosAry.mnDestHeight - 1)); + + // Note: I could theoretically optimize away the + // DrawBitmap below, if the region is empty + // here. Unfortunately, cannot rule out that + // somebody relies on the side effects. + if (!aClipRegionBounds.IsEmpty()) + { + aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX; + aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY; + aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth(); + aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight(); + + aPosAry.mnDestX = aClipRegionBounds.Left(); + aPosAry.mnDestY = aClipRegionBounds.Top(); + aPosAry.mnDestWidth = aClipRegionBounds.GetWidth(); + aPosAry.mnDestHeight = aClipRegionBounds.GetHeight(); + } + } + + mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this); + } + + // #110958# Paint mask to alpha channel. Luckily, the + // black and white representation of the mask maps to + // the alpha channel + + // #i25167# Restrict mask painting to _opaque_ areas + // of the mask, otherwise we spoil areas where no + // bitmap content was ever visible. Interestingly + // enough, this can be achieved by taking the mask as + // the transparency mask of itself + if (mpAlphaVDev) + mpAlphaVDev->DrawBitmapEx(rDestPt, + rDestSize, + BitmapEx(rBitmapEx.GetMask(), + rBitmapEx.GetMask())); + } + else + { + mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, this); + + if (mpAlphaVDev) + { + // #i32109#: Make bitmap area opaque + mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) ); + } + } + } + } +} + +void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + assert(!is_double_buffered_window()); + + Point aOutPt(LogicToPixel(rDestPt)); + Size aOutSz(LogicToPixel(rDestSize)); + tools::Rectangle aDstRect(Point(), GetOutputSizePixel()); + + const bool bHMirr = aOutSz.Width() < 0; + const bool bVMirr = aOutSz.Height() < 0; + + ClipToPaintRegion(aDstRect); + + if (bHMirr) + { + aOutSz.setWidth( -aOutSz.Width() ); + aOutPt.AdjustX( -(aOutSz.Width() - 1) ); + } + + if (bVMirr) + { + aOutSz.setHeight( -aOutSz.Height() ); + aOutPt.AdjustY( -(aOutSz.Height() - 1) ); + } + + if (!aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty()) + { + static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA"); + bool bTryDirectPaint(!pDisableNative && !bHMirr && !bVMirr); + + if (bTryDirectPaint) + { + Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY); + SalTwoRect aTR( + rSrcPtPixel.X(), rSrcPtPixel.Y(), + rSrcSizePixel.Width(), rSrcSizePixel.Height(), + aRelPt.X(), aRelPt.Y(), + aOutSz.Width(), aOutSz.Height()); + + SalBitmap* pSalSrcBmp = rBmp.ImplGetSalBitmap().get(); + SalBitmap* pSalAlphaBmp = rAlpha.ImplGetSalBitmap().get(); + + // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work + // with separate alpha VDev + + // try to blend the alpha bitmap with the alpha virtual device + if (mpAlphaVDev) + { + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) ); + if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get()) + { + if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, this)) + { + mpAlphaVDev->BlendBitmap(aTR, rAlpha); + return; + } + } + } + else + { + if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, this)) + return; + } + } + + // we need to make sure OpenGL never reaches this slow code path + + assert(!SkiaHelper::isVCLSkiaEnabled()); +#if HAVE_FEATURE_OPENGL + assert(!OpenGLHelper::isVCLOpenGLEnabled()); +#endif + tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel()); + if (!aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty()) + { + Point auxOutPt(LogicToPixel(rDestPt)); + Size auxOutSz(LogicToPixel(rDestSize)); + + DrawDeviceAlphaBitmapSlowPath(rBmp, rAlpha, aDstRect, aBmpRect, auxOutSz, auxOutPt); + } + } +} + +namespace +{ + +struct LinearScaleContext +{ + std::unique_ptr<long[]> mpMapX; + std::unique_ptr<long[]> mpMapY; + + std::unique_ptr<long[]> mpMapXOffset; + std::unique_ptr<long[]> mpMapYOffset; + + LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect, + Size const & aOutSize, long nOffX, long nOffY) + + : mpMapX(new long[aDstRect.GetWidth()]) + , mpMapY(new long[aDstRect.GetHeight()]) + , mpMapXOffset(new long[aDstRect.GetWidth()]) + , mpMapYOffset(new long[aDstRect.GetHeight()]) + { + const long nSrcWidth = aBitmapRect.GetWidth(); + const long nSrcHeight = aBitmapRect.GetHeight(); + + generateSimpleMap( + nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(), + aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get()); + + generateSimpleMap( + nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(), + aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get()); + } + +private: + + static void generateSimpleMap(long nSrcDimension, long nDstDimension, long nDstLocation, + long nOutDimention, long nOffset, long* pMap, long* pMapOffset) + { + + const double fReverseScale = (std::abs(nOutDimention) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimention) - 1) : 0.0; + + long nSampleRange = std::max(0L, nSrcDimension - 2); + + for (long i = 0; i < nDstDimension; i++) + { + double fTemp = std::abs((nOffset + i) * fReverseScale); + + pMap[i] = MinMax(nDstLocation + long(fTemp), 0, nSampleRange); + pMapOffset[i] = static_cast<long>((fTemp - pMap[i]) * 128.0); + } + } + +public: + bool blendBitmap( + const BitmapWriteAccess* pDestination, + const BitmapReadAccess* pSource, + const BitmapReadAccess* pSourceAlpha, + const long nDstWidth, + const long nDstHeight) + { + if (pSource && pSourceAlpha && pDestination) + { + ScanlineFormat nSourceFormat = pSource->GetScanlineFormat(); + ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat(); + + switch (nSourceFormat) + { + case ScanlineFormat::N24BitTcRgb: + case ScanlineFormat::N24BitTcBgr: + { + if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra) + || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba)) + { + blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight); + return true; + } + } + break; + default: break; + } + } + return false; + } + + void blendBitmap24( + const BitmapWriteAccess* pDestination, + const BitmapReadAccess* pSource, + const BitmapReadAccess* pSourceAlpha, + const long nDstWidth, + const long nDstHeight) + { + Scanline pLine0, pLine1; + Scanline pLineAlpha0, pLineAlpha1; + Scanline pColorSample1, pColorSample2; + Scanline pDestScanline; + + long nColor1Line1, nColor2Line1, nColor3Line1; + long nColor1Line2, nColor2Line2, nColor3Line2; + long nAlphaLine1, nAlphaLine2; + + sal_uInt8 nColor1, nColor2, nColor3, nAlpha; + + for (long nY = 0; nY < nDstHeight; nY++) + { + const long nMapY = mpMapY[nY]; + const long nMapFY = mpMapYOffset[nY]; + + pLine0 = pSource->GetScanline(nMapY); + // tdf#95481 guard nMapY + 1 to be within bounds + pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0; + + pLineAlpha0 = pSourceAlpha->GetScanline(nMapY); + // tdf#95481 guard nMapY + 1 to be within bounds + pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0; + + pDestScanline = pDestination->GetScanline(nY); + + for (long nX = 0; nX < nDstWidth; nX++) + { + const long nMapX = mpMapX[nX]; + const long nMapFX = mpMapXOffset[nX]; + + pColorSample1 = pLine0 + 3 * nMapX; + pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1; + nColor1Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor2Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor3Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLine1 + 3 * nMapX; + pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1; + nColor1Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor2Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor3Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLineAlpha0 + nMapX; + pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1; + nAlphaLine1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLineAlpha1 + nMapX; + pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1; + nAlphaLine2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7; + nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7; + nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7; + + nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7; + + *pDestScanline = ColorChannelMerge(*pDestScanline, nColor1, nAlpha); + pDestScanline++; + *pDestScanline = ColorChannelMerge(*pDestScanline, nColor2, nAlpha); + pDestScanline++; + *pDestScanline = ColorChannelMerge(*pDestScanline, nColor3, nAlpha); + pDestScanline++; + pDestScanline++; + } + } + } +}; + +struct TradScaleContext +{ + std::unique_ptr<long[]> mpMapX; + std::unique_ptr<long[]> mpMapY; + + TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect, + Size const & aOutSize, long nOffX, long nOffY) + + : mpMapX(new long[aDstRect.GetWidth()]) + , mpMapY(new long[aDstRect.GetHeight()]) + { + const long nSrcWidth = aBitmapRect.GetWidth(); + const long nSrcHeight = aBitmapRect.GetHeight(); + + const bool bHMirr = aOutSize.Width() < 0; + const bool bVMirr = aOutSize.Height() < 0; + + generateSimpleMap( + nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(), + aOutSize.Width(), nOffX, bHMirr, mpMapX.get()); + + generateSimpleMap( + nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(), + aOutSize.Height(), nOffY, bVMirr, mpMapY.get()); + } + +private: + + static void generateSimpleMap(long nSrcDimension, long nDstDimension, long nDstLocation, + long nOutDimention, long nOffset, bool bMirror, long* pMap) + { + long nMirrorOffset = 0; + + if (bMirror) + nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1; + + for (long i = 0; i < nDstDimension; ++i, ++nOffset) + { + pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimention; + if (bMirror) + pMap[i] = nMirrorOffset - pMap[i]; + } + } +}; + + +} // end anonymous namespace + +void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap, + const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint) +{ + assert(!is_double_buffered_window()); + + VirtualDevice* pOldVDev = mpAlphaVDev; + + const bool bHMirr = aOutSize.Width() < 0; + const bool bVMirr = aOutSize.Height() < 0; + + // The scaling in this code path produces really ugly results - it + // does the most trivial scaling with no smoothing. + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap! + mbMap = false; + + Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize())); + + // #109044# The generated bitmap need not necessarily be + // of aDstRect dimensions, it's internally clipped to + // window bounds. Thus, we correct the dest size here, + // since we later use it (in nDstWidth/Height) for pixel + // access) + // #i38887# reading from screen may sometimes fail + if (aBmp.ImplGetSalBitmap()) + { + aDstRect.SetSize(aBmp.GetSizePixel()); + } + + const long nDstWidth = aDstRect.GetWidth(); + const long nDstHeight = aDstRect.GetHeight(); + + // calculate offset in original bitmap + // in RTL case this is a little more complicated since the contents of the + // bitmap is not mirrored (it never is), however the paint region and bmp region + // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these + // is content wise somewhere else and needs to take mirroring into account + const long nOffX = IsRTLEnabled() + ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X()) + : aDstRect.Left() - aOutPoint.X(); + + const long nOffY = aDstRect.Top() - aOutPoint.Y(); + + TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY); + + Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap)); + AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha)); + + DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal || + pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitTcMask, + "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" ); + + // #i38887# reading from screen may sometimes fail + if (aBmp.ImplGetSalBitmap()) + { + Bitmap aNewBitmap; + + if (mpAlphaVDev) + { + aNewBitmap = BlendBitmapWithAlpha( + aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(), + aDstRect, + nOffY, nDstHeight, + nOffX, nDstWidth, + aTradContext.mpMapX.get(), aTradContext.mpMapY.get() ); + } + else + { + LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY); + + if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(), + nDstWidth, nDstHeight)) + { + aNewBitmap = aBmp; + } + else + { + aNewBitmap = BlendBitmap( + aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(), + nOffY, nDstHeight, + nOffX, nDstWidth, + aBmpRect, aOutSize, + bHMirr, bVMirr, + aTradContext.mpMapX.get(), aTradContext.mpMapY.get() ); + } + } + + // #110958# Disable alpha VDev, we're doing the necessary + // stuff explicitly further below + if (mpAlphaVDev) + mpAlphaVDev = nullptr; + + DrawBitmap(aDstRect.TopLeft(), aNewBitmap); + + // #110958# Enable alpha VDev again + mpAlphaVDev = pOldVDev; + } + + mbMap = bOldMap; + mpMetaFile = pOldMetaFile; +} + +void OutputDevice::ScaleBitmap (Bitmap &rBmp, SalTwoRect &rPosAry) +{ + const double nScaleX = rPosAry.mnDestWidth / static_cast<double>( rPosAry.mnSrcWidth ); + const double nScaleY = rPosAry.mnDestHeight / static_cast<double>( rPosAry.mnSrcHeight ); + + // If subsampling, use Bitmap::Scale for subsampling for better quality. + if ( nScaleX < 1.0 || nScaleY < 1.0 ) + { + rBmp.Scale ( nScaleX, nScaleY ); + rPosAry.mnSrcWidth = rPosAry.mnDestWidth; + rPosAry.mnSrcHeight = rPosAry.mnDestHeight; + } +} + +bool OutputDevice::DrawTransformBitmapExDirect( + const basegfx::B2DHomMatrix& aFullTransform, + const BitmapEx& rBitmapEx) +{ + assert(!is_double_buffered_window()); + + bool bDone = false; + + // try to paint directly + const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0)); + const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0)); + const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0)); + SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get(); + Bitmap aAlphaBitmap; + + if(rBitmapEx.IsTransparent()) + { + if(rBitmapEx.IsAlpha()) + { + aAlphaBitmap = rBitmapEx.GetAlpha(); + } + else + { + aAlphaBitmap = rBitmapEx.GetMask(); + } + } + else if (mpAlphaVDev) + { + aAlphaBitmap = Bitmap(rBitmapEx.GetSizePixel(), 1); + aAlphaBitmap.Erase(COL_BLACK); + } + + SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get(); + + bDone = mpGraphics->DrawTransformedBitmap( + aNull, + aTopX, + aTopY, + *pSalSrcBmp, + pSalAlphaBmp, + this); + + if (mpAlphaVDev) + { + // Merge bitmap alpha to alpha device + Bitmap aBlack(rBitmapEx.GetSizePixel(), 1); + aBlack.Erase(COL_BLACK); + mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aBlack, aAlphaBitmap)); + } + + return bDone; +}; + +bool OutputDevice::TransformAndReduceBitmapExToTargetRange( + const basegfx::B2DHomMatrix& aFullTransform, + basegfx::B2DRange &aVisibleRange, + double &fMaximumArea) +{ + // limit TargetRange to existing pixels (if pixel device) + // first get discrete range of object + basegfx::B2DRange aFullPixelRange(aVisibleRange); + + aFullPixelRange.transform(aFullTransform); + + if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight())) + { + // object is outside of visible area + return false; + } + + // now get discrete target pixels; start with OutDev pixel size and evtl. + // intersect with active clipping area + basegfx::B2DRange aOutPixel( + 0.0, + 0.0, + GetOutputSizePixel().Width(), + GetOutputSizePixel().Height()); + + if(IsClipRegion()) + { + tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect()); + + // caution! Range from rectangle, one too much (!) + aRegionRectangle.AdjustRight(-1); + aRegionRectangle.AdjustBottom(-1); + aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) ); + } + + if(aOutPixel.isEmpty()) + { + // no active output area + return false; + } + + // if aFullPixelRange is not completely inside of aOutPixel, + // reduction of target pixels is possible + basegfx::B2DRange aVisiblePixelRange(aFullPixelRange); + + if(!aOutPixel.isInside(aFullPixelRange)) + { + aVisiblePixelRange.intersect(aOutPixel); + + if(aVisiblePixelRange.isEmpty()) + { + // nothing in visible part, reduces to nothing + return false; + } + + // aVisiblePixelRange contains the reduced output area in + // discrete coordinates. To make it useful everywhere, make it relative to + // the object range + basegfx::B2DHomMatrix aMakeVisibleRangeRelative; + + aVisibleRange = aVisiblePixelRange; + aMakeVisibleRangeRelative.translate( + -aFullPixelRange.getMinX(), + -aFullPixelRange.getMinY()); + aMakeVisibleRangeRelative.scale( + 1.0 / aFullPixelRange.getWidth(), + 1.0 / aFullPixelRange.getHeight()); + aVisibleRange.transform(aMakeVisibleRangeRelative); + } + + // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap + // will create another, badly scaled bitmap to do the job. Nonetheless, do a + // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding + // errors in rough estimations + const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight()); + + fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0); + + return true; +} + +// MM02 add some test class to get a simple timer-based output to be able +// to check if it gets faster - and how much. Uncomment next line or set +// DO_TIME_TEST for compile time if you want to use it +// #define DO_TIME_TEST +#ifdef DO_TIME_TEST +#include <tools/time.hxx> +struct LocalTimeTest +{ + const sal_uInt64 nStartTime; + LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {} + ~LocalTimeTest() + { + const sal_uInt64 nEndTime(tools::Time::GetSystemTicks()); + const sal_uInt64 nDiffTime(nEndTime - nStartTime); + + if(nDiffTime > 0) + { + OStringBuffer aOutput("Time: "); + OString aNumber(OString::number(nDiffTime)); + aOutput.append(aNumber); + OSL_FAIL(aOutput.getStr()); + } + } +}; +#endif + +void OutputDevice::DrawTransformedBitmapEx( + const basegfx::B2DHomMatrix& rTransformation, + const BitmapEx& rBitmapEx) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if(rBitmapEx.IsEmpty()) + return; + + // MM02 compared to other public methods of OutputDevice + // this test was missing and led to zero-ptr-accesses + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + const bool bMetafile(nullptr != mpMetaFile); + /* + tdf#135325 typically in these OutputDevice methods the in + record-to-metafile case MetaFile is already written to before the test + against mbOutputClipped to determine that output to the current device + would result in no visual output. In this case the metafile is written + after the test, so we must continue past mbOutputClipped if recording to + a metafile. It's typical to record with a device of nominal size and + play back later against something of a totally different size. + */ + if (mbOutputClipped && !bMetafile) + return; + +#ifdef DO_TIME_TEST + // MM02 start time test when some data (not for trivial stuff). Will + // trigger and show data when leaving this method by destructing helper + static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW")); + static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer); + std::unique_ptr<LocalTimeTest> aTimeTest( + bUseTimer && rBitmapEx.GetSizeBytes() > 10000 + ? new LocalTimeTest() + : nullptr); +#endif + + // MM02 reorganize order: Prefer DrawTransformBitmapExDirect due + // to this having evolved and is improved on quite some systems. + // Check for exclusion parameters that may prevent using it + static bool bAllowPreferDirectPaint(true); + const bool bInvert(RasterOp::Invert == meRasterOp); + const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap )); + const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile); + + if(bAllowPreferDirectPaint && bTryDirectPaint) + { + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx)) + { + // we are done + return; + } + } + + // decompose matrix to check rotation and shear + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + const bool bRotated(!basegfx::fTools::equalZero(fRotate)); + const bool bSheared(!basegfx::fTools::equalZero(fShearX)); + const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0)); + + if(!bRotated && !bSheared && !bMirroredX && !bMirroredY) + { + // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx + // do *not* execute the mirroring here, it's done in the fallback + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY())); + const Size aDestSize( + basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(), + basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y()); + const Point aOrigin = GetMapMode().GetOrigin(); + if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel) + { + aDestPt.Move(aOrigin.getX(), aOrigin.getY()); + EnableMapMode(false); + } + + DrawBitmapEx(aDestPt, aDestSize, rBitmapEx); + if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel) + { + EnableMapMode(); + aDestPt.Move(-aOrigin.getX(), -aOrigin.getY()); + } + return; + } + + // MM02 bAllowPreferDirectPaint may have been false to allow + // to specify order of executions, so give bTryDirectPaint a call + if(bTryDirectPaint) + { + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx)) + { + // we are done + return; + } + } + + // take the fallback when no rotate and shear, but mirror (else we would have done this above) + if(!bRotated && !bSheared) + { + // with no rotation or shear it can be mapped to DrawBitmapEx + // do *not* execute the mirroring here, it's done in the fallback + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY())); + const Size aDestSize( + basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(), + basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y()); + + DrawBitmapEx(aDestPt, aDestSize, rBitmapEx); + return; + } + + // at this point we are either sheared or rotated or both + assert(bSheared || bRotated); + + // fallback; create transformed bitmap the hard way (back-transform + // the pixels) and paint + basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0); + + // limit maximum area to something looking good for non-pixel-based targets (metafile, printer) + // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area + // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum + // to avoid crashes/resource problems (ca. 1500x3000 here) + const Size& rOriginalSizePixel(rBitmapEx.GetSizePixel()); + const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5); + const double fOrigAreaScaled(fOrigArea * 1.44); + double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled))); + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + if(!bMetafile) + { + if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) ) + return; + } + + if(!aVisibleRange.isEmpty()) + { + BitmapEx aTransformed(rBitmapEx); + + // #122923# when the result needs an alpha channel due to being rotated or sheared + // and thus uncovering areas, add these channels so that the own transformer (used + // in getTransformed) also creates a transformed alpha channel + if(!aTransformed.IsTransparent() && (bSheared || bRotated)) + { + // parts will be uncovered, extend aTransformed with a mask bitmap + const Bitmap aContent(aTransformed.GetBitmap()); + + AlphaMask aMaskBmp(aContent.GetSizePixel()); + aMaskBmp.Erase(0); + + aTransformed = BitmapEx(aContent, aMaskBmp); + } + + // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling + // will happen according to aDestSize. + basegfx::B2DVector aFullScale, aFullTranslate; + double fFullRotate, fFullShearX; + aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX); + // Require positive scaling, negative scaling would loose horizontal or vertical flip. + if (aFullScale.getX() > 0 && aFullScale.getY() > 0) + { + basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix( + rOriginalSizePixel.getWidth() / aFullScale.getX(), + rOriginalSizePixel.getHeight() / aFullScale.getY()); + aFullTransform *= aTransform; + } + + double fSourceRatio = 1.0; + if (rOriginalSizePixel.getHeight() != 0) + { + fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight(); + } + double fTargetRatio = 1.0; + if (aFullScale.getY() != 0) + { + fTargetRatio = aFullScale.getX() / aFullScale.getY(); + } + bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio); + if (bSheared || !bAspectRatioKept) + { + // Not only rotation, or scaling does not keep aspect ratio. + aTransformed = aTransformed.getTransformed( + aFullTransform, + aVisibleRange, + fMaximumArea); + } + else + { + // Just rotation, can do that directly. + fFullRotate = fmod(fFullRotate * -1, F_2PI); + if (fFullRotate < 0) + { + fFullRotate += F_2PI; + } + long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10); + aTransformed.Rotate(nAngle10, COL_TRANSPARENT); + } + basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0); + + // get logic object target range + aTargetRange.transform(rTransformation); + + // get from unified/relative VisibleRange to logoc one + aVisibleRange.transform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aTargetRange.getRange(), + aTargetRange.getMinimum())); + + // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY())); + const Size aDestSize( + basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(), + basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y()); + + DrawBitmapEx(aDestPt, aDestSize, aTransformed); + } +} + +void OutputDevice::DrawShadowBitmapEx( + const BitmapEx& rBitmapEx, + ::Color aShadowColor) +{ + Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.maBitmap)); + + if(!pReadAccess) + return; + + for(long y(0); y < pReadAccess->Height(); y++) + { + for(long x(0); x < pReadAccess->Width(); x++) + { + const BitmapColor aColor = pReadAccess->GetColor(y, x); + sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1); + const Color aDestColor( + static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetRed())) >> 8), + static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetGreen())) >> 8), + static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetBlue())) >> 8)); + DrawPixel(Point(x,y), aDestColor); + } + } +} + +void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle ) +{ + assert(!is_double_buffered_window()); + + DrawImage( rPos, Size(), rImage, nStyle ); +} + +void OutputDevice::DrawImage( const Point& rPos, const Size& rSize, + const Image& rImage, DrawImageFlags nStyle ) +{ + assert(!is_double_buffered_window()); + + bool bIsSizeValid = !rSize.IsEmpty(); + + if (!ImplIsRecordLayout()) + { + Image& rNonConstImage = const_cast<Image&>(rImage); + if (bIsSizeValid) + rNonConstImage.Draw(this, rPos, nStyle, &rSize); + else + rNonConstImage.Draw(this, rPos, nStyle); + } +} + +namespace +{ + // Co = Cs + Cd*(1-As) premultiplied alpha -or- + // Co = (AsCs + AdCd*(1-As)) / Ao + sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha, + const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor ) + { + int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor - + static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0; + return sal_uInt8( c ); + } + + BitmapColor AlphaBlend( int nX, int nY, + const long nMapX, + const long nMapY, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + BitmapReadAccess const * pB, + BitmapWriteAccess const * pAlphaW, + sal_uInt8& nResAlpha ) + { + BitmapColor aDstCol,aSrcCol; + aSrcCol = pP->GetColor( nMapY, nMapX ); + aDstCol = pB->GetColor( nY, nX ); + + // vcl stores transparency, not alpha - invert it + const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX ); + const sal_uInt8 nDstAlpha = 255 - pAlphaW->GetPixelIndex( nY, nX ); + + // Perform porter-duff compositing 'over' operation + + // Co = Cs + Cd*(1-As) + // Ad = As + Ad*(1-As) + nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255; + + aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) ); + aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) ); + aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) ); + + return aDstCol; + } +} + +void OutputDevice::BlendBitmap( + const SalTwoRect& rPosAry, + const Bitmap& rBmp ) +{ + mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), this ); +} + +Bitmap OutputDevice::BlendBitmapWithAlpha( + Bitmap& aBmp, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + const tools::Rectangle& aDstRect, + const sal_Int32 nOffY, + const sal_Int32 nDstHeight, + const sal_Int32 nOffX, + const sal_Int32 nDstWidth, + const long* pMapX, + const long* pMapY ) + +{ + BitmapColor aDstCol; + Bitmap res; + int nX, nY; + sal_uInt8 nResAlpha; + + SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" ); + + bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() ); + mpAlphaVDev->EnableMapMode(false); + + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) ); + BitmapScopedWriteAccess pAlphaW(aAlphaBitmap); + + if( GetBitCount() <= 8 ) + { + Bitmap aDither( aBmp.GetSizePixel(), 8 ); + BitmapColor aIndex( 0 ); + Bitmap::ScopedReadAccess pB(aBmp); + BitmapScopedWriteAccess pW(aDither); + + if (pB && pP && pA && pW && pAlphaW) + { + int nOutY; + + for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ ) + { + const long nMapY = pMapY[ nY ]; + const long nModY = ( nOutY & 0x0FL ) << 4; + int nOutX; + + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaW->GetScanline(nY); + for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ ) + { + const long nMapX = pMapX[ nX ]; + const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ]; + + aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha ); + + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) ); + pW->SetPixelOnData( pScanline, nX, aIndex ); + + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] ) ); + pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex ); + } + } + } + pB.reset(); + pW.reset(); + res = aDither; + } + else + { + BitmapScopedWriteAccess pB(aBmp); + if (pB && pP && pA && pAlphaW) + { + for( nY = 0; nY < nDstHeight; nY++ ) + { + const long nMapY = pMapY[ nY ]; + Scanline pScanlineB = pB->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaW->GetScanline(nY); + + for( nX = 0; nX < nDstWidth; nX++ ) + { + const long nMapX = pMapX[ nX ]; + aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha ); + + pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol)); + pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(255L-nResAlpha, 255L-nResAlpha, 255L-nResAlpha))); + } + } + } + pB.reset(); + res = aBmp; + } + + pAlphaW.reset(); + mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap ); + mpAlphaVDev->EnableMapMode( bOldMapMode ); + + return res; +} + +Bitmap OutputDevice::BlendBitmap( + Bitmap& aBmp, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + const sal_Int32 nOffY, + const sal_Int32 nDstHeight, + const sal_Int32 nOffX, + const sal_Int32 nDstWidth, + const tools::Rectangle& aBmpRect, + const Size& aOutSz, + const bool bHMirr, + const bool bVMirr, + const long* pMapX, + const long* pMapY ) +{ + BitmapColor aDstCol; + Bitmap res; + int nX, nY; + + if( GetBitCount() <= 8 ) + { + Bitmap aDither( aBmp.GetSizePixel(), 8 ); + BitmapColor aIndex( 0 ); + Bitmap::ScopedReadAccess pB(aBmp); + BitmapScopedWriteAccess pW(aDither); + + if( pB && pP && pA && pW ) + { + int nOutY; + + for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ ) + { + long nMapY = pMapY[ nY ]; + if (bVMirr) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + const long nModY = ( nOutY & 0x0FL ) << 4; + int nOutX; + + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineAlpha = pA->GetScanline(nMapY); + for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ ) + { + long nMapX = pMapX[ nX ]; + if (bHMirr) + { + nMapX = aBmpRect.Right() - nMapX; + } + const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ]; + + aDstCol = pB->GetColor( nY, nX ); + aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pA->GetIndexFromData( pScanlineAlpha, nMapX ) ); + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) ); + pW->SetPixelOnData( pScanline, nX, aIndex ); + } + } + } + + pB.reset(); + pW.reset(); + res = aDither; + } + else + { + BitmapScopedWriteAccess pB(aBmp); + + bool bFastBlend = false; + if( pP && pA && pB && !bHMirr && !bVMirr ) + { + SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(), + nOffX, nOffY, aOutSz.Width(), aOutSz.Height()); + + bFastBlend = ImplFastBitmapBlending( *pB,*pP,*pA, aTR ); + } + + if( pP && pA && pB && !bFastBlend ) + { + switch( pP->GetScanlineFormat() ) + { + case ScanlineFormat::N8BitPal: + { + for( nY = 0; nY < nDstHeight; nY++ ) + { + long nMapY = pMapY[ nY ]; + if ( bVMirr ) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + Scanline pPScan = pP->GetScanline( nMapY ); + Scanline pAScan = pA->GetScanline( nMapY ); + Scanline pBScan = pB->GetScanline( nY ); + + for( nX = 0; nX < nDstWidth; nX++ ) + { + long nMapX = pMapX[ nX ]; + + if ( bHMirr ) + { + nMapX = aBmpRect.Right() - nMapX; + } + aDstCol = pB->GetPixelFromData( pBScan, nX ); + aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] ); + pB->SetPixelOnData( pBScan, nX, aDstCol ); + } + } + } + break; + + default: + { + + for( nY = 0; nY < nDstHeight; nY++ ) + { + long nMapY = pMapY[ nY ]; + + if ( bVMirr ) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + Scanline pAScan = pA->GetScanline( nMapY ); + Scanline pBScan = pB->GetScanline(nY); + for( nX = 0; nX < nDstWidth; nX++ ) + { + long nMapX = pMapX[ nX ]; + + if ( bHMirr ) + { + nMapX = aBmpRect.Right() - nMapX; + } + aDstCol = pB->GetPixelFromData( pBScan, nX ); + aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] ); + pB->SetPixelOnData( pBScan, nX, aDstCol ); + } + } + } + break; + } + } + + pB.reset(); + res = aBmp; + } + + return res; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |