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