Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx
Line
Count
Source
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
20
#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx>
21
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
22
#include <drawinglayer/geometry/viewinformation2d.hxx>
23
#include <vcl/bitmap.hxx>
24
#include <vcl/graph.hxx>
25
#include <basegfx/range/b2drange.hxx>
26
27
#ifdef DBG_UTIL
28
#include <o3tl/environment.hxx>
29
#include <tools/stream.hxx>
30
#include <vcl/filter/PngImageWriter.hxx>
31
#endif
32
33
namespace drawinglayer::processor2d
34
{
35
void setOffsetXYCreatedBitmap(
36
    drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
37
    const Bitmap& rBitmap)
38
0
{
39
0
    rFillGraphicPrimitive2D.impSetOffsetXYCreatedBitmap(rBitmap);
40
0
}
41
42
void takeCareOfOffsetXY(
43
    const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
44
    Bitmap& rTarget, basegfx::B2DRange& rFillUnitRange)
45
0
{
46
0
    const attribute::FillGraphicAttribute& rFillGraphicAttribute(
47
0
        rFillGraphicPrimitive2D.getFillGraphic());
48
0
    const bool bOffsetXIsUsed(rFillGraphicAttribute.getOffsetX() > 0.0
49
0
                              && rFillGraphicAttribute.getOffsetX() < 1.0);
50
0
    const bool bOffsetYIsUsed(rFillGraphicAttribute.getOffsetY() > 0.0
51
0
                              && rFillGraphicAttribute.getOffsetY() < 1.0);
52
53
0
    if (bOffsetXIsUsed)
54
0
    {
55
0
        if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
56
0
        {
57
0
            const Size aSize(rTarget.GetSizePixel());
58
0
            const tools::Long w(aSize.Width());
59
0
            const tools::Long a(
60
0
                basegfx::fround<tools::Long>(w * (1.0 - rFillGraphicAttribute.getOffsetX())));
61
62
0
            if (0 != a && w != a)
63
0
            {
64
0
                const tools::Long h(aSize.Height());
65
0
                const tools::Long b(w - a);
66
0
                Bitmap aTarget(Size(w, h * 2), rTarget.getPixelFormat());
67
68
0
                aTarget.SetPrefSize(
69
0
                    Size(rTarget.GetPrefSize().Width(), rTarget.GetPrefSize().Height() * 2));
70
0
                const tools::Rectangle aSrcDst(Point(), aSize);
71
0
                aTarget.CopyPixel(aSrcDst, // Dst
72
0
                                  aSrcDst, // Src
73
0
                                  rTarget);
74
0
                const Size aSizeA(b, h);
75
0
                aTarget.CopyPixel(tools::Rectangle(Point(0, h), aSizeA), // Dst
76
0
                                  tools::Rectangle(Point(a, 0), aSizeA), // Src
77
0
                                  rTarget);
78
0
                const Size aSizeB(a, h);
79
0
                aTarget.CopyPixel(tools::Rectangle(Point(b, h), aSizeB), // Dst
80
0
                                  tools::Rectangle(Point(), aSizeB), // Src
81
0
                                  rTarget);
82
83
0
                setOffsetXYCreatedBitmap(
84
0
                    const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>(
85
0
                        rFillGraphicPrimitive2D),
86
0
                    aTarget);
87
0
            }
88
0
        }
89
90
0
        if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
91
0
        {
92
0
            rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap();
93
0
            rFillUnitRange.expand(basegfx::B2DPoint(
94
0
                rFillUnitRange.getMinX(), rFillUnitRange.getMaxY() + rFillUnitRange.getHeight()));
95
0
        }
96
0
    }
97
0
    else if (bOffsetYIsUsed)
98
0
    {
99
0
        if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
100
0
        {
101
0
            const Size aSize(rTarget.GetSizePixel());
102
0
            const tools::Long h(aSize.Height());
103
0
            const tools::Long a(
104
0
                basegfx::fround<tools::Long>(h * (1.0 - rFillGraphicAttribute.getOffsetY())));
105
106
0
            if (0 != a && h != a)
107
0
            {
108
0
                const tools::Long w(aSize.Width());
109
0
                const tools::Long b(h - a);
110
0
                Bitmap aTarget(Size(w * 2, h), rTarget.getPixelFormat());
111
112
0
                aTarget.SetPrefSize(
113
0
                    Size(rTarget.GetPrefSize().Width() * 2, rTarget.GetPrefSize().Height()));
114
0
                const tools::Rectangle aSrcDst(Point(), aSize);
115
0
                aTarget.CopyPixel(aSrcDst, // Dst
116
0
                                  aSrcDst, // Src
117
0
                                  rTarget);
118
0
                const Size aSizeA(w, b);
119
0
                aTarget.CopyPixel(tools::Rectangle(Point(w, 0), aSizeA), // Dst
120
0
                                  tools::Rectangle(Point(0, a), aSizeA), // Src
121
0
                                  rTarget);
122
0
                const Size aSizeB(w, a);
123
0
                aTarget.CopyPixel(tools::Rectangle(Point(w, b), aSizeB), // Dst
124
0
                                  tools::Rectangle(Point(), aSizeB), // Src
125
0
                                  rTarget);
126
127
0
                setOffsetXYCreatedBitmap(
128
0
                    const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>(
129
0
                        rFillGraphicPrimitive2D),
130
0
                    aTarget);
131
0
            }
132
0
        }
133
134
0
        if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
135
0
        {
136
0
            rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap();
137
0
            rFillUnitRange.expand(basegfx::B2DPoint(
138
0
                rFillUnitRange.getMaxX() + rFillUnitRange.getWidth(), rFillUnitRange.getMinY()));
139
0
        }
140
0
    }
141
0
}
142
143
bool prepareBitmapForDirectRender(
144
    const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
145
    const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, Bitmap& rTarget,
146
    basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea)
147
0
{
148
0
    const attribute::FillGraphicAttribute& rFillGraphicAttribute(
149
0
        rFillGraphicPrimitive2D.getFillGraphic());
150
0
    const Graphic& rGraphic(rFillGraphicAttribute.getGraphic());
151
152
0
    if (rFillGraphicAttribute.isDefault() || rGraphic.IsNone())
153
0
    {
154
        // default attributes or GraphicType::NONE, so no fill .-> done
155
0
        return false;
156
0
    }
157
158
0
    if (!rFillGraphicAttribute.getTiling())
159
0
    {
160
        // If no tiling used, the Graphic will need to be painted just once. This
161
        // is perfectly done using the decomposition, so use it.
162
        // What we want to do here is to optimize tiled paint, for two reasons:
163
        // (a) speed: draw one tile, repeat -> obvious
164
        // (b) correctness: not so obvious, but since in AAed paint the same edge
165
        //     of touching polygons both AAed do *not* sum up, but get blended by
166
        //     multiplication (0.5 * 0.5 -> 0.25) the connection will stay visible,
167
        //     not only with filled polygons, but also with bitmaps
168
        // Signal that paint is needed
169
0
        return true;
170
0
    }
171
172
0
    if (rFillUnitRange.isEmpty())
173
0
    {
174
        // no fill range definition, no fill, done
175
0
        return false;
176
0
    }
177
178
0
    const basegfx::B2DHomMatrix aLocalTransform(rViewInformation2D.getObjectToViewTransformation()
179
0
                                                * rFillGraphicPrimitive2D.getTransformation());
180
0
    const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport());
181
182
0
    if (!rDiscreteViewPort.isEmpty())
183
0
    {
184
        // calculate discrete covered pixel area
185
0
        basegfx::B2DRange aDiscreteRange(basegfx::B2DRange::getUnitB2DRange());
186
0
        aDiscreteRange.transform(aLocalTransform);
187
188
0
        if (!aDiscreteRange.overlaps(rDiscreteViewPort))
189
0
        {
190
            // we have a Viewport and visible range of geometry is outside -> not visible, done
191
0
            return false;
192
0
        }
193
0
    }
194
195
0
    if (GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated())
196
0
    {
197
        // Need to prepare specialized AnimatedGraphicPrimitive2D,
198
        // cannot handle here. Signal that paint is needed
199
0
        return true;
200
0
    }
201
202
0
    if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.getVectorGraphicData())
