1 /**
2 * Implement the gradient class. dplug:canvas internals.
3 *
4 * Copyright: Copyright Chris Jones 2020.
5 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module dplug.canvas.gradient;
8 
9 import dplug.core.nogc;
10 import dplug.core.vec;
11 import dplug.canvas.misc;
12 import dplug.graphics.color;
13 
14 /*
15   Gradient class, 
16   The gradient is defined as a list of colours and positions (known as stops) along
17   a single dimension from 0 to 1. 
18   It has a precomputed lookup table for the rasterizer, currently fixed at 256
19   entries. Its a "just get it working for now" solution tbh
20 */
21 class Gradient
22 {
23 nothrow:
24 @nogc:
25     // colour is 32 bit ARGB, pos runs from 0..1 
26 
27     struct ColorStop
28     {
29         uint  color;
30         float pos;
31     }
32 
33     size_t length()
34     {
35         return m_stops.length;
36     }
37 
38     bool hasChanged()
39     {
40         return m_changed;
41     }
42 
43     void reset()
44     {
45         m_stops.clearContents();
46         m_changed = true;
47     }
48 
49     Gradient addStop(float pos, uint color)
50     {
51         m_stops.pushBack(ColorStop(color,clip(pos,0.0,1.0)));
52         m_changed = true;
53         return this;
54     }
55 
56     uint[] getLookup()
57     {
58         if (m_changed) initLookup();
59         return m_lookup[0..lookupLen];
60     }
61 
62     int lutLength()
63     {
64         return lookupLen;
65     }
66     
67 private:
68     
69     // fixed size lookup for now, could probably have lookup tables cached
70     // by the rasterizer rather than stuck in here/
71 
72     enum lookupLen = 256;
73 
74     void initLookup()
75     {
76         import std.algorithm : sort;
77         m_stops[].sort!("a.pos < b.pos")();
78         
79         if (m_stops.length == 0)
80         {
81             foreach(ref c; m_lookup) c = 0;
82         }
83         else if (m_stops.length == 1)
84         {
85             foreach(ref c; m_lookup) c = m_stops[0].color;
86         }
87         else
88         {           
89             int idx = cast(int) (m_stops[0].pos*lookupLen);
90             m_lookup[0..idx] = m_stops[0].color;
91 
92             foreach(size_t i; 1.. m_stops.length)
93             {
94                 int next = cast(int) (m_stops[i].pos*lookupLen);
95 
96                 foreach(int j; idx..next)
97                 {
98                     uint a = (256*(j-idx))/(next-idx);
99                     uint c0 = m_stops[i-1].color;
100                     uint c1 = m_stops[i].color;
101                     uint t0 = (c0 & 0xFF00FF)*(256-a) + (c1 & 0xFF00FF)*a;
102                     uint t1 = ((c0 >> 8) & 0xFF00FF)*(256-a) + ((c1 >> 8) & 0xFF00FF)*a;
103                     m_lookup[j] = ((t0 >> 8) & 0xFF00FF) | (t1 & 0xFF00FF00);
104                 }
105                 idx = next;
106             }
107             m_lookup[idx..$] = m_stops[$-1].color;
108         }
109         m_changed = false;
110     }
111 
112     Vec!ColorStop m_stops;
113     uint[lookupLen] m_lookup;
114     bool m_changed = true;
115 }
116