summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de>2022-11-10 12:03:55 +0100
committerArmin Le Grand <Armin.Le.Grand@me.com>2022-11-13 10:09:20 +0100
commit651142cbc6305b182a22efbdc4524e614483cf88 (patch)
tree6d4d36d9c35a99fc88e09e6b1aef676a59fd8fc7
parent55cd20e6228a06836285c14ca6726adb1bb4ffcb (diff)
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 <Armin.Le.Grand@me.com>
-rw-r--r--drawinglayer/source/tools/converters.cxx104
-rw-r--r--include/vcl/alpha.hxx3
-rw-r--r--include/vcl/bitmap.hxx23
-rw-r--r--vcl/source/bitmap/alpha.cxx28
-rw-r--r--vcl/source/bitmap/bitmap.cxx69
5 files changed, 191 insertions, 36 deletions
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)