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