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 <ae@cy.md> 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 23 { 24 sRGB, /// https://en.wikipedia.org/wiki/SRGB 25 } 26 27 /// Contains a gamma ramp. 28 /// LUM_BASETYPE and PIX_BASETYPE should be numeric types indicating 29 /// the channel type for the colors that will be converted. 30 struct GammaRamp(LUM_BASETYPE, PIX_BASETYPE) 31 { 32 LUM_BASETYPE[PIX_BASETYPE.max+1] pix2lumValues; /// Calculated gamma ramp table. 33 PIX_BASETYPE[LUM_BASETYPE.max+1] lum2pixValues; /// ditto 34 35 /// Create a GammaRamp with the given gamma value. 36 this(double gamma) 37 { 38 foreach (pix; 0..PIX_BASETYPE.max+1) 39 pix2lumValues[pix] = cast(LUM_BASETYPE)(pow(pix/cast(double)PIX_BASETYPE.max, gamma)*LUM_BASETYPE.max); 40 foreach (lum; 0..LUM_BASETYPE.max+1) 41 lum2pixValues[lum] = cast(PIX_BASETYPE)(pow(lum/cast(double)LUM_BASETYPE.max, 1/gamma)*PIX_BASETYPE.max); 42 } 43 44 /// Create a GammaRamp with the given colorspace. 45 this(ColorSpace colorSpace) 46 { 47 final switch(colorSpace) 48 { 49 case ColorSpace.sRGB: 50 { 51 static double sRGB_to_linear(double cf) 52 { 53 if (cf <= 0.0392857) 54 return cf / 12.9232102; 55 else 56 return pow((cf + 0.055)/1.055, 2.4L); 57 } 58 59 static double linear_to_sRGB(double cf) 60 { 61 if (cf <= 0.00303993) 62 return cf * 12.9232102; 63 else 64 return 1.055*pow(cf, 1/2.4L) - 0.055; 65 } 66 67 foreach (pix; 0..PIX_BASETYPE.max+1) 68 pix2lumValues[pix] = cast(LUM_BASETYPE)(sRGB_to_linear(pix/cast(double)PIX_BASETYPE.max)*LUM_BASETYPE.max); 69 foreach (lum; 0..LUM_BASETYPE.max+1) 70 lum2pixValues[lum] = cast(PIX_BASETYPE)(linear_to_sRGB(lum/cast(double)LUM_BASETYPE.max)*PIX_BASETYPE.max); 71 break; 72 } 73 } 74 } 75 76 /// Convert pixel value to linear luminosity. 77 auto pix2lum(PIXCOLOR)(PIXCOLOR c) const 78 { 79 alias LUMCOLOR = ChangeChannelType!(PIXCOLOR, LUM_BASETYPE); 80 return LUMCOLOR.op!q{b[a]}(c, pix2lumValues[]); 81 } 82 83 /// Convert linear luminosity to pixel value. 84 auto lum2pix(LUMCOLOR)(LUMCOLOR c) const 85 { 86 alias PIXCOLOR = ChangeChannelType!(LUMCOLOR, PIX_BASETYPE); 87 return PIXCOLOR.op!q{b[a]}(c, lum2pixValues[]); 88 } 89 } 90 91 /// Return a view which converts luminosity to image pixel data 92 /// using the specified gamma ramp 93 auto lum2pix(SRC, GAMMA)(auto ref SRC src, auto ref GAMMA gamma) 94 if (isView!SRC) 95 { 96 auto g = γ 97 return src.colorMap!(c => g.lum2pix(c)); 98 } 99 100 /// Return a view which converts image pixel data to luminosity 101 /// using the specified gamma ramp 102 auto pix2lum(SRC, GAMMA)(auto ref SRC src, auto ref GAMMA gamma) 103 if (isView!SRC) 104 { 105 auto g = γ 106 return src.colorMap!(c => g.pix2lum(c)); 107 } 108 109 /// Return a reference to a statically-initialized GammaRamp 110 /// with the indicated parameters 111 ref auto gammaRamp(LUM_BASETYPE, PIX_BASETYPE, alias value)() 112 { 113 alias Ramp = GammaRamp!(LUM_BASETYPE, PIX_BASETYPE); 114 static struct S 115 { 116 static immutable Ramp ramp; 117 118 // Need to use static initialization instead of CTFE due to 119 // https://issues.dlang.org/show_bug.cgi?id=12412 120 shared static this() 121 { 122 ramp = Ramp(value); 123 } 124 } 125 return S.ramp; 126 } 127 128 unittest 129 { 130 // test instantiation 131 auto lum = onePixel(RGB16(1, 2, 3)); 132 auto pix = lum.lum2pix(gammaRamp!(ushort, ubyte, ColorSpace.sRGB)); 133 }