[Libreoffice-commits] core.git: solenv/clang-format vcl/Library_vcl.mk vcl/source

Chris Sherlock (via logerrit) logerrit at kemper.freedesktop.org
Mon Aug 30 16:29:18 UTC 2021


 solenv/clang-format/excludelist |    1 
 vcl/Library_vcl.mk              |    1 
 vcl/source/outdev/bitmap.cxx    |  675 ----------------------------------------
 vcl/source/outdev/bitmapex.cxx  |  674 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 684 insertions(+), 667 deletions(-)

New commits:
commit 654c1f6ebe4c0c8504e0b9a74ee48f7146cf5a43
Author:     Chris Sherlock <chris.sherlock79 at gmail.com>
AuthorDate: Wed May 12 19:44:00 2021 +1000
Commit:     Mike Kaganski <mike.kaganski at collabora.com>
CommitDate: Mon Aug 30 18:28:42 2021 +0200

    vcl: move BitmapEx related functions to outdev/bitmapex.cxx
    
    Change-Id: I93d94d3043263b97ae9f9078a8afc6016c1a3531
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115472
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kaganski at collabora.com>

diff --git a/solenv/clang-format/excludelist b/solenv/clang-format/excludelist
index 51d61da54d09..966c347fc17a 100644
--- a/solenv/clang-format/excludelist
+++ b/solenv/clang-format/excludelist
@@ -15030,6 +15030,7 @@ vcl/source/opengl/x11/X11DeviceInfo.cxx
 vcl/source/opengl/x11/context.cxx
 vcl/source/outdev/background.cxx
 vcl/source/outdev/bitmap.cxx
+vcl/source/outdev/bitmapex.cxx
 vcl/source/outdev/clipping.cxx
 vcl/source/outdev/curvedshapes.cxx
 vcl/source/outdev/fill.cxx
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 91b2023d3554..05f33b07115d 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -221,6 +221,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/outdev/transparent \
     vcl/source/outdev/mask \
     vcl/source/outdev/bitmap \
+    vcl/source/outdev/bitmapex \
     vcl/source/outdev/font \
     vcl/source/outdev/text \
     vcl/source/outdev/textline \
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx
index bf0b6031afdf..e86055d9df1e 100644
--- a/vcl/source/outdev/bitmap.cxx
+++ b/vcl/source/outdev/bitmap.cxx
@@ -17,40 +17,22 @@
  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
  */
 
-#include <cassert>
-#include <cstdlib>
-
-#include <vcl/bitmap.hxx>
-#include <vcl/bitmapex.hxx>
-#include <vcl/BitmapFilterStackBlur.hxx>
-#include <vcl/canvastools.hxx>
-#include <vcl/gdimtf.hxx>
-#include <vcl/metaact.hxx>
 #include <config_features.h>
+
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/image.hxx>
+#include <vcl/metaact.hxx>
 #include <vcl/skia/SkiaHelper.hxx>
-#include <vcl/outdev.hxx>
 #include <vcl/virdev.hxx>
-#include <vcl/image.hxx>
-#include <vcl/BitmapMonochromeFilter.hxx>
 
 #include <bitmap/BitmapWriteAccess.hxx>
 #include <bitmap/bmpfast.hxx>
 #include <drawmode.hxx>
-#include <salgdi.hxx>
 #include <salbmp.hxx>
-
-#include <basegfx/matrix/b2dhommatrixtools.hxx>
-#include <memory>
-#include <comphelper/lok.hxx>
-#include <sal/log.hxx>
-#include <osl/diagnose.h>
-#include <tools/helpers.hxx>
-#include <tools/debug.hxx>
-#include <rtl/math.hxx>
-#include <o3tl/unit_conversion.hxx>
-
-#include <vcl/dibtools.hxx>
-#include <tools/stream.hxx>
+#include <salgdi.hxx>
 
 void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap )
 {
@@ -258,104 +240,6 @@ Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz,
     return aBmp;
 }
 
