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