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 }