-void OutputDevice::DrawBitmapEx( const Point& rDestPt,
-                                 const BitmapEx& rBitmapEx )
-{
-    assert(!is_double_buffered_window());
-
-    if( ImplIsRecordLayout() )
-        return;
-
-    if( !rBitmapEx.IsAlpha() )
-    {
-        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 ( !rBitmapEx.IsAlpha() )
-    {
-        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( !rBitmapEx.IsAlpha() )
-    {
-        DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
-    }
-    else
-    {
-        if ( RasterOp::Invert == meRasterOp )
-        {
-            DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
-            return;
-        }
-
-        BitmapEx aBmpEx(vcl::drawmode::GetBitmapEx(rBitmapEx, GetDrawMode()));
-
-        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;
@@ -451,151 +335,6 @@ Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
     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.getPixelFormat() > vcl::PixelFormat::N8_BPP)
-            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.IsEmpty())
-    {
-        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.maAlphaMask.ImplGetSalBitmap();
-
-            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.GetAlpha(),
-                                                       rBitmapEx.GetAlpha()));
-            }
-            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 )
@@ -1014,404 +753,6 @@ void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap,
     mpMetaFile = pOldMetaFile;
 }
 
-bool OutputDevice::DrawTransformBitmapExDirect(
-        const basegfx::B2DHomMatrix& aFullTransform,
-        const BitmapEx& rBitmapEx,
-        double fAlpha)
-{
-    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.IsAlpha())
-    {
-        aAlphaBitmap = rBitmapEx.GetAlpha();
-    }
-    else if (mpAlphaVDev)
-    {
-        aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel());
-        aAlphaBitmap.Erase(COL_BLACK); // opaque
-    }
-
-    SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get();
-
-    bDone = mpGraphics->DrawTransformedBitmap(
-        aNull,
-        aTopX,
-        aTopY,
-        *pSalSrcBmp,
-        pSalAlphaBmp,
-        fAlpha,
-        *this);
-
-    if (mpAlphaVDev)
-    {
-        // Merge bitmap alpha to alpha device
-        AlphaMask aAlpha(rBitmapEx.GetSizePixel());
-        aAlpha.Erase( ( 1 - fAlpha ) * 255 );
-        mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aAlpha, 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,
-    double fAlpha)
-{
-    assert(!is_double_buffered_window());
-
-    if( ImplIsRecordLayout() )
-        return;
-
-    if(rBitmapEx.IsEmpty())
-        return;
-
-    if(rtl::math::approxEqual( fAlpha, 0.0 ))
-        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, for the in
-       record-to-metafile case the  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
-
-    BitmapEx bitmapEx = rBitmapEx;
-
-    const bool bInvert(RasterOp::Invert == meRasterOp);
-    const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
-    const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
-    // 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);
-
-    // First try to handle additional alpha blending, either directly, or modify the bitmap.
-    if(!rtl::math::approxEqual( fAlpha, 1.0 ))
-    {
-        if(bTryDirectPaint)
-        {
-            if(DrawTransformBitmapExDirect(aFullTransform, bitmapEx, fAlpha))
-            {
-                // we are done
-                return;
-            }
-        }
-        // Apply the alpha manually.
-        sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) );
-        AlphaMask aAlpha( bitmapEx.GetSizePixel(), &nColor );
-        if( bitmapEx.IsAlpha())
-            aAlpha.BlendWith( bitmapEx.GetAlpha());
-        bitmapEx = BitmapEx( bitmapEx.GetBitmap(), aAlpha );
-    }
-    if(rtl::math::approxEqual( fAlpha, 1.0 ))
-        fAlpha = 1.0; // avoid the need for approxEqual in backends
-
-    if(bTryDirectPaint && mpGraphics->HasFastDrawTransformedBitmap() && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
-        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, bitmapEx);
-        if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
-        {
-            EnableMapMode();
-            aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
-        }
-        return;
-    }
-
-    if(bTryDirectPaint && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
-    {
-        // 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, bitmapEx);
-        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(bitmapEx.GetSizePixel());
-    const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
-    const double fOrigAreaScaled(fOrigArea * 1.44);
-    double fMaximumArea(std::clamp(fOrigAreaScaled, 1000000.0, 4500000.0));
-
-    if(!bMetafile)
-    {
-        if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
-            return;
-    }
-
-    if(aVisibleRange.isEmpty())
-        return;
-
-    BitmapEx aTransformed(bitmapEx);
-
-    // #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.IsAlpha() && (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;
-        }
-        Degree10 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);
-}
-
 bool OutputDevice::HasFastDrawTransformedBitmap() const
 {
     if( ImplIsRecordLayout() )
diff --git a/vcl/source/outdev/bitmapex.cxx b/vcl/source/outdev/bitmapex.cxx
new file mode 100644
index 000000000000..d0016e81635d
--- /dev/null
+++ b/vcl/source/outdev/bitmapex.cxx
@@ -0,0 +1,674 @@
+/* -*- 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 <config_features.h>
+
+#include <rtl/math.hxx>
+#include <comphelper/lok.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <vcl/canvastools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt,
+                                 const BitmapEx& rBitmapEx )
+{
+    assert(!is_double_buffered_window());
+
+    if( ImplIsRecordLayout() )
+        return;
+
+    if( !rBitmapEx.IsAlpha() )
+    {
+        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 ( !rBitmapEx.IsAlpha() )
+    {
+        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( !rBitmapEx.IsAlpha() )
+    {
+        DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+    }
+    else
+    {
+        if ( RasterOp::Invert == meRasterOp )
+        {
+            DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+            return;
+        }
+
+        BitmapEx aBmpEx(vcl::drawmode::GetBitmapEx(rBitmapEx, GetDrawMode()));
+
+        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 );
+    }
+}
+
+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.getPixelFormat() > vcl::PixelFormat::N8_BPP)
+            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.IsEmpty())
+    {
+        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.maAlphaMask.ImplGetSalBitmap();
+
+            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.GetAlpha(),
+                                                       rBitmapEx.GetAlpha()));
+            }
+            else
+            {
+                mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *this);
+
+                if (mpAlphaVDev)
+                {
+                    // #i32109#: Make bitmap area opaque
+                    mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+                }
+            }
+        }
+    }
+}
+
+bool OutputDevice::DrawTransformBitmapExDirect(
+        const basegfx::B2DHomMatrix& aFullTransform,
+        const BitmapEx& rBitmapEx,
+        double fAlpha)
+{
+    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.IsAlpha())
+    {
+        aAlphaBitmap = rBitmapEx.GetAlpha();
+    }
+    else if (mpAlphaVDev)
+    {
+        aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel());
+        aAlphaBitmap.Erase(COL_BLACK); // opaque
+    }
+
+    SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get();
+
+    bDone = mpGraphics->DrawTransformedBitmap(
+        aNull,
+        aTopX,
+        aTopY,
+        *pSalSrcBmp,
+        pSalAlphaBmp,
+        fAlpha,
+        *this);
+
+    if (mpAlphaVDev)
+    {
+        // Merge bitmap alpha to alpha device
+        AlphaMask aAlpha(rBitmapEx.GetSizePixel());
+        aAlpha.Erase( ( 1 - fAlpha ) * 255 );
+        mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aAlpha, 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,
+    double fAlpha)
+{
+    assert(!is_double_buffered_window());
+
+    if( ImplIsRecordLayout() )
+        return;
+
+    if(rBitmapEx.IsEmpty())
+        return;
+
+    if(rtl::math::approxEqual( fAlpha, 0.0 ))
+        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, for the in
+       record-to-metafile case the  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
+
+    BitmapEx bitmapEx = rBitmapEx;
+
+    const bool bInvert(RasterOp::Invert == meRasterOp);
+    const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
+    const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
+    // 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);
+
+    // First try to handle additional alpha blending, either directly, or modify the bitmap.
+    if(!rtl::math::approxEqual( fAlpha, 1.0 ))
+    {
+        if(bTryDirectPaint)
+        {
+            if(DrawTransformBitmapExDirect(aFullTransform, bitmapEx, fAlpha))
+            {
+                // we are done
+                return;
+            }
+        }
+        // Apply the alpha manually.
+        sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) );
+        AlphaMask aAlpha( bitmapEx.GetSizePixel(), &nColor );
+        if( bitmapEx.IsAlpha())
+            aAlpha.BlendWith( bitmapEx.GetAlpha());
+        bitmapEx = BitmapEx( bitmapEx.GetBitmap(), aAlpha );
+    }
+    if(rtl::math::approxEqual( fAlpha, 1.0 ))
+        fAlpha = 1.0; // avoid the need for approxEqual in backends
+
+    if(bTryDirectPaint && mpGraphics->HasFastDrawTransformedBitmap() && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
+        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, bitmapEx);
+        if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+        {
+            EnableMapMode();
+            aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
+        }
+        return;
+    }
+
+    if(bTryDirectPaint && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
+    {
+        // 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, bitmapEx);
+        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(bitmapEx.GetSizePixel());
+    const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
+    const double fOrigAreaScaled(fOrigArea * 1.44);
+    double fMaximumArea(std::clamp(fOrigAreaScaled, 1000000.0, 4500000.0));
+
+    if(!bMetafile)
+    {
+        if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
+            return;
+    }
+
+    if(aVisibleRange.isEmpty())
+        return;
+
+    BitmapEx aTransformed(bitmapEx);
+
+    // #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.IsAlpha() && (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;
+        }
+        Degree10 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);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */


More information about the Libreoffice-commits mailing list