1 /**
2   This module contains the colour gradient class.  
3 
4   Copyright: Chris Jones
5   License: Boost Software License, Version 1.0
6   Authors: Chris Jones
7 */
8 
9 module dg2d.gradient;
10 
11 import dg2d.misc;
12 
13 /**
14   Colour Gradient class. 
15   
16   The colour gradient is defined by a list of colour stops where each stop specifies
17   the colour at a given position along the gradient axis. It maintains a lookup
18   table that is used by the rasterizer.
19 */
20 
21 class Gradient
22 {
23     // colour is 32 bit ARGB, pos runs from 0..1 
24 
25 private:
26 
27     struct ColorStop
28     {
29         uint  color;
30         float pos;
31     }
32 
33 public:
34 
35     /** Create an empty colour gradient, you can specify the size of the lookuptable */    
36 
37     this(int lookupLength = 512)
38     {
39         setLookupLength(lookupLength);
40     }
41 
42     /** destructor */
43 
44     ~this()
45     {
46         dg2dFree(m_stops);
47         dg2dFree(m_lookup);
48     }
49 
50     /** How many colour stops are there? */
51 
52     uint length()
53     {
54         return m_stopsLength;
55     }
56 
57     /** reset the list of gradient colour stops to empty */
58 
59     void reset()
60     {
61         m_stopsLength = 0;
62         m_changed = true;
63         m_isOpaque = true;
64     }
65 
66     /** get lookup table, this can cause the lookup table to be recomputed if anything
67     significant has changed. */
68 
69     uint[] getLookup()
70     {
71         if (m_changed) initLookup();
72         return m_lookup[0..m_lookupLength];
73     }
74 
75     /** get lookup table length */
76 
77     int lookupLength()
78     {
79         return m_lookupLength;
80     }
81 
82     /** change lookup table length (8192 max) */
83 
84     void setLookupLength(int len)
85     {
86         if (m_lookupLength == len) return;
87         len = roundUpPow2(clip(len,2,8192));
88         m_lookup = dg2dRealloc(m_lookup, len);
89         m_lookupLength = len;
90         m_changed = true;
91     }
92 
93     /** add a color stop, pos will be cliped to 0..1 */
94 
95     Gradient addStop(float pos, uint color)
96     {
97         if (m_stopsLength == m_stopsCapacity)
98         {
99             uint newcap = roundUpPow2((m_stopsCapacity*2)|31);
100             m_stops = dg2dRealloc(m_stops, newcap);
101             m_stopsCapacity = newcap;
102         }
103         m_stops[m_stopsLength].color = color;
104         m_stops[m_stopsLength].pos = clip(pos,0.0f,1.0f);
105         m_stopsLength++;
106         m_isOpaque = m_isOpaque & ((color >> 24) == 0xFF);
107         m_changed = true;
108         return this;
109     }
110 
111     /** is the gradient fully opaque? */
112 
113     bool isOpaque()
114     {
115         return m_isOpaque;
116     }
117 
118     /** 
119       Initialises the gradient to a sequence of equaly spaced colours.
120         
121       The colours are spaced along the gradient with the first at position 0 and
122       the last positioned at 1.
123     */
124 
125     void initEqualSpaced(T...)(T args)
126     {
127         reset();
128         float len = args.length-1;
129         foreach(i, a; args) addStop(i/len, args[i]);
130     }
131 
132 private:
133 
134     void initLookup()
135     {       
136         import std.algorithm : sort;
137         m_stops[0..m_stopsLength].sort!("a.pos < b.pos")();
138         
139         if (m_stopsLength == 0)
140         {
141             m_lookup[0..m_lookupLength] = 0;
142         }
143         else if (m_stopsLength == 1)
144         {
145             m_lookup[0..m_lookupLength] = m_stops[0].color;
146         }
147         else
148         {         
149             
150             int start = cast(int) (m_stops[0].pos*m_lookupLength);
151             m_lookup[0..start] = m_stops[0].color;
152 
153             foreach(i; 1..m_stopsLength)
154             {
155                 int end = cast(int) (m_stops[i].pos*m_lookupLength);
156                 fillGradientArray(m_lookup[start..end], m_stops[i-1].color, m_stops[i].color);
157                 start = end;
158             }         
159 
160             m_lookup[start..m_lookupLength] = m_stops[m_stopsLength-1].color;
161         }
162 
163         m_changed = false;
164     }
165 
166     ColorStop* m_stops;
167     uint m_stopsLength;
168     uint m_stopsCapacity;
169     uint* m_lookup;
170     uint m_lookupLength;
171     bool m_changed;
172     bool m_isOpaque = true;
173 }
174 
175 // Fill an array of uints with a linearly interpolated color gradient
176 
177 private:
178 
179 void fillGradientArray(uint[] array, uint color1, uint color2)
180 {
181     if (array.length == 0) return;
182 
183     immutable __m128i XMZERO = 0;
184 
185     __m128i c0 = _mm_loadu_si32 (&color1);
186     c0 = _mm_unpacklo_epi8 (c0, XMZERO);
187     __m128i c1 = _mm_loadu_si32 (&color2);
188     c1 = _mm_unpacklo_epi8 (c1, XMZERO);
189 
190     uint x;
191     uint delta = 0x10000000 / cast(uint) array.length;
192 
193     array[0] = color1;
194 
195     foreach (i; 1..array.length)
196     {
197         x += delta;
198         __m128i pos = _mm_set1_epi16(cast(ushort) (x >> 12));
199 		
200         __m128i tmp0 = _mm_mulhi_epu16 (c0,pos);
201         __m128i tmp1 = _mm_mulhi_epu16 (c1,pos);
202         __m128i r = _mm_subs_epi16(_mm_adds_epi16(c0, tmp1), tmp0);
203 
204         array[i] = _mm_cvtsi128_si32 ( _mm_packus_epi16(r,r) );
205     }
206 }
207 
208 
209