1 /**
2   This module contains the Canvas and Paint types.
3 
4   Copyright Chris Jones 2020.
5   Distributed under the Boost Software License, Version 1.0.
6   See accompanying file Licence.txt or copy at...
7   https://www.boost.org/LICENSE_1_0.txt
8 */
9 
10 module dg2d.canvas;
11 
12 import dg2d.gradient;
13 import dg2d.rasterizer;
14 import dg2d.path;
15 import dg2d.pathiterator;
16 import dg2d.misc;
17 import dg2d.gradient;
18 import dg2d.font;
19 import dg2d.rect;
20 
21 /*
22   Thoughts...
23 
24   Paint : Defines color at given pixel so base color, gradient, pattern and
25     repeat mode etc...
26 
27   For Paint have single struct paints so...
28 
29   canvas.fill(path,LinearGradient(a,b,c,d,etc),style)
30 
31   So that can be done without there needing to be an allocation. Also have
32   a polymorphic paint type that can be anything, so it wraps those structs
33   into one.
34 
35   Style : More about geometry so, winding rule, stroke or fill. End caps,
36    joins etc..
37 
38   Style should be lightweight and there should be a collection of functions
39   that provide typical defaults. IE..
40 
41   canvas.fill(path,LinearGradient(a,b,c,d,etc),StyleFillEO());
42   canvas.fill(path,LinearGradient(a,b,c,d,etc),StyleStroke(4.0));
43 
44   Style: For now just passing WindingRule as that's all thats actually implemented
45 
46 */
47 
48 /**
49   Linear gradient type
50 */
51 
52 struct LinearGradient
53 {
54     float x0,y0,x1,y1;
55     Gradient gradient;
56     RepeatMode rmode;
57 }
58 
59 /**
60   Radial gradient type
61 */
62 
63 struct RadialGradient
64 {
65     float x0,y0,x1,y1,x2,y2;
66     Gradient gradient;
67     RepeatMode rmode;
68 }
69 
70 /**
71   Angular gradient type
72 */
73 
74 struct AngularGradient
75 {
76     float x0,y0,x1,y1,x2,y2,repeats;
77     Gradient gradient;
78     RepeatMode rmode;
79 }
80 
81 /**
82   Biradial gradient type
83 */
84 
85 struct BiradialGradient
86 {
87     float x0,y0,r0,x1,y1,r1;
88     Gradient gradient;
89     RepeatMode rmode;
90 }
91 
92 /** 
93   Generic paint type
94 */
95 
96 struct Paint
97 {
98     this(uint color)
99     {
100         this.color = color;
101         this.type = PaintType.color;
102     }
103     this(LinearGradient linear)
104     {
105         this.linear = linear;
106         this.type = PaintType.linear;
107     }
108     this(RadialGradient radial)
109     {
110         this.radial = radial;
111         this.type = PaintType.radial;
112     }
113     this(AngularGradient angular)
114     {
115         this.angular = angular;
116         this.type = PaintType.angular;
117     }
118     this(BiradialGradient biradial)
119     {
120         this.biradial = biradial;
121         this.type = PaintType.biradial;
122     }
123 private:
124     union
125     {
126         uint color;
127         LinearGradient linear;
128         RadialGradient radial;
129         AngularGradient angular;
130         BiradialGradient biradial;
131     }
132     enum PaintType
133     {
134         color, linear, radial, angular, biradial
135     }
136     PaintType type;
137 }
138 
139 enum isPaintable(T) = (is(T == uint) || is(T == Paint) || is(T == LinearGradient)
140      || is(T == RadialGradient) || is(T == AngularGradient) || is(T == BiradialGradient));
141 
142 /*
143   Canvas class, provices a user freindly pixel buffer and drawing functions.
144 */
145 
146 class Canvas
147 {
148     /** Constructor */
149 
150     this(int width, int height)
151     {
152         resize(width, height);
153         m_rasterizer = new Rasterizer;
154     }
155 
156     /** Destructor */
157 
158     ~this()
159     {
160         dg2dFree(m_pixels);
161     }
162 
163     /**
164       Set the size in pixels. 
165     
166       Note that pixels will be garbage afterwards and clip/view state will be reset.
167     */
168 
169     void resize(int width, int height)
170     {
171         m_stride = roundUpTo(width,4);
172         m_pixels = dg2dRealloc(m_pixels, m_stride*height);
173         m_width = width;
174         m_height = height;
175         m_view = IRect(0,0,width,height);
176         m_clip = m_view;
177     }
178 
179     /** width of canvas */
180 
181     int width()
182     {
183          return m_width;
184     }
185 
186     /** height of canvas */
187 
188     int height()
189     {
190          return m_height;
191     }
192 
193     /** stride is the length of each scanline in pixels. */
194 
195     int stride()
196     {
197          return m_stride;
198     }
199 
200     /** returns a pointer to the raw pixels in memory */
201 
202     uint* pixels()
203     {
204         return m_pixels;
205     }
206 
207     /** Fill the the current viewport with the specified paint. */
208 
209     void fill(T)(T paint)
210         if (isPaintable!T)
211     {
212         draw(Rect(0,0,m_width,m_height).asPath, paint, WindingRule.NonZero);
213     }
214 
215     /** Fill the rectangle with the specified paint. */
216 
217     void fill(T)(T paint, IRect rect)
218         if (isPaintable!T)
219     {
220         draw(Rect!float(rect.left,rect.top,rect.right,rect.bottom).asPath,
221             paint, WindingRule.NonZero);
222     }
223 
224     /** Fill the rectangle with the specified paint. */
225 
226     void fill(T)(T paint, int x0, int y0, int x1, int y1)
227        if (isPaintable!T)
228     {
229         draw(Rect!float(x0,y0,x1,y1).asPath, paint, WindingRule.NonZero);
230     }
231 
232     /** Draw the path with the specified paint */
233     
234     void draw(T)(auto ref T path, Paint paint, WindingRule wr)
235         if (isPathIterator!T)
236     {
237         if (m_clip.isEmpty) return;
238 
239         switch (paint.type)
240         {
241             case PaintType.color:
242                 draw(path, paint.color, wr);
243                 break;
244             case PaintType.linearGradient:
245                 draw(path, paint.linear, wr);
246                 break;               
247             case PaintType.radialGradient:
248                 draw(path, paint.radial, wr);
249                 break;
250             case PaintType.angularGradient:
251                 draw(path, paint.angular, wr);
252                 break;
253             case PaintType.biradialGradient:
254                 draw(path, paint.biradial, wr);
255                 break;
256             default:
257                assert(0);
258         }
259     }
260 
261     /** Draw the path with the specified color and winding rule */
262 
263     void draw(T)(auto ref T path, uint color, WindingRule wrule)
264         if (isPathIterator!T)
265     {
266         import dg2d.colorblit;
267         if (m_clip.isEmpty) return;
268         m_rasterizer.initialise(m_clip);
269         m_rasterizer.addPath2(path.offset(m_view.left,m_view.top));
270         auto colblit = ColorBlit(m_pixels,m_stride,m_height);
271         colblit.setColor(color);
272         m_rasterizer.rasterize(colblit.getBlitFunc(wrule));
273     }
274 
275     /** Draw the path with a linear gradient */
276 
277     void draw(T)(auto ref T path, LinearGradient lingrad, WindingRule wrule)
278         if (isPathIterator!T)
279     {
280         import dg2d.linearblit;
281         if (m_clip.isEmpty) return;
282         m_rasterizer.initialise(m_clip);
283         m_rasterizer.addPath2(path.offset(m_view.left,m_view.top));
284         auto linblit = LinearBlit(m_pixels, m_stride, m_height);
285         linblit.setPaint(lingrad.gradient, wrule, lingrad.rmode);
286         linblit.setCoords(m_view.left+lingrad.x0, m_view.top+lingrad.y0,
287             m_view.left+lingrad.x1, m_view.top+lingrad.y1);
288         m_rasterizer.rasterize(linblit.getBlitFunc);
289     }
290 
291     /** Draw the path with a radial gradient */
292 
293     void draw(T)(auto ref T path, RadialGradient radgrad, WindingRule wrule)
294         if (isPathIterator!T)
295     {
296         import dg2d.radialblit;
297         if (m_clip.isEmpty) return;
298         m_rasterizer.initialise(m_clip);
299         m_rasterizer.addPath2(path.offset(m_view.left,m_view.top));
300         auto radblit = RadialBlit(m_pixels, m_stride, m_height);
301         radblit.setPaint(radgrad.gradient, wrule, radgrad.rmode);
302         radblit.setCoords(m_view.left+radgrad.x0, m_view.top+radgrad.y0,
303          m_view.left+radgrad.x1, m_view.top+radgrad.y1,m_view.left+radgrad.x2, m_view.top+radgrad.y2);
304         m_rasterizer.rasterize(radblit.getBlitFunc);
305     }
306 
307     /** Draw the path with an angular gradient */
308 
309     void draw(T)(auto ref T path, AngularGradient angrad, WindingRule wrule)
310         if (isPathIterator!T)
311     {
312         import dg2d.angularblit;
313         if (m_clip.isEmpty) return;
314         m_rasterizer.initialise(m_clip);
315         m_rasterizer.addPath2(path.offset(m_view.left,m_view.top));
316         auto anblit = AngularBlit(m_pixels, m_stride, m_height);
317         anblit.setPaint(angrad.gradient, wrule, angrad.rmode, angrad.repeats);
318         anblit.setCoords(m_view.left+angrad.x0, m_view.top+angrad.y0,
319          m_view.left+angrad.x1, m_view.top+angrad.y1,m_view.left+angrad.x2, m_view.top+angrad.y2);
320         m_rasterizer.rasterize(anblit.getBlitFunc);
321     }
322 
323     /** Draw the path with a biradial gradient */
324 
325     void draw(T)(auto ref T path, BiradialGradient bigrad, WindingRule wrule)
326         if (isPathIterator!T)
327     {
328         import dg2d.biradialblit;
329         if (m_clip.isEmpty) return;
330         m_rasterizer.initialise(m_clip);
331         m_rasterizer.addPath2(path.offset(m_view.left,m_view.top));
332         auto biblit = BiradialBlit(m_pixels, m_stride, m_height);
333         biblit.setPaint(bigrad.gradient, wrule, bigrad.rmode);
334         biblit.setCoords(m_view.left+bigrad.x0, m_view.top+bigrad.y0,
335             bigrad.r0, m_view.left+bigrad.x1, m_view.top+bigrad.y1, bigrad.r1);
336         m_rasterizer.rasterize(biblit.getBlitFunc);
337     }
338     
339     /* text stuff needs a lot of work, was just to get it working 
340     for now to see if I could parse font correctly */
341 
342     void drawText(float x, float y, string txt, Font font, uint color)
343     {
344         if (m_clip.isEmpty) return;
345 
346         m_tmppath.reset();
347 
348         for (int i = 0; i < txt.length; i++)
349         {
350             x += font.addChar(m_tmppath, x, y, txt[i]);
351         }
352         draw(m_tmppath, color, WindingRule.NonZero);  
353     }
354 
355     /** Gets the current clip and viewport state */
356 
357     ViewState getViewState()
358     {
359         return ViewState(m_view,m_clip);
360     }
361 
362     /** Set the viewport.
363     
364     Sets a view port rectangle. This new viewport is set relative to the current
365     viewport. All drawing operations are offset to the new viewport.
366 
367     The clip rectangle is shrunk to the intersection of the new viewport and the
368     current clip rectangle.
369     */
370 
371     void setView(int x0, int y0, int x1, int y1)
372     {
373         m_view.right = m_view.left + x1;
374         m_view.bottom = m_view.top + y1;
375         m_view.left = m_view.left + x0;
376         m_view.top = m_view.top + y0;
377         m_clip = intersect(m_view, m_clip);
378     }
379 
380     /** Set the viewport relative to a specific ViewState
381     
382     Sets a view port rectangle. This new viewport is set relative to the specified
383     ViewState. All drawing operations are offset to the viewport.
384 
385     The clip rectangle is shrunk to the intersection of the new viewport and the
386     current clip rectangle.
387     */
388 
389     void setView(ref ViewState state, int x0, int y0, int x1, int y1)
390     {
391         m_view.left = state.view.left + x0;
392         m_view.top = state.view.top + y0;
393         m_view.right = state.view.left + x1;
394         m_view.bottom = state.view.top + y1;
395         m_clip = intersect(m_view, state.clip);
396     }
397 
398     /** Reset the to previous viewport and clip state */
399 
400     void resetState(ref ViewState state)
401     {
402         m_view = state.view;
403         m_clip = state.clip;
404     }
405 
406     /** Is there any valid region we can draw on? */
407 
408     bool isClipEmpty()
409     {
410         return m_clip.isEmpty;
411     }
412 
413     /** Set the clip rectangle
414     
415       The clip is set relative to the current viewport.
416     */  
417 
418     void setClip(int x0, int y0, int x1, int y1)
419     {
420         m_clip = intersect(m_clip, offset(IRect(x0,y0,x1,y1),m_view.left,m_view.top));
421     }
422 
423     /** Completely reset the viewport and clip
424     
425     Resets to the full bounds of the canvas.
426     */ 
427 
428     void resetView()
429     {
430         m_view = IRect(0,0,m_width,m_height);
431         m_clip = m_view;
432     }
433 
434 private:
435 
436     uint*        m_pixels;
437     int          m_width;
438     int          m_height;
439     int          m_stride;
440     IRect        m_view;
441     IRect        m_clip;
442     Rasterizer   m_rasterizer;
443     Path         m_tmppath;     // workspace
444 }
445 
446 /** Viewport and clip rectangle */
447 
448 struct ViewState
449 {
450 private:
451     IRect view;
452     IRect clip;
453 }
454