1 /** 2 * Gamma conversion. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.utils.graphics.gamma; 15 16 import std.math; 17 18 import ae.utils.graphics.color; 19 import ae.utils.graphics.view; 20 21 /// Predefined colorspaces. 22 enum ColorSpace { sRGB } 23 24 /// Contains a gamma ramp. 25 /// LUM_BASETYPE and PIX_BASETYPE should be numeric types indicating 26 /// the channel type for the colors that will be converted. 27 struct GammaRamp(LUM_BASETYPE, PIX_BASETYPE) 28 { 29 LUM_BASETYPE[PIX_BASETYPE.max+1] pix2lumValues; 30 PIX_BASETYPE[LUM_BASETYPE.max+1] lum2pixValues; 31 32 /// Create a GammaRamp with the given gamma value. 33 this(double gamma) 34 { 35 foreach (pix; 0..PIX_BASETYPE.max+1) 36 pix2lumValues[pix] = cast(LUM_BASETYPE)(pow(pix/cast(double)PIX_BASETYPE.max, gamma)*LUM_BASETYPE.max); 37 foreach (lum; 0..LUM_BASETYPE.max+1) 38 lum2pixValues[lum] = cast(PIX_BASETYPE)(pow(lum/cast(double)LUM_BASETYPE.max, 1/gamma)*PIX_BASETYPE.max); 39 } 40 41 /// Create a GammaRamp with the given colorspace. 42 this(ColorSpace colorSpace) 43 { 44 final switch(colorSpace) 45 { 46 case ColorSpace.sRGB: 47 { 48 static double sRGB_to_linear(double cf) 49 { 50 if (cf <= 0.0392857) 51 return cf / 12.9232102; 52 else 53 return pow((cf + 0.055)/1.055, 2.4L); 54 } 55 56 static double linear_to_sRGB(double cf) 57 { 58 if (cf <= 0.00303993) 59 return cf * 12.9232102; 60 else 61 return 1.055*pow(cf, 1/2.4L) - 0.055; 62 } 63 64 foreach (pix; 0..PIX_BASETYPE.max+1) 65 pix2lumValues[pix] = cast(LUM_BASETYPE)(sRGB_to_linear(pix/cast(double)PIX_BASETYPE.max)*LUM_BASETYPE.max); 66 foreach (lum; 0..LUM_BASETYPE.max+1) 67 lum2pixValues[lum] = cast(PIX_BASETYPE)(linear_to_sRGB(lum/cast(double)LUM_BASETYPE.max)*PIX_BASETYPE.max); 68 break; 69 } 70 } 71 } 72 73 auto pix2lum(PIXCOLOR)(PIXCOLOR c) const 74 { 75 alias LUMCOLOR = ChangeChannelType!(PIXCOLOR, LUM_BASETYPE); 76 return LUMCOLOR.op!q{b[a]}(c, pix2lumValues[]); 77 } 78 79 auto lum2pix(LUMCOLOR)(LUMCOLOR c) const 80 { 81 alias PIXCOLOR = ChangeChannelType!(LUMCOLOR, PIX_BASETYPE); 82 return PIXCOLOR.op!q{b[a]}(c, lum2pixValues[]); 83 } 84 } 85 86 /// Return a view which converts luminosity to image pixel data 87 /// using the specified gamma ramp 88 auto lum2pix(SRC, GAMMA)(auto ref SRC src, auto ref GAMMA gamma) 89 if (isView!SRC) 90 { 91 auto g = γ 92 return src.colorMap!(c => g.lum2pix(c)); 93 } 94 95 /// Return a view which converts image pixel data to luminosity 96 /// using the specified gamma ramp 97 auto pix2lum(SRC, GAMMA)(auto ref SRC src, auto ref GAMMA gamma) 98 if (isView!SRC) 99 { 100 auto g = γ 101 return src.colorMap!(c => g.pix2lum(c)); 102 } 103 104 /// Return a reference to a statically-initialized GammaRamp 105 /// with the indicated parameters 106 ref auto gammaRamp(LUM_BASETYPE, PIX_BASETYPE, alias value)() 107 { 108 alias Ramp = GammaRamp!(LUM_BASETYPE, PIX_BASETYPE); 109 static struct S 110 { 111 static immutable Ramp ramp; 112 113 // Need to use static initialization instead of CTFE due to 114 // https://d.puremagic.com/issues/show_bug.cgi?id=12412 115 shared static this() 116 { 117 ramp = Ramp(value); 118 } 119 } 120 return S.ramp; 121 } 122 123 unittest 124 { 125 // test instantiation 126 auto lum = onePixel(RGB16(1, 2, 3)); 127 auto pix = lum.lum2pix(gammaRamp!(ushort, ubyte, ColorSpace.sRGB)); 128 }