/src/libreoffice/drawinglayer/source/primitive2d/svggradientprimitive2d.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/primitive2d/svggradientprimitive2d.hxx> |
21 | | #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> |
22 | | #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> |
23 | | #include <basegfx/matrix/b2dhommatrixtools.hxx> |
24 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
25 | | #include <basegfx/polygon/b2dpolygon.hxx> |
26 | | #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> |
27 | | #include <drawinglayer/primitive2d/transformprimitive2d.hxx> |
28 | | #include <drawinglayer/primitive2d/maskprimitive2d.hxx> |
29 | | #include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx> |
30 | | #include <osl/diagnose.h> |
31 | | #include <sal/log.hxx> |
32 | | #include <cmath> |
33 | | #include <utility> |
34 | | #include <vcl/skia/SkiaHelper.hxx> |
35 | | #include <ranges> |
36 | | |
37 | | using namespace com::sun::star; |
38 | | |
39 | | |
40 | | namespace |
41 | | { |
42 | | sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, const basegfx::BColor& rColorB, double fDelta, double fDiscreteUnit) |
43 | 0 | { |
44 | | // use color distance, assume to do every color step (full quality) |
45 | 0 | sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0)); |
46 | |
|
47 | 0 | if(nSteps) |
48 | 0 | { |
49 | | // calc discrete length to change color all 1.5 discrete units (pixels) |
50 | 0 | const sal_uInt32 nDistSteps(basegfx::fround(fDelta / (fDiscreteUnit * 1.5))); |
51 | |
|
52 | 0 | nSteps = std::min(nSteps, nDistSteps); |
53 | 0 | } |
54 | | |
55 | | // roughly cut when too big or too small |
56 | 0 | nSteps = std::min(nSteps, sal_uInt32(255)); |
57 | 0 | nSteps = std::max(nSteps, sal_uInt32(1)); |
58 | |
|
59 | 0 | return nSteps; |
60 | 0 | } |
61 | | } // end of anonymous namespace |
62 | | |
63 | | |
64 | | namespace drawinglayer::primitive2d |
65 | | { |
66 | | Primitive2DReference SvgGradientHelper::createSingleGradientEntryFill() const |
67 | 0 | { |
68 | 0 | const SvgGradientEntryVector& rEntries(getGradientEntries()); |
69 | 0 | const sal_uInt32 nCount(rEntries.size()); |
70 | |
|
71 | 0 | if(0 == nCount) |
72 | 0 | { |
73 | | // no entries, done |
74 | 0 | OSL_ENSURE(false, "Single gradient entry construction without entry (!)"); |
75 | 0 | return nullptr; |
76 | 0 | } |
77 | | |
78 | 0 | const SvgGradientEntry& rSingleEntry(rEntries[nCount - 1]); |
79 | 0 | const double fOpacity(rSingleEntry.getOpacity()); |
80 | |
|
81 | 0 | if (fOpacity <= 0.0 || basegfx::fTools::equalZero(fOpacity)) |
82 | 0 | { |
83 | | // completely opaque, done |
84 | 0 | return nullptr; |
85 | 0 | } |
86 | | |
87 | 0 | if (basegfx::fTools::moreOrEqual(fOpacity, 1.0)) |
88 | 0 | { |
89 | | // no opacity |
90 | 0 | return Primitive2DReference { |
91 | 0 | new PolyPolygonColorPrimitive2D( |
92 | 0 | getPolyPolygon(), |
93 | 0 | rSingleEntry.getColor()) }; |
94 | 0 | } |
95 | | |
96 | | // if transparent, use PolyPolygonRGBAPrimitive2D |
97 | 0 | return Primitive2DReference { |
98 | 0 | new PolyPolygonRGBAPrimitive2D( |
99 | 0 | getPolyPolygon(), |
100 | 0 | rSingleEntry.getColor(), |
101 | 0 | 1.0 - fOpacity) }; |
102 | 0 | } |
103 | | |
104 | | void SvgGradientHelper::checkPreconditions() |
105 | 53 | { |
106 | 53 | const SvgGradientEntryVector& rEntries = getGradientEntries(); |
107 | | |
108 | 53 | if(rEntries.empty()) |
109 | 0 | { |
110 | | // no fill at all, done |
111 | 0 | return; |
112 | 0 | } |
113 | | |
114 | | // sort maGradientEntries by offset, small to big |
115 | 53 | std::sort(maGradientEntries.begin(), maGradientEntries.end()); |
116 | | |
117 | | // gradient with at least two colors |
118 | 53 | bool bAllInvisible(true); |
119 | 53 | bool bInvalidEntries(false); |
120 | | |
121 | 53 | for(const SvgGradientEntry& rCandidate : rEntries) |
122 | 106 | { |
123 | 106 | if(basegfx::fTools::equalZero(rCandidate.getOpacity())) |
124 | 52 | { |
125 | | // invisible |
126 | 52 | mbFullyOpaque = false; |
127 | 52 | } |
128 | 54 | else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0)) |
129 | 1 | { |
130 | | // completely opaque |
131 | 1 | bAllInvisible = false; |
132 | 1 | } |
133 | 53 | else |
134 | 53 | { |
135 | | // opacity |
136 | 53 | bAllInvisible = false; |
137 | 53 | mbFullyOpaque = false; |
138 | 53 | } |
139 | | |
140 | 106 | if(!basegfx::fTools::betweenOrEqualEither(rCandidate.getOffset(), 0.0, 1.0)) |
141 | 0 | { |
142 | 0 | bInvalidEntries = true; |
143 | 0 | } |
144 | 106 | } |
145 | | |
146 | 53 | if(bAllInvisible) |
147 | 1 | { |
148 | | // all invisible, nothing to do |
149 | 1 | return; |
150 | 1 | } |
151 | | |
152 | 52 | if(bInvalidEntries) |
153 | 0 | { |
154 | | // invalid entries, do nothing |
155 | 0 | SAL_WARN("drawinglayer", "SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]"); |
156 | 0 | return; |
157 | 0 | } |
158 | | |
159 | 52 | const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); |
160 | | |
161 | 52 | if(aPolyRange.isEmpty()) |
162 | 0 | { |
163 | | // no range to fill, nothing to do |
164 | 0 | return; |
165 | 0 | } |
166 | | |
167 | 52 | const double fPolyWidth(aPolyRange.getWidth()); |
168 | 52 | const double fPolyHeight(aPolyRange.getHeight()); |
169 | | |
170 | 52 | if(basegfx::fTools::equalZero(fPolyWidth) || basegfx::fTools::equalZero(fPolyHeight)) |
171 | 52 | { |
172 | | // no width/height to fill, nothing to do |
173 | 52 | return; |
174 | 52 | } |
175 | | |
176 | 0 | mbCreatesContent = true; |
177 | |
|
178 | 0 | if(1 == rEntries.size()) |
179 | 0 | { |
180 | | // fill with single existing color |
181 | 0 | setSingleEntry(); |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | const SvgGradientEntry& SvgGradientHelper::FindEntryLessOrEqual( |
186 | | sal_Int32& rInt, |
187 | | const double fFrac) const |
188 | 0 | { |
189 | 0 | const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); |
190 | 0 | const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries()); |
191 | |
|
192 | 0 | for(SvgGradientEntryVector::const_reverse_iterator aIter(rCurrent.rbegin()); aIter != rCurrent.rend(); ++aIter) |
193 | 0 | { |
194 | 0 | if(basegfx::fTools::lessOrEqual(aIter->getOffset(), fFrac)) |
195 | 0 | { |
196 | 0 | return *aIter; |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | | // walk over gap to the left, be prepared for missing 0.0/1.0 entries |
201 | 0 | rInt--; |
202 | 0 | const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); |
203 | 0 | const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries()); |
204 | 0 | return rCurrent2.back(); |
205 | 0 | } |
206 | | |
207 | | const SvgGradientEntry& SvgGradientHelper::FindEntryMore( |
208 | | sal_Int32& rInt, |
209 | | const double fFrac) const |
210 | 0 | { |
211 | 0 | const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); |
212 | 0 | const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries()); |
213 | |
|
214 | 0 | for(SvgGradientEntryVector::const_iterator aIter(rCurrent.begin()); aIter != rCurrent.end(); ++aIter) |
215 | 0 | { |
216 | 0 | if(basegfx::fTools::more(aIter->getOffset(), fFrac)) |
217 | 0 | { |
218 | 0 | return *aIter; |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | // walk over gap to the right, be prepared for missing 0.0/1.0 entries |
223 | 0 | rInt++; |
224 | 0 | const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); |
225 | 0 | const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries()); |
226 | 0 | return rCurrent2.front(); |
227 | 0 | } |
228 | | |
229 | | // tdf#124424 Adapted creation of color runs to do in a single effort. Previous |
230 | | // version tried to do this from [0.0 .. 1.0] and to re-use transformed versions |
231 | | // in the caller if SpreadMethod was on some repeat mode, but had problems when |
232 | | // e.g. like in the bugdoc from the task a negative-only fStart/fEnd run was |
233 | | // requested in which case it did nothing. Even when reusing the spread might |
234 | | // not have been a full one from [0.0 .. 1.0]. |
235 | | // This gets complicated due to mirrored runs, but also for gradient definitions |
236 | | // with missing entries for 0.0 and 1.0 in which case these have to be guessed |
237 | | // to be there with same parametrisation as their nearest existing entries. These |
238 | | // *could* have been added at checkPreconditions() but would then create unnecessary |
239 | | // spreads on zone overlaps. |
240 | | void SvgGradientHelper::createRun( |
241 | | Primitive2DContainer& rTargetColor, |
242 | | Primitive2DContainer& rTargetOpacity, |
243 | | double fStart, |
244 | | double fEnd) const |
245 | 0 | { |
246 | 0 | double fInt(0.0); |
247 | 0 | double fFrac(0.0); |
248 | 0 | double fEnd2(0.0); |
249 | |
|
250 | 0 | if(SpreadMethod::Pad == getSpreadMethod()) |
251 | 0 | { |
252 | 0 | if(fStart < 0.0) |
253 | 0 | { |
254 | 0 | fFrac = std::modf(fStart, &fInt); |
255 | 0 | const SvgGradientEntry& rFront(getGradientEntries().front()); |
256 | 0 | const SvgGradientEntry aTemp(1.0 + fFrac, rFront.getColor(), rFront.getOpacity()); |
257 | 0 | createAtom(rTargetColor, rTargetOpacity, aTemp, rFront, static_cast<sal_Int32>(fInt - 1), 0); |
258 | 0 | fStart = rFront.getOffset(); |
259 | 0 | } |
260 | |
|
261 | 0 | if(fEnd > 1.0) |
262 | 0 | { |
263 | | // change fEnd early, but create geometry later (after range below) |
264 | 0 | fEnd2 = fEnd; |
265 | 0 | fEnd = getGradientEntries().back().getOffset(); |
266 | 0 | } |
267 | 0 | } |
268 | |
|
269 | 0 | while(fStart < fEnd) |
270 | 0 | { |
271 | 0 | fFrac = std::modf(fStart, &fInt); |
272 | |
|
273 | 0 | if(fFrac < 0.0) |
274 | 0 | { |
275 | 0 | fInt -= 1; |
276 | 0 | fFrac = 1.0 + fFrac; |
277 | 0 | } |
278 | |
|
279 | 0 | sal_Int32 nIntLeft(static_cast<sal_Int32>(fInt)); |
280 | 0 | sal_Int32 nIntRight(nIntLeft); |
281 | |
|
282 | 0 | const SvgGradientEntry& rLeft(FindEntryLessOrEqual(nIntLeft, fFrac)); |
283 | 0 | const SvgGradientEntry& rRight(FindEntryMore(nIntRight, fFrac)); |
284 | 0 | createAtom(rTargetColor, rTargetOpacity, rLeft, rRight, nIntLeft, nIntRight); |
285 | |
|
286 | 0 | const double fNextfStart(static_cast<double>(nIntRight) + rRight.getOffset()); |
287 | |
|
288 | 0 | if(basegfx::fTools::more(fNextfStart, fStart)) |
289 | 0 | { |
290 | 0 | fStart = fNextfStart; |
291 | 0 | } |
292 | 0 | else |
293 | 0 | { |
294 | 0 | SAL_WARN("drawinglayer", "SvgGradientHelper spread error"); |
295 | 0 | fStart += 1.0; |
296 | 0 | } |
297 | 0 | } |
298 | | |
299 | 0 | if(fEnd2 > 1.0) |
300 | 0 | { |
301 | | // create end run for SpreadMethod::Pad late to keep correct creation order |
302 | 0 | fFrac = std::modf(fEnd2, &fInt); |
303 | 0 | const SvgGradientEntry& rBack(getGradientEntries().back()); |
304 | 0 | const SvgGradientEntry aTemp(fFrac, rBack.getColor(), rBack.getOpacity()); |
305 | 0 | createAtom(rTargetColor, rTargetOpacity, rBack, aTemp, 0, static_cast<sal_Int32>(fInt)); |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | Primitive2DReference SvgGradientHelper::createResult( |
310 | | Primitive2DContainer aTargetColor, |
311 | | Primitive2DContainer aTargetOpacity, |
312 | | const basegfx::B2DHomMatrix& rUnitGradientToObject, |
313 | | bool bInvert) const |
314 | 0 | { |
315 | 0 | Primitive2DContainer aTargetColorEntries(aTargetColor.maybeInvert(bInvert)); |
316 | 0 | Primitive2DContainer aTargetOpacityEntries(aTargetOpacity.maybeInvert(bInvert)); |
317 | |
|
318 | 0 | if(aTargetColorEntries.empty()) |
319 | 0 | return nullptr; |
320 | | |
321 | 0 | Primitive2DReference xRefContent; |
322 | |
|
323 | 0 | if(!aTargetOpacityEntries.empty()) |
324 | 0 | { |
325 | 0 | const Primitive2DReference xRefOpacity = new TransparencePrimitive2D( |
326 | 0 | std::move(aTargetColorEntries), |
327 | 0 | std::move(aTargetOpacityEntries)); |
328 | |
|
329 | 0 | xRefContent = new TransformPrimitive2D( |
330 | 0 | rUnitGradientToObject, |
331 | 0 | Primitive2DContainer { xRefOpacity }); |
332 | 0 | } |
333 | 0 | else |
334 | 0 | { |
335 | 0 | xRefContent = new TransformPrimitive2D( |
336 | 0 | rUnitGradientToObject, |
337 | 0 | std::move(aTargetColorEntries)); |
338 | 0 | } |
339 | |
|
340 | 0 | return new MaskPrimitive2D( |
341 | 0 | getPolyPolygon(), |
342 | 0 | Primitive2DContainer { xRefContent }); |
343 | 0 | } |
344 | | |
345 | | SvgGradientHelper::SvgGradientHelper( |
346 | | basegfx::B2DHomMatrix aGradientTransform, |
347 | | basegfx::B2DPolyPolygon aPolyPolygon, |
348 | | SvgGradientEntryVector&& rGradientEntries, |
349 | | const basegfx::B2DPoint& rStart, |
350 | | bool bUseUnitCoordinates, |
351 | | SpreadMethod aSpreadMethod) |
352 | 53 | : maGradientTransform(std::move(aGradientTransform)), |
353 | 53 | maPolyPolygon(std::move(aPolyPolygon)), |
354 | 53 | maGradientEntries(std::move(rGradientEntries)), |
355 | 53 | maStart(rStart), |
356 | 53 | maSpreadMethod(aSpreadMethod), |
357 | 53 | mbCreatesContent(false), |
358 | 53 | mbSingleEntry(false), |
359 | 53 | mbFullyOpaque(true), |
360 | 53 | mbUseUnitCoordinates(bUseUnitCoordinates) |
361 | 53 | { |
362 | 53 | } |
363 | | |
364 | | SvgGradientHelper::~SvgGradientHelper() |
365 | 53 | { |
366 | 53 | } |
367 | | |
368 | | const SvgGradientEntryVector& SvgGradientHelper::getMirroredGradientEntries() const |
369 | 0 | { |
370 | 0 | if(maMirroredGradientEntries.empty() && !getGradientEntries().empty()) |
371 | 0 | { |
372 | 0 | const_cast< SvgGradientHelper* >(this)->createMirroredGradientEntries(); |
373 | 0 | } |
374 | |
|
375 | 0 | return maMirroredGradientEntries; |
376 | 0 | } |
377 | | |
378 | | void SvgGradientHelper::createMirroredGradientEntries() |
379 | 0 | { |
380 | 0 | if(!maMirroredGradientEntries.empty() || getGradientEntries().empty()) |
381 | 0 | return; |
382 | | |
383 | 0 | maMirroredGradientEntries.clear(); |
384 | 0 | maMirroredGradientEntries.reserve(getGradientEntries().size()); |
385 | |
|
386 | 0 | const SvgGradientEntryVector& rEntries(getGradientEntries()); |
387 | 0 | for(const SvgGradientEntry& rCandidate : std::views::reverse(rEntries)) |
388 | 0 | { |
389 | 0 | maMirroredGradientEntries.emplace_back( |
390 | 0 | 1.0 - rCandidate.getOffset(), |
391 | 0 | rCandidate.getColor(), |
392 | 0 | rCandidate.getOpacity()); |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | bool SvgGradientHelper::operator==(const SvgGradientHelper& rSvgGradientHelper) const |
397 | 0 | { |
398 | 0 | const SvgGradientHelper& rCompare = rSvgGradientHelper; |
399 | |
|
400 | 0 | return (getGradientTransform() == rCompare.getGradientTransform() |
401 | 0 | && getPolyPolygon() == rCompare.getPolyPolygon() |
402 | 0 | && getGradientEntries() == rCompare.getGradientEntries() |
403 | 0 | && getStart() == rCompare.getStart() |
404 | 0 | && getUseUnitCoordinates() == rCompare.getUseUnitCoordinates() |
405 | 0 | && getSpreadMethod() == rCompare.getSpreadMethod()); |
406 | 0 | } |
407 | | |
408 | | } // end of namespace drawinglayer::primitive2d |
409 | | |
410 | | |
411 | | namespace drawinglayer::primitive2d |
412 | | { |
413 | | void SvgLinearGradientPrimitive2D::checkPreconditions() |
414 | 53 | { |
415 | | // call parent |
416 | 53 | SvgGradientHelper::checkPreconditions(); |
417 | | |
418 | 53 | if(getCreatesContent()) |
419 | 0 | { |
420 | | // Check Vector |
421 | 0 | const basegfx::B2DVector aVector(getEnd() - getStart()); |
422 | |
|
423 | 0 | if(basegfx::fTools::equalZero(aVector.getX()) && basegfx::fTools::equalZero(aVector.getY())) |
424 | 0 | { |
425 | | // fill with single color using last stop color |
426 | 0 | setSingleEntry(); |
427 | 0 | } |
428 | 0 | } |
429 | 53 | } |
430 | | |
431 | | void SvgLinearGradientPrimitive2D::createAtom( |
432 | | Primitive2DContainer& rTargetColor, |
433 | | Primitive2DContainer& rTargetOpacity, |
434 | | const SvgGradientEntry& rFrom, |
435 | | const SvgGradientEntry& rTo, |
436 | | sal_Int32 nOffsetFrom, |
437 | | sal_Int32 nOffsetTo) const |
438 | 0 | { |
439 | | // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset()) |
440 | 0 | if(rFrom.getOffset() == rTo.getOffset()) |
441 | 0 | { |
442 | 0 | OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)"); |
443 | 0 | } |
444 | 0 | else |
445 | 0 | { |
446 | 0 | rTargetColor.push_back( |
447 | 0 | new SvgLinearAtomPrimitive2D( |
448 | 0 | rFrom.getColor(), rFrom.getOffset() + nOffsetFrom, |
449 | 0 | rTo.getColor(), rTo.getOffset() + nOffsetTo)); |
450 | |
|
451 | 0 | if(!getFullyOpaque()) |
452 | 0 | { |
453 | 0 | const double fTransFrom(1.0 - rFrom.getOpacity()); |
454 | 0 | const double fTransTo(1.0 - rTo.getOpacity()); |
455 | 0 | const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom); |
456 | 0 | const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo); |
457 | |
|
458 | 0 | rTargetOpacity.push_back( |
459 | 0 | new SvgLinearAtomPrimitive2D( |
460 | 0 | aColorFrom, rFrom.getOffset() + nOffsetFrom, |
461 | 0 | aColorTo, rTo.getOffset() + nOffsetTo)); |
462 | 0 | } |
463 | 0 | } |
464 | 0 | } |
465 | | |
466 | | basegfx::B2DHomMatrix SvgLinearGradientPrimitive2D::createUnitGradientToObjectTransformation() const |
467 | 0 | { |
468 | 0 | const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); |
469 | 0 | const double fPolyWidth(aPolyRange.getWidth()); |
470 | 0 | const double fPolyHeight(aPolyRange.getHeight()); |
471 | | |
472 | | // create ObjectTransform based on polygon range |
473 | 0 | const basegfx::B2DHomMatrix aObjectTransform( |
474 | 0 | basegfx::utils::createScaleTranslateB2DHomMatrix( |
475 | 0 | fPolyWidth, fPolyHeight, |
476 | 0 | aPolyRange.getMinX(), aPolyRange.getMinY())); |
477 | 0 | basegfx::B2DHomMatrix aUnitGradientToObject; |
478 | |
|
479 | 0 | if(getUseUnitCoordinates()) |
480 | 0 | { |
481 | | // interpret in unit coordinate system -> object aspect ratio will scale result |
482 | | // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given |
483 | | // gradient vector defined by Start,End |
484 | 0 | const basegfx::B2DVector aVector(getEnd() - getStart()); |
485 | 0 | const double fVectorLength(aVector.getLength()); |
486 | |
|
487 | 0 | aUnitGradientToObject.scale(fVectorLength, 1.0); |
488 | 0 | aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX())); |
489 | 0 | aUnitGradientToObject.translate(getStart().getX(), getStart().getY()); |
490 | |
|
491 | 0 | aUnitGradientToObject *= getGradientTransform(); |
492 | | |
493 | | // create full transform from unit gradient coordinates to object coordinates |
494 | | // including the SvgGradient transformation |
495 | 0 | aUnitGradientToObject *= aObjectTransform; |
496 | 0 | } |
497 | 0 | else |
498 | 0 | { |
499 | | // interpret in object coordinate system -> object aspect ratio will not scale result |
500 | 0 | const basegfx::B2DPoint aStart(aObjectTransform * getStart()); |
501 | 0 | const basegfx::B2DPoint aEnd(aObjectTransform * getEnd()); |
502 | 0 | const basegfx::B2DVector aVector(aEnd - aStart); |
503 | |
|
504 | 0 | aUnitGradientToObject.scale(aVector.getLength(), 1.0); |
505 | 0 | aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX())); |
506 | 0 | aUnitGradientToObject.translate(aStart.getX(), aStart.getY()); |
507 | |
|
508 | 0 | aUnitGradientToObject *= getGradientTransform(); |
509 | 0 | } |
510 | |
|
511 | 0 | return aUnitGradientToObject; |
512 | 0 | } |
513 | | |
514 | | Primitive2DReference SvgLinearGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const |
515 | 0 | { |
516 | 0 | if(getSingleEntry()) |
517 | 0 | { |
518 | | // fill with last existing color |
519 | 0 | return createSingleGradientEntryFill(); |
520 | 0 | } |
521 | 0 | else if(getCreatesContent()) |
522 | 0 | { |
523 | | // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely |
524 | | // invisible, width and height to fill are not empty |
525 | 0 | basegfx::B2DHomMatrix aUnitGradientToObject(createUnitGradientToObjectTransformation()); |
526 | | |
527 | | // create inverse from it |
528 | 0 | basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject); |
529 | 0 | aObjectToUnitGradient.invert(); |
530 | | |
531 | | // back-transform polygon to unit gradient coordinates and get |
532 | | // UnitRage. This is the range the gradient has to cover |
533 | 0 | basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon()); |
534 | 0 | aUnitPoly.transform(aObjectToUnitGradient); |
535 | 0 | const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange()); |
536 | | |
537 | | // prepare result vectors |
538 | 0 | Primitive2DContainer aTargetColor; |
539 | 0 | Primitive2DContainer aTargetOpacity; |
540 | |
|
541 | 0 | if(aUnitRange.getWidth() > 0.0) |
542 | 0 | { |
543 | | // add a pre-multiply to aUnitGradientToObject to allow |
544 | | // multiplication of the polygon(xl, 0.0, xr, 1.0) |
545 | 0 | const basegfx::B2DHomMatrix aPreMultiply( |
546 | 0 | basegfx::utils::createScaleTranslateB2DHomMatrix( |
547 | 0 | 1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY())); |
548 | 0 | aUnitGradientToObject = aUnitGradientToObject * aPreMultiply; |
549 | | |
550 | | // create full color run, including all SpreadMethod variants |
551 | 0 | createRun( |
552 | 0 | aTargetColor, |
553 | 0 | aTargetOpacity, |
554 | 0 | aUnitRange.getMinX(), |
555 | 0 | aUnitRange.getMaxX()); |
556 | 0 | } |
557 | |
|
558 | 0 | return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject); |
559 | 0 | } |
560 | 0 | return nullptr; |
561 | 0 | } |
562 | | |
563 | | SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D( |
564 | | const basegfx::B2DHomMatrix& rGradientTransform, |
565 | | const basegfx::B2DPolyPolygon& rPolyPolygon, |
566 | | SvgGradientEntryVector&& rGradientEntries, |
567 | | const basegfx::B2DPoint& rStart, |
568 | | const basegfx::B2DPoint& rEnd, |
569 | | bool bUseUnitCoordinates, |
570 | | SpreadMethod aSpreadMethod) |
571 | 53 | : SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod), |
572 | 53 | maEnd(rEnd) |
573 | 53 | { |
574 | | // ensure Preconditions are checked |
575 | 53 | checkPreconditions(); |
576 | 53 | } |
577 | | |
578 | | SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D() |
579 | 53 | { |
580 | 53 | } |
581 | | |
582 | | bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
583 | 0 | { |
584 | 0 | const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); |
585 | |
|
586 | 0 | if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper)) |
587 | 0 | { |
588 | 0 | const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive); |
589 | |
|
590 | 0 | return (getEnd() == rCompare.getEnd()); |
591 | 0 | } |
592 | | |
593 | 0 | return false; |
594 | 0 | } |
595 | | |
596 | | basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const |
597 | 53 | { |
598 | | // return ObjectRange |
599 | 53 | return getPolyPolygon().getB2DRange(); |
600 | 53 | } |
601 | | |
602 | | // provide unique ID |
603 | | sal_uInt32 SvgLinearGradientPrimitive2D::getPrimitive2DID() const |
604 | 0 | { |
605 | 0 | return PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D; |
606 | 0 | } |
607 | | |
608 | | } // end of namespace drawinglayer::primitive2d |
609 | | |
610 | | |
611 | | namespace drawinglayer::primitive2d |
612 | | { |
613 | | void SvgRadialGradientPrimitive2D::checkPreconditions() |
614 | 0 | { |
615 | | // call parent |
616 | 0 | SvgGradientHelper::checkPreconditions(); |
617 | |
|
618 | 0 | if(getCreatesContent()) |
619 | 0 | { |
620 | | // Check Radius |
621 | 0 | if(basegfx::fTools::equalZero(getRadius())) |
622 | 0 | { |
623 | | // fill with single color using last stop color |
624 | 0 | setSingleEntry(); |
625 | 0 | } |
626 | 0 | } |
627 | 0 | } |
628 | | |
629 | | void SvgRadialGradientPrimitive2D::createAtom( |
630 | | Primitive2DContainer& rTargetColor, |
631 | | Primitive2DContainer& rTargetOpacity, |
632 | | const SvgGradientEntry& rFrom, |
633 | | const SvgGradientEntry& rTo, |
634 | | sal_Int32 nOffsetFrom, |
635 | | sal_Int32 nOffsetTo) const |
636 | 0 | { |
637 | | // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset()) |
638 | 0 | if(rFrom.getOffset() == rTo.getOffset()) |
639 | 0 | { |
640 | 0 | OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)"); |
641 | 0 | } |
642 | 0 | else |
643 | 0 | { |
644 | 0 | const double fScaleFrom(rFrom.getOffset() + nOffsetFrom); |
645 | 0 | const double fScaleTo(rTo.getOffset() + nOffsetTo); |
646 | |
|
647 | 0 | if(isFocalSet()) |
648 | 0 | { |
649 | 0 | const basegfx::B2DVector aFocalVector(getFocal() - getStart()); |
650 | 0 | const basegfx::B2DVector aTranslateFrom(aFocalVector * (maFocalLength - fScaleFrom)); |
651 | 0 | const basegfx::B2DVector aTranslateTo(aFocalVector * (maFocalLength - fScaleTo)); |
652 | |
|
653 | 0 | rTargetColor.push_back( |
654 | 0 | new SvgRadialAtomPrimitive2D( |
655 | 0 | rFrom.getColor(), fScaleFrom, aTranslateFrom, |
656 | 0 | rTo.getColor(), fScaleTo, aTranslateTo)); |
657 | 0 | } |
658 | 0 | else |
659 | 0 | { |
660 | 0 | rTargetColor.push_back( |
661 | 0 | new SvgRadialAtomPrimitive2D( |
662 | 0 | rFrom.getColor(), fScaleFrom, |
663 | 0 | rTo.getColor(), fScaleTo)); |
664 | 0 | } |
665 | |
|
666 | 0 | if(!getFullyOpaque()) |
667 | 0 | { |
668 | 0 | const double fTransFrom(1.0 - rFrom.getOpacity()); |
669 | 0 | const double fTransTo(1.0 - rTo.getOpacity()); |
670 | 0 | const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom); |
671 | 0 | const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo); |
672 | |
|
673 | 0 | if(isFocalSet()) |
674 | 0 | { |
675 | 0 | const basegfx::B2DVector aFocalVector(getFocal() - getStart()); |
676 | 0 | const basegfx::B2DVector aTranslateFrom(aFocalVector * (maFocalLength - fScaleFrom)); |
677 | 0 | const basegfx::B2DVector aTranslateTo(aFocalVector * (maFocalLength - fScaleTo)); |
678 | |
|
679 | 0 | rTargetOpacity.push_back( |
680 | 0 | new SvgRadialAtomPrimitive2D( |
681 | 0 | aColorFrom, fScaleFrom, aTranslateFrom, |
682 | 0 | aColorTo, fScaleTo, aTranslateTo)); |
683 | 0 | } |
684 | 0 | else |
685 | 0 | { |
686 | 0 | rTargetOpacity.push_back( |
687 | 0 | new SvgRadialAtomPrimitive2D( |
688 | 0 | aColorFrom, fScaleFrom, |
689 | 0 | aColorTo, fScaleTo)); |
690 | 0 | } |
691 | 0 | } |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | | basegfx::B2DHomMatrix SvgRadialGradientPrimitive2D::createUnitGradientToObjectTransformation() const |
696 | 0 | { |
697 | 0 | const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); |
698 | 0 | const double fPolyWidth(aPolyRange.getWidth()); |
699 | 0 | const double fPolyHeight(aPolyRange.getHeight()); |
700 | | |
701 | | // create ObjectTransform based on polygon range |
702 | 0 | const basegfx::B2DHomMatrix aObjectTransform( |
703 | 0 | basegfx::utils::createScaleTranslateB2DHomMatrix( |
704 | 0 | fPolyWidth, fPolyHeight, |
705 | 0 | aPolyRange.getMinX(), aPolyRange.getMinY())); |
706 | 0 | basegfx::B2DHomMatrix aUnitGradientToObject; |
707 | |
|
708 | 0 | if(getUseUnitCoordinates()) |
709 | 0 | { |
710 | | // interpret in unit coordinate system -> object aspect ratio will scale result |
711 | | // create unit transform from unit vector to given linear gradient vector |
712 | 0 | aUnitGradientToObject.scale(getRadius(), getRadius()); |
713 | 0 | aUnitGradientToObject.translate(getStart().getX(), getStart().getY()); |
714 | |
|
715 | 0 | if(!getGradientTransform().isIdentity()) |
716 | 0 | { |
717 | 0 | aUnitGradientToObject = getGradientTransform() * aUnitGradientToObject; |
718 | 0 | } |
719 | | |
720 | | // create full transform from unit gradient coordinates to object coordinates |
721 | | // including the SvgGradient transformation |
722 | 0 | aUnitGradientToObject = aObjectTransform * aUnitGradientToObject; |
723 | 0 | } |
724 | 0 | else |
725 | 0 | { |
726 | | // interpret in object coordinate system -> object aspect ratio will not scale result |
727 | | // use X-Axis with radius, it was already made relative to object width when coming from |
728 | | // SVG import |
729 | 0 | const double fRadius((aObjectTransform * basegfx::B2DVector(getRadius(), 0.0)).getLength()); |
730 | 0 | const basegfx::B2DPoint aStart(aObjectTransform * getStart()); |
731 | |
|
732 | 0 | aUnitGradientToObject.scale(fRadius, fRadius); |
733 | 0 | aUnitGradientToObject.translate(aStart.getX(), aStart.getY()); |
734 | |
|
735 | 0 | aUnitGradientToObject *= getGradientTransform(); |
736 | 0 | } |
737 | |
|
738 | 0 | return aUnitGradientToObject; |
739 | 0 | } |
740 | | |
741 | | Primitive2DReference SvgRadialGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const |
742 | 0 | { |
743 | 0 | if(getSingleEntry()) |
744 | 0 | { |
745 | | // fill with last existing color |
746 | 0 | return createSingleGradientEntryFill(); |
747 | 0 | } |
748 | 0 | else if(getCreatesContent()) |
749 | 0 | { |
750 | | // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely |
751 | | // invisible, width and height to fill are not empty |
752 | 0 | basegfx::B2DHomMatrix aUnitGradientToObject(createUnitGradientToObjectTransformation()); |
753 | | |
754 | | // create inverse from it |
755 | 0 | basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject); |
756 | 0 | aObjectToUnitGradient.invert(); |
757 | | |
758 | | // back-transform polygon to unit gradient coordinates and get |
759 | | // UnitRage. This is the range the gradient has to cover |
760 | 0 | basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon()); |
761 | 0 | aUnitPoly.transform(aObjectToUnitGradient); |
762 | 0 | const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange()); |
763 | | |
764 | | // create range which the gradient has to cover to cover the whole given geometry. |
765 | | // For circle, go from 0.0 to max radius in all directions (the corners) |
766 | 0 | double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength()); |
767 | 0 | fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength()); |
768 | 0 | fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength()); |
769 | 0 | fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength()); |
770 | | |
771 | | // prepare result vectors |
772 | 0 | Primitive2DContainer aTargetColor; |
773 | 0 | Primitive2DContainer aTargetOpacity; |
774 | |
|
775 | 0 | if(0.0 < fMax) |
776 | 0 | { |
777 | | // prepare maFocalVector |
778 | 0 | if(isFocalSet()) |
779 | 0 | { |
780 | 0 | const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax; |
781 | 0 | } |
782 | | |
783 | | // create full color run, including all SpreadMethod variants |
784 | 0 | createRun( |
785 | 0 | aTargetColor, |
786 | 0 | aTargetOpacity, |
787 | 0 | 0.0, |
788 | 0 | fMax); |
789 | 0 | } |
790 | |
|
791 | 0 | return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject, true); |
792 | 0 | } |
793 | 0 | return nullptr; |
794 | 0 | } |
795 | | |
796 | | SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D( |
797 | | const basegfx::B2DHomMatrix& rGradientTransform, |
798 | | const basegfx::B2DPolyPolygon& rPolyPolygon, |
799 | | SvgGradientEntryVector&& rGradientEntries, |
800 | | const basegfx::B2DPoint& rStart, |
801 | | double fRadius, |
802 | | bool bUseUnitCoordinates, |
803 | | SpreadMethod aSpreadMethod, |
804 | | const basegfx::B2DPoint* pFocal) |
805 | 0 | : SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod), |
806 | 0 | mfRadius(fRadius), |
807 | 0 | maFocal(rStart), |
808 | 0 | maFocalLength(0.0) |
809 | 0 | { |
810 | 0 | if(pFocal && !pFocal->equal(getStart())) |
811 | 0 | { |
812 | 0 | maFocal = *pFocal; |
813 | 0 | } |
814 | | |
815 | | // ensure Preconditions are checked |
816 | 0 | checkPreconditions(); |
817 | 0 | } |
818 | | |
819 | | SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D() |
820 | 0 | { |
821 | 0 | } |
822 | | |
823 | | bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
824 | 0 | { |
825 | 0 | const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); |
826 | |
|
827 | 0 | if(!pSvgGradientHelper || !SvgGradientHelper::operator==(*pSvgGradientHelper)) |
828 | 0 | return false; |
829 | | |
830 | 0 | const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive); |
831 | |
|
832 | 0 | if(getRadius() == rCompare.getRadius()) |
833 | 0 | { |
834 | 0 | if(isFocalSet() == rCompare.isFocalSet()) |
835 | 0 | { |
836 | 0 | if(isFocalSet()) |
837 | 0 | { |
838 | 0 | return getFocal() == rCompare.getFocal(); |
839 | 0 | } |
840 | 0 | else |
841 | 0 | { |
842 | 0 | return true; |
843 | 0 | } |
844 | 0 | } |
845 | 0 | } |
846 | | |
847 | 0 | return false; |
848 | 0 | } |
849 | | |
850 | | basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const |
851 | 0 | { |
852 | | // return ObjectRange |
853 | 0 | return getPolyPolygon().getB2DRange(); |
854 | 0 | } |
855 | | |
856 | | // provide unique ID |
857 | | sal_uInt32 SvgRadialGradientPrimitive2D::getPrimitive2DID() const |
858 | 0 | { |
859 | 0 | return PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D; |
860 | 0 | } |
861 | | |
862 | | } // end of namespace drawinglayer::primitive2d |
863 | | |
864 | | |
865 | | // SvgLinearAtomPrimitive2D class |
866 | | |
867 | | namespace drawinglayer::primitive2d |
868 | | { |
869 | | Primitive2DReference SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const |
870 | 0 | { |
871 | 0 | const double fDelta(getOffsetB() - getOffsetA()); |
872 | |
|
873 | 0 | if(basegfx::fTools::equalZero(fDelta)) |
874 | 0 | return nullptr; |
875 | | |
876 | | // use one discrete unit for overlap (one pixel) |
877 | 0 | const double fDiscreteUnit(getDiscreteUnit()); |
878 | | |
879 | | // use color distance and discrete lengths to calculate step count |
880 | 0 | const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit)); |
881 | | |
882 | | // HACK: Splitting a gradient into adjacent polygons with gradually changing color is silly. |
883 | | // If antialiasing is used to draw them, the AA-ed adjacent edges won't line up perfectly |
884 | | // because of the AA (see SkiaSalGraphicsImpl::mergePolyPolygonToPrevious()). |
885 | | // Make the polygons a bit wider, so they the partial overlap "fixes" this. |
886 | 0 | const double fixup = SkiaHelper::isVCLSkiaEnabled() ? fDiscreteUnit / 2 : 0; |
887 | | |
888 | | // tdf#117949 Use a small amount of discrete overlap at the edges. Usually this |
889 | | // should be exactly 0.0 and 1.0, but there were cases when this gets clipped |
890 | | // against the mask polygon which got numerically problematic. |
891 | | // This change is unnecessary in that respect, but avoids that numerical havoc |
892 | | // by at the same time doing no real harm AFAIK |
893 | | // TTTT: Remove again when clipping is fixed (!) |
894 | | |
895 | | // prepare polygon in needed width at start position (with discrete overlap) |
896 | 0 | const basegfx::B2DPolygon aPolygon( |
897 | 0 | basegfx::utils::createPolygonFromRect( |
898 | 0 | basegfx::B2DRange( |
899 | 0 | getOffsetA() - fDiscreteUnit, |
900 | 0 | -0.0001, // TTTT -> should be 0.0, see comment above |
901 | 0 | getOffsetA() + (fDelta / nSteps) + fDiscreteUnit + fixup, |
902 | 0 | 1.0001))); // TTTT -> should be 1.0, see comment above |
903 | | |
904 | | // prepare loop (inside to outside, [0.0 .. 1.0[) |
905 | 0 | double fUnitScale(0.0); |
906 | 0 | const double fUnitStep(1.0 / nSteps); |
907 | |
|
908 | 0 | Primitive2DContainer aContainer; |
909 | 0 | aContainer.resize(nSteps); |
910 | 0 | for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) |
911 | 0 | { |
912 | 0 | basegfx::B2DPolygon aNew(aPolygon); |
913 | |
|
914 | 0 | aNew.translate(fDelta * fUnitScale, 0.0); |
915 | 0 | aContainer[a] = new PolyPolygonColorPrimitive2D( |
916 | 0 | basegfx::B2DPolyPolygon(aNew), |
917 | 0 | basegfx::interpolate(getColorA(), getColorB(), fUnitScale)); |
918 | 0 | } |
919 | 0 | return new GroupPrimitive2D(std::move(aContainer)); |
920 | 0 | } |
921 | | |
922 | | SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D( |
923 | | const basegfx::BColor& aColorA, double fOffsetA, |
924 | | const basegfx::BColor& aColorB, double fOffsetB) |
925 | 0 | : maColorA(aColorA), |
926 | 0 | maColorB(aColorB), |
927 | 0 | mfOffsetA(fOffsetA), |
928 | 0 | mfOffsetB(fOffsetB) |
929 | 0 | { |
930 | 0 | if(mfOffsetA > mfOffsetB) |
931 | 0 | { |
932 | 0 | OSL_ENSURE(false, "Wrong offset order (!)"); |
933 | 0 | std::swap(mfOffsetA, mfOffsetB); |
934 | 0 | } |
935 | 0 | } |
936 | | |
937 | | bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
938 | 0 | { |
939 | 0 | if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) |
940 | 0 | { |
941 | 0 | const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive); |
942 | |
|
943 | 0 | return (getColorA() == rCompare.getColorA() |
944 | 0 | && getColorB() == rCompare.getColorB() |
945 | 0 | && getOffsetA() == rCompare.getOffsetA() |
946 | 0 | && getOffsetB() == rCompare.getOffsetB()); |
947 | 0 | } |
948 | | |
949 | 0 | return false; |
950 | 0 | } |
951 | | |
952 | | // provide unique ID |
953 | | sal_uInt32 SvgLinearAtomPrimitive2D::getPrimitive2DID() const |
954 | 0 | { |
955 | 0 | return PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D; |
956 | 0 | } |
957 | | |
958 | | } // end of namespace drawinglayer::primitive2d |
959 | | |
960 | | |
961 | | // SvgRadialAtomPrimitive2D class |
962 | | |
963 | | namespace drawinglayer::primitive2d |
964 | | { |
965 | | Primitive2DReference SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const |
966 | 0 | { |
967 | 0 | const double fDeltaScale(getScaleB() - getScaleA()); |
968 | |
|
969 | 0 | if(basegfx::fTools::equalZero(fDeltaScale)) |
970 | 0 | return nullptr; |
971 | | |
972 | | // use one discrete unit for overlap (one pixel) |
973 | 0 | const double fDiscreteUnit(getDiscreteUnit()); |
974 | | |
975 | | // use color distance and discrete lengths to calculate step count |
976 | 0 | const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit)); |
977 | | |
978 | | // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes) |
979 | 0 | double fUnitScale(0.0); |
980 | 0 | const double fUnitStep(1.0 / nSteps); |
981 | |
|
982 | 0 | Primitive2DContainer aContainer; |
983 | 0 | aContainer.resize(nSteps); |
984 | 0 | for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) |
985 | 0 | { |
986 | 0 | basegfx::B2DHomMatrix aTransform; |
987 | 0 | const double fEndScale(getScaleB() - (fDeltaScale * fUnitScale)); |
988 | |
|
989 | 0 | if(isTranslateSet()) |
990 | 0 | { |
991 | 0 | const basegfx::B2DVector aTranslate( |
992 | 0 | basegfx::interpolate( |
993 | 0 | getTranslateB(), |
994 | 0 | getTranslateA(), |
995 | 0 | fUnitScale)); |
996 | |
|
997 | 0 | aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix( |
998 | 0 | fEndScale, |
999 | 0 | fEndScale, |
1000 | 0 | aTranslate.getX(), |
1001 | 0 | aTranslate.getY()); |
1002 | 0 | } |
1003 | 0 | else |
1004 | 0 | { |
1005 | 0 | aTransform = basegfx::utils::createScaleB2DHomMatrix( |
1006 | 0 | fEndScale, |
1007 | 0 | fEndScale); |
1008 | 0 | } |
1009 | |
|
1010 | 0 | basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle()); |
1011 | |
|
1012 | 0 | aNew.transform(aTransform); |
1013 | 0 | aContainer[a] = new PolyPolygonColorPrimitive2D( |
1014 | 0 | basegfx::B2DPolyPolygon(aNew), |
1015 | 0 | basegfx::interpolate(getColorB(), getColorA(), fUnitScale)); |
1016 | 0 | } |
1017 | 0 | return new GroupPrimitive2D(std::move(aContainer)); |
1018 | 0 | } |
1019 | | |
1020 | | SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( |
1021 | | const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA, |
1022 | | const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB) |
1023 | 0 | : maColorA(aColorA), |
1024 | 0 | maColorB(aColorB), |
1025 | 0 | mfScaleA(fScaleA), |
1026 | 0 | mfScaleB(fScaleB) |
1027 | 0 | { |
1028 | | // check and evtl. set translations |
1029 | 0 | if(!rTranslateA.equal(rTranslateB)) |
1030 | 0 | { |
1031 | 0 | mpTranslate.reset( new VectorPair(rTranslateA, rTranslateB) ); |
1032 | 0 | } |
1033 | | |
1034 | | // scale A and B have to be positive |
1035 | 0 | mfScaleA = std::max(mfScaleA, 0.0); |
1036 | 0 | mfScaleB = std::max(mfScaleB, 0.0); |
1037 | | |
1038 | | // scale B has to be bigger than scale A; swap if different |
1039 | 0 | if(mfScaleA > mfScaleB) |
1040 | 0 | { |
1041 | 0 | OSL_ENSURE(false, "Wrong offset order (!)"); |
1042 | 0 | std::swap(mfScaleA, mfScaleB); |
1043 | |
|
1044 | 0 | if(mpTranslate) |
1045 | 0 | { |
1046 | 0 | std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB); |
1047 | 0 | } |
1048 | 0 | } |
1049 | 0 | } |
1050 | | |
1051 | | SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( |
1052 | | const basegfx::BColor& aColorA, double fScaleA, |
1053 | | const basegfx::BColor& aColorB, double fScaleB) |
1054 | 0 | : maColorA(aColorA), |
1055 | 0 | maColorB(aColorB), |
1056 | 0 | mfScaleA(fScaleA), |
1057 | 0 | mfScaleB(fScaleB) |
1058 | 0 | { |
1059 | | // scale A and B have to be positive |
1060 | 0 | mfScaleA = std::max(mfScaleA, 0.0); |
1061 | 0 | mfScaleB = std::max(mfScaleB, 0.0); |
1062 | | |
1063 | | // scale B has to be bigger than scale A; swap if different |
1064 | 0 | if(mfScaleA > mfScaleB) |
1065 | 0 | { |
1066 | 0 | OSL_ENSURE(false, "Wrong offset order (!)"); |
1067 | 0 | std::swap(mfScaleA, mfScaleB); |
1068 | 0 | } |
1069 | 0 | } |
1070 | | |
1071 | | SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D() |
1072 | 0 | { |
1073 | 0 | } |
1074 | | |
1075 | | bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
1076 | 0 | { |
1077 | 0 | if(!DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) |
1078 | 0 | return false; |
1079 | | |
1080 | 0 | const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive); |
1081 | |
|
1082 | 0 | if(getColorA() == rCompare.getColorA() |
1083 | 0 | && getColorB() == rCompare.getColorB() |
1084 | 0 | && getScaleA() == rCompare.getScaleA() |
1085 | 0 | && getScaleB() == rCompare.getScaleB()) |
1086 | 0 | { |
1087 | 0 | if(isTranslateSet() && rCompare.isTranslateSet()) |
1088 | 0 | { |
1089 | 0 | return (getTranslateA() == rCompare.getTranslateA() |
1090 | 0 | && getTranslateB() == rCompare.getTranslateB()); |
1091 | 0 | } |
1092 | 0 | else if(!isTranslateSet() && !rCompare.isTranslateSet()) |
1093 | 0 | { |
1094 | 0 | return true; |
1095 | 0 | } |
1096 | 0 | } |
1097 | | |
1098 | 0 | return false; |
1099 | 0 | } |
1100 | | |
1101 | | // provide unique ID |
1102 | | sal_uInt32 SvgRadialAtomPrimitive2D::getPrimitive2DID() const |
1103 | 0 | { |
1104 | 0 | return PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D; |
1105 | 0 | } |
1106 | | |
1107 | | } // end of namespace |
1108 | | |
1109 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |