1 /**
2   2D software renderer.
3   See an example of a Canvas-enabled UIElement in:
4       `dplug.flatwidgets.windowresizer.UIWindowResizer`
5 
6   This is a DPlug-specific rework of dg2d by Cerjones.
7   https://github.com/cerjones/dg2d
8   - removal of truetype functionnality (since covered by dplug:graphics)
9   - nothrow @nogc
10   - rework of the Canvas itself, to resemble more the HTML5 Canvas API
11   - Blitter delegate made explicit with a userData pointer
12   - added html color parsing
13   - no alignment requirements
14   - clipping is done with the ImageRef input
15   - added a few blendmodes
16   However a failure of this fork is that for transforms and stroke() support
17   you do need path abstraction in the end.
18 
19   dplug:canvas is pretty fast and writes 4 pixels at once.
20 
21   Bug: you can't use it on a widget that is full-size in your plugin.
22   
23   Copyright: Copyright Chris Jones 2020.
24   Copyright: Copyright Guillaume Piolat 2020.
25   License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
26 */
27 module dplug.canvas;
28 
29 import std.math: cos, sin, tan, PI;
30 
31 import dplug.core.vec;
32 import dplug.core.nogc;
33 import dplug.core.math;
34 
35 import dplug.graphics.color;
36 import dplug.graphics.image;
37 
38 import dplug.canvas.htmlcolors;
39 import dplug.canvas.gradient;
40 import dplug.canvas.colorblit;
41 import dplug.canvas.linearblit;
42 import dplug.canvas.ellipticalblit;
43 import dplug.canvas.rasterizer;
44 
45 
46 // dplug:canvas whole public API should live here.
47 
48 public import dplug.math.vector;
49 public import dplug.math.box;
50 
51 /// `dplug:canvas` operates on RGBA 8-bit buffers.
52 alias ImageDest = ImageRef!RGBA;
53 
54 /// How to fill pixels.
55 enum FillRule
56 {
57     /// Fill pixels whose scanline intersects a non-zero number of edges.
58     nonZero,
59 
60     /// Fill pixels whose scanline intersects an odd number of edges.
61     evenOdd
62 }
63 
64 /// How to composite against background (background is always considered opaque in dplug:canvas).
65 enum CompositeOperation
66 {
67     /// (default) Fastest mode, blend source over the background.
68     /// Note that this goes considerable faster than other blend modes.
69     /// This is because this mode skips holes instead of compositing it,
70     /// And traverse pixels only once.
71     sourceOver,
72 
73     /// Add source color (weighted by alpha) to background.
74     add,
75     lighter = add,///ditto
76 
77     /// Subtract source color (weighted by alpha) from background.
78     subtract,
79 
80     /// Choose maximum of color values. Do not mistake for "lighter" which is an add, not a max.
81     lighten,
82 
83     /// Choose minimum of color values.
84     darken,
85 }
86 
87 
88 /// A 2D Canvas able to render complex pathes into an `ImageRef!RGBA` buffer.
89 /// `Canvas` tries to follow loosely the HTML 5 Canvas API.
90 ///
91 /// See_also: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
92 ///
93 /// Important:
94 ///     * Only works on RGBA output.
95 ///     * There need at least 12 extra bytes between lines (`trailingSamples = 3` in `OwnedImage`).
96 ///       You can use OwnedImage to have that guarantee. See https://github.com/AuburnSounds/Dplug/issues/563
97 ///       For now, avoid full-UI controls that use a Canvas.
98 struct Canvas
99 {
100 public:
101 nothrow:
102 @nogc:
103 
104     /// Initialize the Canvas object with this target.
105     void initialize(ImageRef!RGBA imageDest)
106     {
107         _imageDest = imageDest;
108         _gradientUsed = 0;
109         fillStyle(RGBA(0, 0, 0, 255));
110 
111         int xmaxRounded4Up = (imageDest.w + 3) & 0xfffffffc;
112         assert((xmaxRounded4Up & 3) == 0);
113 
114         // This is a limitation of dplug:canvas
115         // Because this rasterizer writes to 4 pixels at once at all times,
116         // there need up to 3 extra samples (12 bytes) between lines.
117         // You can use OwnedImage to have that guarantee.
118         // Or you can avoid full-UI controls that use a Canvas.
119         assert(xmaxRounded4Up*4 <= imageDest.pitch);
120 
121         _stateStack.resize(1);
122         _stateStack[0].transform = Transform2D.identity();
123         _stateStack[0].fillRule = FillRule.nonZero;
124         _stateStack[0].compositeOp = CompositeOperation.sourceOver;
125     }
126 
127     ~this()
128     {
129         // delete all created gradients
130 
131         foreach(gradient; _gradients[])
132         {
133             destroyFree(gradient);
134         }
135     }
136 
137     @disable this(this);
138 
139     /// Set the fill style. The fill style can be a plain color fill, a `CanvasGradient`,
140     /// or an HTML-compatible text string.
141     void fillStyle(RGBA color)
142     {
143         uint color_as_uint = *cast(uint*)&color;
144         _plainColorBlit.init(color_as_uint);
145         _currentBlitter.userData = &_plainColorBlit;
146         _blitType = BlitType.color;
147     }
148     ///ditto
149     void fillStyle(const(char)[] htmlColorString)
150     {
151         string error;
152         RGBA rgba;
153         if (parseHTMLColor(htmlColorString, rgba, error))
154         {
155             fillStyle(rgba);
156         }
157         else
158             assert(false);
159     }
160     ///ditto
161     void fillStyle(CanvasGradient gradient)
162     {
163         final switch(gradient.type)
164         {
165             case CanvasGradient.Type.linear:
166                 _linearGradientBlit.init(gradient._gradient,
167                                          gradient.x0, gradient.y0, gradient.x1, gradient.y1);
168                 _currentBlitter.userData = &_linearGradientBlit;
169                 _blitType = BlitType.linear;
170                 break;
171 
172             case CanvasGradient.Type.elliptical:
173                 _ellipticalGradientBlit.init(gradient._gradient,
174                                              gradient.x0, gradient.y0, 
175                                              gradient.x1, gradient.y1, gradient.r2);
176                 _currentBlitter.userData = &_ellipticalGradientBlit;
177                 _blitType = BlitType.elliptical;
178                 break;
179 
180             case CanvasGradient.Type.radial:
181             case CanvasGradient.Type.angular:
182                 assert(false); // not implemented yet
183         }
184     }
185 
186     /// Set current fill rule.
187     void fillRule(FillRule rule)
188     {
189         _stateStack[$-1].fillRule = rule;
190     }
191     /// Get current fill rule.
192     FillRule fillRule()
193     {
194         return _stateStack[$-1].fillRule;
195     }
196 
197     ///Set global composite operation.
198     void globalCompositeOperation(CompositeOperation op)
199     {
200         _stateStack[$-1].compositeOp = op;
201     }
202     /// Get global composite operation.
203     CompositeOperation globalCompositeOperation()
204     {
205         return _stateStack[$-1].compositeOp;
206     }
207 
208 
209     // <PATH> functions
210 
211     /// Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path.
212     void beginPath()
213     {
214         int left = 0;
215         int top = 0;
216         int right = _imageDest.w;
217         int bottom = _imageDest.h;
218         _rasterizer.initialise(left, top, right, bottom);
219     }
220 
221     /// Adds a straight line to the path, going to the start of the current sub-path.
222     void closePath()
223     {
224         _rasterizer.closePath();
225     }
226 
227     /// Moves the starting point of a new sub-path to the (x, y) coordinates.
228     void moveTo(float x, float y)
229     {
230         vec2f pt = transformPoint(x, y);
231         _rasterizer.moveTo(pt.x, pt.y);
232     }
233     ///ditto
234     void moveTo(vec2f point)
235     {
236         moveTo(point.x, point.y);
237     }
238 
239     /// Connects the last point in the current sub-path to the specified (x, y) coordinates with a straight line.
240     /// If several points are provided, it is equivalent to consecutive single-point `lineTo` calls.
241     void lineTo(float x, float y)
242     {
243         vec2f pt = transformPoint(x, y);
244         _rasterizer.lineTo(pt.x, pt.y);
245     }
246     ///ditto
247     void lineTo(vec2f point)
248     {
249         lineTo(point.x, point.y);
250     }
251     ///ditto
252     void lineTo(vec2f[] points...) // an helper for chaining lineTo calls.
253     {
254         Transform2D M = currentTransform();
255         foreach(pt; points)
256         {
257             float fx = pt.x * M.a + pt.y * M.b + M.c;
258             float fy = pt.x * M.d + pt.y * M.e + M.f;
259             _rasterizer.lineTo(fx, fy);
260         }
261     }
262 
263     /// Adds a cubic Bezier curve to the current path.
264     void bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y)
265     {
266         vec2f cp1 = transformPoint(cp1x, cp1y);
267         vec2f cp2 = transformPoint(cp2x, cp2y);
268         vec2f pt = transformPoint(x, y);
269         _rasterizer.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, pt.x, pt.y);
270     }
271     ///ditto
272     void bezierCurveTo(vec2f controlPoint1, vec2f controlPoint2, vec2f dest)
273     {
274         bezierCurveTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, dest.x, dest.y);
275     }
276 
277     /// Adds a quadratic Bézier curve to the current path.
278     void quadraticCurveTo(float cpx, float cpy, float x, float y)
279     {
280         vec2f cp = transformPoint(cpx, cpy);
281         vec2f pt = transformPoint(x, y);
282         _rasterizer.quadTo(cp.x, cp.y, pt.x, pt.y);
283     }
284     ///ditto
285     void quadraticCurveTo(vec2f controlPoint, vec2f dest)
286     {
287         quadraticCurveTo(controlPoint.x, controlPoint.y, dest.x, dest.y);
288     }
289 
290     /// Add a rect to the current path.
291     void rect(float x, float y, float width, float height)
292     {
293         moveTo(x, y);
294         lineTo(x + width, y);
295         lineTo(x + width, y + height);
296         lineTo(x, y + height);
297         lineTo(x, y);
298     }
299     ///ditto
300     void rect(vec2f topLeftPoint, vec2f dimension)
301     {
302         rect(topLeftPoint.x, topLeftPoint.y, dimension.x, dimension.y);
303     }
304     ///ditto
305     void rect(box2f rectangle)
306     {
307         rect(rectangle.min.x, rectangle.min.y, rectangle.width, rectangle.height);
308     }
309     ///ditto
310     void rect(box2i rectangle)
311     {
312         rect(rectangle.min.x, rectangle.min.y, rectangle.width, rectangle.height);
313     }
314 
315     /// Adds an arc to the current path (used to create circles, or parts of circles).
316     void arc(float x, float y, float radius, float startAngle, float endAngle, bool anticlockwise = false)
317     {
318         assert(radius >= 0);
319 
320         // See https://github.com/AuburnSounds/Dplug/issues/468
321         // for the complexities of startAngle, endAngle, and clockwise things
322 
323         // "If anticlockwise is false and endAngle-startAngle is equal to or 
324         //  greater than 2π, or, if anticlockwise is true and startAngle-endAngle 
325         //  is equal to or greater than 2π, then the arc is the whole circumference 
326         //  of this ellipse, and the point at startAngle along this circle's
327         //  circumference, measured in radians clockwise from the ellipse's semi-major
328         //  axis, acts as both the start point and the end point."
329 
330         // "Otherwise, the points at startAngle and endAngle along this circle's
331         //  circumference, measured in radians clockwise from the ellipse's 
332         //  semi-major axis, are the start and end points respectively, and 
333         //  the arc is the path along the circumference of this ellipse from 
334         //  the start point to the end point, going anti-clockwise if 
335         //  anticlockwise is true, and clockwise otherwise. Since the points 
336         //  are on the ellipse, as opposed to being simply angles from zero, 
337         //  the arc can never cover an angle greater than 2π radians.
338         if (!anticlockwise)
339         {
340             float endMinusStart = endAngle - startAngle;
341             if (endMinusStart >= 2 * PI)
342                 endMinusStart = 2 * PI;
343             else
344             {
345                 endMinusStart = normalizePhase(endMinusStart);
346                 if (endMinusStart < 0) endMinusStart += 2 * PI;
347             }
348 
349             // Modify endAngle so that startAngle <= endAngle <= startAngle + 2 * PI
350             endAngle = startAngle + endMinusStart;
351             assert(endAngle >= startAngle);
352         }
353         else
354         {
355             float endMinusStart = endAngle - startAngle;
356             if (endMinusStart <= -2 * PI)
357                 endMinusStart = -2 * PI;
358             else
359             {
360                 endMinusStart = normalizePhase(endMinusStart);
361                 if (endMinusStart > 0) endMinusStart -= 2 * PI;
362             }
363 
364             // Modify endAngle so that startAngle >= endAngle >= startAngle - 2 * PI
365             endAngle = startAngle + endMinusStart;
366             assert(endAngle <= startAngle);
367         }
368 
369         // find tangential start point xt,yt
370         float xt = x + fast_cos(startAngle) * radius;
371         float yt = y + fast_sin(startAngle) * radius;
372 
373         // Make a line to there
374         lineTo(xt, yt);
375 
376         enum float MAX_ANGLE_FOR_SINGLE_BEZIER_CURVE = PI / 2.0;
377 
378         // From https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
379         // The optimal distance to the control points, in the sense that the 
380         // middle of the curve lies on the circle itself, is (4/3)*tan(pi/(2n)).
381 
382         float angleDiff = endAngle - startAngle;
383         if (startAngle == endAngle || angleDiff == 0)
384             return;
385         
386         // How many bezier curves will we draw?
387         // The angle will be evenly split between those parts.
388         // The 1e-2 offset is to avoid a circle made with 5 curves.
389         int numCurves = cast(int)(fast_ceil( (fast_fabs(angleDiff) - 1e-2f) / MAX_ANGLE_FOR_SINGLE_BEZIER_CURVE));
390         assert(numCurves >= 0);
391         if (numCurves == 0)
392             numCurves = 1;
393 
394         float currentAngle = startAngle;
395         float angleIncr = angleDiff / cast(float)numCurves;
396 
397         // Compute where control points should be placed
398         // How many segments does this correspond to for a full 2*pi circle?
399         float numCurvesIfThisWereACircle = (2.0f * PI * numCurves) / angleDiff;
400 
401         // Then compute optimal distance of the control points
402         float xx = cast(float)PI / (2.0f * numCurvesIfThisWereACircle);
403         float optimalDistance = (4.0f / 3.0f) * tan(xx);
404         optimalDistance *= radius;
405 
406         float angle0 = startAngle;
407         float cos0 = fast_cos(angle0);
408         float sin0 = fast_sin(angle0);
409 
410         // Using complex rotation here to save some cos/sin operations.
411         float phasorX = fast_cos(angleIncr); 
412         float phasorY = fast_sin(angleIncr);
413 
414         foreach(curve; 0..numCurves) 
415         {
416             float cos1 = cos0 * phasorX - sin0 * phasorY;
417             float sin1 = cos0 * phasorY + sin0 * phasorX;
418 
419             // compute end points of the curve
420             float x0 = x + cos0 * radius;
421             float y0 = y + sin0 * radius;
422             float x1 = x + cos1 * radius;
423             float y1 = y + sin1 * radius;
424 
425             // compute control points
426             float cp0x = x0 - sin0 * optimalDistance;
427             float cp0y = y0 + cos0 * optimalDistance;
428             float cp1x = x1 + sin1 * optimalDistance;
429             float cp1y = y1 - cos1 * optimalDistance;
430             bezierCurveTo(cp0x, cp0y, cp1x, cp1y, x1, y1);
431 
432             cos0 = cos1;
433             sin0 = sin1;
434         }
435     }
436     ///ditto
437     void arc(vec2f center, float radius, float startAngle, float endAngle, bool anticlockwise = false)
438     {
439         arc(center.x, center.y, radius, startAngle, endAngle, anticlockwise);
440     }
441 
442     /// Fills all subpaths of the current path using the current `fillStyle`.
443     /// Open subpaths are implicitly closed when being filled.
444     void fill()
445     {
446         closePath();
447 
448         // Select a particular blitter function here, depending on current state.
449         _currentBlitter.doBlit = getBlitFunction();
450 
451         CompositeOp op;
452         final switch(_stateStack[$-1].compositeOp)
453         {
454             case CompositeOperation.sourceOver: op = CompositeOp.SourceOver; break;
455             case CompositeOperation.add:        op = CompositeOp.Add; break;
456             case CompositeOperation.subtract:   op = CompositeOp.Subtract; break;
457             case CompositeOperation.lighten:    op = CompositeOp.LightenOnly; break;
458             case CompositeOperation.darken:     op = CompositeOp.DarkenOnly; break;
459         }
460 
461         _rasterizer.rasterize(cast(ubyte*) _imageDest.pixels,
462                               _imageDest.pitch,
463                               _imageDest.h,
464                               _currentBlitter,
465                               op);
466     }
467 
468     /// Fill a rectangle using the current `fillStyle`.
469     /// Note: affects the current path.
470     void fillRect(float x, float y, float width, float height)
471     {
472         beginPath();
473         rect(x, y, width, height);
474         fill();
475     }
476     ///ditto
477     void fillRect(vec2f topLeft, vec2f dimension)
478     {
479         fillRect(topLeft.x, topLeft.y, dimension.x, dimension.y);
480     }
481     ///ditto
482     void fillRect(box2f rect)
483     {
484         fillRect(rect.min.x, rect.min.y, rect.width, rect.height);
485     }
486     ///ditto
487     void fillRect(box2i rect)
488     {
489         fillRect(rect.min.x, rect.min.y, rect.width, rect.height);
490     }
491 
492     /// Fill a disc using the current `fillStyle`.
493     /// Note: affects the current path.
494     void fillCircle(float x, float y, float radius)
495     {
496         beginPath();
497         moveTo(x + radius, y);
498         arc(x, y, radius, 0, 2 * PI);
499         fill();
500     }
501     ///ditto
502     void fillCircle(vec2f center, float radius)
503     {
504         fillCircle(center.x, center.y, radius);
505     }
506 
507     // </PATH> functions
508 
509 
510     // <GRADIENT> functions
511 
512     /// Creates a linear gradient along the line given by the coordinates 
513     /// represented by the parameters.
514     CanvasGradient createLinearGradient(float x0, float y0, float x1, float y1)
515     {
516         // TODO: delay this transform upon point of use with CTM
517         vec2f pt0 = transformPoint(x0, y0);
518         vec2f pt1 = transformPoint(x1, y1);
519 
520         CanvasGradient result = newOrReuseGradient();
521         result.type = CanvasGradient.Type.linear;
522         result.x0 = pt0.x;
523         result.y0 = pt0.y;
524         result.x1 = pt1.x;
525         result.y1 = pt1.y;
526         return result;
527     }
528     ///ditto
529     CanvasGradient createLinearGradient(vec2f pt0, vec2f pt1)
530     {
531         return createLinearGradient(pt0.x, pt0.y, pt1.x, pt1.y);
532     }
533 
534 
535     /// Creates a circular gradient, centered in (x, y) and going from 0 to endRadius.
536     CanvasGradient createCircularGradient(float centerX, float centerY, float endRadius)
537     {
538         float x1 = centerX + endRadius;
539         float y1 = centerY;
540         float r2 = endRadius;
541         return createEllipticalGradient(centerX, centerY, x1, y1, r2);
542     }
543     ///ditto
544     CanvasGradient createCircularGradient(vec2f center, float endRadius)
545     {
546         return createCircularGradient(center.x, center.y, endRadius);
547     }
548 
549     /// Creates an elliptical gradient.
550     /// First radius is given by (x1, y1), second radius with a radius at 90° with the first one).
551     CanvasGradient createEllipticalGradient(float x0, float y0, float x1, float y1, float r2)
552     { 
553         // TODO: delay this transform upon point of use with CTM
554         vec2f pt0 = transformPoint(x0, y0);
555         vec2f pt1 = transformPoint(x1, y1);
556 
557         // Transform r2 radius
558         vec2f diff = vec2f(x1 - x0, y1 - y0).normalized; // TODO: this could crash with radius zero
559         vec2f pt2 = vec2f(x0 - diff.y * r2, y0 + diff.x * r2);
560         pt2 = transformPoint(pt2);
561         float tr2 = pt2.distanceTo(pt0);
562 
563         CanvasGradient result = newOrReuseGradient();
564         result.type = CanvasGradient.Type.elliptical;
565         result.x0 = pt0.x;
566         result.y0 = pt0.y;
567         result.x1 = pt1.x;
568         result.y1 = pt1.y;
569         result.r2 = tr2;
570         return result;
571     }
572     ///ditto
573     CanvasGradient createEllipticalGradient(vec2f pt0, vec2f pt1, float r2)
574     {
575         return createEllipticalGradient(pt0.x, pt0.y, pt1.x, pt1.y, r2);
576     }
577 
578     // </GRADIENT> functions
579 
580 
581     // <STATE> functions
582 
583     /// Save:
584     ///   - current transform
585     void save()
586     {
587         _stateStack ~= _stateStack[$-1]; // just duplicate current state
588     }
589 
590     /// Restores state corresponding to `save()`.
591     void restore()
592     {
593         _stateStack.popBack();
594         if (_stateStack.length == 0)
595             assert(false); // too many restore() without corresponding save()
596     }
597 
598     /// Retrieves the current transformation matrix.
599     Transform2D currentTransform()
600     {
601         return _stateStack[$-1].transform;
602     }
603     alias getTransform = currentTransform; ///ditto
604 
605     /// Adds a rotation to the transformation matrix. The angle argument represents 
606     /// a clockwise rotation angle and is expressed in radians.
607     void rotate(float angle)
608     {
609         float cosa = cos(angle);
610         float sina = sin(angle);
611         curMatrix() = curMatrix().scaleMulRot(cosa, sina);
612     }
613 
614     /// Adds a scaling transformation to the canvas units by x horizontally and by y vertically.
615     void scale(float x, float y)
616     {
617         curMatrix() = curMatrix().scaleMulOpt(x, y);
618     }
619     ///ditto
620     void scale(vec2f xy)
621     {
622         scale(xy.x, xy.y);
623     }
624     ///ditto
625     void scale(float xy)
626     {
627         scale(xy, xy);
628     }
629 
630     /// Adds a translation transformation by moving the canvas and its origin `x`
631     /// horizontally and `y` vertically on the grid.
632     void translate(float x, float y)
633     {
634         curMatrix() = curMatrix().translateMulOpt(x, y);
635     }
636     ///ditto
637     void translate(vec2f position)
638     {
639         translate(position.x, position.y);
640     }
641 
642     /// Multiplies the current transformation matrix with the matrix described by its arguments.
643     void transform(float a, float b, float c,
644                    float d, float e, float f)
645     {
646         curMatrix() *= Transform2D(a, c, e, 
647                                    b, d, f);
648     }
649 
650     void setTransform(float a, float b, float c,
651                       float d, float e, float f)
652     {
653         curMatrix() = Transform2D(a, c, e, 
654                                   b, d, f);
655     }
656 
657     ///ditto
658     void setTransform(Transform2D transform)
659     {
660         curMatrix() = transform;
661     }
662 
663     /// Changes the current transformation matrix to the identity matrix.
664     void resetTransform()
665     {
666         curMatrix() = Transform2D.identity();
667     }
668 
669     // </STATE>
670 
671 private:
672 
673     ImageRef!RGBA _imageDest;
674 
675     enum BrushStyle
676     {
677         plainColor
678     }
679 
680     enum BlitType
681     {
682         color, // Blit is a ColorBlit
683         linear,
684         elliptical
685     }
686 
687     Rasterizer   _rasterizer;
688 
689     Blitter _currentBlitter;
690     BlitType _blitType;
691 
692     // depends upon current fill rule and blit type.
693     auto getBlitFunction() pure
694     {
695         FillRule rule = _stateStack[$-1].fillRule;
696         bool nonZero = (rule == FillRule.nonZero);
697         final switch(_blitType)
698         {
699             case BlitType.color:
700                 return nonZero
701                           ? &doBlit_ColorBlit_NonZero
702                           : &doBlit_ColorBlit_EvenOdd;
703             case BlitType.linear:
704                 return nonZero
705                         ? &doBlit_LinearBlit_NonZero
706                         : &doBlit_LinearBlit_EvenOdd;
707             case BlitType.elliptical:
708                 return nonZero
709                         ? &doBlit_EllipticalBlit_NonZero
710                         : &doBlit_EllipticalBlit_EvenOdd;
711         }
712 
713     }
714 
715 
716     // Blitters (only one used at once)
717     union
718     {
719         ColorBlit _plainColorBlit;
720         LinearBlit _linearGradientBlit;
721         EllipticalBlit _ellipticalGradientBlit;
722     }
723 
724     // Gradient cache
725     // You're expected to recreate gradient in draw code.
726     int _gradientUsed; // number of gradients in _gradients in active use
727     Vec!CanvasGradient _gradients; // all gradients here are created on demand, 
728                                    // and possibly reusable after `initialize`
729 
730     CanvasGradient newOrReuseGradient()
731     {
732         if (_gradientUsed < _gradients.length)
733         {
734             _gradients[_gradientUsed].reset();
735             return _gradients[_gradientUsed++];
736         }
737         else
738         {
739             CanvasGradient result = mallocNew!CanvasGradient();
740             _gradients.pushBack(result);
741             return result;
742         }
743     }
744 
745     // State stack.
746     // Current state is the last element.
747     Vec!State _stateStack;
748 
749     // What is saved by `save`.
750     struct State
751     {
752         Transform2D transform;
753         FillRule fillRule;
754         CompositeOperation compositeOp;
755     }
756 
757     ref Transform2D curMatrix()
758     {
759         return _stateStack[$-1].transform;
760     }
761 
762     vec2f transformPoint(float x, float y)
763     {
764         Transform2D M = currentTransform();
765         float fx = x * M.a + y * M.b + M.c;
766         float fy = x * M.d + y * M.e + M.f;
767         return vec2f(fx, fy);
768     }
769 
770     vec2f transformPoint(vec2f pt)
771     {
772         return transformPoint(pt.x, pt.y);
773     }
774 }
775 
776 /// Holds both gradient data/table and positioning information.
777 ///
778 /// You can create a gradient with `createLinearGradient`, `createCircularGradient`, or `createEllipticalGradient`,
779 /// every frame.
780 /// Then use `addColorStop` to set the color information.
781 ///
782 /// The gradient data is managed  by the `Canvas` object itself. All gradients are invalidated once
783 /// `initialize` has been called.
784 class CanvasGradient
785 {
786 public:
787 nothrow:
788 @nogc:
789 
790     this()
791     {
792         _gradient = mallocNew!Gradient();
793     }
794 
795     /// Adds a new color stop, defined by an offset and a color, to a given canvas gradient.
796     void addColorStop(float offset, RGBA color)
797     {
798         uint color_as_uint = *cast(uint*)(&color);
799         _gradient.addStop(offset, color_as_uint);
800     }
801 
802 package:
803 
804     enum Type
805     {
806         linear,
807         radial,
808         elliptical,
809         angular,
810     }
811 
812     Type type;
813 
814     void reset()
815     {
816         _gradient.reset();
817     }
818 
819     float x0, y0, x1, y1, r2;
820     Gradient _gradient;
821 }
822 
823 /// The transform type used by dplug:canvas. It's a 3x3 float matrix
824 /// with the form:
825 /// (a b c)
826 /// (d e f)
827 /// (0 0 1)
828 struct Transform2D
829 {
830 pure nothrow @nogc:
831     float a = 1, b = 0, c = 0, 
832           d = 0, e = 1, f = 0;
833 
834     static Transform2D identity()
835     {
836         return Transform2D.init;
837     }
838 
839     void opOpAssign(string op)(Transform2D o) if (op == "*")
840     {
841         //          a  b  c
842         //          d  e  f
843         //          0  0  1
844         // a  b  c  A  B  C
845         // d  e  f  D  E  F
846         // 0  0  1  0  0  1
847         
848         float A = a * o.a + b * o.d;
849         float B = a * o.b + b * o.e;
850         float C = a * o.c + b * o.f + c;
851         float D = d * o.a + e * o.d;
852         float E = d * o.b + e * o.e;
853         float F = d * o.c + e * o.f + f;
854         this = Transform2D(A, B, C, D, E, F);
855     }
856 
857     /// Return this * Transform2D(1, 0, x,
858     ///                           0, 1, y);
859     Transform2D translateMulOpt(float x, float y)
860     {
861         //           1  0  x
862         //           0  1  y
863         //           0  0  1
864         //           -------
865         // a  b  c | a  b  C
866         // d  e  f | d  e  F
867         // 0  0  1 | 0  0  1
868         float C = a * x + b * y + c;
869         float F = d * x + e * y + f;
870         return Transform2D(a, b, C, d, e, F);
871     }
872 
873     /// Return this * Transform2D(x, 0, 0,
874     ///                           0, y, 0);
875     Transform2D scaleMulOpt(float x, float y)
876     {
877         //           x  0  0
878         //           0  y  0
879         //           0  0  1
880         //           -------
881         // a  b  c | A  B  c
882         // d  e  f | D  E  f
883         // 0  0  1 | 0  0  1
884         float A = x * a;
885         float B = y * b;
886         float D = x * d;
887         float E = y * e;
888         return Transform2D(A, B, c, D, E, f);
889     }
890 
891 
892     /// Return this * Transform2D(cosa, -sina, 0,
893     ///                           sina, cosa, 0)
894     Transform2D scaleMulRot(float cosa, float sina)
895     {
896         //           g -h  0
897         //           h  g  0
898         //           0  0  1
899         //           -------
900         // a  b  c | A  B  c
901         // d  e  f | D  E  f
902         // 0  0  1 | 0  0  1
903         float A = cosa * a + sina * b;
904         float B = cosa * b - sina * a;
905         float D = cosa * d + sina * e;
906         float E = cosa * e - sina * d;
907         return Transform2D(A, B, c, 
908                            D, E, f);
909     }
910 }