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