drawinglayer/source/tools/converters.cxx |  104 ++++++++++++++++++++-----------
 include/vcl/alpha.hxx                    |    3 
 include/vcl/bitmap.hxx                   |   23 ++++++
 vcl/source/bitmap/alpha.cxx              |   28 ++++++++
 vcl/source/bitmap/bitmap.cxx             |   69 ++++++++++++++++++++
 5 files changed, 191 insertions(+), 36 deletions(-)

New commits:
commit 651142cbc6305b182a22efbdc4524e614483cf88
Author:     Armin Le Grand (allotropia) <[email protected]>
AuthorDate: Thu Nov 10 12:03:55 2022 +0100
Commit:     Armin Le Grand <[email protected]>
CommitDate: Sun Nov 13 10:09:20 2022 +0100

    Adapted convertToBitmapEx to simpler BitmapEx creation
    
    As long as not all our mechanisms are changed to RGBA
    completely, mixing OutDev with Alpha (2x VDev) and RGB
    target rendering is just too dangerous and expensive
    and may to wrong or deliver bad quality results (see
    comments in code for details).
    
    Nonetheless we need a RGBA result for convert to
    BitmapEx. Luckily we are able to create a copmplete
    and valid AlphaChannel using 'createAlphaMask'.
    
    Based n that we know the content (RGB result from
    renderer), alpha (result from createAlphaMask) and
    the start condition (content rendered usually
    against COL_WHITE). Tht makes it possible to
    calculate back the content, quasi 'remove' that
    initial blending against COL_WHITE.
    
    That is what the helper Bitmap::RemoveBlendedStartColor
    does. Luckily we only need it for convert To BitmapEx,
    not in any other rendering.
    
    This gives good results, it is in principle comparable
    with the results using pre-multiplied alpha tooling,
    also slightly reducing the range of color values where
    high alpha vlaues are used, but in areas that are
    highly transparent anyways.
    
    Also important is that this will work with RGB-based
    system-dependent renderers, too. The method before
    could only work with the VCL-based primitive renderers
    by principle (only there - by coincidence - OutputDevice
    with Alpha worked).
    
    NOTE: Had to re-add usage of *unused* alpha channel
    in convertToBitmapEx due to test SdPNGExportTest. It
    somehow creates an Alpha in Bitmap size when I *remove*
    Alpha in convertToBitmapEx, so I just keep it for now,
    it is created anyways, just wanted to make it sleeker.
    
    Change-Id: I12e47327f5793d6ed87e217a2355c608f528246f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/142547
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <[email protected]>

diff --git a/drawinglayer/source/tools/converters.cxx 
b/drawinglayer/source/tools/converters.cxx
index 48a5b404b056..332d91dcc7c4 100644
--- a/drawinglayer/source/tools/converters.cxx
+++ b/drawinglayer/source/tools/converters.cxx
@@ -32,9 +32,12 @@
 
 #ifdef DBG_UTIL
 #include <tools/stream.hxx>
-#include <vcl/filter/PngImageWriter.hxx>
+// #include <vcl/filter/PngImageWriter.hxx>
+#include <vcl/dibtools.hxx>
 #endif
 
+// #include <vcl/BitmapReadAccess.hxx>
+
 namespace
 {
 bool implPrepareConversion(drawinglayer::primitive2d::Primitive2DContainer& 
rSequence,
@@ -137,39 +140,56 @@ BitmapEx 
convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSe
                            sal_uInt32 nDiscreteWidth, sal_uInt32 
nDiscreteHeight,
                            sal_uInt32 nMaxSquarePixels)
 {
-    BitmapEx aRetval;
     drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq));
 
     if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, 
nMaxSquarePixels))
     {
-        return aRetval;
+        return BitmapEx();
     }
 
     const Point aEmptyPoint;
     const Size aSizePixel(nDiscreteWidth, nDiscreteHeight);
 
