1 module magicalrainbows.utils; 2 3 import std.traits; 4 5 package: 6 7 T read(T)(const ubyte[] input) if (isMutable!T) 8 in(input.length == T.sizeof, "Mismatch between input buffer size and expected value size") 9 { 10 union Result { 11 ubyte[T.sizeof] raw; 12 T val; 13 } 14 Result result; 15 result.raw = input; 16 return result.val; 17 } 18 19 ubyte[T.sizeof] asBytes(T)(T input) if (isMutable!T) { 20 union Result { 21 T val; 22 ubyte[T.sizeof] raw; 23 } 24 return Result(input).raw; 25 } 26 27 enum isColourFormat(T) = hasRed!T && hasGreen!T && hasBlue!T; 28 29 enum hasRed(T) = __traits(hasMember, T, "redSize") && (T.redSize > 0); 30 enum hasGreen(T) = __traits(hasMember, T, "greenSize") && (T.greenSize > 0); 31 enum hasBlue(T) = __traits(hasMember, T, "blueSize") && (T.blueSize > 0); 32 enum hasAlpha(T) = __traits(hasMember, T, "alphaSize") && (T.alphaSize > 0); 33 34 enum maxValue(ulong Bits) = (1<<Bits) - 1; 35 36 enum maxRed(T) = maxValue!(T.redSize); 37 enum maxGreen(T) = maxValue!(T.greenSize); 38 enum maxBlue(T) = maxValue!(T.blueSize); 39 enum maxAlpha(T) = maxValue!(T.alphaSize); 40 41 enum isDigital(T) = hasRed!T && hasGreen!T && hasBlue!T; 42 43 Precision redFP(Precision = double, Colour)(const Colour colour) if (hasRed!Colour) { 44 return (cast(Precision)colour.red / maxRed!Colour); 45 } 46 void redFP(Precision, Colour)(ref Colour colour, Precision value) if (hasRed!Colour) { 47 colour.red = cast(typeof(colour.red))(value * maxRed!Colour); 48 } 49 50 Precision greenFP(Precision = double, Colour)(const Colour colour) if (hasGreen!Colour) { 51 return (cast(Precision)colour.green / maxGreen!Colour); 52 } 53 void greenFP(Precision, Colour)(ref Colour colour, Precision value) if (hasGreen!Colour) { 54 colour.green = cast(typeof(colour.green))(value * maxGreen!Colour); 55 } 56 57 Precision blueFP(Precision = double, Colour)(const Colour colour) if (hasBlue!Colour) { 58 return (cast(Precision)colour.blue / maxBlue!Colour); 59 } 60 void blueFP(Precision, Colour)(ref Colour colour, Precision value) if (hasBlue!Colour) { 61 colour.blue = cast(typeof(colour.blue))(value * maxBlue!Colour); 62 } 63 64 Precision alphaFP(Precision = double, Colour)(const Colour colour) if (hasAlpha!Colour) { 65 return (cast(Precision)colour.alpha / maxAlpha!Colour); 66 } 67 void alphaFP(Precision, Colour)(ref Colour colour, Precision value) if (hasAlpha!Colour) { 68 colour.alpha = cast(typeof(colour.alpha))(value * maxAlpha!Colour); 69 } 70 71 @safe pure unittest { 72 import magicalrainbows.formats : RGB888; 73 import std.math : isClose; 74 assert(RGB888(255, 128, 0).redFP() == 1.0); 75 assert(RGB888(255, 128, 0).greenFP().isClose(0.5019607843137254)); 76 assert(RGB888(255, 128, 0).blueFP() == 0.0); 77 assert(RGB888(0, 255, 128).redFP() == 0.0); 78 assert(RGB888(0, 255, 128).greenFP() == 1.0); 79 assert(RGB888(0, 255, 128).blueFP().isClose(0.5019607843137254)); 80 assert(RGB888(128, 0, 255).redFP().isClose(0.5019607843137254)); 81 assert(RGB888(128, 0, 255).greenFP() == 0.0); 82 assert(RGB888(128, 0, 255).blueFP() == 1.0); 83 } 84 85 template LinearFormatOf(ColourFormat, Precision) { 86 static if (hasAlpha!ColourFormat) { 87 import magicalrainbows.formats : AnalogRGBA; 88 alias LinearFormatOf = AnalogRGBA!Precision; 89 } else { 90 import magicalrainbows.formats : AnalogRGB; 91 alias LinearFormatOf = AnalogRGB!Precision; 92 } 93 } 94 95 LinearFormatOf!(Format, Precision) asLinearRGB(Precision = double, Format)(const Format colour) { 96 static if (isDigital!Format) { 97 const red = colour.redFP!Precision.toLinearRGB; 98 const green = colour.greenFP!Precision.toLinearRGB; 99 const blue = colour.blueFP!Precision.toLinearRGB; 100 static if (hasAlpha!Format) { 101 const alpha = colour.alphaFP!Precision.toLinearRGB; 102 } 103 } else { 104 const red = colour.red.toLinearRGB; 105 const green = colour.green.toLinearRGB; 106 const blue = colour.blue.toLinearRGB; 107 static if (hasAlpha!Format) { 108 const alpha = colour.alpha.toLinearRGB; 109 } 110 } 111 static if (hasAlpha!Format) { 112 return LinearFormatOf!(Format, Precision)(red, green, blue, alpha); 113 } else { 114 return LinearFormatOf!(Format, Precision)(red, green, blue); 115 } 116 } 117 118 private Precision toLinearRGB(Precision)(const Precision input) @safe pure @nogc nothrow { 119 return input > 0.03928 ? 120 ((input + 0.055) / 1.055) ^^ 2.4 : 121 input / 12.92 ; 122 } 123 124 private Precision gammaCorrect(Precision)(const Precision input, const Precision factor = 2.2) @safe pure @nogc nothrow { 125 return input ^^ (1.0 / factor); 126 } 127 128 T colourConvert(T, size_t Size1, size_t Size2, Source)(Source val ) { 129 static if (Size1 > Size2) { 130 return cast(T)(val << (Size1 - Size2)); 131 } else static if (Size1 < Size2) { 132 return cast(T)(val >> (Size2 - Size1)); 133 } else { 134 return cast(T)val; 135 } 136 } 137 138 mixin template colourConstructors() { 139 this(uint red, uint green, uint blue) pure @safe 140 in(red < (1<<redSize), "Red value out of range") 141 in(green < (1<<greenSize), "Green value out of range") 142 in(blue < (1<<blueSize), "Blue value out of range") 143 { 144 this.red = cast(typeof(this.red))red; 145 this.green = cast(typeof(this.green))green; 146 this.blue = cast(typeof(this.blue))blue; 147 } 148 this(Precision)(const AnalogRGB!Precision analog) pure @safe 149 in(analog.red <= 1.0, "Red value out of range") 150 in(analog.red >= 0.0, "Red value out of range") 151 in(analog.green <= 1.0, "Green value out of range") 152 in(analog.green >= 0.0, "Green value out of range") 153 in(analog.blue <= 1.0, "Blue value out of range") 154 in(analog.blue >= 0.0, "Blue value out of range") 155 { 156 this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this))); 157 this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this))); 158 this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this))); 159 static if(hasAlpha!(typeof(this))) { 160 this.alpha = maxAlpha!(typeof(this)); 161 } 162 } 163 T opCast(T: AnalogRGB!Precision, Precision)() const { 164 return AnalogRGB(this.redFP, this.greenFP, this.blueFP); 165 } 166 static if (hasAlpha!(typeof(this))) { 167 this(uint red, uint green, uint blue, uint alpha) pure @safe 168 in(red < (1<<redSize), "Red value out of range") 169 in(green < (1<<greenSize), "Green value out of range") 170 in(blue < (1<<blueSize), "Blue value out of range") 171 in(alpha < (1<<alphaSize), "Alpha value out of range") 172 { 173 this.red = cast(typeof(this.red))red; 174 this.green = cast(typeof(this.green))green; 175 this.blue = cast(typeof(this.blue))blue; 176 this.alpha = cast(typeof(this.alpha))alpha; 177 } 178 this(Precision)(AnalogRGBA!Precision analog) pure @safe 179 in(analog.red <= 1.0, "Red value out of range") 180 in(analog.red >= 0.0, "Red value out of range") 181 in(analog.green <= 1.0, "Green value out of range") 182 in(analog.green >= 0.0, "Green value out of range") 183 in(analog.blue <= 1.0, "Blue value out of range") 184 in(analog.blue >= 0.0, "Blue value out of range") 185 in(analog.alpha <= 1.0, "Blue value out of range") 186 in(analog.alpha >= 0.0, "Blue value out of range") 187 { 188 this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this))); 189 this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this))); 190 this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this))); 191 this.alpha = cast(typeof(this.alpha))(analog.alpha * maxAlpha!(typeof(this))); 192 } 193 T opCast(T: AnalogRGBA!Precision, Precision)() const { 194 return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, this.alphaFP); 195 } 196 } else { 197 // Missing alpha channel is the equivalent of 100% opacity 198 T opCast(T: AnalogRGBA!Precision, Precision)() const { 199 return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, 1.0); 200 } 201 } 202 }