1 /*
2  * Copyright (c) 2007 Ivan Leben
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library in the file COPYING;
16  * if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20 
21 #include <vg/openvg.h>
22 #include "shContext.h"
23 #include "shPaint.h"
24 #include <stdio.h>
25 
26 #define _ITEM_T SHStop
27 #define _ARRAY_T SHStopArray
28 #define _FUNC_T shStopArray
29 #define _COMPARE_T(s1,s2) 0
30 #define _ARRAY_DEFINE
31 #include "shArrayBase.h"
32 
33 #define _ITEM_T SHPaint*
34 #define _ARRAY_T SHPaintArray
35 #define _FUNC_T shPaintArray
36 #define _ARRAY_DEFINE
37 #include "shArrayBase.h"
38 
39 // We currently do not use gradients which need textures, so disable them to
40 // prevent freeing resources outside the correct OpenGL thread/context.
41 #define SH_NO_PAINT_TEXTURE
42 
SHPaint_ctor(SHPaint * p)43 void SHPaint_ctor(SHPaint *p)
44 {
45   int i;
46 
47   p->type = VG_PAINT_TYPE_COLOR;
48   CSET(p->color, 0,0,0,1);
49   SH_INITOBJ(SHStopArray, p->instops);
50   SH_INITOBJ(SHStopArray, p->stops);
51   p->premultiplied = VG_FALSE;
52   p->spreadMode = VG_COLOR_RAMP_SPREAD_PAD;
53   p->tilingMode = VG_TILE_FILL;
54   for (i=0; i<4; ++i) p->linearGradient[i] = 0.0f;
55   for (i=0; i<5; ++i) p->radialGradient[i] = 0.0f;
56   p->pattern = VG_INVALID_HANDLE;
57 
58 #ifndef SH_NO_PAINT_TEXTURE
59   glGenTextures(1, &p->texture);
60   glBindTexture(GL_TEXTURE_1D, p->texture);
61   glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, SH_GRADIENT_TEX_SIZE, 0,
62                GL_RGBA, GL_FLOAT, NULL);
63 #else
64   p->texture = 0;
65 #endif
66 }
67 
SHPaint_dtor(SHPaint * p)68 void SHPaint_dtor(SHPaint *p)
69 {
70   SH_DEINITOBJ(SHStopArray, p->instops);
71   SH_DEINITOBJ(SHStopArray, p->stops);
72 
73 #ifndef SH_NO_PAINT_TEXTURE
74   if (glIsTexture(p->texture))
75     glDeleteTextures(1, &p->texture);
76 #endif
77 }
78 
vgCreatePaint(void)79 VG_API_CALL VGPaint vgCreatePaint(void)
80 {
81   SHPaint *p = NULL;
82   VG_GETCONTEXT(VG_INVALID_HANDLE);
83 
84   /* Create new paint object */
85   SH_NEWOBJ(SHPaint, p);
86   VG_RETURN_ERR_IF(!p, VG_OUT_OF_MEMORY_ERROR,
87                    VG_INVALID_HANDLE);
88 
89   /* Add to resource list */
90   shPaintArrayPushBack(&context->paints, p);
91 
92   VG_RETURN((VGPaint)p);
93 }
94 
vgDestroyPaint(VGPaint paint)95 VG_API_CALL void vgDestroyPaint(VGPaint paint)
96 {
97   SHint index;
98   VG_GETCONTEXT(VG_NO_RETVAL);
99 
100   /* Check if handle valid */
101   index = shPaintArrayFind(&context->paints, (SHPaint*)paint);
102   VG_RETURN_ERR_IF(index == -1, VG_BAD_HANDLE_ERROR, VG_NO_RETVAL);
103 
104   /* Delete object and remove resource */
105   SH_DELETEOBJ(SHPaint, (SHPaint*)paint);
106   shPaintArrayRemoveAt(&context->paints, index);
107 
108   VG_RETURN(VG_NO_RETVAL);
109 }
110 
vgSetPaint(VGPaint paint,VGbitfield paintModes)111 VG_API_CALL void vgSetPaint(VGPaint paint, VGbitfield paintModes)
112 {
113   VG_GETCONTEXT(VG_NO_RETVAL);
114 
115   /* Check if handle valid */
116   VG_RETURN_ERR_IF(!shIsValidPaint(context, paint) &&
117                    paint != VG_INVALID_HANDLE,
118                    VG_BAD_HANDLE_ERROR, VG_NO_RETVAL);
119 
120   /* Check for invalid mode */
121   VG_RETURN_ERR_IF(paintModes & ~(VG_STROKE_PATH | VG_FILL_PATH),
122                    VG_ILLEGAL_ARGUMENT_ERROR, VG_NO_RETVAL);
123 
124   /* Set stroke / fill */
125   if (paintModes & VG_STROKE_PATH)
126     context->strokePaint = (SHPaint*)paint;
127   if (paintModes & VG_FILL_PATH)
128     context->fillPaint = (SHPaint*)paint;
129 
130   VG_RETURN(VG_NO_RETVAL);
131 }
132 
vgPaintPattern(VGPaint paint,VGImage pattern)133 VG_API_CALL void vgPaintPattern(VGPaint paint, VGImage pattern)
134 {
135   VG_GETCONTEXT(VG_NO_RETVAL);
136 
137   /* Check if handle valid */
138   VG_RETURN_ERR_IF(!shIsValidPaint(context, paint),
139                    VG_BAD_HANDLE_ERROR, VG_NO_RETVAL);
140 
141   /* Check if pattern image valid */
142   VG_RETURN_ERR_IF(!shIsValidImage(context, pattern),
143                    VG_BAD_HANDLE_ERROR, VG_NO_RETVAL);
144 
145   /* TODO: Check if pattern image is current rendering target */
146 
147   /* Set pattern image */
148   ((SHPaint*)paint)->pattern = pattern;
149 
150   VG_RETURN(VG_NO_RETVAL);
151 }
152 
shUpdateColorRampTexture(SHPaint * p)153 void shUpdateColorRampTexture(SHPaint *p)
154 {
155 #ifndef SH_NO_PAINT_TEXTURE
156   SHint s=0;
157   SHStop *stop1, *stop2;
158   SHfloat rgba[SH_GRADIENT_TEX_COORDSIZE];
159   SHint x1=0, x2=0, dx, x;
160   SHColor dc, c;
161   SHfloat k;
162 
163   /* Write first pixel color */
164   stop1 = &p->stops.items[0];
165   CSTORE_RGBA1D_F(stop1->color, rgba, x1);
166 
167   /* Walk stops */
168   for (s=1; s<p->stops.size; ++s, x1=x2, stop1=stop2) {
169 
170     /* Pick next stop */
171     stop2 = &p->stops.items[s];
172     x2 = (SHint)(stop2->offset * (SH_GRADIENT_TEX_SIZE-1));
173 
174     SH_ASSERT(x1 >= 0 && x1 < SH_GRADIENT_TEX_SIZE &&
175               x2 >= 0 && x2 < SH_GRADIENT_TEX_SIZE &&
176               x1 <= x2);
177 
178     dx = x2 - x1;
179     CSUBCTO(stop2->color, stop1->color, dc);
180 
181     /* Interpolate inbetween */
182     for (x=x1+1; x<=x2; ++x) {
183 
184       k = (SHfloat)(x-x1)/dx;
185       CSETC(c, stop1->color);
186       CADDCK(c, dc, k);
187       CSTORE_RGBA1D_F(c, rgba, x);
188     }
189   }
190 
191   /* Update texture image */
192   glBindTexture(GL_TEXTURE_1D, p->texture);
193   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
194   glTexSubImage1D(GL_TEXTURE_1D, 0, 0, SH_GRADIENT_TEX_SIZE,
195                   GL_RGBA, GL_FLOAT, rgba);
196 #else
197   printf("ShivaVG: gradients not supported!");
198 #endif
199 }
200 
shValidateInputStops(SHPaint * p)201 void shValidateInputStops(SHPaint *p)
202 {
203   SHStop *instop, stop = {0, {0,0,0,0}};
204   SHfloat lastOffset=0.0f;
205   int i;
206 
207   shStopArrayClear(&p->stops);
208   shStopArrayReserve(&p->stops, p->instops.size);
209 
210   /* Assure input stops are properly defined */
211   for (i=0; i<p->instops.size; ++i) {
212 
213     /* Copy stop color */
214     instop = &p->instops.items[i];
215     stop.color = instop->color;
216 
217     /* Offset must be in [0,1] */
218     if (instop->offset < 0.0f || instop->offset > 1.0f)
219       continue;
220 
221     /* Discard whole sequence if not in ascending order */
222     if (instop->offset < lastOffset)
223       {shStopArrayClear(&p->stops); break;}
224 
225     /* Add stop at offset 0 with same color if first not at 0 */
226     if (p->stops.size == 0 && instop->offset != 0.0f) {
227       stop.offset = 0.0f;
228       shStopArrayPushBackP(&p->stops, &stop);}
229 
230     /* Add current stop to array */
231     stop.offset = instop->offset;
232     shStopArrayPushBackP(&p->stops, &stop);
233 
234     /* Save last offset */
235     lastOffset = instop->offset;
236   }
237 
238   /* Add stop at offset 1 with same color if last not at 1 */
239   if (p->stops.size > 0 && lastOffset != 1.0f) {
240     stop.offset = 1.0f;
241     shStopArrayPushBackP(&p->stops, &stop);
242   }
243 
244   /* Add 2 default stops if no valid found */
245   if (p->stops.size == 0) {
246     /* First opaque black */
247     stop.offset = 0.0f;
248     CSET(stop.color, 0,0,0,1);
249     shStopArrayPushBackP(&p->stops, &stop);
250     /* Last opaque white */
251     stop.offset = 1.0f;
252     CSET(stop.color, 1,1,1,1);
253     shStopArrayPushBackP(&p->stops, &stop);
254   }
255 
256   /* Update texture */
257   shUpdateColorRampTexture(p);
258 }
259 
shGenerateStops(SHPaint * p,SHfloat minOffset,SHfloat maxOffset,SHStopArray * outStops)260 void shGenerateStops(SHPaint *p, SHfloat minOffset, SHfloat maxOffset,
261                      SHStopArray *outStops)
262 {
263   SHStop *s1,*s2;
264   SHint i1,i2;
265   SHfloat o=0.0f;
266   SHfloat ostep=0.0f;
267   SHint istep=1;
268   SHint istart=0;
269   SHint iend=p->stops.size-1;
270   SHint minDone=0;
271   SHint maxDone=0;
272   SHStop outStop;
273 
274   /* Start below zero? */
275   if (minOffset < 0.0f) {
276     if (p->spreadMode == VG_COLOR_RAMP_SPREAD_PAD) {
277       /* Add min offset stop */
278       outStop = p->stops.items[0];
279       outStop.offset = minOffset;
280       shStopArrayPushBackP(outStops, &outStop);
281       /* Add max offset stop and exit */
282       if (maxOffset < 0.0f) {
283         outStop.offset = maxOffset;
284         shStopArrayPushBackP(outStops, &outStop);
285         return; }
286     }else{
287       /* Pad starting offset to nearest factor of 2 */
288       SHint ioff = (SHint)SH_FLOOR(minOffset);
289       o = (SHfloat)(ioff - (ioff & 1));
290     }
291   }
292 
293   /* Construct stops until max offset reached */
294   for (i1=istart, i2=istart+istep; maxDone!=1;
295        i1+=istep, i2+=istep, o+=ostep) {
296 
297     /* All stops consumed? */
298     if (i1==iend) { switch(p->spreadMode) {
299 
300       case VG_COLOR_RAMP_SPREAD_PAD:
301         /* Pick last stop */
302         outStop = p->stops.items[i1];
303         if (!minDone) {
304           /* Add min offset stop with last color */
305           outStop.offset = minOffset;
306           shStopArrayPushBackP(outStops, &outStop); }
307         /* Add max offset stop with last color */
308         outStop.offset = maxOffset;
309         shStopArrayPushBackP(outStops, &outStop);
310         return;
311 
312       case VG_COLOR_RAMP_SPREAD_REPEAT:
313         /* Reset iteration */
314         i1=istart; i2=istart+istep;
315         /* Add stop1 if past min offset */
316         if (minDone) {
317           outStop = p->stops.items[0];
318           outStop.offset = o;
319           shStopArrayPushBackP(outStops, &outStop); }
320         break;
321 
322       case VG_COLOR_RAMP_SPREAD_REFLECT:
323         /* Reflect iteration direction */
324         istep = -istep;
325         i2 = i1 + istep;
326         iend = (istep==1) ? p->stops.size-1 : 0;
327         break;
328       }
329     }
330 
331     /* 2 stops and their offset distance */
332     s1 = &p->stops.items[i1];
333     s2 = &p->stops.items[i2];
334     ostep = s2->offset - s1->offset;
335     ostep = SH_ABS(ostep);
336 
337     /* Add stop1 if reached min offset */
338     if (!minDone && o+ostep > minOffset) {
339       minDone = 1;
340       outStop = *s1;
341       outStop.offset = o;
342       shStopArrayPushBackP(outStops, &outStop);
343     }
344 
345     /* Mark done if reached max offset */
346     if (o+ostep > maxOffset)
347       maxDone = 1;
348 
349     /* Add stop2 if past min offset */
350     if (minDone) {
351       outStop = *s2;
352       outStop.offset = o+ostep;
353       shStopArrayPushBackP(outStops, &outStop);
354     }
355   }
356 }
357 
shSetGradientTexGLState(SHPaint * p)358 void shSetGradientTexGLState(SHPaint *p)
359 {
360 #ifndef SH_NO_PAINT_TEXTURE
361   glBindTexture(GL_TEXTURE_1D, p->texture);
362   glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
363   glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
364 
365   switch (p->spreadMode) {
366   case VG_COLOR_RAMP_SPREAD_PAD:
367     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); break;
368   case VG_COLOR_RAMP_SPREAD_REPEAT:
369     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); break;
370   case VG_COLOR_RAMP_SPREAD_REFLECT:
371     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); break;
372   }
373 
374   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
375   glColor4f(1,1,1,1);
376 #else
377   printf("ShivaVG: gradients not supported!");
378 #endif
379 }
380 
shSetPatternTexGLState(SHPaint * p,VGContext * c)381 void shSetPatternTexGLState(SHPaint *p, VGContext *c)
382 {
383   glBindTexture(GL_TEXTURE_2D, ((SHImage*)p->pattern)->texture);
384   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
385   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
386 
387   switch(p->tilingMode) {
388   case VG_TILE_FILL:
389     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
390     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
391     glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR,
392                      (GLfloat*)&c->tileFillColor);
393     break;
394   case VG_TILE_PAD:
395     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
396     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
397     break;
398   case VG_TILE_REPEAT:
399     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
400     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
401     break;
402   case VG_TILE_REFLECT:
403     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
404     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
405     break;
406   }
407 
408   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
409   glColor4f(1,1,1,1);
410 }
411 
shDrawLinearGradientMesh(SHPaint * p,SHVector2 * min,SHVector2 * max,VGPaintMode mode,GLenum texUnit)412 int shDrawLinearGradientMesh(SHPaint *p, SHVector2 *min, SHVector2 *max,
413                              VGPaintMode mode, GLenum texUnit)
414 {
415   SHint i;
416   SHfloat n;
417 
418   SHfloat x1 = p->linearGradient[0];
419   SHfloat y1 = p->linearGradient[1];
420   SHfloat x2 = p->linearGradient[2];
421   SHfloat y2 = p->linearGradient[3];
422   SHVector2 c, ux, uy;
423   SHVector2 cc, uux, uuy;
424 
425   SHMatrix3x3 *m = 0;
426   SHMatrix3x3 mi;
427   SHint invertible;
428   SHVector2 corners[4];
429   SHfloat minOffset = 0.0f;
430   SHfloat maxOffset = 0.0f;
431   SHfloat left = 0.0f;
432   SHfloat right = 0.0f;
433   SHVector2 l1,r1,l2,r2;
434 
435   /* Pick paint transform matrix */
436   SH_GETCONTEXT(0);
437   if (mode == VG_FILL_PATH)
438     m = &context->fillTransform;
439   else if (mode == VG_STROKE_PATH)
440     m = &context->strokeTransform;
441 
442   /* Gradient center and unit vectors */
443   SET2(c, x1, y1);
444   SET2(ux, x2-x1, y2-y1);
445   SET2(uy, -ux.y, ux.x);
446   n = NORM2(ux);
447   DIV2(ux, n);
448   NORMALIZE2(uy);
449 
450   /* Apply paint-to-user transformation */
451   ADD2V(ux, c); ADD2V(uy, c);
452   TRANSFORM2TO(c, (*m), cc);
453   TRANSFORM2TO(ux, (*m), uux);
454   TRANSFORM2TO(uy, (*m), uuy);
455   SUB2V(ux,c); SUB2V(uy,c);
456   SUB2V(uux,cc); SUB2V(uuy,cc);
457 
458   /* Boundbox corners */
459   SET2(corners[0], min->x, min->y);
460   SET2(corners[1], max->x, min->y);
461   SET2(corners[2], max->x, max->y);
462   SET2(corners[3], min->x, max->y);
463 
464   /* Find inverse transformation (back to paint space) */
465   invertible = shInvertMatrix(m, &mi);
466   if (!invertible || n==0.0f) {
467 
468     /* Fill boundbox with color at offset 1 */
469     SHColor *c = &p->stops.items[p->stops.size-1].color;
470     glColor4fv((GLfloat*)c); glBegin(GL_QUADS);
471     for (i=0; i<4; ++i) glVertex2fv((GLfloat*)&corners[i]);
472     glEnd();
473     return 1;
474   }
475 
476   /*--------------------------------------------------------*/
477 
478   for (i=0; i<4; ++i) {
479 
480     /* Find min/max offset and perpendicular span */
481     SHfloat o, s;
482     TRANSFORM2(corners[i], mi);
483     SUB2V(corners[i], c);
484     o = DOT2(corners[i], ux) / n;
485     s = DOT2(corners[i], uy);
486     if (o < minOffset || i==0) minOffset = o;
487     if (o > maxOffset || i==0) maxOffset = o;
488     if (s < left || i==0) left = s;
489     if (s > right || i==0) right = s;
490   }
491 
492   /*---------------------------------------------------------*/
493 
494   /* Corners of boundbox in gradient system */
495   SET2V(l1, cc); SET2V(r1, cc);
496   SET2V(l2, cc); SET2V(r2, cc);
497   OFFSET2V(l1, uuy, left);  OFFSET2V(l1, uux, minOffset * n);
498   OFFSET2V(r1, uuy, right); OFFSET2V(r1, uux, minOffset * n);
499   OFFSET2V(l2, uuy, left);  OFFSET2V(l2, uux, maxOffset * n);
500   OFFSET2V(r2, uuy, right); OFFSET2V(r2, uux, maxOffset * n);
501 
502   /* Draw quad using color-ramp texture */
503   glActiveTexture(texUnit);
504   shSetGradientTexGLState(p);
505 
506   glEnable(GL_TEXTURE_1D);
507   glBegin(GL_QUAD_STRIP);
508 
509   glMultiTexCoord1f(texUnit, minOffset);
510   glVertex2fv((GLfloat*)&r1);
511   glVertex2fv((GLfloat*)&l1);
512 
513   glMultiTexCoord1f(texUnit, maxOffset);
514   glVertex2fv((GLfloat*)&r2);
515   glVertex2fv((GLfloat*)&l2);
516 
517   glEnd();
518   glDisable(GL_TEXTURE_1D);
519 
520   return 1;
521 }
522 
shDrawRadialGradientMesh(SHPaint * p,SHVector2 * min,SHVector2 * max,VGPaintMode mode,GLenum texUnit)523 int shDrawRadialGradientMesh(SHPaint *p, SHVector2 *min, SHVector2 *max,
524                              VGPaintMode mode, GLenum texUnit)
525 {
526   SHint i, j;
527   float a, n;
528 
529   SHfloat cx = p->radialGradient[0];
530   SHfloat cy = p->radialGradient[1];
531   SHfloat fx = p->radialGradient[2];
532   SHfloat fy = p->radialGradient[3];
533   float r = p->radialGradient[4];
534   float fcx, fcy, rr, C;
535 
536   SHVector2 ux;
537   SHVector2 uy;
538   SHVector2 c, f;
539   SHVector2 cf;
540 
541   SHMatrix3x3 *m = 0;
542   SHMatrix3x3 mi;
543   SHint invertible;
544   SHVector2 corners[4];
545   SHVector2 fcorners[4];
546   SHfloat minOffset=0.0f;
547   SHfloat maxOffset=0.0f;
548 
549   SHint maxI=0, maxJ=0;
550   SHfloat maxA=0.0f;
551   SHfloat startA=0.0f;
552 
553   int numsteps = 100;
554   float step = 2*PI/numsteps;
555   SHVector2 tmin, tmax;
556   SHVector2 min1, max1, min2, max2;
557 
558   /* Pick paint transform matrix */
559   SH_GETCONTEXT(0);
560   if (mode == VG_FILL_PATH)
561     m = &context->fillTransform;
562   else if (mode == VG_STROKE_PATH)
563     m = &context->strokeTransform;
564 
565   /* Move focus into circle if outside */
566   SET2(cf, fx,fy);
567   SUB2(cf, cx,cy);
568   n = NORM2(cf);
569   if (n > r) {
570     DIV2(cf, n);
571     fx = cx + 0.995f * r * cf.x;
572     fy = cy + 0.995f * r * cf.y;
573   }
574 
575   /* Precalculations */
576   rr = r*r;
577   fcx = fx - cx;
578   fcy = fy - cy;
579   C = fcx*fcx + fcy*fcy - rr;
580 
581   /* Apply paint-to-user transformation
582      to focus and unit vectors */
583   SET2(f, fx, fy);
584   SET2(c, cx, cy);
585   SET2(ux, 1, 0);
586   SET2(uy, 0, 1);
587   ADD2(ux, cx, cy);
588   ADD2(uy, cx, cy);
589   TRANSFORM2(f, (*m));
590   TRANSFORM2(c, (*m));
591   TRANSFORM2(ux, (*m));
592   TRANSFORM2(uy, (*m));
593   SUB2V(ux, c); SUB2V(uy, c);
594 
595   /* Boundbox corners */
596   SET2(corners[0], min->x, min->y);
597   SET2(corners[1], max->x, min->y);
598   SET2(corners[2], max->x, max->y);
599   SET2(corners[3], min->x, max->y);
600 
601   /* Find inverse transformation (back to paint space) */
602   invertible = shInvertMatrix(m, &mi);
603   if (!invertible || r <= 0.0f) {
604 
605     /* Fill boundbox with color at offset 1 */
606     SHColor *c = &p->stops.items[p->stops.size-1].color;
607     glColor4fv((GLfloat*)c); glBegin(GL_QUADS);
608     for (i=0; i<4; ++i) glVertex2fv((GLfloat*)&corners[i]);
609     glEnd();
610     return 1;
611   }
612 
613   /*--------------------------------------------------------*/
614 
615   /* Find min/max offset */
616   for (i=0; i<4; ++i) {
617 
618     /* Transform to paint space */
619     SHfloat ax,ay, A,B,D,t, off;
620     TRANSFORM2TO(corners[i], mi, fcorners[i]);
621     SUB2(fcorners[i], fx, fy);
622     n = NORM2(fcorners[i]);
623     if (n == 0.0f) {
624 
625       /* Avoid zero-length vectors */
626       off = 0.0f;
627 
628     }else{
629 
630       /* Distance from focus to circle at corner angle */
631       DIV2(fcorners[i], n);
632       ax = fcorners[i].x;
633       ay = fcorners[i].y;
634       A = ax*ax + ay*ay;
635       B = 2 * (fcx*ax + fcy*ay);
636       D = B*B - 4*A*C;
637       t = (-B + SH_SQRT(D)) / (2*A);
638 
639       /* Relative offset of boundbox corner */
640       if (D <= 0.0f) off = 1.0f;
641       else off = n / t;
642     }
643 
644     /* Find smallest and largest offset */
645     if (off < minOffset || i==0) minOffset = off;
646     if (off > maxOffset || i==0) maxOffset = off;
647   }
648 
649   /* Is transformed focus inside original boundbox? */
650   if (f.x >= min->x && f.x <= max->x &&
651       f.y >= min->y && f.y <= max->y) {
652 
653     /* Draw whole circle */
654     minOffset = 0.0f;
655     startA = 0.0f;
656     maxA = 2*PI;
657 
658   }else{
659 
660     /* Find most distant corner pair */
661     for (i=0; i<3; ++i) {
662       if (ISZERO2(fcorners[i])) continue;
663       for (j=i+1; j<4; ++j) {
664         if (ISZERO2(fcorners[j])) continue;
665         a = ANGLE2N(fcorners[i], fcorners[j]);
666         if (a > maxA || maxA == 0.0f)
667           {maxA=a; maxI=i; maxJ=j;}
668       }}
669 
670     /* Pick starting angle */
671     if (CROSS2(fcorners[maxI],fcorners[maxJ]) > 0.0f)
672       startA = shVectorOrientation(&fcorners[maxI]);
673     else startA = shVectorOrientation(&fcorners[maxJ]);
674   }
675 
676   /*---------------------------------------------------------*/
677 
678   /* TODO: for minOffset we'd actually need to find minimum
679      of the gradient function when X and Y are substitued
680      with a line equation for each bound-box edge. As a
681      workaround we use 0.0f for now. */
682   minOffset = 0.0f;
683   step = PI/50;
684   numsteps = (SHint)SH_CEIL(maxA / step) + 1;
685 
686   glActiveTexture(texUnit);
687   shSetGradientTexGLState(p);
688 
689   glEnable(GL_TEXTURE_1D);
690   glBegin(GL_QUADS);
691 
692   /* Walk the steps and draw gradient mesh */
693   for (i=0, a=startA; i<numsteps; ++i, a+=step) {
694 
695     /* Distance from focus to circle border
696          at current angle (gradient space) */
697     float ax = SH_COS(a);
698     float ay = SH_SIN(a);
699     float A = ax*ax + ay*ay;
700     float B = 2 * (fcx*ax + fcy*ay);
701     float D = B*B - 4*A*C;
702     float t = (-B + SH_SQRT(D)) / (2*A);
703     if (D <= 0.0f) t = 0.0f;
704 
705     /* Vectors pointing towards minimum and maximum
706          offset at current angle (gradient space) */
707     tmin.x = ax * t * minOffset;
708     tmin.y = ay * t * minOffset;
709     tmax.x = ax * t * maxOffset;
710     tmax.y = ay * t * maxOffset;
711 
712     /* Transform back to user space */
713     min2.x = f.x + tmin.x * ux.x + tmin.y * uy.x;
714     min2.y = f.y + tmin.x * ux.y + tmin.y * uy.y;
715     max2.x = f.x + tmax.x * ux.x + tmax.y * uy.x;
716     max2.y = f.y + tmax.x * ux.y + tmax.y * uy.y;
717 
718     /* Draw quad */
719     if (i!=0) {
720       glMultiTexCoord1f(texUnit, minOffset);
721       glVertex2fv((GLfloat*)&min1);
722       glVertex2fv((GLfloat*)&min2);
723       glMultiTexCoord1f(texUnit, maxOffset);
724       glVertex2fv((GLfloat*)&max2);
725       glVertex2fv((GLfloat*)&max1);
726     }
727 
728     /* Save prev points */
729     min1 = min2;
730     max1 = max2;
731   }
732 
733   glEnd();
734   glDisable(GL_TEXTURE_1D);
735 
736   return 1;
737 }
738 
shDrawPatternMesh(SHPaint * p,SHVector2 * min,SHVector2 * max,VGPaintMode mode,GLenum texUnit)739 int shDrawPatternMesh(SHPaint *p, SHVector2 *min, SHVector2 *max,
740                       VGPaintMode mode, GLenum texUnit)
741 {
742   SHMatrix3x3 *m = 0;
743   SHMatrix3x3 mi;
744   SHfloat migl[16];
745   SHint invertible;
746   SHVector2 corners[4];
747   VGfloat sx, sy;
748   SHImage *img;
749   int i;
750 
751   /* Pick paint transform matrix */
752   SH_GETCONTEXT(0);
753   if (mode == VG_FILL_PATH)
754     m = &context->fillTransform;
755   else if (mode == VG_STROKE_PATH)
756     m = &context->strokeTransform;
757 
758   /* Boundbox corners */
759   SET2(corners[0], min->x, min->y);
760   SET2(corners[1], max->x, min->y);
761   SET2(corners[2], max->x, max->y);
762   SET2(corners[3], min->x, max->y);
763 
764   /* Find inverse transformation (back to paint space) */
765   invertible = shInvertMatrix(m, &mi);
766   if (!invertible) {
767 
768     /* Fill boundbox with tile fill color */
769     SHColor *c = &context->tileFillColor;
770     glColor4fv((GLfloat*)c); glBegin(GL_QUADS);
771     for (i=0; i<4; ++i) glVertex2fv((GLfloat*)&corners[i]);
772     glEnd();
773     return 1;
774   }
775 
776 
777   /* Setup texture coordinate transform */
778   img = (SHImage*)p->pattern;
779   sx = 1.0f/(VGfloat)img->texwidth;
780   sy = 1.0f/(VGfloat)img->texheight;
781 
782   glActiveTexture(texUnit);
783   shMatrixToGL(&mi, migl);
784   glMatrixMode(GL_TEXTURE);
785   glPushMatrix();
786   glScalef(sx, sy, 1.0f);
787   glMultMatrixf(migl);
788 
789 
790   /* Draw boundbox with same texture coordinates
791      that will get transformed back to paint space */
792   shSetPatternTexGLState(p, context);
793   glEnable(GL_TEXTURE_2D);
794   glBegin(GL_QUADS);
795 
796   for (i=0; i<4; ++i) {
797     glMultiTexCoord2f(texUnit, corners[i].x, corners[i].y);
798     glVertex2fv((GLfloat*)&corners[i]);
799   }
800 
801   glEnd();
802   glDisable(GL_TEXTURE_2D);
803   glPopMatrix();
804   glMatrixMode(GL_MODELVIEW);
805   return 1;
806 }
807