-    // Create target VirtualDevice. Use a VirtualDevice in the Alpha-mode.
-    // This creates the needed alpha channel 'in parallel'. It is not
-    // cheaper though since the VDev in that mode internally uses two VDevs,
-    // so resource-wise it's more expensive, speed-wise pretty much the same
-    // (the former two-path rendering created content & alpha separately in
-    // two runs). The former method always created the correct Alpha, but
-    // when transparent geometry was involved, the created content was
-    // blended against white (COL_WHITE) due to the starting conditions of
-    // creation.
-    // There are more ways than this to do this correctly, but this is the
-    // most simple for now. Due to hoping to be able to render to RGBA in the
-    // future anyways there is no need to experiment trying to do the correct
-    // thing using an expanded version of the former method.
-    ScopedVclPtrInstance<VirtualDevice> 
pContent(*Application::GetDefaultDevice(),
-                                                 DeviceFormat::DEFAULT, 
DeviceFormat::DEFAULT);
+    // Create target VirtualDevice. Go back to using a simple RGB
+    // target version (comared with former version, see history).
+    // Reasons are manyfold:
+    // - Avoid the RGBA mode for VirtualDevice (two VDevs)
+    //   - It's not suggested to be used outside presentation engine
+    //   - It only works *by chance* with VCLPrimitiveRenderer
+    // - Usage of two-VDev alpha-VDev avoided alpha blending against
+    //   COL_WHITE in the 1st layer of targets (not in buffers below)
+    //   but is kind of a 'hack' doing so
+    // - Other renderers (system-dependent PrimitiveRenderers, other
+    //   than the VCL-based ones) will probably not support splitted
+    //   VDevs for content/alpha, so require a method that works with
+    //   RGB targeting (for now)
+    // - Less ressource usage, better speed (no 2 VDevs, no merge of
+    //   AlphaChannels)
+    // As long as not all our mechanisms are changed to RGBA completely,
+    // mixing these is just too dangerous and expensive and may to wrong
+    // or deliver bad quality results.
+    // Nonetheless we need a RGBA result here. Luckily we are able to
+    // create a copmplete and valid AlphaChannel using 'createAlphaMask'
+    // above.
+    // When we know the content (RGB result from renderer), alpha
+    // (result from createAlphaMask) and the start condition (content
+    // rendered against COL_WHITE), it is possible to calculate back
+    // the content, quasi 'remove' that initial blending against
+    // COL_WHITE.
+    // That is what the helper Bitmap::RemoveBlendedStartColor does.
+    // Luckily we only need it for this 'convertToBitmapEx', not in
+    // any other rendering. It could be further optimized, too.
+    // This gives good results, it is in principle comparable with
+    // the results using pre-multiplied alpha tooling, also reducing
+    // the range of values where high alpha vlaues are used.
+    ScopedVclPtrInstance< VirtualDevice > 
pContent(*Application::GetDefaultDevice());
 
     // prepare vdev
     if (!pContent->SetOutputSizePixel(aSizePixel, false))
     {
         SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << 
aSizePixel.Width() << "x"
                                                               << 
aSizePixel.Height());
-        return aRetval;
+        return BitmapEx();
     }
 
     // We map to pixel, use that MapMode. Init by erasing.
@@ -185,8 +205,8 @@ BitmapEx 
convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSe
     // render content
     pContentProcessor->process(aSequence);
 
-    // create final BitmapEx result
-    aRetval = pContent->GetBitmapEx(aEmptyPoint, aSizePixel);
+    // create final BitmapEx result (content)
+    Bitmap aRetval(pContent->GetBitmap(aEmptyPoint, aSizePixel));
 
 #ifdef DBG_UTIL
     static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
@@ -197,28 +217,40 @@ BitmapEx 
convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSe
             OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
         if (!sDumpPath.isEmpty())
         {
-            SvFileStream aNew(sDumpPath + "test_combined.png",
-                              StreamMode::WRITE | StreamMode::TRUNC);
-            vcl::PngImageWriter aPNGWriter(aNew);
-            aPNGWriter.write(aRetval);
+            SvFileStream aNew(sDumpPath + "test_content.bmp", 
StreamMode::WRITE | StreamMode::TRUNC);
+            WriteDIB(aRetval, aNew, false, true);
         }
     }
 #endif
 
-    // Combined/mixed usage of VirtualDevice in Alpha-mode here and
-    // the faster method for divided content/alpha in the VclPixelProcessor2D
-    // *can* go wrong e.g. for objects filled with TransparenceGradients,
-    // so unfortunately for now we have to reliably re-create the
-    // AlphaMask using a method that does this always correct (also used
+    // Create the AlphaMask using a method that does this always correct (also 
used
     // now in GlowPrimitive2D and ShadowPrimitive2D which both only need the
     // AlphaMask to do their job, so speeding that up, too).
