1 /**
2  * This module contains the implementation for the internal pixel storage
3  * mechanisms.
4  */
5 module daffodil.pixel;
6 
7 import std.conv;
8 import std.array;
9 import std.format;
10 import std.traits;
11 import std.algorithm;
12 
13 import daffodil.colorspace;
14 
15 /**
16  * The storage struct for a color.
17  */
18 struct Pixel(V) if (isColorValue!V) {
19     /// The type used to store individual values for a color
20     alias Value = V;
21 
22     /// The values of the color
23     Value[] values;
24 
25     /// The color space used for operations with the color
26     const ColorSpace* colorSpace;
27 
28     alias values this;
29 
30     ///
31     this(Value[] values, const ColorSpace* colorSpace) {
32         this.values = values;
33         this.colorSpace = colorSpace;
34     }
35 
36     /// Ditto
37     static if (!is(V == real)) {
38         this(real[] values, const ColorSpace* colorSpace) {
39             this(values.toColorValues!V, colorSpace);
40         }
41     }
42 
43     /// Ditto
44     this(size_t size, const ColorSpace* colorSpace) {
45         this(new Value[size], colorSpace);
46     }
47 
48     // TODO: Memory optimisation
49 
50     ///
51     Pixel!V opBinary(string op : "*")(const real other) const {
52         real[] target = new real[this.length];
53         colorSpace.opScalarMul(values.toReals, other, target);
54         return Pixel!V(target, colorSpace);
55     }
56 
57     ///
58     Pixel!V opBinary(string op : "+")(const Pixel!V other) const {
59         // TODO: Check other.colorSpace
60         real[] target = new real[this.length];
61         colorSpace.opColorAdd(values.toReals, other.values.toReals, target);
62         return Pixel!V(target, colorSpace);
63     }
64 
65     ///
66     void opOpAssign(string op : "*")(const real other) {
67         colorSpace.opScalarMul(values, other, values);
68     }
69 
70     ///
71     void opOpAssign(string op : "+")(const Pixel!V other) {
72         colorSpace.opColorAdd(values, other, values);
73     }
74 
75     ///
76     void opAssign(const Pixel!V other) {
77         assert(other.length == this.length);
78         foreach (index; 0..this.length) {
79             this[index] = other[index];
80         }
81     }
82 
83     /// Clear all the color values to 0
84     void clear() {
85         foreach (index; 0..this.length) {
86             this[index] = 0;
87         }
88     }
89 
90     /// Return a duplicate color in the same color space
91     @property auto dup() {
92         return Pixel!V(values.dup, colorSpace);
93     }
94 }
95 
96 /**
97  * Template for checking whether a type is a valid color value. Color values are
98  * what daffodil stores internally.
99  *
100  * Any floating point type, unsigned integreal or valid
101  * :d:func:`isCustomColorValue` is a valid color value.
102  */
103 template isColorValue(V) {
104     enum isColorValue = isFloatingPoint!V ||
105                         isIntegral!V && isUnsigned!V ||
106                         isCustomColorValue!V;
107 }
108 
109 ///
110 @("isColorValue")
111 unittest {
112     assert(isColorValue!ubyte);
113     assert(isColorValue!uint);
114     assert(isColorValue!ulong);
115     assert(!isColorValue!int);
116     assert(isColorValue!float);
117     assert(isColorValue!real);
118 }
119 
120 /**
121  * Template for checking whether a type is a valid custom color value. A custom
122  * color value must have a static ``init`` property, a static ``fromReal`` that
123  * converts a real to ``V``, and a ``toReal`` function that converts a ``V``
124  * back to a real.
125  */
126 template isCustomColorValue(V) {
127     enum isCustomColorValue = is(typeof(
128         (inout int = 0) {
129             V v = V.init;
130             v = V.fromReal(cast(real)1.0);
131             real r = v.toReal();
132         }
133     ));
134 }
135 
136 ///
137 @("isCustomColorValue")
138 unittest {
139     static struct IntCV {
140         int value = 0;
141 
142         static auto fromReal(real v) {
143             return IntCV(cast(int)(v / int.max));
144         }
145 
146         real toReal() {
147             return cast(real)value / int.max;
148         }
149     }
150 
151     assert(isCustomColorValue!IntCV);
152     assert(isColorValue!IntCV);
153 }
154 
155 /**
156  * Converts a real to a specified color value.
157  */
158 V toColorValue(V)(const real value) if (isColorValue!V) {
159     static if (isFloatingPoint!V) {
160         return value;
161     } else static if (isIntegral!V) {
162         return cast(V)(V.max * value.clamp(0, 1));
163     } else {
164         return V.fromReal(value);
165     }
166 }
167 
168 /**
169  * Converts an array of reals to an array of specified color values.
170  */
171 V[] toColorValues(V)(const real[] values) if (isColorValue!V) {
172     return values.map!(toColorValue!V).array;
173 }
174 
175 /**
176  * Converts a valid color value to a real.
177  */
178 real toReal(V)(const V value) if (isColorValue!V) {
179     static if (isFloatingPoint!V) {
180         return value;
181     } else static if (isIntegral!V) {
182         return cast(real)value / V.max;
183     } else {
184         return value.toReal();
185     }
186 }
187 
188 /**
189  * Converts an array of valid color values to an array of reals.
190  */
191 real[] toReals(V)(const V[] values) if (isColorValue!V) {
192     return values.map!(toReal!V).array;
193 }