diff options
-rw-r--r-- | drawinglayer/source/primitive2d/glowprimitive2d.cxx | 53 | ||||
-rw-r--r-- | drawinglayer/source/primitive2d/softedgeprimitive2d.cxx | 298 | ||||
-rw-r--r-- | drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx | 1 | ||||
-rw-r--r-- | drawinglayer/source/processor2d/vclpixelprocessor2d.cxx | 68 | ||||
-rw-r--r-- | drawinglayer/source/processor2d/vclpixelprocessor2d.hxx | 1 | ||||
-rw-r--r-- | drawinglayer/source/tools/converters.cxx | 316 | ||||
-rw-r--r-- | emfio/source/reader/emfreader.cxx | 16 | ||||
-rw-r--r-- | include/drawinglayer/primitive2d/softedgeprimitive2d.hxx | 34 |
8 files changed, 508 insertions, 279 deletions
diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx index d3c8539eddf8..f8c503759e7d 100644 --- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -178,19 +178,19 @@ void GlowPrimitive2D::create2DDecomposition( { // We may have to take a corrective scaling into account when the // MaximumQuadraticPixel limit was used/triggered - double fScaleX(1.0); - double fScaleY(1.0); + double fScale(1.0); - if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth) + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) { - fScaleX = static_cast<double>(rBitmapExSizePixel.Width()) - / static_cast<double>(nDiscreteClippedWidth); - } - - if (static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) - { - fScaleY = static_cast<double>(rBitmapExSizePixel.Height()) - / static_cast<double>(nDiscreteClippedHeight); + // scale in X and Y should be the same (see fReduceFactor in convertToBitmapEx), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + fScale = (fScaleX + fScaleY) * 0.5; } // fDiscreteGlowRadius is the size of the halo from each side of the object. The halo is the @@ -199,8 +199,8 @@ void GlowPrimitive2D::create2DDecomposition( // fades to both sides by the blur radius; thus blur radius is half of glow radius. // Consider glow transparency (initial transparency near the object edge) const AlphaMask mask(ProcessAndBlurAlphaMask( - aBitmapEx.GetAlpha(), fDiscreteGlowRadius * fScaleX / 2.0, - fDiscreteGlowRadius * fScaleY / 2.0, 255 - getGlowColor().GetAlpha())); + aBitmapEx.GetAlpha(), fDiscreteGlowRadius * fScale / 2.0, + fDiscreteGlowRadius * fScale / 2.0, 255 - getGlowColor().GetAlpha())); // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask Bitmap bmp = aBitmapEx.GetBitmap(); @@ -211,16 +211,16 @@ void GlowPrimitive2D::create2DDecomposition( static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore if (bDoSaveForVisualControl) { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\test_glow.png" -#else - "~/test_glow.png" -#endif - , - StreamMode::WRITE | StreamMode::TRUNC); - vcl::PngImageWriter aPNGWriter(aNew); - aPNGWriter.write(result); + // 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_glow.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(result); + } } #endif @@ -317,6 +317,13 @@ void GlowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisit } } + if (getBuffered2DDecomposition().empty()) + { + // refresh last used DiscreteGlowRadius and ClippedRange to new remembered values + const_cast<GlowPrimitive2D*>(this)->mfLastDiscreteGlowRadius = fDiscreteGlowRadius; + const_cast<GlowPrimitive2D*>(this)->maLastClippedRange = aClippedRange; + } + // call parent, that will check for empty, call create2DDecomposition and // set as decomposition BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx index 59cf59cd7679..55cddb919aa6 100644 --- a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx @@ -18,20 +18,32 @@ */ #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <drawinglayer/converters.hxx> +#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx> + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif namespace drawinglayer::primitive2d { SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& aChildren) - : GroupPrimitive2D(std::move(aChildren)) + : BufferedDecompositionGroupPrimitive2D(std::move(aChildren)) , mfRadius(fRadius) + , mfLastDiscreteSoftRadius(0.0) + , maLastClippedRange() { } bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { - if (GroupPrimitive2D::operator==(rPrimitive)) + if (BufferedDecompositionGroupPrimitive2D::operator==(rPrimitive)) { auto& rCompare = static_cast<const SoftEdgePrimitive2D&>(rPrimitive); return getRadius() == rCompare.getRadius(); @@ -40,27 +52,285 @@ bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const return false; } -void SoftEdgePrimitive2D::get2DDecomposition( - Primitive2DDecompositionVisitor& rVisitor, +bool SoftEdgePrimitive2D::prepareValuesAndcheckValidity( + basegfx::B2DRange& rSoftRange, basegfx::B2DRange& rClippedRange, + basegfx::B2DVector& rDiscreteSoftSize, double& rfDiscreteSoftRadius, const geometry::ViewInformation2D& rViewInformation) const { + // no SoftRadius defined, done + if (getRadius() <= 0.0) + return false; + + // no geometry, done if (getChildren().empty()) + return false; + + // no pixel target, done + if (rViewInformation.getObjectToViewTransformation().isIdentity()) + return false; + + // get geometry range that defines area that needs to be pixelated + rSoftRange = getChildren().getB2DRange(rViewInformation); + + // no range of geometry, done + if (rSoftRange.isEmpty()) + return false; + + // initialize ClippedRange to full SoftRange -> all is visible + rClippedRange = rSoftRange; + + // get Viewport and check if used. If empty, all is visible (see + // ViewInformation2D definition in viewinformation2d.hxx) + if (!rViewInformation.getViewport().isEmpty()) + { + // if used, extend by SoftRadius to ensure needed parts are included + // that are not visible, but influence the visible parts + basegfx::B2DRange aVisibleArea(rViewInformation.getViewport()); + aVisibleArea.grow(getRadius() * 2); + + // calculate ClippedRange + rClippedRange.intersect(aVisibleArea); + + // if SoftRange is completely outside of VisibleArea, ClippedRange + // will be empty and we are done + if (rClippedRange.isEmpty()) + return false; + } + + // calculate discrete pixel size of SoftRange. If it's too small to visualize, we are done + rDiscreteSoftSize = rViewInformation.getObjectToViewTransformation() * rSoftRange.getRange(); + if (ceil(rDiscreteSoftSize.getX()) < 2.0 || ceil(rDiscreteSoftSize.getY()) < 2.0) + return false; + + // calculate discrete pixel size of SoftRadius. If it's too small to visualize, we are done + rfDiscreteSoftRadius = ceil( + (rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(getRadius(), 0)) + .getLength()); + if (rfDiscreteSoftRadius < 1.0) + return false; + + return true; +} + +void SoftEdgePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +{ + // Use endless while-loop-and-break mechanism due to having multiple + // exit scenarios that all have to do the same thing when exiting + while (true) + { + basegfx::B2DRange aSoftRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteSoftSize; + double fDiscreteSoftRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aSoftRange, aClippedRange, aDiscreteSoftSize, + fDiscreteSoftRadius, rViewInformation)) + break; + + // Create embedding transformation from object to top-left zero-aligned + // target pixel geometry (discrete form of ClippedRange) + // First, move to top-left of SoftRange + const sal_uInt32 nDiscreteSoftWidth(ceil(aDiscreteSoftSize.getX())); + const sal_uInt32 nDiscreteSoftHeight(ceil(aDiscreteSoftSize.getY())); + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aClippedRange.getMinX(), -aClippedRange.getMinY())); + // Second, scale to discrete bitmap size + // Even when using the offset from ClippedRange, we need to use the + // scaling from the full representation, thus from SoftRange + aEmbedding.scale(nDiscreteSoftWidth / aSoftRange.getWidth(), + nDiscreteSoftHeight / aSoftRange.getHeight()); + + // Embed content graphics to TransformPrimitive2D + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D(aEmbedding, Primitive2DContainer(getChildren()))); + primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef }; + + // Create BitmapEx using drawinglayer tooling, including a MaximumQuadraticPixel + // limitation to be safe and not go runtime/memory havoc. Use a pretty small + // limit due to this is softEdge functionality and will look good with bitmap scaling + // anyways. The value of 250.000 square pixels below maybe adapted as needed. + const basegfx::B2DVector aDiscreteClippedSize( + rViewInformation.getObjectToViewTransformation() * aClippedRange.getRange()); + const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX())); + const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY())); + const geometry::ViewInformation2D aViewInformation2D; + const sal_uInt32 nMaximumQuadraticPixels(250000); + const BitmapEx aBitmapEx(::drawinglayer::convertToBitmapEx( + std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight, + nMaximumQuadraticPixels)); + + if (aBitmapEx.IsEmpty()) + break; + + // Get BitmapEx and check size. If no content, we are done + const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel()); + if (!(rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0)) + break; + + // We may have to take a corrective scaling into account when the + // MaximumQuadraticPixel limit was used/triggered + double fScale(1.0); + + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) + { + // scale in X and Y should be the same (see fReduceFactor in convertToBitmapEx), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + fScale = (fScaleX + fScaleY) * 0.5; + } + + // Get the Alpha and use as base to blur and apply the effect + AlphaMask aMask(aBitmapEx.GetAlpha()); + const AlphaMask blurMask(drawinglayer::primitive2d::ProcessAndBlurAlphaMask( + aMask, -fDiscreteSoftRadius * fScale, fDiscreteSoftRadius * fScale, 0)); + aMask.BlendWith(blurMask); + + // The end result is the original bitmap with blurred 8-bit alpha mask + BitmapEx result(aBitmapEx.GetBitmap(), aMask); + +#ifdef DBG_UTIL + static bool bDoSaveForVisualControl(true); // loplugin:constvars:ignore + 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_softedge.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(result); + } + } +#endif + + // Independent from discrete sizes of soft alpha creation, always + // map and project soft result to geometry range extended by soft + // radius, but to the eventually clipped instance (ClippedRange) + const primitive2d::Primitive2DReference xEmbedRefBitmap( + new BitmapPrimitive2D(VCLUnoHelper::CreateVCLXBitmap(result), + basegfx::utils::createScaleTranslateB2DHomMatrix( + aClippedRange.getWidth(), aClippedRange.getHeight(), + aClippedRange.getMinX(), aClippedRange.getMinY()))); + + rContainer = primitive2d::Primitive2DContainer{ xEmbedRefBitmap }; + + // we made it, return return; + } + + // creation failed for some of many possible reasons, use original + // content, so the unmodified original geometry will be the result, + // just without any softEdge effect + rContainer = getChildren(); +} - if (!mbInMaskGeneration) +void SoftEdgePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + // Use endless while-loop-and-break mechanism due to having multiple + // exit scenarios that all have to do the same thing when exiting + while (true) { - GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + basegfx::B2DRange aSoftRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteSoftSize; + double fDiscreteSoftRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aSoftRange, aClippedRange, aDiscreteSoftSize, + fDiscreteSoftRadius, rViewInformation)) + break; + + if (!getBuffered2DDecomposition().empty()) + { + // First check is to detect if the last created decompose is capable + // to represent the now requested visualization (see similar + // implementation at GlowPrimitive2D). + if (!maLastClippedRange.isEmpty() && !maLastClippedRange.isInside(aClippedRange)) + { + basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange); + + if (!rViewInformation.getObjectToViewTransformation().isIdentity()) + { + // Grow by view-dependent size of 1/2 pixel + const double fHalfPixel((rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(0.5, 0)) + .getLength()); + aLastClippedRangeAndHairline.grow(fHalfPixel); + } + + if (!aLastClippedRangeAndHairline.isInside(aClippedRange)) + { + // Conditions of last local decomposition have changed, delete + const_cast<SoftEdgePrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + } + } + + if (!getBuffered2DDecomposition().empty()) + { + // Second check is to react on changes of the DiscreteSoftRadius when + // zooming in/out (see similar implementation at GlowPrimitive2D). + bool bFree(mfLastDiscreteSoftRadius <= 0.0 || fDiscreteSoftRadius <= 0.0); + + if (!bFree) + { + const double fDiff(fabs(mfLastDiscreteSoftRadius - fDiscreteSoftRadius)); + const double fLen(fabs(mfLastDiscreteSoftRadius) + fabs(fDiscreteSoftRadius)); + const double fRelativeChange(fDiff / fLen); + + // Use a lower value here, soft edge keeps it's content so avoid that it gets too + // unsharp in the pixel visualization + // Value is in the range of ]0.0 .. 1.0] + bFree = fRelativeChange >= 0.075; + } + + if (bFree) + { + // Conditions of last local decomposition have changed, delete + const_cast<SoftEdgePrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + } + + if (getBuffered2DDecomposition().empty()) + { + // refresh last used DiscreteSoftRadius and ClippedRange to new remembered values + const_cast<SoftEdgePrimitive2D*>(this)->mfLastDiscreteSoftRadius = fDiscreteSoftRadius; + const_cast<SoftEdgePrimitive2D*>(this)->maLastClippedRange = aClippedRange; + } + + // call parent, that will check for empty, call create2DDecomposition and + // set as decomposition + BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + + // we made it, return 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()); + // No soft edge needed for some of many possible reasons, use original content + rVisitor.visit(getChildren()); +} - const Primitive2DReference xRef( - new ModifiedColorPrimitive2D(Primitive2DContainer(getChildren()), aBColorModifier)); - rVisitor.visit(xRef); +basegfx::B2DRange +SoftEdgePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // Hint: Do *not* use GroupPrimitive2D::getB2DRange, that will (unnecessarily) + // use the decompose - what works, but is not needed here. + // We know the to-be-visualized geometry and the radius it needs to be extended, + // so simply calculate the exact needed range. + return getChildren().getB2DRange(rViewInformation); } sal_uInt32 SoftEdgePrimitive2D::getPrimitive2DID() const diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index 96e6daa66ab5..926835788383 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -929,7 +929,6 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi break; } case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: - case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: { processPrimitive2DOnPixelProcessor(rCandidate); break; diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index f00a38d49374..976e5cb1dccb 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -56,7 +56,6 @@ #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> #include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> #include <drawinglayer/primitive2d/epsprimitive2d.hxx> -#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> #include <drawinglayer/primitive2d/shadowprimitive2d.hxx> #include <drawinglayer/primitive2d/patternfillprimitive2d.hxx> #include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx> @@ -406,12 +405,6 @@ void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate)); break; } - case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: - { - processSoftEdgePrimitive2D( - static_cast<const drawinglayer::primitive2d::SoftEdgePrimitive2D&>(rCandidate)); - break; - } case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: { processShadowPrimitive2D( @@ -966,67 +959,6 @@ void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrim } } -void VclPixelProcessor2D::processSoftEdgePrimitive2D( - const primitive2d::SoftEdgePrimitive2D& rCandidate) -{ - const double nRadius(rCandidate.getRadius()); - // Avoid wrong effect on the cut-off side; so expand by diameter - const auto aExpandedViewInfo(::drawinglayer::primitive2d::expandB2DRangeAtViewInformation2D( - getViewInformation2D(), nRadius * 2)); - - basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo)); - aRange.transform(maCurrentTransformation); - basegfx::B2DVector aRadiusVector(nRadius, 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, false); - if (aBufferDevice.isVisible()) - { - // remember last OutDev and set to content - OutputDevice* pLastOutputDevice = mpOutputDevice; - mpOutputDevice = &aBufferDevice.getContent(); - // Since the effect converts all children to bitmap, we can't disable antialiasing here, - // because it would result in poor quality in areas not affected by the effect - process(rCandidate); - - // Limit the bitmap size to the visible area. - basegfx::B2DRange bitmapRange(aRange); - if (!aExpandedViewInfo.getDiscreteViewport().isEmpty()) - bitmapRange.intersect(aExpandedViewInfo.getDiscreteViewport()); - if (!bitmapRange.isEmpty()) - { - const tools::Rectangle aRect( - static_cast<tools::Long>(std::floor(bitmapRange.getMinX())), - static_cast<tools::Long>(std::floor(bitmapRange.getMinY())), - static_cast<tools::Long>(std::ceil(bitmapRange.getMaxX())), - static_cast<tools::Long>(std::ceil(bitmapRange.getMaxY()))); - BitmapEx bitmap = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize()); - - AlphaMask aMask = bitmap.GetAlpha(); - AlphaMask blurMask = drawinglayer::primitive2d::ProcessAndBlurAlphaMask( - aMask, -fBlurRadius, fBlurRadius, 0); - - aMask.BlendWith(blurMask); - - // The end result is the original bitmap with blurred 8-bit alpha mask - BitmapEx result(bitmap.GetBitmap(), aMask); - - // back to old OutDev - mpOutputDevice = pLastOutputDevice; - mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result); - } - else - { - mpOutputDevice = pLastOutputDevice; - } - } - else - SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible"); -} - void VclPixelProcessor2D::processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate) { if (rCandidate.getShadowBlur() == 0) diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx index e083e951afbf..d7494ccbbfff 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx @@ -98,7 +98,6 @@ class VclPixelProcessor2D final : public VclProcessor2D processBorderLinePrimitive2D(const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder); void processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate); void processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate); - void processSoftEdgePrimitive2D(const primitive2d::SoftEdgePrimitive2D& rCandidate); void processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate); void processFillGradientPrimitive2D(const primitive2d::FillGradientPrimitive2D& rPrimitive); void processPatternFillPrimitive2D(const primitive2d::PatternFillPrimitive2D& rPrimitive); diff --git a/drawinglayer/source/tools/converters.cxx b/drawinglayer/source/tools/converters.cxx index 84294b24af26..3c4ad791a3c3 100644 --- a/drawinglayer/source/tools/converters.cxx +++ b/drawinglayer/source/tools/converters.cxx @@ -37,197 +37,189 @@ namespace drawinglayer { - BitmapEx convertToBitmapEx( - drawinglayer::primitive2d::Primitive2DContainer&& rSeq, - const geometry::ViewInformation2D& rViewInformation2D, - sal_uInt32 nDiscreteWidth, - sal_uInt32 nDiscreteHeight, - sal_uInt32 nMaxSquarePixels) - { - BitmapEx aRetval; - - if(rSeq.empty()) - return aRetval; - - if(nDiscreteWidth <= 0 || nDiscreteHeight <= 0) - return aRetval; +BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, + sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, + sal_uInt32 nMaxSquarePixels) +{ + BitmapEx aRetval; - // get destination size in pixels - const MapMode aMapModePixel(MapUnit::MapPixel); - const sal_uInt32 nViewVisibleArea(nDiscreteWidth * nDiscreteHeight); - drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); + if (rSeq.empty()) + return aRetval; - if(nViewVisibleArea > nMaxSquarePixels) - { - // reduce render size - double fReduceFactor = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); - nDiscreteWidth = basegfx::fround(static_cast<double>(nDiscreteWidth) * fReduceFactor); - nDiscreteHeight = basegfx::fround(static_cast<double>(nDiscreteHeight) * fReduceFactor); + if (nDiscreteWidth <= 0 || nDiscreteHeight <= 0) + return aRetval; - const drawinglayer::primitive2d::Primitive2DReference aEmbed( - new drawinglayer::primitive2d::TransformPrimitive2D( - basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor), - std::move(aSequence))); + // get destination size in pixels + const MapMode aMapModePixel(MapUnit::MapPixel); + const sal_uInt32 nViewVisibleArea(nDiscreteWidth * nDiscreteHeight); + drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); - aSequence = drawinglayer::primitive2d::Primitive2DContainer { aEmbed }; - } + if (nViewVisibleArea > nMaxSquarePixels) + { + // reduce render size + double fReduceFactor + = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); + nDiscreteWidth = basegfx::fround(static_cast<double>(nDiscreteWidth) * fReduceFactor); + nDiscreteHeight = basegfx::fround(static_cast<double>(nDiscreteHeight) * fReduceFactor); + + const drawinglayer::primitive2d::Primitive2DReference aEmbed( + new drawinglayer::primitive2d::TransformPrimitive2D( + basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor), + std::move(aSequence))); + + aSequence = drawinglayer::primitive2d::Primitive2DContainer{ aEmbed }; + } - 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); - - // prepare vdev - if (!pContent->SetOutputSizePixel(aSizePixel, false)) - { - SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << aSizePixel.Width() << "x" << aSizePixel.Height()); - return aRetval; - } + 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); + + // prepare vdev + if (!pContent->SetOutputSizePixel(aSizePixel, false)) + { + SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << aSizePixel.Width() << "x" + << aSizePixel.Height()); + return aRetval; + } - // We map to pixel, use that MapMode. Init by erasing. - pContent->SetMapMode(aMapModePixel); - pContent->Erase(); + // We map to pixel, use that MapMode. Init by erasing. + pContent->SetMapMode(aMapModePixel); + pContent->Erase(); - // create pixel processor, also already takes care of AAing and - // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If - // not wanted, change after this call as needed - std::unique_ptr<processor2d::BaseProcessor2D> pContentProcessor = processor2d::createPixelProcessor2DFromOutputDevice( - *pContent, - rViewInformation2D); + // create pixel processor, also already takes care of AAing and + // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If + // not wanted, change after this call as needed + std::unique_ptr<processor2d::BaseProcessor2D> pContentProcessor + = processor2d::createPixelProcessor2DFromOutputDevice(*pContent, rViewInformation2D); - // render content - pContentProcessor->process(aSequence); + // render content + pContentProcessor->process(aSequence); - // create final BitmapEx result - aRetval = pContent->GetBitmapEx(aEmptyPoint, aSizePixel); + // create final BitmapEx result + aRetval = pContent->GetBitmapEx(aEmptyPoint, aSizePixel); #ifdef DBG_UTIL - static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore - if(bDoSaveForVisualControl) + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + 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( -#ifdef _WIN32 - "c:\\test_combined.png" -#else - "~/test_combined.png" -#endif - , StreamMode::WRITE|StreamMode::TRUNC); + SvFileStream aNew(sDumpPath + "test_combined.png", + StreamMode::WRITE | StreamMode::TRUNC); vcl::PngImageWriter aPNGWriter(aNew); aPNGWriter.write(aRetval); } + } #endif - return aRetval; - } + return aRetval; +} + +BitmapEx convertPrimitive2DContainerToBitmapEx(primitive2d::Primitive2DContainer&& rSequence, + const basegfx::B2DRange& rTargetRange, + sal_uInt32 nMaximumQuadraticPixels, + const o3tl::Length eTargetUnit, + const std::optional<Size>& rTargetDPI) +{ + if (rSequence.empty()) + return BitmapEx(); - BitmapEx convertPrimitive2DContainerToBitmapEx( - primitive2d::Primitive2DContainer&& rSequence, - const basegfx::B2DRange& rTargetRange, - sal_uInt32 nMaximumQuadraticPixels, - const o3tl::Length eTargetUnit, - const std::optional<Size>& rTargetDPI) + try { - if(rSequence.empty()) + css::geometry::RealRectangle2D aRealRect; + aRealRect.X1 = rTargetRange.getMinX(); + aRealRect.Y1 = rTargetRange.getMinY(); + aRealRect.X2 = rTargetRange.getMaxX(); + aRealRect.Y2 = rTargetRange.getMaxY(); + + // get system DPI + Size aDPI( + Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); + if (rTargetDPI.has_value()) + { + aDPI = *rTargetDPI; + } + + ::sal_uInt32 DPI_X = aDPI.getWidth(); + ::sal_uInt32 DPI_Y = aDPI.getHeight(); + const basegfx::B2DRange aRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2); + const double fWidth(aRange.getWidth()); + const double fHeight(aRange.getHeight()); + + if (!(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0))) return BitmapEx(); - try + if (0 == DPI_X) { - css::geometry::RealRectangle2D aRealRect; - aRealRect.X1 = rTargetRange.getMinX(); - aRealRect.Y1 = rTargetRange.getMinY(); - aRealRect.X2 = rTargetRange.getMaxX(); - aRealRect.Y2 = rTargetRange.getMaxY(); - - // get system DPI - Size aDPI(Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - if (rTargetDPI.has_value()) - { - aDPI = *rTargetDPI; - } - - ::sal_uInt32 DPI_X = aDPI.getWidth(); - ::sal_uInt32 DPI_Y = aDPI.getHeight(); - const basegfx::B2DRange aRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2); - const double fWidth(aRange.getWidth()); - const double fHeight(aRange.getHeight()); - - if(!(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0))) - return BitmapEx(); - - if(0 == DPI_X) - { - DPI_X = 75; - } - - if(0 == DPI_Y) - { - DPI_Y = 75; - } - - if(0 == nMaximumQuadraticPixels) - { - nMaximumQuadraticPixels = 500000; - } - - const auto aViewInformation2D = geometry::createViewInformation2D({}); - const sal_uInt32 nDiscreteWidth(basegfx::fround(o3tl::convert(fWidth, eTargetUnit, o3tl::Length::in) * DPI_X)); - const sal_uInt32 nDiscreteHeight(basegfx::fround(o3tl::convert(fHeight, eTargetUnit, o3tl::Length::in) * DPI_Y)); - - basegfx::B2DHomMatrix aEmbedding( - basegfx::utils::createTranslateB2DHomMatrix( - -aRange.getMinX(), - -aRange.getMinY())); - - aEmbedding.scale( - nDiscreteWidth / fWidth, - nDiscreteHeight / fHeight); - - const primitive2d::Primitive2DReference xEmbedRef( - new primitive2d::TransformPrimitive2D( - aEmbedding, - std::move(rSequence))); - primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; - - BitmapEx aBitmapEx( - convertToBitmapEx( - std::move(xEmbedSeq), - aViewInformation2D, - nDiscreteWidth, - nDiscreteHeight, - nMaximumQuadraticPixels)); - - if(aBitmapEx.IsEmpty()) - return BitmapEx(); - aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); - aBitmapEx.SetPrefSize(Size(basegfx::fround(fWidth), basegfx::fround(fHeight))); - - return aBitmapEx; + DPI_X = 75; } - catch (const css::uno::Exception&) + + if (0 == DPI_Y) { - TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!"); + DPI_Y = 75; } - catch (const std::exception& e) + + if (0 == nMaximumQuadraticPixels) { - SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what()); + nMaximumQuadraticPixels = 500000; } - return BitmapEx(); + const auto aViewInformation2D = geometry::createViewInformation2D({}); + const sal_uInt32 nDiscreteWidth( + basegfx::fround(o3tl::convert(fWidth, eTargetUnit, o3tl::Length::in) * DPI_X)); + const sal_uInt32 nDiscreteHeight( + basegfx::fround(o3tl::convert(fHeight, eTargetUnit, o3tl::Length::in) * DPI_Y)); + + basegfx::B2DHomMatrix aEmbedding( + basegfx::utils::createTranslateB2DHomMatrix(-aRange.getMinX(), -aRange.getMinY())); + + aEmbedding.scale(nDiscreteWidth / fWidth, nDiscreteHeight / fHeight); + + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D(aEmbedding, std::move(rSequence))); + primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef }; + + BitmapEx aBitmapEx(convertToBitmapEx(std::move(xEmbedSeq), aViewInformation2D, + nDiscreteWidth, nDiscreteHeight, + nMaximumQuadraticPixels)); + + if (aBitmapEx.IsEmpty()) + return BitmapEx(); + aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + aBitmapEx.SetPrefSize(Size(basegfx::fround(fWidth), basegfx::fround(fHeight))); + + return aBitmapEx; } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!"); + } + catch (const std::exception& e) + { + SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what()); + } + + return BitmapEx(); +} } // end of namespace drawinglayer /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/source/reader/emfreader.cxx b/emfio/source/reader/emfreader.cxx index 2d50a9a057bb..37978478bdbe 100644 --- a/emfio/source/reader/emfreader.cxx +++ b/emfio/source/reader/emfreader.cxx @@ -1496,16 +1496,22 @@ namespace emfio } } - #ifdef DBG_UTIL +#ifdef DBG_UTIL static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore if(bDoSaveForVisualControl) { - SvFileStream aNew("c:\\metafile_content.png", StreamMode::WRITE|StreamMode::TRUNC); - vcl::PngImageWriter aPNGWriter(aNew); - aPNGWriter.write(aBitmapEx); + // 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 + "metafile_content.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(aBitmapEx); + } } - #endif +#endif maBmpSaveList.emplace_back(aBitmapEx, aRect, SRCAND|SRCINVERT); } } diff --git a/include/drawinglayer/primitive2d/softedgeprimitive2d.hxx b/include/drawinglayer/primitive2d/softedgeprimitive2d.hxx index 90ada61f7b2e..4a49444560c5 100644 --- a/include/drawinglayer/primitive2d/softedgeprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/softedgeprimitive2d.hxx @@ -20,30 +20,54 @@ #pragma once #include <drawinglayer/drawinglayerdllapi.h> -#include <drawinglayer/primitive2d/groupprimitive2d.hxx> +#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx> namespace drawinglayer::primitive2d { -class DRAWINGLAYER_DLLPUBLIC SoftEdgePrimitive2D final : public GroupPrimitive2D +class DRAWINGLAYER_DLLPUBLIC SoftEdgePrimitive2D final + : public BufferedDecompositionGroupPrimitive2D { private: /// Soft edge size, in logical units (100ths of mm) double mfRadius; - mutable bool mbInMaskGeneration = false; + + /// last used DiscreteSoftRadius and ClippedRange + double mfLastDiscreteSoftRadius; + basegfx::B2DRange maLastClippedRange; + + /// helpers + bool prepareValuesAndcheckValidity(basegfx::B2DRange& rSoftRange, + basegfx::B2DRange& rClippedRange, + basegfx::B2DVector& rDiscreteSoftSize, + double& rfDiscreteSoftRadius, + const geometry::ViewInformation2D& rViewInformation) const; + +protected: + /** method which is to be used to implement the local decomposition of a 2D primitive. */ + virtual void + create2DDecomposition(Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& rViewInformation) const override; public: + /// constructor SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& aChildren); + /// data read access double getRadius() const { return mfRadius; } - void setMaskGeneration(bool bVal = true) const { mbInMaskGeneration = bVal; } - + /// compare operator virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + /// get range + virtual basegfx::B2DRange + getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override; + + /// The default implementation will return an empty sequence virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override; + /// provide unique ID virtual sal_uInt32 getPrimitive2DID() const override; }; } // end of namespace drawinglayer::primitive2d |