summaryrefslogtreecommitdiff
path: root/drawinglayer
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2020-05-12 20:05:54 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2020-05-13 23:29:29 +0200
commitcbc13ac0624685582ebd4634812681274db803aa (patch)
treef9a5e856161f4ea5290cc31ea892843a75d8bcc0 /drawinglayer
parent4907531966880f2bb4bb14b1c159865909000842 (diff)
tdf#49247: draw soft edges
This factors out the common code for blurring used both in glow and soft edges into ProcessAndBlurAlphaMask. Also this reverts commit a98bdbae459ad7341bf7f484c402e77e4062cd16, since its use was removed from VclPixelProcessor2D. Change-Id: Icd7fdb06bef3932ff3b9ce7e283b515b15d246a5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94087 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'drawinglayer')
-rw-r--r--drawinglayer/Library_drawinglayer.mk1
-rw-r--r--drawinglayer/source/primitive2d/Tools.cxx2
-rw-r--r--drawinglayer/source/primitive2d/softedgeprimitive2d.cxx69
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.cxx160
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.hxx2
5 files changed, 190 insertions, 44 deletions
diff --git a/drawinglayer/Library_drawinglayer.mk b/drawinglayer/Library_drawinglayer.mk
index 2a0f1030a789..24b8055836d0 100644
--- a/drawinglayer/Library_drawinglayer.mk
+++ b/drawinglayer/Library_drawinglayer.mk
@@ -115,6 +115,7 @@ $(eval $(call gb_Library_add_exception_objects,drawinglayer,\
drawinglayer/source/primitive2d/sceneprimitive2d \
drawinglayer/source/primitive2d/sdrdecompositiontools2d \
drawinglayer/source/primitive2d/shadowprimitive2d \
+ drawinglayer/source/primitive2d/softedgeprimitive2d \
drawinglayer/source/primitive2d/structuretagprimitive2d \
drawinglayer/source/primitive2d/svggradientprimitive2d \
drawinglayer/source/primitive2d/textbreakuphelper \
diff --git a/drawinglayer/source/primitive2d/Tools.cxx b/drawinglayer/source/primitive2d/Tools.cxx
index 2a8b8239a569..7db3a94c8d04 100644
--- a/drawinglayer/source/primitive2d/Tools.cxx
+++ b/drawinglayer/source/primitive2d/Tools.cxx
@@ -230,6 +230,8 @@ OUString idToString(sal_uInt32 nId)
return "PAGEHIERARCHY";
case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
return "GLOWPRIMITIVE";
+ case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
+ return "SOFTEDGEPRIMITIVE";
default:
return OUString::number((nId >> 16) & 0xFF) + "|" + OUString::number(nId & 0xFF);
}
diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
new file mode 100644
index 000000000000..4c5b1b2c6102
--- /dev/null
+++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
+
+namespace drawinglayer::primitive2d
+{
+SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, const Primitive2DContainer& rChildren)
+ : GroupPrimitive2D(rChildren)
+ , mfRadius(fRadius)
+{
+}
+
+bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
+{
+ if (GroupPrimitive2D::operator==(rPrimitive))
+ {
+ auto& rCompare = static_cast<const SoftEdgePrimitive2D&>(rPrimitive);
+ return getRadius() == rCompare.getRadius();
+ }
+
+ return false;
+}
+
+void SoftEdgePrimitive2D::get2DDecomposition(
+ Primitive2DDecompositionVisitor& rVisitor,
+ const geometry::ViewInformation2D& rViewInformation) const
+{
+ if (getChildren().empty())
+ return;
+
+ if (!mbInMaskGeneration)
+ {
+ GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
+ return;
+ }
+
+ // create a modifiedColorPrimitive containing the *black* color and the content. Using black
+ // on white allows creating useful mask in VclPixelProcessor2D::processSoftEdgePrimitive2D.
+ basegfx::BColorModifierSharedPtr aBColorModifier
+ = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor());
+
+ const Primitive2DReference xRef(new ModifiedColorPrimitive2D(getChildren(), aBColorModifier));
+ rVisitor.append(xRef);
+}
+
+ImplPrimitive2DIDBlock(SoftEdgePrimitive2D, PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D)
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 4f7d6a22f723..0e74fe9bf2aa 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -24,9 +24,7 @@
#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <vcl/BitmapBasicMorphologyFilter.hxx>
-#include <vcl/BitmapColorReplaceFilter.hxx>
#include <vcl/BitmapFilterStackBlur.hxx>
-#include <vcl/BitmapMonochromeFilter.hxx>
#include <vcl/outdev.hxx>
#include <vcl/dibtools.hxx>
#include <vcl/hatch.hxx>
@@ -62,6 +60,7 @@
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
+#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/awt/XControl.hpp>
@@ -387,6 +386,12 @@ void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv
static_cast<const drawinglayer::primitive2d::GlowPrimitive2D&>(rCandidate));
break;
}
+ case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
+ {
+ processSoftEdgePrimitive2D(
+ static_cast<const drawinglayer::primitive2d::SoftEdgePrimitive2D&>(rCandidate));
+ break;
+ }
default:
{
SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
@@ -911,6 +916,58 @@ void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrim
}
}
+namespace
+{
+/* Returns 8-bit alpha mask created from passed mask. The result may be scaled down; it's
+ expected that it will be automatically scaled up back when applied to the bitmap.
+
+ Negative fErodeDilateRadius values mean erode, positive - dilate.
+ nTransparency defines minimal transparency level.
+*/
+AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rBWMask, double fErodeDilateRadius,
+ double fBlurRadius, sal_uInt8 nTransparency)
+{
+ // Only completely white pixels on the initial mask must be considered for transparency. Any
+ // other color must be treated as black. This creates 1-bit B&W bitmap.
+ BitmapEx mask(rBWMask.CreateMask(COL_WHITE));
+
+ // Scaling down increases performance without noticeable quality loss. Additionally,
+ // current blur implementation can only handle blur radius between 2 and 254.
+ Size aSize = mask.GetSizePixel();
+ double fScale = 1.0;
+ while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
+ {
+ fScale /= 2;
+ fBlurRadius /= 2;
+ fErodeDilateRadius /= 2;
+ aSize.setHeight(aSize.Height() / 2);
+ aSize.setWidth(aSize.Width() / 2);
+ }
+
+ // BmpScaleFlag::Fast is important for following color replacement
+ mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
+
+ if (fErodeDilateRadius > 0)
+ BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius));
+ else if (fErodeDilateRadius < 0)
+ BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 0xFF));
+
+ if (nTransparency)
+ {
+ const Color aTransparency(nTransparency, nTransparency, nTransparency);
+ mask.Replace(COL_BLACK, aTransparency);
+ }
+
+ // We need 8-bit grey mask for blurring
+ mask.Convert(BmpConversion::N8BitGreys);
+
+ // calculate blurry effect
+ BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
+
+ return AlphaMask(mask.GetBitmap());
+}
+}
+
void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate)
{
basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
@@ -918,7 +975,14 @@ void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitiv
basegfx::B2DVector aGlowRadiusVector(rCandidate.getGlowRadius(), 0);
// Calculate the pixel size of glow radius in current transformation
aGlowRadiusVector *= maCurrentTransformation;
- const double fGlowRadius = aGlowRadiusVector.getLength();
+ // Glow radius is the size of the halo from each side of the object. The halo is the
+ // border of glow color that fades from glow transparency level to fully transparent
+ // When blurring a sharp boundary (our case), it gets 50% of original intensity, and
+ // fades to both sides by the blur radius; thus blur radius is half of glow radius.
+ const double fBlurRadius = aGlowRadiusVector.getLength() / 2;
+ // Consider glow transparency (initial transparency near the object edge)
+ const sal_uInt8 nTransparency = rCandidate.getGlowColor().GetTransparency();
+
impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
if (aBufferDevice.isVisible())
{
@@ -933,53 +997,60 @@ void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitiv
Bitmap bitmap = mpOutputDevice->GetBitmap(Point(aRange.getMinX(), aRange.getMinY()),
Size(aRange.getWidth(), aRange.getHeight()));
- BitmapEx mask(bitmap); // copy the bitmap to mask
- // Only completely transparent parts will be completely white; only those must be
- // considered white on the initial B&W alpha mask. Any other color must be treated
- // as black.
- BitmapFilter::Filter(mask, BitmapMonochromeFilter(255));
-
- // Scaling down increases performance without noticeable quality loss. Additionally,
- // current blur implementation can only handle blur radius between 2 and 254.
- Size aSize = mask.GetSizePixel();
- double fScale = 1.0;
- // Glow radius is the size of the halo from each side of the object. The halo is the
- // border of glow color that fades from glow transparency level to fully transparent
- // When blurring a sharp boundary (our case), it gets 50% of original intensity, and
- // fades to both sides by the blur radius; thus blur radius is half of glow radius.
- double fBlurRadius = fGlowRadius / 2;
- while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
- {
- fScale /= 2;
- fBlurRadius /= 2;
- aSize.setHeight(aSize.Height() / 2);
- aSize.setWidth(aSize.Width() / 2);
- }
-
- // BmpScaleFlag::Fast is important for following color replacement
- mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
-
- // Dilate the black pixels using blur radius, to make blur start at actual object margins.
- // This differentiates glow from blurry shadow; so potentially extend this function to also
- // handle blurry shadow, and conditionally skip this step
- BitmapFilter::Filter(mask, BitmapDilateFilter(fBlurRadius));
-
- // We need 8-bit grey mask for blurring
- mask.Convert(BmpConversion::N8BitGreys);
- // Consider glow transparency (initial transparency near the object edge)
- const sal_uInt8 nTransparency = rCandidate.getGlowColor().GetTransparency();
- const Color aTransparency(nTransparency, nTransparency, nTransparency);
- BitmapFilter::Filter(mask, BitmapColorReplaceFilter(COL_BLACK, aTransparency));
-
- // calculate blurry effect
- BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
+ AlphaMask mask = ProcessAndBlurAlphaMask(bitmap, fBlurRadius, fBlurRadius, nTransparency);
// The end result is the bitmap filled with glow color and blurred 8-bit alpha mask
const basegfx::BColor aGlowColor(
maBColorModifierStack.getModifiedColor(rCandidate.getGlowColor().getBColor()));
bitmap.Erase(Color(aGlowColor));
// alpha mask will be scaled up automatically to match bitmap
- BitmapEx result(bitmap, AlphaMask(mask.GetBitmap()));
+ BitmapEx result(bitmap, mask);
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+ mpOutputDevice->DrawBitmapEx(Point(aRange.getMinX(), aRange.getMinY()), result);
+ }
+ else
+ SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
+}
+
+void VclPixelProcessor2D::processSoftEdgePrimitive2D(
+ const primitive2d::SoftEdgePrimitive2D& rCandidate)
+{
+ basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
+ aRange.transform(maCurrentTransformation);
+ basegfx::B2DVector aRadiusVector(rCandidate.getRadius(), 0);
+ // Calculate the pixel size of soft edge radius in current transformation
+ aRadiusVector *= maCurrentTransformation;
+ // Blur radius is equal to soft edge radius
+ const double fBlurRadius = aRadiusVector.getLength();
+
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
+ if (aBufferDevice.isVisible())
+ {
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+ // Processing will draw whatever geometry on white background, applying *black*
+ // replacement color
+ mpOutputDevice->Erase();
+ rCandidate.setMaskGeneration();
+ process(rCandidate);
+ rCandidate.setMaskGeneration(false);
+ Bitmap bitmap = mpOutputDevice->GetBitmap(Point(aRange.getMinX(), aRange.getMinY()),
+ Size(aRange.getWidth(), aRange.getHeight()));
+
+ AlphaMask mask = ProcessAndBlurAlphaMask(bitmap, -fBlurRadius, fBlurRadius, 0);
+
+ // The end result is the original bitmap with blurred 8-bit alpha mask
+
+ mpOutputDevice->Erase();
+ process(rCandidate);
+ bitmap = mpOutputDevice->GetBitmap(Point(aRange.getMinX(), aRange.getMinY()),
+ Size(aRange.getWidth(), aRange.getHeight()));
+
+ // alpha mask will be scaled up automatically to match bitmap
+ BitmapEx result(bitmap, mask);
// back to old OutDev
mpOutputDevice = pLastOutputDevice;
@@ -988,6 +1059,7 @@ void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitiv
else
SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
}
+
} // end of namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
index fd72efe41d16..e78d1f08225a 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
@@ -40,6 +40,7 @@ class FillHatchPrimitive2D;
class BackgroundColorPrimitive2D;
class BorderLinePrimitive2D;
class GlowPrimitive2D;
+class SoftEdgePrimitive2D;
}
namespace drawinglayer::processor2d
@@ -95,6 +96,7 @@ class VclPixelProcessor2D final : public VclProcessor2D
void processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate);
void processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate);
void processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate);
+ void processSoftEdgePrimitive2D(const primitive2d::SoftEdgePrimitive2D& rCandidate);
public:
/// constructor/destructor