Use CComPtr and its method to create instance by prog id
[LibreOffice.git] / drawinglayer / source / primitive2d / softedgeprimitive2d.cxx
blobe6f92f312f59bc365cb61e55969b69f25917b33b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
21 #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
22 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
23 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
24 #include <basegfx/matrix/b2dhommatrixtools.hxx>
25 #include <toolkit/helper/vclunohelper.hxx>
26 #include <drawinglayer/converters.hxx>
27 #include "GlowSoftEgdeShadowTools.hxx"
29 #ifdef DBG_UTIL
30 #include <tools/stream.hxx>
31 #include <vcl/filter/PngImageWriter.hxx>
32 #endif
34 namespace drawinglayer::primitive2d
36 SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& aChildren)
37 : BufferedDecompositionGroupPrimitive2D(std::move(aChildren))
38 , mfRadius(fRadius)
39 , mfLastDiscreteSoftRadius(0.0)
40 , maLastClippedRange()
42 // activate callback to flush buffered decomposition content
43 setCallbackSeconds(15);
46 bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
48 if (BufferedDecompositionGroupPrimitive2D::operator==(rPrimitive))
50 auto& rCompare = static_cast<const SoftEdgePrimitive2D&>(rPrimitive);
51 return getRadius() == rCompare.getRadius();
54 return false;
57 bool SoftEdgePrimitive2D::prepareValuesAndcheckValidity(
58 basegfx::B2DRange& rSoftRange, basegfx::B2DRange& rClippedRange,
59 basegfx::B2DVector& rDiscreteSoftSize, double& rfDiscreteSoftRadius,
60 const geometry::ViewInformation2D& rViewInformation) const
62 // no SoftRadius defined, done
63 if (getRadius() <= 0.0)
64 return false;
66 // no geometry, done
67 if (getChildren().empty())
68 return false;
70 // no pixel target, done
71 if (rViewInformation.getObjectToViewTransformation().isIdentity())
72 return false;
74 // get geometry range that defines area that needs to be pixelated
75 rSoftRange = getChildren().getB2DRange(rViewInformation);
77 // no range of geometry, done
78 if (rSoftRange.isEmpty())
79 return false;
81 // initialize ClippedRange to full SoftRange -> all is visible
82 rClippedRange = rSoftRange;
84 // get Viewport and check if used. If empty, all is visible (see
85 // ViewInformation2D definition in viewinformation2d.hxx)
86 if (!rViewInformation.getViewport().isEmpty())
88 // if used, extend by SoftRadius to ensure needed parts are included
89 // that are not visible, but influence the visible parts
90 basegfx::B2DRange aVisibleArea(rViewInformation.getViewport());
91 aVisibleArea.grow(getRadius() * 2);
93 // To do this correctly, it needs to be done in discrete coordinates.
94 // The object may be transformed relative to the original#
95 // ObjectTransformation, e.g. when re-used in shadow
96 aVisibleArea.transform(rViewInformation.getViewTransformation());
97 rClippedRange.transform(rViewInformation.getObjectToViewTransformation());
99 // calculate ClippedRange
100 rClippedRange.intersect(aVisibleArea);
102 // if SoftRange is completely outside of VisibleArea, ClippedRange
103 // will be empty and we are done
104 if (rClippedRange.isEmpty())
105 return false;
107 // convert result back to object coordinates
108 rClippedRange.transform(rViewInformation.getInverseObjectToViewTransformation());
111 // calculate discrete pixel size of SoftRange. If it's too small to visualize, we are done
112 rDiscreteSoftSize = rViewInformation.getObjectToViewTransformation() * rSoftRange.getRange();
113 if (ceil(rDiscreteSoftSize.getX()) < 2.0 || ceil(rDiscreteSoftSize.getY()) < 2.0)
114 return false;
116 // calculate discrete pixel size of SoftRadius. If it's too small to visualize, we are done
117 rfDiscreteSoftRadius = ceil(
118 (rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(getRadius(), 0))
119 .getLength());
120 if (rfDiscreteSoftRadius < 1.0)
121 return false;
123 return true;
126 void SoftEdgePrimitive2D::create2DDecomposition(
127 Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const
129 // Use endless while-loop-and-break mechanism due to having multiple
130 // exit scenarios that all have to do the same thing when exiting
131 while (true)
133 basegfx::B2DRange aSoftRange;
134 basegfx::B2DRange aClippedRange;
135 basegfx::B2DVector aDiscreteSoftSize;
136 double fDiscreteSoftRadius(0.0);
138 // Check various validity details and calculate/prepare values. If false, we are done
139 if (!prepareValuesAndcheckValidity(aSoftRange, aClippedRange, aDiscreteSoftSize,
140 fDiscreteSoftRadius, rViewInformation))
141 break;
143 // Create embedding transformation from object to top-left zero-aligned
144 // target pixel geometry (discrete form of ClippedRange)
145 // First, move to top-left of SoftRange
146 const sal_uInt32 nDiscreteSoftWidth(ceil(aDiscreteSoftSize.getX()));
147 const sal_uInt32 nDiscreteSoftHeight(ceil(aDiscreteSoftSize.getY()));
148 basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix(
149 -aClippedRange.getMinX(), -aClippedRange.getMinY()));
150 // Second, scale to discrete bitmap size
151 // Even when using the offset from ClippedRange, we need to use the
152 // scaling from the full representation, thus from SoftRange
153 aEmbedding.scale(nDiscreteSoftWidth / aSoftRange.getWidth(),
154 nDiscreteSoftHeight / aSoftRange.getHeight());
156 // Embed content graphics to TransformPrimitive2D
157 const primitive2d::Primitive2DReference xEmbedRef(
158 new primitive2d::TransformPrimitive2D(aEmbedding, Primitive2DContainer(getChildren())));
159 primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef };
161 // Create BitmapEx using drawinglayer tooling, including a MaximumQuadraticPixel
162 // limitation to be safe and not go runtime/memory havoc. Use a pretty small
163 // limit due to this is softEdge functionality and will look good with bitmap scaling
164 // anyways. The value of 250.000 square pixels below maybe adapted as needed.
165 const basegfx::B2DVector aDiscreteClippedSize(
166 rViewInformation.getObjectToViewTransformation() * aClippedRange.getRange());
167 const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX()));
168 const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY()));
169 const geometry::ViewInformation2D aViewInformation2D;
170 const sal_uInt32 nMaximumQuadraticPixels(250000);
171 // tdf#156808 force an alpha mask to be created even if it has no alpha
172 // We need an alpha mask, even if it is totally opaque, so that
173 // drawinglayer::primitive2d::ProcessAndBlurAlphaMask() can be called.
174 // Otherwise, blurring of edges will fail in cases like running in a
175 // slideshow or exporting to PDF.
176 const BitmapEx aBitmapEx(::drawinglayer::convertToBitmapEx(
177 std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight,
178 nMaximumQuadraticPixels, true));
180 if (aBitmapEx.IsEmpty())
181 break;
183 // Get BitmapEx and check size. If no content, we are done
184 const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel());
185 if (!(rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0))
186 break;
188 // We may have to take a corrective scaling into account when the
189 // MaximumQuadraticPixel limit was used/triggered
190 double fScale(1.0);
192 if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth
193 || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight)
195 // scale in X and Y should be the same (see fReduceFactor in convertToBitmapEx),
196 // so adapt numerically to a single scale value, they are integer rounded values
197 const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width())
198 / static_cast<double>(nDiscreteClippedWidth));
199 const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height())
200 / static_cast<double>(nDiscreteClippedHeight));
202 fScale = (fScaleX + fScaleY) * 0.5;
205 // Get the Alpha and use as base to blur and apply the effect
206 AlphaMask aMask(aBitmapEx.GetAlphaMask());
207 if (aMask.IsEmpty()) // There is no mask, fully opaque
208 break;
209 AlphaMask blurMask(drawinglayer::primitive2d::ProcessAndBlurAlphaMask(
210 aMask, -fDiscreteSoftRadius * fScale, fDiscreteSoftRadius * fScale, 0));
211 aMask.BlendWith(blurMask);
213 // The end result is the original bitmap with blurred 8-bit alpha mask
214 BitmapEx result(aBitmapEx.GetBitmap(), aMask);
216 #ifdef DBG_UTIL
217 static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
218 if (bDoSaveForVisualControl)
220 // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
221 static const OUString sDumpPath(
222 OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
223 if (!sDumpPath.isEmpty())
225 SvFileStream aNew(sDumpPath + "test_softedge.png",
226 StreamMode::WRITE | StreamMode::TRUNC);
227 vcl::PngImageWriter aPNGWriter(aNew);
228 aPNGWriter.write(result);
231 #endif
233 // Independent from discrete sizes of soft alpha creation, always
234 // map and project soft result to geometry range extended by soft
235 // radius, but to the eventually clipped instance (ClippedRange)
236 const primitive2d::Primitive2DReference xEmbedRefBitmap(
237 new BitmapPrimitive2D(result, basegfx::utils::createScaleTranslateB2DHomMatrix(
238 aClippedRange.getWidth(), aClippedRange.getHeight(),
239 aClippedRange.getMinX(), aClippedRange.getMinY())));
241 rContainer = primitive2d::Primitive2DContainer{ xEmbedRefBitmap };
243 // we made it, return
244 return;
247 // creation failed for some of many possible reasons, use original
248 // content, so the unmodified original geometry will be the result,
249 // just without any softEdge effect
250 rContainer = getChildren();
253 void SoftEdgePrimitive2D::get2DDecomposition(
254 Primitive2DDecompositionVisitor& rVisitor,
255 const geometry::ViewInformation2D& rViewInformation) const
257 // Use endless while-loop-and-break mechanism due to having multiple
258 // exit scenarios that all have to do the same thing when exiting
259 while (true)
261 basegfx::B2DRange aSoftRange;
262 basegfx::B2DRange aClippedRange;
263 basegfx::B2DVector aDiscreteSoftSize;
264 double fDiscreteSoftRadius(0.0);
266 // Check various validity details and calculate/prepare values. If false, we are done
267 if (!prepareValuesAndcheckValidity(aSoftRange, aClippedRange, aDiscreteSoftSize,
268 fDiscreteSoftRadius, rViewInformation))
269 break;
271 if (!getBuffered2DDecomposition().empty())
273 // First check is to detect if the last created decompose is capable
274 // to represent the now requested visualization (see similar
275 // implementation at GlowPrimitive2D).
276 if (!maLastClippedRange.isEmpty() && !maLastClippedRange.isInside(aClippedRange))
278 basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange);
280 if (!rViewInformation.getObjectToViewTransformation().isIdentity())
282 // Grow by view-dependent size of 1/2 pixel
283 const double fHalfPixel((rViewInformation.getInverseObjectToViewTransformation()
284 * basegfx::B2DVector(0.5, 0))
285 .getLength());
286 aLastClippedRangeAndHairline.grow(fHalfPixel);
289 if (!aLastClippedRangeAndHairline.isInside(aClippedRange))
291 // Conditions of last local decomposition have changed, delete
292 const_cast<SoftEdgePrimitive2D*>(this)->setBuffered2DDecomposition(
293 Primitive2DContainer());
298 if (!getBuffered2DDecomposition().empty())
300 // Second check is to react on changes of the DiscreteSoftRadius when
301 // zooming in/out (see similar implementation at GlowPrimitive2D).
302 bool bFree(mfLastDiscreteSoftRadius <= 0.0 || fDiscreteSoftRadius <= 0.0);
304 if (!bFree)
306 const double fDiff(fabs(mfLastDiscreteSoftRadius - fDiscreteSoftRadius));
307 const double fLen(fabs(mfLastDiscreteSoftRadius) + fabs(fDiscreteSoftRadius));
308 const double fRelativeChange(fDiff / fLen);
310 // Use a lower value here, soft edge keeps it's content so avoid that it gets too
311 // unsharp in the pixel visualization
312 // Value is in the range of ]0.0 .. 1.0]
313 bFree = fRelativeChange >= 0.075;
316 if (bFree)
318 // Conditions of last local decomposition have changed, delete
319 const_cast<SoftEdgePrimitive2D*>(this)->setBuffered2DDecomposition(
320 Primitive2DContainer());
324 if (getBuffered2DDecomposition().empty())
326 // refresh last used DiscreteSoftRadius and ClippedRange to new remembered values
327 const_cast<SoftEdgePrimitive2D*>(this)->mfLastDiscreteSoftRadius = fDiscreteSoftRadius;
328 const_cast<SoftEdgePrimitive2D*>(this)->maLastClippedRange = aClippedRange;
331 // call parent, that will check for empty, call create2DDecomposition and
332 // set as decomposition
333 BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
335 // we made it, return
336 return;
339 // No soft edge needed for some of many possible reasons, use original content
340 rVisitor.visit(getChildren());
343 basegfx::B2DRange
344 SoftEdgePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
346 // Hint: Do *not* use GroupPrimitive2D::getB2DRange, that will (unnecessarily)
347 // use the decompose - what works, but is not needed here.
348 // We know the to-be-visualized geometry and the radius it needs to be extended,
349 // so simply calculate the exact needed range.
350 return getChildren().getB2DRange(rViewInformation);
353 sal_uInt32 SoftEdgePrimitive2D::getPrimitive2DID() const
355 return PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D;
358 } // end of namespace
360 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */