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 {
operator ==(const ODFGradientInfo & rODFGradientInfo) const34     bool ODFGradientInfo::operator==(const ODFGradientInfo& rODFGradientInfo) const
35     {
36         return getTextureTransform() == rODFGradientInfo.getTextureTransform()
37             && getAspectRatio() == rODFGradientInfo.getAspectRatio()
38             && getSteps() == rODFGradientInfo.getSteps();
39     }
40 
getBackTextureTransform() const41     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     */
init1DGradientInfo(const B2DRange & rTargetRange,sal_uInt32 nSteps,double fBorder,double fAngle,bool bAxial)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     */
initEllipticalGradientInfo(const B2DRange & rTargetRange,const B2DVector & rOffset,sal_uInt32 nSteps,double fBorder,double fAngle,bool bCircular)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     */
initRectGradientInfo(const B2DRange & rTargetRange,const B2DVector & rOffset,sal_uInt32 nSteps,double fBorder,double fAngle,bool bSquare)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     {
createLinearODFGradientInfo(const B2DRange & rTargetArea,sal_uInt32 nSteps,double fBorder,double fAngle)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 
createAxialODFGradientInfo(const B2DRange & rTargetArea,sal_uInt32 nSteps,double fBorder,double fAngle)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 
createRadialODFGradientInfo(const B2DRange & rTargetArea,const B2DVector & rOffset,sal_uInt32 nSteps,double fBorder)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 
createEllipticalODFGradientInfo(const B2DRange & rTargetArea,const B2DVector & rOffset,sal_uInt32 nSteps,double fBorder,double fAngle)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 
createSquareODFGradientInfo(const B2DRange & rTargetArea,const B2DVector & rOffset,sal_uInt32 nSteps,double fBorder,double fAngle)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 
createRectangularODFGradientInfo(const B2DRange & rTargetArea,const B2DVector & rOffset,sal_uInt32 nSteps,double fBorder,double fAngle)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 
getLinearGradientAlpha(const B2DPoint & rUV,const ODFGradientInfo & rGradInfo)361         double getLinearGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
362         {
363             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
364 
365             // Ignore Y, this is not needed at all for Y-Oriented gradients
366             // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
367             // {
368             //     return 0.0;
369             // }
370 
371             if(aCoor.getY() <= 0.0)
372             {
373                 return 0.0; // start value for inside
374             }
375 
376             if(aCoor.getY() >= 1.0)
377             {
378                 return 1.0; // end value for outside
379             }
380 
381             const sal_uInt32 nSteps(rGradInfo.getSteps());
382 
383             if(nSteps)
384             {
385                 return floor(aCoor.getY() * nSteps) / double(nSteps - 1);
386             }
387 
388             return aCoor.getY();
389         }
390 
getAxialGradientAlpha(const B2DPoint & rUV,const ODFGradientInfo & rGradInfo)391         double getAxialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
392         {
393             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
394 
395             // Ignore Y, this is not needed at all for Y-Oriented gradients
396             //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
397             //{
398             //    return 0.0;
399             //}
400 
401             const double fAbsY(fabs(aCoor.getY()));
402 
403             if(fAbsY >= 1.0)
404             {
405                 return 1.0; // use end value when outside in Y
406             }
407 
408             const sal_uInt32 nSteps(rGradInfo.getSteps());
409 
410             if(nSteps)
411             {
412                 return floor(fAbsY * nSteps) / double(nSteps - 1);
413             }
414 
415             return fAbsY;
416         }
417 
getRadialGradientAlpha(const B2DPoint & rUV,const ODFGradientInfo & rGradInfo)418         double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
419         {
420             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
421 
422             if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0)
423             {
424                 return 0.0;
425             }
426 
427             const double t(1.0 - sqrt(aCoor.getX() * aCoor.getX() + aCoor.getY() * aCoor.getY()));
428             const sal_uInt32 nSteps(rGradInfo.getSteps());
429 
430             if(nSteps && t < 1.0)
431             {
432                 return floor(t * nSteps) / double(nSteps - 1);
433             }
434 
435             return t;
436         }
437 
getEllipticalGradientAlpha(const B2DPoint & rUV,const ODFGradientInfo & rGradInfo)438         double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
439         {
440             return getRadialGradientAlpha(rUV, rGradInfo); // only matrix setup differs
441         }
442 
getSquareGradientAlpha(const B2DPoint & rUV,const ODFGradientInfo & rGradInfo)443         double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
444         {
445             const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
446             const double fAbsX(fabs(aCoor.getX()));
447 
448             if(fAbsX >= 1.0)
449             {
450                 return 0.0;
451             }
452 
453             const double fAbsY(fabs(aCoor.getY()));
454 
455             if(fAbsY >= 1.0)
456             {
457                 return 0.0;
458             }
459 
460             const double t(1.0 - std::max(fAbsX, fAbsY));
461             const sal_uInt32 nSteps(rGradInfo.getSteps());
462 
463             if(nSteps && t < 1.0)
464             {
465                 return floor(t * nSteps) / double(nSteps - 1);
466             }
467 
468             return t;
469         }
470 
getRectangularGradientAlpha(const B2DPoint & rUV,const ODFGradientInfo & rGradInfo)471         double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
472         {
473             return getSquareGradientAlpha(rUV, rGradInfo); // only matrix setup differs
474         }
475     } // namespace tools
476 } // namespace basegfx
477 
478 // eof
479