-    // Note: This could be further approved by creating a small ::B2DProcessor
-    //       that checks if rSeq contains a TransparenceGradient fill and only 
then use
-    //       this correction.
-    const AlphaMask aAlpha(implcreateAlphaMask(aSequence, rViewInformation2D, 
aSizePixel));
-    aRetval = BitmapEx(aRetval.GetBitmap(), aAlpha);
+    AlphaMask aAlpha(implcreateAlphaMask(aSequence, rViewInformation2D, 
aSizePixel));
+
+#ifdef DBG_UTIL
+    if (bDoSaveForVisualControl)
+    {
+        // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
+        static const OUString sDumpPath(
+            OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+        if (!sDumpPath.isEmpty())
+        {
+            SvFileStream aNew(sDumpPath + "test_alpha.bmp", StreamMode::WRITE 
| StreamMode::TRUNC);
+            WriteDIB(aAlpha.GetBitmap(), aNew, false, true);
+        }
+    }
+#endif
+
+    if (aAlpha.hasAlpha())
+    {
+        // Need to correct content using known alpha to get to background-free
+        // RGBA result, usable e.g. in PNG export(s) or convert-to-bitmap
+        aRetval.RemoveBlendedStartColor(COL_WHITE, aAlpha);
+    }
 
-    return aRetval;
+    // return combined result
+    return BitmapEx(aRetval, aAlpha);
 }
 
 BitmapEx 
convertPrimitive2DContainerToBitmapEx(primitive2d::Primitive2DContainer&& 
rSequence,
diff --git a/include/vcl/alpha.hxx b/include/vcl/alpha.hxx
index 1078ce06fe8e..855485d4399e 100644
--- a/include/vcl/alpha.hxx
+++ b/include/vcl/alpha.hxx
@@ -51,6 +51,9 @@ public:
     void        Erase( sal_uInt8 cTransparency );
     void        BlendWith(const Bitmap& rOther);
 
+    // check if alpha is used, returns true if at least one pixel has 
transparence
+    bool        hasAlpha() const;
+
     BitmapReadAccess*  AcquireAlphaReadAccess() { return 
Bitmap::AcquireReadAccess(); }
     BitmapWriteAccess* AcquireAlphaWriteAccess() { return 
Bitmap::AcquireWriteAccess(); }
 
diff --git a/include/vcl/bitmap.hxx b/include/vcl/bitmap.hxx
index ecbff7fbb8ef..123b48fcd752 100644
--- a/include/vcl/bitmap.hxx
+++ b/include/vcl/bitmap.hxx
@@ -499,6 +499,29 @@ public:
                                 bool bInvert = false,
                                 bool msoBrightness = false );
 
+    /** Remove existing blending against COL_WHITE based on given AlphaMask
+
+        Inside convertToBitmapEx the content gets rendered to RGB target (no 
'A'),
+        so it gets blended against the start condition of the target device 
which
+        is blank (usually white background, but others may be used).
+        Usually rendering to RGB is sufficient (e.g. EditViews), but for 
conversion
+        to BitmapEx the alpha channel is needed to e.g. allow 
export/conversion to
+        pixel target formats which support Alpha, e.g. PNG.
+        It is possible though to create the fully valid and correct 
AlphaChannel.
+        If the content, the start condition and the alpha values are known it 
is
+        possible to calculate back ("remove") the white blending from the 
result,
+        and this is what this method does.
+
+        @param rColor
+        The Color we know this Bitmap is blended against (usually COL_WHITE)
+
+        @param rAlphaMask
+        The AlphaMask which was used to blend white against this
+     */
+    void                    RemoveBlendedStartColor(
+                                const Color& rColor,
+                                const AlphaMask& rAlphaMask);
+
     // access to SystemDependentDataHolder, to support overload in derived 
class(es)
     const basegfx::SystemDependentDataHolder* 
accessSystemDependentDataHolder() const;
 
diff --git a/vcl/source/bitmap/alpha.cxx b/vcl/source/bitmap/alpha.cxx
index deeba1280c74..196ba30d5f57 100644
--- a/vcl/source/bitmap/alpha.cxx
+++ b/vcl/source/bitmap/alpha.cxx
@@ -112,6 +112,34 @@ void AlphaMask::BlendWith(const Bitmap& rOther)
     }
 }
 
