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 }