1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_basegfx.hxx"
26 
27 #include <basegfx/tools/gradienttools.hxx>
28 #include <basegfx/point/b2dpoint.hxx>
29 #include <basegfx/range/b2drange.hxx>
30 #include <basegfx/matrix/b2dhommatrixtools.hxx>
31 
32 namespace basegfx
33 {
34     bool ODFGradientInfo::operator==(const ODFGradientInfo& rODFGradientInfo) const
35     {
36         return getTextureTransform() == rODFGradientInfo.getTextureTransform()
37             && getAspectRatio() == rODFGradientInfo.getAspectRatio()
38             && getSteps() == rODFGradientInfo.getSteps();
39     }
40 
41     const B2DHomMatrix& ODFGradientInfo::getBackTextureTransform() const
42     {
43         if(maBackTextureTransform.isIdentity())
44         {
45             const_cast< ODFGradientInfo* >(this)->maBackTextureTransform = getTextureTransform();
46             const_cast< ODFGradientInfo* >(this)->maBackTextureTransform.invert();
47         }
48 
49         return maBackTextureTransform;
50     }
51 
52     /** Most of the setup for linear & axial gradient is the same, except
53         for the border treatment. Factored out here.
54     */
55     ODFGradientInfo init1DGradientInfo(
56         const B2DRange& rTargetRange,
57         sal_uInt32 nSteps,
58         double fBorder,
59         double fAngle,
60         bool bAxial)
61     {
62         B2DHomMatrix aTextureTransform;
63 
64         fAngle = -fAngle;
65 
66         double fTargetSizeX(rTargetRange.getWidth());
67         double fTargetSizeY(rTargetRange.getHeight());
68         double fTargetOffsetX(rTargetRange.getMinX());
69         double fTargetOffsetY(rTargetRange.getMinY());
70 
71         // add object expansion
72         const bool bAngleUsed(!fTools::equalZero(fAngle));
73 
74         if(bAngleUsed)
75         {
76             const double fAbsCos(fabs(cos(fAngle)));
77             const double fAbsSin(fabs(sin(fAngle)));
78             const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
79             const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
80 
81             fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
82             fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
83             fTargetSizeX = fNewX;
84             fTargetSizeY = fNewY;
85         }
86 
87         const double fSizeWithoutBorder(1.0 - fBorder);
88 
89         if(bAxial)
90         {
91             aTextureTransform.scale(1.0, fSizeWithoutBorder * 0.5);
92             aTextureTransform.translate(0.0, 0.5);
93         }
94         else
95         {
96             if(!fTools::equal(fSizeWithoutBorder, 1.0))
97             {
98                 aTextureTransform.scale(1.0, fSizeWithoutBorder);
99                 aTextureTransform.translate(0.0, fBorder);
100             }
101         }
102 
103         aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
104 
105         // add texture rotate after scale to keep perpendicular angles
106         if(bAngleUsed)
107         {
108             const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
109 
110             aTextureTransform *= basegfx::tools::createRotateAroundPoint(aCenter, fAngle);
111         }
112 
113         // add object translate
114         aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
115 
116         // prepare aspect for texture
117         const double fAspectRatio(fTools::equalZero(fTargetSizeY) ?  1.0 : fTargetSizeX / fTargetSizeY);
118 
119         return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
120     }
121 
122     /** Most of the setup for radial & ellipsoidal gradient is the same,
123         except for the border treatment. Factored out here.
124     */
125     ODFGradientInfo initEllipticalGradientInfo(
126         const B2DRange& rTargetRange,
127         const B2DVector& rOffset,
128         sal_uInt32 nSteps,
129         double fBorder,
130         double fAngle,
131         bool bCircular)
132     {
133         B2DHomMatrix aTextureTransform;
134 
135         fAngle = -fAngle;
136 
137         double fTargetSizeX(rTargetRange.getWidth());
138         double fTargetSizeY(rTargetRange.getHeight());
139         double fTargetOffsetX(rTargetRange.getMinX());
140         double fTargetOffsetY(rTargetRange.getMinY());
141 
142         // add object expansion
143         if(bCircular)
144         {
145             const double fOriginalDiag(sqrt((fTargetSizeX * fTargetSizeX) + (fTargetSizeY * fTargetSizeY)));
146 
147             fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0;
148             fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0;
149             fTargetSizeX = fOriginalDiag;
150             fTargetSizeY = fOriginalDiag;
151         }
152         else
153         {
154             fTargetOffsetX -= (0.4142 / 2.0 ) * fTargetSizeX;
155             fTargetOffsetY -= (0.4142 / 2.0 ) * fTargetSizeY;
156             fTargetSizeX = 1.4142 * fTargetSizeX;
157             fTargetSizeY = 1.4142 * fTargetSizeY;
158         }
159 
160         const double fHalfBorder((1.0 - fBorder) * 0.5);
161 
162         aTextureTransform.scale(fHalfBorder, fHalfBorder);
163         aTextureTransform.translate(0.5, 0.5);
164         aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
165 
166         // add texture rotate after scale to keep perpendicular angles
167         if(!bCircular && !fTools::equalZero(fAngle))
168         {
169             const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
170 
171             aTextureTransform *= basegfx::tools::createRotateAroundPoint(aCenter, fAngle);
172         }
173 
174         // add defined offsets after rotation
175         if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
176         {
177             // use original target size
178             fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
179             fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
180         }
181 
182         // add object translate
183         aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
184 
185         // prepare aspect for texture
186         const double fAspectRatio((0.0 != fTargetSizeY) ?  fTargetSizeX / fTargetSizeY : 1.0);
187 
188         return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
189     }
190 
191     /** Setup for rect & square gradient is exactly the same. Factored out
192         here.
193     */
194     ODFGradientInfo initRectGradientInfo(
195         const B2DRange& rTargetRange,
196         const B2DVector& rOffset,
197         sal_uInt32 nSteps,
198         double fBorder,
199         double fAngle,
200         bool bSquare)
201     {
202         B2DHomMatrix aTextureTransform;
203 
204         fAngle = -fAngle;
205 
206         double fTargetSizeX(rTargetRange.getWidth());
207         double fTargetSizeY(rTargetRange.getHeight());
208         double fTargetOffsetX(rTargetRange.getMinX());
209         double fTargetOffsetY(rTargetRange.getMinY());
210 
211         // add object expansion
212         if(bSquare)
213         {
214             const double fSquareWidth(std::max(fTargetSizeX, fTargetSizeY));
215 
216             fTargetOffsetX -= (fSquareWidth - fTargetSizeX) / 2.0;
217             fTargetOffsetY -= (fSquareWidth - fTargetSizeY) / 2.0;
218             fTargetSizeX = fTargetSizeY = fSquareWidth;
219         }
220 
221         // add object expansion
222         const bool bAngleUsed(!fTools::equalZero(fAngle));
223 
224         if(bAngleUsed)
225         {
226             const double fAbsCos(fabs(cos(fAngle)));
227             const double fAbsSin(fabs(sin(fAngle)));
228             const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
229             const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
230 
231             fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
232             fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
233             fTargetSizeX = fNewX;
234             fTargetSizeY = fNewY;
235         }
236 
237         const double fHalfBorder((1.0 - fBorder) * 0.5);
238 
239         aTextureTransform.scale(fHalfBorder, fHalfBorder);
240         aTextureTransform.translate(0.5, 0.5);
241         aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
242 
243         // add texture rotate after scale to keep perpendicular angles
244         if(bAngleUsed)
245         {
246             const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
247 
248             aTextureTransform *= basegfx::tools::createRotateAroundPoint(aCenter, fAngle);
249         }
250 
251         // add defined offsets after rotation
252         if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
253         {
254             // use scaled target size
255             fTargetOffsetX += (rOffset.getX() - 0.5) * fTargetSizeX;
256             fTargetOffsetY += (rOffset.getY() - 0.5) * fTargetSizeY;
257         }
258 
259         // add object translate
260         aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
261 
262         // prepare aspect for texture
263         const double fAspectRatio((0.0 != fTargetSizeY) ?  fTargetSizeX / fTargetSizeY : 1.0);
264 
265         return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
266     }
267 
268     namespace tools
269     {
270         ODFGradientInfo createLinearODFGradientInfo(
271             const B2DRange& rTargetArea,
272             sal_uInt32 nSteps,
273             double fBorder,
274             double fAngle)
275         {
276             return init1DGradientInfo(
277                 rTargetArea,
278                 nSteps,
279                 fBorder,
280                 fAngle,
281                 false);
282         }
283 
284         ODFGradientInfo createAxialODFGradientInfo(
285             const B2DRange& rTargetArea,
286             sal_uInt32 nSteps,
287             double fBorder,
288             double fAngle)
289         {
290             return init1DGradientInfo(
291                 rTargetArea,
292                 nSteps,
293                 fBorder,
294                 fAngle,
295                 true);
296         }
297 
298         ODFGradientInfo createRadialODFGradientInfo(
299             const B2DRange& rTargetArea,
300             const B2DVector& rOffset,
301             sal_uInt32 nSteps,
302             double fBorder)
303         {
304             return initEllipticalGradientInfo(
305                 rTargetArea,
306                 rOffset,
307                 nSteps,
308                 fBorder,
309                 0.0,
310                 true);
311         }
312 
313         ODFGradientInfo createEllipticalODFGradientInfo(
314             const B2DRange& rTargetArea,
315             const B2DVector& rOffset,
316             sal_uInt32 nSteps,
317             double fBorder,
318             double fAngle)
319         {
320             return initEllipticalGradientInfo(
321                 rTargetArea,
322                 rOffset,
323                 nSteps,
324                 fBorder,
325                 fAngle,
326                 false);
327         }
328 
329         ODFGradientInfo createSquareODFGradientInfo(
330             const B2DRange& rTargetArea,
331             const B2DVector& rOffset,
332             sal_uInt32 nSteps,
333             double fBorder,
334             double fAngle)
335         {
336             return initRectGradientInfo(
337                 rTargetArea,
338                 rOffset,
339                 nSteps,
340                 fBorder,
341                 fAngle,
342                 true);
343         }
344 
345         ODFGradientInfo createRectangularODFGradientInfo(
346             const B2DRange& rTargetArea,
347             const B2DVector& rOffset,
348             sal_uInt32 nSteps,
349             double fBorder,
350             double fAngle)
351         {
352             return initRectGradientInfo(
353                 rTargetArea,
354                 rOffset,
355                 nSteps,
356                 fBorder,
357                 fAngle,
358                 false);
359         }
360 
361         double getLinearGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
362         {
363             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
364             const double t(clamp(aCoor.getY(), 0.0, 1.0));
365             const sal_uInt32 nSteps(rGradInfo.getSteps());
366 
367             if(nSteps)
368             {
369                 return floor(t * nSteps) / double(nSteps + 1L);
370             }
371 
372             return t;
373         }
374 
375         double getAxialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
376         {
377             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
378             const double t(clamp(fabs(aCoor.getY()), 0.0, 1.0));
379             const sal_uInt32 nSteps(rGradInfo.getSteps());
380             const double fInternalSteps((nSteps * 2) - 1);
381 
382             if(nSteps)
383             {
384                 return floor(((t * fInternalSteps) + 1.0) / 2.0) / double(nSteps - 1L);
385             }
386 
387             return t;
388         }
389 
390         double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
391         {
392             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
393             const double fDist(clamp(aCoor.getX() * aCoor.getX() + aCoor.getY() * aCoor.getY(), 0.0, 1.0));
394             const double t(1.0 - sqrt(fDist));
395             const sal_uInt32 nSteps(rGradInfo.getSteps());
396 
397             if(nSteps)
398             {
399                 return floor(t * nSteps) / double(nSteps - 1L);
400             }
401 
402             return t;
403         }
404 
405         double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
406         {
407             return getRadialGradientAlpha(rUV, rGradInfo); // only matrix setup differs
408         }
409 
410         double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
411         {
412             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
413             const double fAbsX(fabs(aCoor.getX()));
414             const double fAbsY(fabs(aCoor.getY()));
415 
416             if(fTools::moreOrEqual(fAbsX, 1.0) || fTools::moreOrEqual(fAbsY, 1.0))
417             {
418                 return 0.0;
419             }
420 
421             const double t(1.0 - std::max(fAbsX, fAbsY));
422             const sal_uInt32 nSteps(rGradInfo.getSteps());
423 
424             if(nSteps)
425             {
426                 return floor(t * nSteps) / double(nSteps - 1L);
427             }
428 
429             return t;
430         }
431 
432         double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
433         {
434             return getSquareGradientAlpha(rUV, rGradInfo); // only matrix setup differs
435         }
436     } // namespace tools
437 } // namespace basegfx
438 
439 // eof
440