203
0
    {
204
        // bitmap graphic, always handle locally, so get bitmap data independent
205
        // if it'sie or it's discrete display size
206
0
        rTarget = rGraphic.GetBitmap();
207
0
    }
208
0
    else
209
0
    {
210
        // Vector Graphic Data fill, including metafile:
211
        // We can know about discrete pixel size here, calculate and use it.
212
        // To do so, using Vectors is sufficient to get the lengths. It is
213
        // not necessary to transform the whole target coordinate system.
214
0
        const basegfx::B2DVector aDiscreteXAxis(
215
0
            aLocalTransform
216
0
            * basegfx::B2DVector(rFillUnitRange.getMaxX() - rFillUnitRange.getMinX(), 0.0));
217
0
        const basegfx::B2DVector aDiscreteYAxis(
218
0
            aLocalTransform
219
0
            * basegfx::B2DVector(0.0, rFillUnitRange.getMaxY() - rFillUnitRange.getMinY()));
220
221
        // get and ensure minimal size
222
0
        const double fDiscreteWidth(std::max(1.0, aDiscreteXAxis.getLength()));
223
0
        const double fDiscreteHeight(std::max(1.0, aDiscreteYAxis.getLength()));
224
225
        // compare with a big visualization size in discrete pixels
226
0
        const double fTargetDiscreteArea(fDiscreteWidth * fDiscreteHeight);
227
228
0
        if (fTargetDiscreteArea > fBigDiscreteArea)
229
0
        {
230
            // When the vector data is visualized big it is better to not handle here
231
            // but use decomposition fallback which then will visualize the vector data
232
            // directly -> better quality, acceptable number of tile repeat(s)
233
            // signal that paint is needed
234
0
            return true;
235
0
        }
236
0
        else
237
0
        {
238
            // If visualized small, the amount of repeated fills gets expensive, so
239
            // in that case use a Bitmap and the Brush technique below.
240
            // The Bitmap may be created here exactly for the needed target size
241
            // (using local D2DBitmapPixelProcessor2D and the vector data),
242
            // but since we have a HW renderer and re-use of system-dependent data
243
            // at Bitmap is possible, just get the default fallback Bitmap from the
244
            // vector data to continue. Trust the existing converters for now to
245
            // do something with good quality.
246
0
            rTarget = rGraphic.GetBitmap();
247
0
        }
248
0
    }
249
250
0
    if (rTarget.IsEmpty() || rTarget.GetSizePixel().IsEmpty())
251
0
    {
252
        // no pixel data, done
253
0
        return false;
254
0
    }
255
256
    // react if OffsetX/OffsetY of the FillGraphicAttribute is used
257
0
    takeCareOfOffsetXY(rFillGraphicPrimitive2D, rTarget, rFillUnitRange);
258
259
#ifdef DBG_UTIL
260
    // allow to check bitmap data, e.g. control OffsetX/OffsetY stuff
261
    static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
262
    if (bDoSaveForVisualControl)
263
    {
264
        static const OUString sDumpPath(o3tl::getEnvironment(u"VCL_DUMP_BMP_PATH"_ustr));
265
        if (!sDumpPath.isEmpty())
266
        {
267
            SvFileStream aNew(sDumpPath + "test_getreplacement.png",
268
                              StreamMode::WRITE | StreamMode::TRUNC);
269
            vcl::PngImageWriter aPNGWriter(aNew);
270
            aPNGWriter.write(rTarget);
271
        }
272
    }
273
#endif
274
275
    // signal to render it
276
0
    return true;
277
0
}
278
279
void calculateDiscreteVisibleRange(
280
    basegfx::B2DRange& rDiscreteVisibleRange, const basegfx::B2DRange& rContentRange,
281
    const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
282
0
{
283
0
    if (rContentRange.isEmpty())
284
0
    {
285
        // no content, done
286
0
        rDiscreteVisibleRange.reset();
287
0
        return;
288
0
    }
289
290
0
    basegfx::B2DRange aDiscreteRange(rContentRange);
291
0
    aDiscreteRange.transform(rViewInformation2D.getObjectToViewTransformation());
292
0
    const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport());
293
0
    rDiscreteVisibleRange = aDiscreteRange;
294
295
0
    if (!rDiscreteViewPort.isEmpty())
296
0
    {
297
0
        rDiscreteVisibleRange.intersect(rDiscreteViewPort);
298
0
    }
299
0
}
300
} // end of namespace
301
302
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */