LibreOffice Module drawinglayer (master) 1
converters.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
26#include <vcl/svapp.hxx>
27#include <vcl/virdev.hxx>
28#include <com/sun/star/geometry/RealRectangle2D.hpp>
30
32
33#ifdef DBG_UTIL
34#include <tools/stream.hxx>
35// #include <vcl/filter/PngImageWriter.hxx>
36#include <vcl/dibtools.hxx>
37#endif
38
39// #include <vcl/BitmapReadAccess.hxx>
40
41namespace
42{
43bool implPrepareConversion(drawinglayer::primitive2d::Primitive2DContainer& rSequence,
44 sal_uInt32& rnDiscreteWidth, sal_uInt32& rnDiscreteHeight,
45 const sal_uInt32 nMaxSquarePixels)
46{
47 if (rSequence.empty())
48 return false;
49
50 if (rnDiscreteWidth <= 0 || rnDiscreteHeight <= 0)
51 return false;
52
53 const sal_uInt32 nViewVisibleArea(rnDiscreteWidth * rnDiscreteHeight);
54
55 if (nViewVisibleArea > nMaxSquarePixels)
56 {
57 // reduce render size
58 double fReduceFactor
59 = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea));
60 rnDiscreteWidth = basegfx::fround(static_cast<double>(rnDiscreteWidth) * fReduceFactor);
61 rnDiscreteHeight = basegfx::fround(static_cast<double>(rnDiscreteHeight) * fReduceFactor);
62
65 basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor),
66 std::move(rSequence)));
67
69 }
70
71 return true;
72}
73
75 const drawinglayer::geometry::ViewInformation2D& rViewInformation2D,
76 const Size& rSizePixel, bool bUseLuminance)
77{
79
80 // prepare vdev
81 if (!pContent->SetOutputSizePixel(rSizePixel, false))
82 {
83 SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << rSizePixel.Width() << "x"
84 << rSizePixel.Height());
85 return AlphaMask();
86 }
87
88 // create pixel processor, also already takes care of AAing and
89 // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If
90 // not wanted, change after this call as needed
91 std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pContentProcessor
93 rViewInformation2D);
94
95 // prepare for mask creation
96 pContent->SetMapMode(MapMode(MapUnit::MapPixel));
97
98 // set transparency to all white (fully transparent)
99 pContent->Erase();
100
101 basegfx::BColorModifierSharedPtr aBColorModifier;
102 if (bUseLuminance)
103 {
104 // new mode: bUseLuminance allows simple creation of alpha channels
105 // for any content (e.g. gradients)
106 aBColorModifier = std::make_shared<basegfx::BColorModifier_luminance_to_alpha>();
107 }
108 else
109 {
110 // Embed primitives to paint them black (fully opaque)
111 aBColorModifier
112 = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0.0, 0.0, 0.0));
113 }
116 aBColorModifier));
118
119 // render
120 pContentProcessor->process(xSeq);
121 pContentProcessor.reset();
122
123 // get alpha channel from vdev
124 pContent->EnableMapMode(false);
125 const Point aEmptyPoint;
126
127 // Convert from transparency->alpha.
128 // FIXME in theory I should be able to directly construct alpha by using black as background
129 // and white as foreground, but that doesn't work for some reason.
130 Bitmap aContentBitmap = pContent->GetBitmap(aEmptyPoint, rSizePixel);
131 aContentBitmap.Invert();
132
133 return AlphaMask(aContentBitmap);
134}
135}
136
137namespace drawinglayer
138{
140 const geometry::ViewInformation2D& rViewInformation2D,
141 sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight,
142 sal_uInt32 nMaxSquarePixels, bool bUseLuminance)
143{
144 drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq));
145
146 if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels))
147 {
148 return AlphaMask();
149 }
150
151 const Size aSizePixel(nDiscreteWidth, nDiscreteHeight);
152
153 return implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel, bUseLuminance);
154}
155
157 const geometry::ViewInformation2D& rViewInformation2D,
158 sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight,
159 sal_uInt32 nMaxSquarePixels)
160{
161 drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq));
162
163 if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels))
164 {
165 return BitmapEx();
166 }
167
168 const Point aEmptyPoint;
169 const Size aSizePixel(nDiscreteWidth, nDiscreteHeight);
170
171 // Create target VirtualDevice. Go back to using a simple RGB
172 // target version (compared with former version, see history).
173 // Reasons are manyfold:
174 // - Avoid the RGBA mode for VirtualDevice (two VDevs)
175 // - It's not suggested to be used outside presentation engine
176 // - It only works *by chance* with VCLPrimitiveRenderer
177 // - Usage of two-VDev alpha-VDev avoided alpha blending against
178 // COL_WHITE in the 1st layer of targets (not in buffers below)
179 // but is kind of a 'hack' doing so
180 // - Other renderers (system-dependent PrimitiveRenderers, other
181 // than the VCL-based ones) will probably not support splitted
182 // VDevs for content/alpha, so require a method that works with
183 // RGB targeting (for now)
184 // - Less resource usage, better speed (no 2 VDevs, no merge of
185 // AlphaChannels)
186 // As long as not all our mechanisms are changed to RGBA completely,
187 // mixing these is just too dangerous and expensive and may to wrong
188 // or deliver bad quality results.
189 // Nonetheless we need a RGBA result here. Luckily we are able to
190 // create a complete and valid AlphaChannel using 'createAlphaMask'
191 // above.
192 // When we know the content (RGB result from renderer), alpha
193 // (result from createAlphaMask) and the start condition (content
194 // rendered against COL_WHITE), it is possible to calculate back
195 // the content, quasi 'remove' that initial blending against
196 // COL_WHITE.
197 // That is what the helper Bitmap::RemoveBlendedStartColor does.
198 // Luckily we only need it for this 'convertToBitmapEx', not in
199 // any other rendering. It could be further optimized, too.
200 // This gives good results, it is in principle comparable with
201 // the results using pre-multiplied alpha tooling, also reducing
202 // the range of values where high alpha values are used.
204
205 // prepare vdev
206 if (!pContent->SetOutputSizePixel(aSizePixel, false))
207 {
208 SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << aSizePixel.Width() << "x"
209 << aSizePixel.Height());
210 return BitmapEx();
211 }
212
213 // We map to pixel, use that MapMode. Init by erasing.
214 pContent->SetMapMode(MapMode(MapUnit::MapPixel));
215 pContent->Erase();
216
217 // create pixel processor, also already takes care of AAing and
218 // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If
219 // not wanted, change after this call as needed
220 std::unique_ptr<processor2d::BaseProcessor2D> pContentProcessor
221 = processor2d::createPixelProcessor2DFromOutputDevice(*pContent, rViewInformation2D);
222
223 // render content
224 pContentProcessor->process(aSequence);
225
226 // create final BitmapEx result (content)
227 Bitmap aRetval(pContent->GetBitmap(aEmptyPoint, aSizePixel));
228
229#ifdef DBG_UTIL
230 static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
231 if (bDoSaveForVisualControl)
232 {
233 // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
234 static const OUString sDumpPath(
235 OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
236 if (!sDumpPath.isEmpty())
237 {
238 SvFileStream aNew(sDumpPath + "test_content.bmp",
239 StreamMode::WRITE | StreamMode::TRUNC);
240 WriteDIB(aRetval, aNew, false, true);
241 }
242 }
243#endif
244
245 // Create the AlphaMask using a method that does this always correct (also used
246 // now in GlowPrimitive2D and ShadowPrimitive2D which both only need the
247 // AlphaMask to do their job, so speeding that up, too).
248 AlphaMask aAlpha(implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel, false));
249
250#ifdef DBG_UTIL
251 if (bDoSaveForVisualControl)
252 {
253 // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
254 static const OUString sDumpPath(
255 OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
256 if (!sDumpPath.isEmpty())
257 {
258 SvFileStream aNew(sDumpPath + "test_alpha.bmp", StreamMode::WRITE | StreamMode::TRUNC);
259 WriteDIB(aAlpha.GetBitmap(), aNew, false, true);
260 }
261 }
262#endif
263
264 if (aAlpha.hasAlpha())
265 {
266 // Need to correct content using known alpha to get to background-free
267 // RGBA result, usable e.g. in PNG export(s) or convert-to-bitmap.
268 // Now that vcl supports bitmaps with an alpha channel, only apply
269 // this correction to bitmaps without an alpha channel.
270 if (pContent->GetBitCount() < 32)
271 aRetval.RemoveBlendedStartColor(COL_WHITE, aAlpha);
272 // return combined result
273 return BitmapEx(aRetval, aAlpha);
274 }
275 else
276 return BitmapEx(aRetval);
277}
278
280 const basegfx::B2DRange& rTargetRange,
281 sal_uInt32 nMaximumQuadraticPixels,
282 const o3tl::Length eTargetUnit,
283 const std::optional<Size>& rTargetDPI)
284{
285 if (rSequence.empty())
286 return BitmapEx();
287
288 try
289 {
290 css::geometry::RealRectangle2D aRealRect;
291 aRealRect.X1 = rTargetRange.getMinX();
292 aRealRect.Y1 = rTargetRange.getMinY();
293 aRealRect.X2 = rTargetRange.getMaxX();
294 aRealRect.Y2 = rTargetRange.getMaxY();
295
296 // get system DPI
297 Size aDPI(
298 Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
299 if (rTargetDPI.has_value())
300 {
301 aDPI = *rTargetDPI;
302 }
303
304 ::sal_uInt32 DPI_X = aDPI.getWidth();
305 ::sal_uInt32 DPI_Y = aDPI.getHeight();
306 const basegfx::B2DRange aRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2);
307 const double fWidth(aRange.getWidth());
308 const double fHeight(aRange.getHeight());
309
310 if (!(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0)))
311 return BitmapEx();
312
313 if (0 == DPI_X)
314 {
315 DPI_X = 75;
316 }
317
318 if (0 == DPI_Y)
319 {
320 DPI_Y = 75;
321 }
322
323 if (0 == nMaximumQuadraticPixels)
324 {
325 nMaximumQuadraticPixels = 500000;
326 }
327
328 const auto aViewInformation2D = geometry::createViewInformation2D({});
329 const sal_uInt32 nDiscreteWidth(
330 basegfx::fround(o3tl::convert(fWidth, eTargetUnit, o3tl::Length::in) * DPI_X));
331 const sal_uInt32 nDiscreteHeight(
332 basegfx::fround(o3tl::convert(fHeight, eTargetUnit, o3tl::Length::in) * DPI_Y));
333
334 basegfx::B2DHomMatrix aEmbedding(
336
337 aEmbedding.scale(nDiscreteWidth / fWidth, nDiscreteHeight / fHeight);
338
339 const primitive2d::Primitive2DReference xEmbedRef(
340 new primitive2d::TransformPrimitive2D(aEmbedding, std::move(rSequence)));
341 primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef };
342
343 BitmapEx aBitmapEx(convertToBitmapEx(std::move(xEmbedSeq), aViewInformation2D,
344 nDiscreteWidth, nDiscreteHeight,
345 nMaximumQuadraticPixels));
346
347 if (aBitmapEx.IsEmpty())
348 return BitmapEx();
349 aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
350 aBitmapEx.SetPrefSize(Size(basegfx::fround(fWidth), basegfx::fround(fHeight)));
351
352 return aBitmapEx;
353 }
354 catch (const css::uno::Exception&)
355 {
356 TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!");
357 }
358 catch (const std::exception& e)
359 {
360 SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what());
361 }
362
363 return BitmapEx();
364}
365} // end of namespace drawinglayer
366
367/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Bitmap const & GetBitmap() const
bool hasAlpha() const
static OutputDevice * GetDefaultDevice()
void SetPrefMapMode(const MapMode &rPrefMapMode)
void SetPrefSize(const Size &rPrefSize)
bool IsEmpty() const
void RemoveBlendedStartColor(const Color &rColor, const AlphaMask &rAlphaMask)
bool Invert()
constexpr tools::Long getHeight() const
constexpr tools::Long Height() const
constexpr tools::Long getWidth() const
constexpr tools::Long Width() const
void scale(double fX, double fY)
TYPE getMaxX() const
TYPE getWidth() const
TYPE getMinX() const
TYPE getMinY() const
TYPE getMaxY() const
TYPE getHeight() const
constexpr ::Color COL_WHITE(0xFF, 0xFF, 0xFF)
#define TOOLS_WARN_EXCEPTION(area, stream)
bool VCL_DLLPUBLIC WriteDIB(const Bitmap &rSource, SvStream &rOStm, bool bCompressed, bool bFileHeader)
#define SAL_WARN(area, stream)
bool more(const T &rfValA, const T &rfValB)
B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY)
B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY)
std::shared_ptr< BColorModifier > BColorModifierSharedPtr
B2IRange fround(const B2DRange &rRange)
ViewInformation2D createViewInformation2D(const css::uno::Sequence< css::beans::PropertyValue > &rViewParameters)
std::unique_ptr< BaseProcessor2D > createPixelProcessor2DFromOutputDevice(OutputDevice &rTargetOutDev, const drawinglayer::geometry::ViewInformation2D &rViewInformation2D)
create the best available pixel based BaseProcessor2D (which may be system-dependent)
BitmapEx convertPrimitive2DContainerToBitmapEx(primitive2d::Primitive2DContainer &&rSequence, const basegfx::B2DRange &rTargetRange, sal_uInt32 nMaximumQuadraticPixels, const o3tl::Length eTargetUnit, const std::optional< Size > &rTargetDPI)
Definition: converters.cxx:279
AlphaMask createAlphaMask(drawinglayer::primitive2d::Primitive2DContainer &&rSeq, const geometry::ViewInformation2D &rViewInformation2D, sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, sal_uInt32 nMaxSquarePixels, bool bUseLuminance)
Definition: converters.cxx:139
BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer &&rSeq, const geometry::ViewInformation2D &rViewInformation2D, sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, sal_uInt32 nMaxSquarePixels)
Definition: converters.cxx:156
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)