bfccaa04b925a3aacd6d359a8b9680a2d1899373
[movit] / gamma_expansion_effect.cpp
1 #include <math.h>
2 #include <assert.h>
3
4 #include "effect_util.h"
5 #include "gamma_expansion_effect.h"
6 #include "util.h"
7
8 GammaExpansionEffect::GammaExpansionEffect()
9         : source_curve(GAMMA_LINEAR)
10 {
11         register_int("source_curve", (int *)&source_curve);
12 }
13
14 std::string GammaExpansionEffect::output_fragment_shader()
15 {
16         if (source_curve == GAMMA_LINEAR) {
17                 return read_file("identity.frag");
18         }
19         if (source_curve == GAMMA_sRGB ||
20             source_curve == GAMMA_REC_709 ||  // Also includes Rec. 601, and 10-bit Rec. 2020.
21             source_curve == GAMMA_REC_2020_12_BIT) {
22                 return read_file("gamma_expansion_effect.frag");
23         }
24         assert(false);
25 }
26
27 void GammaExpansionEffect::set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num)
28 {
29         Effect::set_gl_state(glsl_program_num, prefix, sampler_num);
30
31         // All of these curves follow a continuous curve that's piecewise defined;
32         // very low values (up to some β) are linear. Above β, we have a power curve
33         // that looks like this:
34         //
35         //   y = ((x + ɑ - 1) / ɑ)^ɣ
36         //
37         // However, pow() is relatively slow in GLSL, so we approximate this
38         // part by a minimax polynomial, whose coefficients are precalculated
39         // in Maple. (It is very hard to accurately model the curve as a whole
40         // using minimax polynomials; both Maple and Mathematically generally
41         // just error out if you ask them to optimize over 0..1 with a higher-degree
42         // polynomial.)
43         //
44         // We put some extra weight on areas near β to keep a continuous curve,
45         // and near 1.0, since we'd really like f(1.0) = 1.0, or approximately so.
46         // The following Maple commands, using sRGB below as an example, will
47         // compute the coefficients:
48         //
49         // > alpha := 1.055;
50         // > beta := 0.04045;
51         // > gamma_ := 2.4;
52         // > w := x -> piecewise(x < beta + 0.001, 10, x > 0.999, 10, 1);
53         // > numapprox[minimax](((x + alpha - 1) / alpha)^gamma_, x=beta..1, [4,0], w(x), 'maxerror');
54         //
55         // The variable 'maxerror' will then contain the maximum absolute error
56         // at any point of the curve, and we report this along with the absolute
57         // error at beta and at 1.0. Keep in mind that along this curve,
58         // the smallest minimum difference between any two 8-bit sRGB pixel levels
59         // (in the exponential part of the curve) in linear light is that
60         // between 11/255 and 12/255, which is about 0.00033 (or three to four
61         // times of the sRGB maxerror below). The choice of a fourth-degree
62         // polynomial was made with this in mind; we have not cared equally
63         // much about 10- and 12-bit Rec. 2020.
64         //
65         // NOTE: The error at beta is compared to the _linear_ part of the curve.
66         // Since the standards give these with only a few decimals, it means that
67         // the linear and exponential parts will not match up exactly, and even
68         // a perfect approximation will have error > 0 here; sometimes, even larger
69         // than maxerror for the curve itself.
70
71         if (source_curve == GAMMA_sRGB) {
72                 // From the Wikipedia article on sRGB; ɑ (called a+1 there) = 1.055,
73                 // β = 0.04045, ɣ = 2.4.
74                 // maxerror      = 0.000094
75                 // error at beta = 0.000012
76                 // error at 1.0  = 0.000012
77                 //
78                 // Note that the worst _relative_ error by far is just at the beginning
79                 // of the exponential curve, ie., just around β.
80                 set_uniform_float(glsl_program_num, prefix, "linear_scale", 1.0 / 12.92);
81                 set_uniform_float(glsl_program_num, prefix, "c0", 0.001324469581);
82                 set_uniform_float(glsl_program_num, prefix, "c1", 0.02227416690);
83                 set_uniform_float(glsl_program_num, prefix, "c2", 0.5917615253);
84                 set_uniform_float(glsl_program_num, prefix, "c3", 0.4733532353);
85                 set_uniform_float(glsl_program_num, prefix, "c4", -0.08880738120);
86                 set_uniform_float(glsl_program_num, prefix, "beta", 0.04045);
87         }
88         if (source_curve == GAMMA_REC_709) {  // Also includes Rec. 601, and 10-bit Rec. 2020.
89                 // Rec. 2020, page 3; ɑ = 1.099, β = 0.018 * 4.5, ɣ = 1/0.45.
90                 // maxerror      = 0.000043
91                 // error at beta = 0.000051 (see note above!)
92                 // error at 1.0  = 0.000004
93                 //
94                 // Note that Rec. 2020 only gives the other direction, which is why
95                 // our beta and gamma are different from the numbers mentioned
96                 // (we've inverted the formula).
97                 set_uniform_float(glsl_program_num, prefix, "linear_scale", 1.0 / 4.5);
98                 set_uniform_float(glsl_program_num, prefix, "c0", 0.005137028744);
99                 set_uniform_float(glsl_program_num, prefix, "c1", 0.09802596889);
100                 set_uniform_float(glsl_program_num, prefix, "c2", 0.7255768864);
101                 set_uniform_float(glsl_program_num, prefix, "c3", 0.2135067966);
102                 set_uniform_float(glsl_program_num, prefix, "c4", -0.04225094667);
103                 set_uniform_float(glsl_program_num, prefix, "beta", 0.018 * 4.5);
104         }
105         if (source_curve == GAMMA_REC_2020_12_BIT) {
106                 // Rec. 2020, page 3; ɑ = 1.0993, β = 0.0181 * 4.5, ɣ = 1/0.45.
107                 // maxerror      = 0.000042
108                 // error at beta = 0.000005
109                 // error at 1.0  = 0.000004
110                 //
111                 // Note that Rec. 2020 only gives the other direction, which is why
112                 // our beta and gamma are different from the numbers mentioned
113                 // (we've inverted the formula).
114                 set_uniform_float(glsl_program_num, prefix, "linear_scale", 1.0 / 4.5);
115                 set_uniform_float(glsl_program_num, prefix, "c0", 0.005167545928);
116                 set_uniform_float(glsl_program_num, prefix, "c1", 0.09835585809);
117                 set_uniform_float(glsl_program_num, prefix, "c2", 0.7254820139);
118                 set_uniform_float(glsl_program_num, prefix, "c3", 0.2131291155);
119                 set_uniform_float(glsl_program_num, prefix, "c4", -0.04213877222);
120                 set_uniform_float(glsl_program_num, prefix, "beta", 0.0181 * 4.5);
121         }
122 }