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 = &gamma;
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 = &gamma;
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 }