+bool AlphaMask::hasAlpha() const
+{
+    // no content, no alpha
+    if(IsEmpty())
+        return false;
+
+    ScopedReadAccess pAcc(const_cast<AlphaMask&>(*this));
+    const tools::Long nHeight(pAcc->Height());
+    const tools::Long nWidth(pAcc->Width());
+
+    // no content, no alpha
+    if(0 == nHeight || 0 == nWidth)
+        return false;
+
+    for (tools::Long y = 0; y < nHeight; ++y)
+    {
+        for (tools::Long x = 0; x < nWidth; ++x)
+        {
+            if (0 != pAcc->GetColor(y, x).GetRed())
+            {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
 void AlphaMask::ReleaseAccess( BitmapReadAccess* pAccess )
 {
     if( pAccess )
diff --git a/vcl/source/bitmap/bitmap.cxx b/vcl/source/bitmap/bitmap.cxx
index d4edd8f711bc..11e58e2f32de 100644
--- a/vcl/source/bitmap/bitmap.cxx
+++ b/vcl/source/bitmap/bitmap.cxx
@@ -1717,6 +1717,75 @@ bool Bitmap::Adjust( short nLuminancePercent, short 
nContrastPercent,
     return bRet;
 }
 
+namespace
+{
+inline sal_uInt8 backBlendAlpha(sal_uInt16 alpha, sal_uInt16 srcCol, 
sal_uInt16 startCol)
+{
+    const sal_uInt16 nAlpha((alpha * startCol) / 255);
+    if(srcCol > nAlpha)
+    {
+        return static_cast<sal_uInt8>(((srcCol - nAlpha) * 255) / (255 - 
nAlpha));
+    }
+
+    return 0;
+}
+}
+
+void Bitmap::RemoveBlendedStartColor(
+    const Color& rStartColor,
+    const AlphaMask& rAlphaMask)
+{
+    // no content, done
+    if(IsEmpty())
+        return;
+
+    BitmapScopedWriteAccess pAcc(*this);
+    const tools::Long nHeight(pAcc->Height());
+    const tools::Long nWidth(pAcc->Width());
+
+    // no content, done
+    if(0 == nHeight || 0 == nWidth)
+        return;
+
+    AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlphaMask));
+
+    // inequal sizes of content and alpha, avoid change (maybe assert?)
+    if(pAlphaAcc->Height() != nHeight || pAlphaAcc->Width() != nWidth)
+        return;
+
+    // prepare local values as sal_uInt16 to avoid multiple conversions
+    const sal_uInt16 nStartColRed(rStartColor.GetRed());
+    const sal_uInt16 nStartColGreen(rStartColor.GetGreen());
+    const sal_uInt16 nStartColBlue(rStartColor.GetBlue());
+
+    for (tools::Long y = 0; y < nHeight; ++y)
+    {
+        for (tools::Long x = 0; x < nWidth; ++x)
+        {
+            // get alpha value
+            const sal_uInt8 nAlpha8(pAlphaAcc->GetColor(y, x).GetRed());
+
+            // not or completely transparent, no adaption needed
+            if(0 == nAlpha8 || 255 == nAlpha8)
+                continue;
+
+            // prepare local value as sal_uInt16 to avoid multiple conversions
+            const sal_uInt16 nAlpha16(static_cast<sal_uInt16>(nAlpha8));
+
+            // get source color
+            BitmapColor aColor(pAcc->GetColor(y, x));
+
+            // modify/blend back source color
+            aColor.SetRed(backBlendAlpha(nAlpha16, 
static_cast<sal_uInt16>(aColor.GetRed()), nStartColRed));
+            aColor.SetGreen(backBlendAlpha(nAlpha16, 
static_cast<sal_uInt16>(aColor.GetGreen()), nStartColGreen));
+            aColor.SetBlue(backBlendAlpha(nAlpha16, 
static_cast<sal_uInt16>(aColor.GetBlue()), nStartColBlue));
+
+            // write result back
+            pAcc->SetPixel(y, x, aColor);
+        }
+    }
+}
+
 const basegfx::SystemDependentDataHolder* 
Bitmap::accessSystemDependentDataHolder() const
 {
     if(!mxSalBmp)

Reply via email to