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         sortStopsInPlace(m_stops[]);
77 
78         if (m_stops.length == 0)
79         {
80             foreach(ref c; m_lookup) c = 0;
81         }
82         else if (m_stops.length == 1)
83         {
84             foreach(ref c; m_lookup) c = m_stops[0].color;
85         }
86         else
87         {           
88             int idx = cast(int) (m_stops[0].pos*lookupLen);
89             m_lookup[0..idx] = m_stops[0].color;
90 
91             foreach(size_t i; 1.. m_stops.length)
92             {
93                 int next = cast(int) (m_stops[i].pos*lookupLen);
94 
95                 foreach(int j; idx..next)
96                 {
97                     uint a = (256*(j-idx))/(next-idx);
98                     uint c0 = m_stops[i-1].color;
99                     uint c1 = m_stops[i].color;
100                     uint t0 = (c0 & 0xFF00FF)*(256-a) + (c1 & 0xFF00FF)*a;
101                     uint t1 = ((c0 >> 8) & 0xFF00FF)*(256-a) + ((c1 >> 8) & 0xFF00FF)*a;
102                     m_lookup[j] = ((t0 >> 8) & 0xFF00FF) | (t1 & 0xFF00FF00);
103                 }
104                 idx = next;
105             }
106             m_lookup[idx..$] = m_stops[$-1].color;
107         }
108         m_changed = false;
109     }
110 
111     Vec!ColorStop m_stops;
112     uint[lookupLen] m_lookup;
113     bool m_changed = true;
114 
115     static void sortStopsInPlace(ColorStop[] stops)
116     {    
117         size_t i = 1;
118         while (i < stops.length)
119         {
120             size_t j = i;
121             while (j > 0 && (stops[j-1].pos > stops[j].pos))
122             {
123                 ColorStop tmp = stops[j-1];
124                 stops[j-1] = stops[j];
125                 stops[j] = tmp;
126                 j = j - 1;
127             }
128             i = i + 1;
129         }
130     }
131 }
132 
133 unittest
134 {
135     Gradient.ColorStop[3] stops = [Gradient.ColorStop(0xff0000, 1.0f),
136                                    Gradient.ColorStop(0x00ff00, 0.4f),
137                                    Gradient.ColorStop(0x0000ff, 0.0f)];
138     Gradient.sortStopsInPlace(stops[]);
139     assert(stops[0].pos == 0.0f);
140     assert(stops[1].pos == 0.4f);
141     assert(stops[2].pos == 1.0f);
142 
143     Gradient.sortStopsInPlace([]);
144     Gradient.sortStopsInPlace(stops[0..1]);
145 }