1 module magicalrainbows.utils; 2 3 import std.traits; 4 5 package: 6 7 T read(T)(const ubyte[] input) if (isMutable!T) in { 8 assert(input.length == T.sizeof, "Mismatch between input buffer size and expected value size"); 9 } body { 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 real redFP(Colour)(const Colour colour) if (hasRed!Colour) { 44 return (cast(real)colour.red / maxRed!Colour); 45 } 46 47 real greenFP(Colour)(const Colour colour) if (hasGreen!Colour) { 48 return (cast(real)colour.green / maxGreen!Colour); 49 } 50 51 real blueFP(Colour)(const Colour colour) if (hasBlue!Colour) { 52 return (cast(real)colour.blue / maxBlue!Colour); 53 } 54 real alphaFP(Colour)(const Colour colour) if (hasAlpha!Colour) { 55 return (cast(real)colour.alpha / maxAlpha!Colour); 56 } 57 @safe pure unittest { 58 import magicalrainbows.formats : RGB888; 59 import std.math : approxEqual; 60 assert(RGB888(255, 128, 0).redFP() == 1.0); 61 assert(RGB888(255, 128, 0).greenFP().approxEqual(0.502)); 62 assert(RGB888(255, 128, 0).blueFP() == 0.0); 63 assert(RGB888(0, 255, 128).redFP() == 0.0); 64 assert(RGB888(0, 255, 128).greenFP() == 1.0); 65 assert(RGB888(0, 255, 128).blueFP().approxEqual(0.502)); 66 assert(RGB888(128, 0, 255).redFP().approxEqual(0.502)); 67 assert(RGB888(128, 0, 255).greenFP() == 0.0); 68 assert(RGB888(128, 0, 255).blueFP() == 1.0); 69 } 70 71 auto asLinearRGB(Format)(const Format colour) { 72 static if (isDigital!Format) { 73 const red = colour.redFP.toLinearRGB; 74 const green = colour.greenFP.toLinearRGB; 75 const blue = colour.blueFP.toLinearRGB; 76 static if (hasAlpha!Format) { 77 const alpha = colour.alphaFP.toLinearRGB; 78 } 79 } else { 80 const red = colour.red.toLinearRGB; 81 const green = colour.green.toLinearRGB; 82 const blue = colour.blue.toLinearRGB; 83 static if (hasAlpha!Format) { 84 const alpha = colour.alpha.toLinearRGB; 85 } 86 } 87 static if (hasAlpha!Format) { 88 import magicalrainbows.formats : AnalogRGBA; 89 return AnalogRGBA(red, green, blue, alpha); 90 } else { 91 import magicalrainbows.formats : AnalogRGB; 92 return AnalogRGB(red, green, blue); 93 } 94 } 95 96 private real toLinearRGB(const real input) @safe pure @nogc nothrow { 97 return input > 0.03928 ? 98 ((input + 0.055) / 1.055) ^^ 2.4 : 99 input / 12.92 ; 100 } 101 102 private real gammaCorrect(const real input, const real factor = 2.2) @safe pure @nogc nothrow { 103 return input ^^ (1.0 / factor); 104 } 105 106 T colourConvert(T, size_t Size1, size_t Size2, Source)(Source val ) { 107 static if (Size1 > Size2) { 108 return cast(T)(val << (Size1 - Size2)); 109 } else static if (Size1 < Size2) { 110 return cast(T)(val >> (Size2 - Size1)); 111 } else { 112 return cast(T)val; 113 } 114 } 115 116 mixin template colourConstructors() { 117 this(uint red, uint green, uint blue) pure @safe 118 in(red < (1<<redSize), "Red value out of range") 119 in(green < (1<<greenSize), "Green value out of range") 120 in(blue < (1<<blueSize), "Blue value out of range") 121 { 122 this.red = cast(typeof(this.red))red; 123 this.green = cast(typeof(this.green))green; 124 this.blue = cast(typeof(this.blue))blue; 125 } 126 this(const AnalogRGB analog) pure @safe 127 in(analog.red <= 1.0, "Red value out of range") 128 in(analog.red >= 0.0, "Red value out of range") 129 in(analog.green <= 1.0, "Green value out of range") 130 in(analog.green >= 0.0, "Green value out of range") 131 in(analog.blue <= 1.0, "Blue value out of range") 132 in(analog.blue >= 0.0, "Blue value out of range") 133 { 134 this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this))); 135 this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this))); 136 this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this))); 137 } 138 auto opCast(T: AnalogRGB)() const { 139 return AnalogRGB(this.redFP, this.greenFP, this.blueFP); 140 } 141 static if (hasAlpha!(typeof(this))) { 142 this(uint red, uint green, uint blue, uint alpha) pure @safe 143 in(red < (1<<redSize), "Red value out of range") 144 in(green < (1<<greenSize), "Green value out of range") 145 in(blue < (1<<blueSize), "Blue value out of range") 146 in(alpha < (1<<alphaSize), "Alpha value out of range") 147 { 148 this.red = cast(typeof(this.red))red; 149 this.green = cast(typeof(this.green))green; 150 this.blue = cast(typeof(this.blue))blue; 151 this.alpha = cast(typeof(this.alpha))alpha; 152 } 153 this(AnalogRGBA analog) pure @safe 154 in(analog.red <= 1.0, "Red value out of range") 155 in(analog.red >= 0.0, "Red value out of range") 156 in(analog.green <= 1.0, "Green value out of range") 157 in(analog.green >= 0.0, "Green value out of range") 158 in(analog.blue <= 1.0, "Blue value out of range") 159 in(analog.blue >= 0.0, "Blue value out of range") 160 in(analog.alpha <= 1.0, "Blue value out of range") 161 in(analog.alpha >= 0.0, "Blue value out of range") 162 { 163 this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this))); 164 this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this))); 165 this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this))); 166 this.alpha = cast(typeof(this.alpha))(analog.alpha * maxAlpha!(typeof(this))); 167 } 168 auto opCast(T: AnalogRGBA)() const { 169 return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, this.alphaFP); 170 } 171 } else { 172 // Missing alpha channel is the equivalent of 100% opacity 173 auto opCast(T: AnalogRGBA)() const { 174 return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, 1.0); 175 } 176 } 177 }