xref: /aosp_15_r20/external/libaom/examples/photon_noise_table.c (revision 77c1e3ccc04c968bd2bc212e87364f250e820521)
1*77c1e3ccSAndroid Build Coastguard Worker /*
2*77c1e3ccSAndroid Build Coastguard Worker  * Copyright (c) 2021, Alliance for Open Media. All rights reserved.
3*77c1e3ccSAndroid Build Coastguard Worker  *
4*77c1e3ccSAndroid Build Coastguard Worker  * This source code is subject to the terms of the BSD 2 Clause License and
5*77c1e3ccSAndroid Build Coastguard Worker  * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6*77c1e3ccSAndroid Build Coastguard Worker  * was not distributed with this source code in the LICENSE file, you can
7*77c1e3ccSAndroid Build Coastguard Worker  * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8*77c1e3ccSAndroid Build Coastguard Worker  * Media Patent License 1.0 was not distributed with this source code in the
9*77c1e3ccSAndroid Build Coastguard Worker  * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
10*77c1e3ccSAndroid Build Coastguard Worker  */
11*77c1e3ccSAndroid Build Coastguard Worker 
12*77c1e3ccSAndroid Build Coastguard Worker // This tool creates a film grain table, for use in stills and videos,
13*77c1e3ccSAndroid Build Coastguard Worker // representing the noise that one would get by shooting with a digital camera
14*77c1e3ccSAndroid Build Coastguard Worker // at a given light level. Much of the noise in digital images is photon shot
15*77c1e3ccSAndroid Build Coastguard Worker // noise, which is due to the characteristics of photon arrival and grows in
16*77c1e3ccSAndroid Build Coastguard Worker // standard deviation as the square root of the expected number of photons
17*77c1e3ccSAndroid Build Coastguard Worker // captured.
18*77c1e3ccSAndroid Build Coastguard Worker // https://www.photonstophotos.net/Emil%20Martinec/noise.html#shotnoise
19*77c1e3ccSAndroid Build Coastguard Worker //
20*77c1e3ccSAndroid Build Coastguard Worker // The proxy used by this tool for the amount of light captured is the ISO value
21*77c1e3ccSAndroid Build Coastguard Worker // such that the focal plane exposure at the time of capture would have been
22*77c1e3ccSAndroid Build Coastguard Worker // mapped by a 35mm camera to the output lightness observed in the image. That
23*77c1e3ccSAndroid Build Coastguard Worker // is, if one were to shoot on a 35mm camera (36×24mm sensor) at the nominal
24*77c1e3ccSAndroid Build Coastguard Worker // exposure for that ISO setting, the resulting image should contain noise of
25*77c1e3ccSAndroid Build Coastguard Worker // the same order of magnitude as generated by this tool.
26*77c1e3ccSAndroid Build Coastguard Worker //
27*77c1e3ccSAndroid Build Coastguard Worker // Example usage:
28*77c1e3ccSAndroid Build Coastguard Worker //
29*77c1e3ccSAndroid Build Coastguard Worker //     ./photon_noise_table --width=3840 --height=2160 --iso=25600 -o noise.tbl
30*77c1e3ccSAndroid Build Coastguard Worker //     # Then, for example:
31*77c1e3ccSAndroid Build Coastguard Worker //     aomenc --film-grain-table=noise.tbl ...
32*77c1e3ccSAndroid Build Coastguard Worker //     # Or:
33*77c1e3ccSAndroid Build Coastguard Worker //     avifenc -c aom -a film-grain-table=noise.tbl ...
34*77c1e3ccSAndroid Build Coastguard Worker //
35*77c1e3ccSAndroid Build Coastguard Worker // The (mostly) square-root relationship between light intensity and noise
36*77c1e3ccSAndroid Build Coastguard Worker // amplitude holds in linear light, but AV1 streams are most often encoded
37*77c1e3ccSAndroid Build Coastguard Worker // non-linearly, and the film grain is applied to those non-linear values.
38*77c1e3ccSAndroid Build Coastguard Worker // Therefore, this tool must account for the non-linearity, and this is
39*77c1e3ccSAndroid Build Coastguard Worker // controlled by the optional `--transfer-function` (or `-t`) parameter, which
40*77c1e3ccSAndroid Build Coastguard Worker // specifies the tone response curve that will be used when encoding the actual
41*77c1e3ccSAndroid Build Coastguard Worker // image. The default for this tool is sRGB, which is approximately similar to
42*77c1e3ccSAndroid Build Coastguard Worker // an encoding gamma of 1/2.2 (i.e. a decoding gamma of 2.2) though not quite
43*77c1e3ccSAndroid Build Coastguard Worker // identical.
44*77c1e3ccSAndroid Build Coastguard Worker //
45*77c1e3ccSAndroid Build Coastguard Worker // As alluded to above, the tool assumes that the image is taken from the
46*77c1e3ccSAndroid Build Coastguard Worker // entirety of a 36×24mm (“35mm format”) sensor. If that assumption does not
47*77c1e3ccSAndroid Build Coastguard Worker // hold, then a “35mm-equivalent ISO value” that can be passed to the tool can
48*77c1e3ccSAndroid Build Coastguard Worker // be obtained by multiplying the true ISO value by the ratio of 36×24mm to the
49*77c1e3ccSAndroid Build Coastguard Worker // area that was actually used. For formats that approximately share the same
50*77c1e3ccSAndroid Build Coastguard Worker // aspect ratio, this is often expressed as the square of the “equivalence
51*77c1e3ccSAndroid Build Coastguard Worker // ratio” which is the ratio of their diagonals. For example, APS-C (often
52*77c1e3ccSAndroid Build Coastguard Worker // ~24×16mm) is said to have an equivalence ratio of 1.5 relative to the 35mm
53*77c1e3ccSAndroid Build Coastguard Worker // format, and therefore ISO 1000 on APS-C and ISO 1000×1.5² = 2250 on 35mm
54*77c1e3ccSAndroid Build Coastguard Worker // produce an image of the same lightness from the same amount of light spread
55*77c1e3ccSAndroid Build Coastguard Worker // onto their respective surface areas (resulting in different focal plane
56*77c1e3ccSAndroid Build Coastguard Worker // exposures), and those images will thus have similar amounts of noise if the
57*77c1e3ccSAndroid Build Coastguard Worker // cameras are of similar technology. https://doi.org/10.1117/1.OE.57.11.110801
58*77c1e3ccSAndroid Build Coastguard Worker //
59*77c1e3ccSAndroid Build Coastguard Worker // The tool needs to know the resolution of the images to which its grain tables
60*77c1e3ccSAndroid Build Coastguard Worker // will be applied so that it can know how the light on the sensor was shared
61*77c1e3ccSAndroid Build Coastguard Worker // between its pixels. As a general rule, while a higher pixel count will lead
62*77c1e3ccSAndroid Build Coastguard Worker // to more noise per pixel, when the final image is viewed at the same physical
63*77c1e3ccSAndroid Build Coastguard Worker // size, that noise will tend to “average out” to the same amount over a given
64*77c1e3ccSAndroid Build Coastguard Worker // area, since there will be more pixels in it which, in aggregate, will have
65*77c1e3ccSAndroid Build Coastguard Worker // received essentially as much light. Put differently, the amount of noise
66*77c1e3ccSAndroid Build Coastguard Worker // depends on the scale at which it is measured, and the decision for this tool
67*77c1e3ccSAndroid Build Coastguard Worker // was to make that scale relative to the image instead of its constituent
68*77c1e3ccSAndroid Build Coastguard Worker // samples. For more on this, see:
69*77c1e3ccSAndroid Build Coastguard Worker //
70*77c1e3ccSAndroid Build Coastguard Worker // https://www.photonstophotos.net/Emil%20Martinec/noise-p3.html#pixelsize
71*77c1e3ccSAndroid Build Coastguard Worker // https://www.dpreview.com/articles/5365920428/the-effect-of-pixel-and-sensor-sizes-on-noise/2
72*77c1e3ccSAndroid Build Coastguard Worker // https://www.dpreview.com/videos/7940373140/dpreview-tv-why-lower-resolution-sensors-are-not-better-in-low-light
73*77c1e3ccSAndroid Build Coastguard Worker 
74*77c1e3ccSAndroid Build Coastguard Worker #include <math.h>
75*77c1e3ccSAndroid Build Coastguard Worker #include <stdio.h>
76*77c1e3ccSAndroid Build Coastguard Worker #include <stdlib.h>
77*77c1e3ccSAndroid Build Coastguard Worker #include <string.h>
78*77c1e3ccSAndroid Build Coastguard Worker 
79*77c1e3ccSAndroid Build Coastguard Worker #include "aom_dsp/grain_table.h"
80*77c1e3ccSAndroid Build Coastguard Worker #include "common/args.h"
81*77c1e3ccSAndroid Build Coastguard Worker #include "common/tools_common.h"
82*77c1e3ccSAndroid Build Coastguard Worker 
83*77c1e3ccSAndroid Build Coastguard Worker static const char *exec_name;
84*77c1e3ccSAndroid Build Coastguard Worker 
85*77c1e3ccSAndroid Build Coastguard Worker static const struct arg_enum_list transfer_functions[] = {
86*77c1e3ccSAndroid Build Coastguard Worker   { "bt470m", AOM_CICP_TC_BT_470_M }, { "bt470bg", AOM_CICP_TC_BT_470_B_G },
87*77c1e3ccSAndroid Build Coastguard Worker   { "srgb", AOM_CICP_TC_SRGB },       { "smpte2084", AOM_CICP_TC_SMPTE_2084 },
88*77c1e3ccSAndroid Build Coastguard Worker   { "hlg", AOM_CICP_TC_HLG },         ARG_ENUM_LIST_END
89*77c1e3ccSAndroid Build Coastguard Worker };
90*77c1e3ccSAndroid Build Coastguard Worker 
91*77c1e3ccSAndroid Build Coastguard Worker static arg_def_t help_arg =
92*77c1e3ccSAndroid Build Coastguard Worker     ARG_DEF("h", "help", 0, "Show the available options");
93*77c1e3ccSAndroid Build Coastguard Worker static arg_def_t width_arg =
94*77c1e3ccSAndroid Build Coastguard Worker     ARG_DEF("w", "width", 1, "Width of the image in pixels (required)");
95*77c1e3ccSAndroid Build Coastguard Worker static arg_def_t height_arg =
96*77c1e3ccSAndroid Build Coastguard Worker     ARG_DEF("l", "height", 1, "Height of the image in pixels (required)");
97*77c1e3ccSAndroid Build Coastguard Worker static arg_def_t iso_arg = ARG_DEF(
98*77c1e3ccSAndroid Build Coastguard Worker     "i", "iso", 1, "ISO setting indicative of the light level (required)");
99*77c1e3ccSAndroid Build Coastguard Worker static arg_def_t output_arg =
100*77c1e3ccSAndroid Build Coastguard Worker     ARG_DEF("o", "output", 1,
101*77c1e3ccSAndroid Build Coastguard Worker             "Output file to which to write the film grain table (required)");
102*77c1e3ccSAndroid Build Coastguard Worker static arg_def_t transfer_function_arg =
103*77c1e3ccSAndroid Build Coastguard Worker     ARG_DEF_ENUM("t", "transfer-function", 1,
104*77c1e3ccSAndroid Build Coastguard Worker                  "Transfer function used by the encoded image (default = sRGB)",
105*77c1e3ccSAndroid Build Coastguard Worker                  transfer_functions);
106*77c1e3ccSAndroid Build Coastguard Worker 
usage_exit(void)107*77c1e3ccSAndroid Build Coastguard Worker void usage_exit(void) {
108*77c1e3ccSAndroid Build Coastguard Worker   fprintf(stderr,
109*77c1e3ccSAndroid Build Coastguard Worker           "Usage: %s [--transfer-function=<tf>] --width=<width> "
110*77c1e3ccSAndroid Build Coastguard Worker           "--height=<height> --iso=<iso> --output=<output.tbl>\n",
111*77c1e3ccSAndroid Build Coastguard Worker           exec_name);
112*77c1e3ccSAndroid Build Coastguard Worker   exit(EXIT_FAILURE);
113*77c1e3ccSAndroid Build Coastguard Worker }
114*77c1e3ccSAndroid Build Coastguard Worker 
115*77c1e3ccSAndroid Build Coastguard Worker typedef struct {
116*77c1e3ccSAndroid Build Coastguard Worker   float (*to_linear)(float);
117*77c1e3ccSAndroid Build Coastguard Worker   float (*from_linear)(float);
118*77c1e3ccSAndroid Build Coastguard Worker   // In linear output light. This would typically be 0.18 for SDR (this matches
119*77c1e3ccSAndroid Build Coastguard Worker   // the definition of Standard Output Sensitivity from ISO 12232:2019), but in
120*77c1e3ccSAndroid Build Coastguard Worker   // HDR, we certainly do not want to consider 18% of the maximum output a
121*77c1e3ccSAndroid Build Coastguard Worker   // “mid-tone”, as it would be e.g. 1800 cd/m² for SMPTE ST 2084 (PQ).
122*77c1e3ccSAndroid Build Coastguard Worker   float mid_tone;
123*77c1e3ccSAndroid Build Coastguard Worker } transfer_function_t;
124*77c1e3ccSAndroid Build Coastguard Worker 
125*77c1e3ccSAndroid Build Coastguard Worker static const transfer_function_t *find_transfer_function(
126*77c1e3ccSAndroid Build Coastguard Worker     aom_transfer_characteristics_t tc);
127*77c1e3ccSAndroid Build Coastguard Worker 
128*77c1e3ccSAndroid Build Coastguard Worker typedef struct {
129*77c1e3ccSAndroid Build Coastguard Worker   int width;
130*77c1e3ccSAndroid Build Coastguard Worker   int height;
131*77c1e3ccSAndroid Build Coastguard Worker   int iso_setting;
132*77c1e3ccSAndroid Build Coastguard Worker 
133*77c1e3ccSAndroid Build Coastguard Worker   const transfer_function_t *transfer_function;
134*77c1e3ccSAndroid Build Coastguard Worker 
135*77c1e3ccSAndroid Build Coastguard Worker   const char *output_filename;
136*77c1e3ccSAndroid Build Coastguard Worker } photon_noise_args_t;
137*77c1e3ccSAndroid Build Coastguard Worker 
parse_args(int argc,char ** argv,photon_noise_args_t * photon_noise_args)138*77c1e3ccSAndroid Build Coastguard Worker static void parse_args(int argc, char **argv,
139*77c1e3ccSAndroid Build Coastguard Worker                        photon_noise_args_t *photon_noise_args) {
140*77c1e3ccSAndroid Build Coastguard Worker   static const arg_def_t *args[] = { &help_arg,   &width_arg,
141*77c1e3ccSAndroid Build Coastguard Worker                                      &height_arg, &iso_arg,
142*77c1e3ccSAndroid Build Coastguard Worker                                      &output_arg, &transfer_function_arg,
143*77c1e3ccSAndroid Build Coastguard Worker                                      NULL };
144*77c1e3ccSAndroid Build Coastguard Worker   struct arg arg;
145*77c1e3ccSAndroid Build Coastguard Worker   int width_set = 0, height_set = 0, iso_set = 0, output_set = 0, i;
146*77c1e3ccSAndroid Build Coastguard Worker 
147*77c1e3ccSAndroid Build Coastguard Worker   photon_noise_args->transfer_function =
148*77c1e3ccSAndroid Build Coastguard Worker       find_transfer_function(AOM_CICP_TC_SRGB);
149*77c1e3ccSAndroid Build Coastguard Worker 
150*77c1e3ccSAndroid Build Coastguard Worker   for (i = 1; i < argc; i += arg.argv_step) {
151*77c1e3ccSAndroid Build Coastguard Worker     arg.argv_step = 1;
152*77c1e3ccSAndroid Build Coastguard Worker     if (arg_match(&arg, &help_arg, argv + i)) {
153*77c1e3ccSAndroid Build Coastguard Worker       arg_show_usage(stdout, args);
154*77c1e3ccSAndroid Build Coastguard Worker       exit(EXIT_SUCCESS);
155*77c1e3ccSAndroid Build Coastguard Worker     } else if (arg_match(&arg, &width_arg, argv + i)) {
156*77c1e3ccSAndroid Build Coastguard Worker       photon_noise_args->width = arg_parse_int(&arg);
157*77c1e3ccSAndroid Build Coastguard Worker       width_set = 1;
158*77c1e3ccSAndroid Build Coastguard Worker     } else if (arg_match(&arg, &height_arg, argv + i)) {
159*77c1e3ccSAndroid Build Coastguard Worker       photon_noise_args->height = arg_parse_int(&arg);
160*77c1e3ccSAndroid Build Coastguard Worker       height_set = 1;
161*77c1e3ccSAndroid Build Coastguard Worker     } else if (arg_match(&arg, &iso_arg, argv + i)) {
162*77c1e3ccSAndroid Build Coastguard Worker       photon_noise_args->iso_setting = arg_parse_int(&arg);
163*77c1e3ccSAndroid Build Coastguard Worker       iso_set = 1;
164*77c1e3ccSAndroid Build Coastguard Worker     } else if (arg_match(&arg, &output_arg, argv + i)) {
165*77c1e3ccSAndroid Build Coastguard Worker       photon_noise_args->output_filename = arg.val;
166*77c1e3ccSAndroid Build Coastguard Worker       output_set = 1;
167*77c1e3ccSAndroid Build Coastguard Worker     } else if (arg_match(&arg, &transfer_function_arg, argv + i)) {
168*77c1e3ccSAndroid Build Coastguard Worker       const aom_transfer_characteristics_t tc = arg_parse_enum(&arg);
169*77c1e3ccSAndroid Build Coastguard Worker       photon_noise_args->transfer_function = find_transfer_function(tc);
170*77c1e3ccSAndroid Build Coastguard Worker     } else {
171*77c1e3ccSAndroid Build Coastguard Worker       fatal("unrecognized argument \"%s\", see --help for available options",
172*77c1e3ccSAndroid Build Coastguard Worker             argv[i]);
173*77c1e3ccSAndroid Build Coastguard Worker     }
174*77c1e3ccSAndroid Build Coastguard Worker   }
175*77c1e3ccSAndroid Build Coastguard Worker 
176*77c1e3ccSAndroid Build Coastguard Worker   if (!width_set) {
177*77c1e3ccSAndroid Build Coastguard Worker     fprintf(stderr, "Missing required parameter --width\n");
178*77c1e3ccSAndroid Build Coastguard Worker     exit(EXIT_FAILURE);
179*77c1e3ccSAndroid Build Coastguard Worker   }
180*77c1e3ccSAndroid Build Coastguard Worker 
181*77c1e3ccSAndroid Build Coastguard Worker   if (!height_set) {
182*77c1e3ccSAndroid Build Coastguard Worker     fprintf(stderr, "Missing required parameter --height\n");
183*77c1e3ccSAndroid Build Coastguard Worker     exit(EXIT_FAILURE);
184*77c1e3ccSAndroid Build Coastguard Worker   }
185*77c1e3ccSAndroid Build Coastguard Worker 
186*77c1e3ccSAndroid Build Coastguard Worker   if (!iso_set) {
187*77c1e3ccSAndroid Build Coastguard Worker     fprintf(stderr, "Missing required parameter --iso\n");
188*77c1e3ccSAndroid Build Coastguard Worker     exit(EXIT_FAILURE);
189*77c1e3ccSAndroid Build Coastguard Worker   }
190*77c1e3ccSAndroid Build Coastguard Worker 
191*77c1e3ccSAndroid Build Coastguard Worker   if (!output_set) {
192*77c1e3ccSAndroid Build Coastguard Worker     fprintf(stderr, "Missing required parameter --output\n");
193*77c1e3ccSAndroid Build Coastguard Worker     exit(EXIT_FAILURE);
194*77c1e3ccSAndroid Build Coastguard Worker   }
195*77c1e3ccSAndroid Build Coastguard Worker }
196*77c1e3ccSAndroid Build Coastguard Worker 
maxf(float a,float b)197*77c1e3ccSAndroid Build Coastguard Worker static float maxf(float a, float b) { return a > b ? a : b; }
minf(float a,float b)198*77c1e3ccSAndroid Build Coastguard Worker static float minf(float a, float b) { return a < b ? a : b; }
199*77c1e3ccSAndroid Build Coastguard Worker 
gamma22_to_linear(float g)200*77c1e3ccSAndroid Build Coastguard Worker static float gamma22_to_linear(float g) { return powf(g, 2.2f); }
gamma22_from_linear(float l)201*77c1e3ccSAndroid Build Coastguard Worker static float gamma22_from_linear(float l) { return powf(l, 1 / 2.2f); }
gamma28_to_linear(float g)202*77c1e3ccSAndroid Build Coastguard Worker static float gamma28_to_linear(float g) { return powf(g, 2.8f); }
gamma28_from_linear(float l)203*77c1e3ccSAndroid Build Coastguard Worker static float gamma28_from_linear(float l) { return powf(l, 1 / 2.8f); }
204*77c1e3ccSAndroid Build Coastguard Worker 
srgb_to_linear(float srgb)205*77c1e3ccSAndroid Build Coastguard Worker static float srgb_to_linear(float srgb) {
206*77c1e3ccSAndroid Build Coastguard Worker   return srgb <= 0.04045f ? srgb / 12.92f
207*77c1e3ccSAndroid Build Coastguard Worker                           : powf((srgb + 0.055f) / 1.055f, 2.4f);
208*77c1e3ccSAndroid Build Coastguard Worker }
srgb_from_linear(float linear)209*77c1e3ccSAndroid Build Coastguard Worker static float srgb_from_linear(float linear) {
210*77c1e3ccSAndroid Build Coastguard Worker   return linear <= 0.0031308f ? 12.92f * linear
211*77c1e3ccSAndroid Build Coastguard Worker                               : 1.055f * powf(linear, 1 / 2.4f) - 0.055f;
212*77c1e3ccSAndroid Build Coastguard Worker }
213*77c1e3ccSAndroid Build Coastguard Worker 
214*77c1e3ccSAndroid Build Coastguard Worker static const float kPqM1 = 2610.f / 16384;
215*77c1e3ccSAndroid Build Coastguard Worker static const float kPqM2 = 128 * 2523.f / 4096;
216*77c1e3ccSAndroid Build Coastguard Worker static const float kPqC1 = 3424.f / 4096;
217*77c1e3ccSAndroid Build Coastguard Worker static const float kPqC2 = 32 * 2413.f / 4096;
218*77c1e3ccSAndroid Build Coastguard Worker static const float kPqC3 = 32 * 2392.f / 4096;
pq_to_linear(float pq)219*77c1e3ccSAndroid Build Coastguard Worker static float pq_to_linear(float pq) {
220*77c1e3ccSAndroid Build Coastguard Worker   const float pq_pow_inv_m2 = powf(pq, 1.f / kPqM2);
221*77c1e3ccSAndroid Build Coastguard Worker   return powf(maxf(0, pq_pow_inv_m2 - kPqC1) / (kPqC2 - kPqC3 * pq_pow_inv_m2),
222*77c1e3ccSAndroid Build Coastguard Worker               1.f / kPqM1);
223*77c1e3ccSAndroid Build Coastguard Worker }
pq_from_linear(float linear)224*77c1e3ccSAndroid Build Coastguard Worker static float pq_from_linear(float linear) {
225*77c1e3ccSAndroid Build Coastguard Worker   const float linear_pow_m1 = powf(linear, kPqM1);
226*77c1e3ccSAndroid Build Coastguard Worker   return powf((kPqC1 + kPqC2 * linear_pow_m1) / (1 + kPqC3 * linear_pow_m1),
227*77c1e3ccSAndroid Build Coastguard Worker               kPqM2);
228*77c1e3ccSAndroid Build Coastguard Worker }
229*77c1e3ccSAndroid Build Coastguard Worker 
230*77c1e3ccSAndroid Build Coastguard Worker // Note: it is perhaps debatable whether “linear” for HLG should be scene light
231*77c1e3ccSAndroid Build Coastguard Worker // or display light. Here, it is implemented in terms of display light assuming
232*77c1e3ccSAndroid Build Coastguard Worker // a nominal peak display luminance of 1000 cd/m², hence the system γ of 1.2. To
233*77c1e3ccSAndroid Build Coastguard Worker // make it scene light instead, the OOTF (powf(x, 1.2f)) and its inverse should
234*77c1e3ccSAndroid Build Coastguard Worker // be removed from the functions below, and the .mid_tone should be replaced
235*77c1e3ccSAndroid Build Coastguard Worker // with powf(26.f / 1000, 1 / 1.2f).
236*77c1e3ccSAndroid Build Coastguard Worker static const float kHlgA = 0.17883277f;
237*77c1e3ccSAndroid Build Coastguard Worker static const float kHlgB = 0.28466892f;
238*77c1e3ccSAndroid Build Coastguard Worker static const float kHlgC = 0.55991073f;
hlg_to_linear(float hlg)239*77c1e3ccSAndroid Build Coastguard Worker static float hlg_to_linear(float hlg) {
240*77c1e3ccSAndroid Build Coastguard Worker   // EOTF = OOTF ∘ OETF⁻¹
241*77c1e3ccSAndroid Build Coastguard Worker   const float linear =
242*77c1e3ccSAndroid Build Coastguard Worker       hlg <= 0.5f ? hlg * hlg / 3 : (expf((hlg - kHlgC) / kHlgA) + kHlgB) / 12;
243*77c1e3ccSAndroid Build Coastguard Worker   return powf(linear, 1.2f);
244*77c1e3ccSAndroid Build Coastguard Worker }
hlg_from_linear(float linear)245*77c1e3ccSAndroid Build Coastguard Worker static float hlg_from_linear(float linear) {
246*77c1e3ccSAndroid Build Coastguard Worker   // EOTF⁻¹ = OETF ∘ OOTF⁻¹
247*77c1e3ccSAndroid Build Coastguard Worker   linear = powf(linear, 1.f / 1.2f);
248*77c1e3ccSAndroid Build Coastguard Worker   return linear <= 1.f / 12 ? sqrtf(3 * linear)
249*77c1e3ccSAndroid Build Coastguard Worker                             : kHlgA * logf(12 * linear - kHlgB) + kHlgC;
250*77c1e3ccSAndroid Build Coastguard Worker }
251*77c1e3ccSAndroid Build Coastguard Worker 
find_transfer_function(aom_transfer_characteristics_t tc)252*77c1e3ccSAndroid Build Coastguard Worker static const transfer_function_t *find_transfer_function(
253*77c1e3ccSAndroid Build Coastguard Worker     aom_transfer_characteristics_t tc) {
254*77c1e3ccSAndroid Build Coastguard Worker   static const transfer_function_t
255*77c1e3ccSAndroid Build Coastguard Worker       kGamma22TransferFunction = { .to_linear = &gamma22_to_linear,
256*77c1e3ccSAndroid Build Coastguard Worker                                    .from_linear = &gamma22_from_linear,
257*77c1e3ccSAndroid Build Coastguard Worker                                    .mid_tone = 0.18f },
258*77c1e3ccSAndroid Build Coastguard Worker       kGamma28TransferFunction = { .to_linear = &gamma28_to_linear,
259*77c1e3ccSAndroid Build Coastguard Worker                                    .from_linear = &gamma28_from_linear,
260*77c1e3ccSAndroid Build Coastguard Worker                                    .mid_tone = 0.18f },
261*77c1e3ccSAndroid Build Coastguard Worker       kSRgbTransferFunction = { .to_linear = &srgb_to_linear,
262*77c1e3ccSAndroid Build Coastguard Worker                                 .from_linear = &srgb_from_linear,
263*77c1e3ccSAndroid Build Coastguard Worker                                 .mid_tone = 0.18f },
264*77c1e3ccSAndroid Build Coastguard Worker       kPqTransferFunction = { .to_linear = &pq_to_linear,
265*77c1e3ccSAndroid Build Coastguard Worker                               .from_linear = &pq_from_linear,
266*77c1e3ccSAndroid Build Coastguard Worker                               // https://www.itu.int/pub/R-REP-BT.2408-4-2021
267*77c1e3ccSAndroid Build Coastguard Worker                               // page 6 (PDF page 8)
268*77c1e3ccSAndroid Build Coastguard Worker                               .mid_tone = 26.f / 10000 },
269*77c1e3ccSAndroid Build Coastguard Worker       kHlgTransferFunction = { .to_linear = &hlg_to_linear,
270*77c1e3ccSAndroid Build Coastguard Worker                                .from_linear = &hlg_from_linear,
271*77c1e3ccSAndroid Build Coastguard Worker                                .mid_tone = 26.f / 1000 };
272*77c1e3ccSAndroid Build Coastguard Worker 
273*77c1e3ccSAndroid Build Coastguard Worker   switch (tc) {
274*77c1e3ccSAndroid Build Coastguard Worker     case AOM_CICP_TC_BT_470_M: return &kGamma22TransferFunction;
275*77c1e3ccSAndroid Build Coastguard Worker     case AOM_CICP_TC_BT_470_B_G: return &kGamma28TransferFunction;
276*77c1e3ccSAndroid Build Coastguard Worker     case AOM_CICP_TC_SRGB: return &kSRgbTransferFunction;
277*77c1e3ccSAndroid Build Coastguard Worker     case AOM_CICP_TC_SMPTE_2084: return &kPqTransferFunction;
278*77c1e3ccSAndroid Build Coastguard Worker     case AOM_CICP_TC_HLG: return &kHlgTransferFunction;
279*77c1e3ccSAndroid Build Coastguard Worker 
280*77c1e3ccSAndroid Build Coastguard Worker     default: fatal("unimplemented transfer function %d", tc);
281*77c1e3ccSAndroid Build Coastguard Worker   }
282*77c1e3ccSAndroid Build Coastguard Worker }
283*77c1e3ccSAndroid Build Coastguard Worker 
generate_photon_noise(const photon_noise_args_t * photon_noise_args,aom_film_grain_t * film_grain)284*77c1e3ccSAndroid Build Coastguard Worker static void generate_photon_noise(const photon_noise_args_t *photon_noise_args,
285*77c1e3ccSAndroid Build Coastguard Worker                                   aom_film_grain_t *film_grain) {
286*77c1e3ccSAndroid Build Coastguard Worker   // Assumes a daylight-like spectrum.
287*77c1e3ccSAndroid Build Coastguard Worker   // https://www.strollswithmydog.com/effective-quantum-efficiency-of-sensor/#:~:text=11%2C260%20photons/um%5E2/lx-s
288*77c1e3ccSAndroid Build Coastguard Worker   static const float kPhotonsPerLxSPerUm2 = 11260;
289*77c1e3ccSAndroid Build Coastguard Worker 
290*77c1e3ccSAndroid Build Coastguard Worker   // Order of magnitude for cameras in the 2010-2020 decade, taking the CFA into
291*77c1e3ccSAndroid Build Coastguard Worker   // account.
292*77c1e3ccSAndroid Build Coastguard Worker   static const float kEffectiveQuantumEfficiency = 0.20f;
293*77c1e3ccSAndroid Build Coastguard Worker 
294*77c1e3ccSAndroid Build Coastguard Worker   // Also reasonable values for current cameras. The read noise is typically
295*77c1e3ccSAndroid Build Coastguard Worker   // higher than this at low ISO settings but it matters less there.
296*77c1e3ccSAndroid Build Coastguard Worker   static const float kPhotoResponseNonUniformity = 0.005f;
297*77c1e3ccSAndroid Build Coastguard Worker   static const float kInputReferredReadNoise = 1.5f;
298*77c1e3ccSAndroid Build Coastguard Worker 
299*77c1e3ccSAndroid Build Coastguard Worker   // Focal plane exposure for a mid-tone (typically a 18% reflectance card), in
300*77c1e3ccSAndroid Build Coastguard Worker   // lx·s.
301*77c1e3ccSAndroid Build Coastguard Worker   const float mid_tone_exposure = 10.f / photon_noise_args->iso_setting;
302*77c1e3ccSAndroid Build Coastguard Worker 
303*77c1e3ccSAndroid Build Coastguard Worker   // In microns. Assumes a 35mm sensor (36mm × 24mm).
304*77c1e3ccSAndroid Build Coastguard Worker   const float pixel_area_um2 = (36000 * 24000.f) / (photon_noise_args->width *
305*77c1e3ccSAndroid Build Coastguard Worker                                                     photon_noise_args->height);
306*77c1e3ccSAndroid Build Coastguard Worker 
307*77c1e3ccSAndroid Build Coastguard Worker   const float mid_tone_electrons_per_pixel = kEffectiveQuantumEfficiency *
308*77c1e3ccSAndroid Build Coastguard Worker                                              kPhotonsPerLxSPerUm2 *
309*77c1e3ccSAndroid Build Coastguard Worker                                              mid_tone_exposure * pixel_area_um2;
310*77c1e3ccSAndroid Build Coastguard Worker   const float max_electrons_per_pixel =
311*77c1e3ccSAndroid Build Coastguard Worker       mid_tone_electrons_per_pixel /
312*77c1e3ccSAndroid Build Coastguard Worker       photon_noise_args->transfer_function->mid_tone;
313*77c1e3ccSAndroid Build Coastguard Worker 
314*77c1e3ccSAndroid Build Coastguard Worker   int i;
315*77c1e3ccSAndroid Build Coastguard Worker 
316*77c1e3ccSAndroid Build Coastguard Worker   film_grain->num_y_points = 14;
317*77c1e3ccSAndroid Build Coastguard Worker   for (i = 0; i < film_grain->num_y_points; ++i) {
318*77c1e3ccSAndroid Build Coastguard Worker     float x = i / (film_grain->num_y_points - 1.f);
319*77c1e3ccSAndroid Build Coastguard Worker     const float linear = photon_noise_args->transfer_function->to_linear(x);
320*77c1e3ccSAndroid Build Coastguard Worker     const float electrons_per_pixel = max_electrons_per_pixel * linear;
321*77c1e3ccSAndroid Build Coastguard Worker     // Quadrature sum of the relevant sources of noise, in electrons rms. Photon
322*77c1e3ccSAndroid Build Coastguard Worker     // shot noise is sqrt(electrons) so we can skip the square root and the
323*77c1e3ccSAndroid Build Coastguard Worker     // squaring.
324*77c1e3ccSAndroid Build Coastguard Worker     // https://en.wikipedia.org/wiki/Addition_in_quadrature
325*77c1e3ccSAndroid Build Coastguard Worker     // https://doi.org/10.1117/3.725073
326*77c1e3ccSAndroid Build Coastguard Worker     const float noise_in_electrons =
327*77c1e3ccSAndroid Build Coastguard Worker         sqrtf(kInputReferredReadNoise * kInputReferredReadNoise +
328*77c1e3ccSAndroid Build Coastguard Worker               electrons_per_pixel +
329*77c1e3ccSAndroid Build Coastguard Worker               (kPhotoResponseNonUniformity * kPhotoResponseNonUniformity *
330*77c1e3ccSAndroid Build Coastguard Worker                electrons_per_pixel * electrons_per_pixel));
331*77c1e3ccSAndroid Build Coastguard Worker     const float linear_noise = noise_in_electrons / max_electrons_per_pixel;
332*77c1e3ccSAndroid Build Coastguard Worker     const float linear_range_start = maxf(0.f, linear - 2 * linear_noise);
333*77c1e3ccSAndroid Build Coastguard Worker     const float linear_range_end = minf(1.f, linear + 2 * linear_noise);
334*77c1e3ccSAndroid Build Coastguard Worker     const float tf_slope =
335*77c1e3ccSAndroid Build Coastguard Worker         (photon_noise_args->transfer_function->from_linear(linear_range_end) -
336*77c1e3ccSAndroid Build Coastguard Worker          photon_noise_args->transfer_function->from_linear(
337*77c1e3ccSAndroid Build Coastguard Worker              linear_range_start)) /
338*77c1e3ccSAndroid Build Coastguard Worker         (linear_range_end - linear_range_start);
339*77c1e3ccSAndroid Build Coastguard Worker     float encoded_noise = linear_noise * tf_slope;
340*77c1e3ccSAndroid Build Coastguard Worker 
341*77c1e3ccSAndroid Build Coastguard Worker     x = roundf(255 * x);
342*77c1e3ccSAndroid Build Coastguard Worker     encoded_noise = minf(255.f, roundf(255 * 7.88f * encoded_noise));
343*77c1e3ccSAndroid Build Coastguard Worker 
344*77c1e3ccSAndroid Build Coastguard Worker     film_grain->scaling_points_y[i][0] = (int)x;
345*77c1e3ccSAndroid Build Coastguard Worker     film_grain->scaling_points_y[i][1] = (int)encoded_noise;
346*77c1e3ccSAndroid Build Coastguard Worker   }
347*77c1e3ccSAndroid Build Coastguard Worker 
348*77c1e3ccSAndroid Build Coastguard Worker   film_grain->apply_grain = 1;
349*77c1e3ccSAndroid Build Coastguard Worker   film_grain->update_parameters = 1;
350*77c1e3ccSAndroid Build Coastguard Worker   film_grain->num_cb_points = 0;
351*77c1e3ccSAndroid Build Coastguard Worker   film_grain->num_cr_points = 0;
352*77c1e3ccSAndroid Build Coastguard Worker   film_grain->scaling_shift = 8;
353*77c1e3ccSAndroid Build Coastguard Worker   film_grain->ar_coeff_lag = 0;
354*77c1e3ccSAndroid Build Coastguard Worker   film_grain->ar_coeffs_cb[0] = 0;
355*77c1e3ccSAndroid Build Coastguard Worker   film_grain->ar_coeffs_cr[0] = 0;
356*77c1e3ccSAndroid Build Coastguard Worker   film_grain->ar_coeff_shift = 6;
357*77c1e3ccSAndroid Build Coastguard Worker   film_grain->cb_mult = 0;
358*77c1e3ccSAndroid Build Coastguard Worker   film_grain->cb_luma_mult = 0;
359*77c1e3ccSAndroid Build Coastguard Worker   film_grain->cb_offset = 0;
360*77c1e3ccSAndroid Build Coastguard Worker   film_grain->cr_mult = 0;
361*77c1e3ccSAndroid Build Coastguard Worker   film_grain->cr_luma_mult = 0;
362*77c1e3ccSAndroid Build Coastguard Worker   film_grain->cr_offset = 0;
363*77c1e3ccSAndroid Build Coastguard Worker   film_grain->overlap_flag = 1;
364*77c1e3ccSAndroid Build Coastguard Worker   film_grain->random_seed = 7391;
365*77c1e3ccSAndroid Build Coastguard Worker   film_grain->chroma_scaling_from_luma = 0;
366*77c1e3ccSAndroid Build Coastguard Worker }
367*77c1e3ccSAndroid Build Coastguard Worker 
main(int argc,char ** argv)368*77c1e3ccSAndroid Build Coastguard Worker int main(int argc, char **argv) {
369*77c1e3ccSAndroid Build Coastguard Worker   photon_noise_args_t photon_noise_args;
370*77c1e3ccSAndroid Build Coastguard Worker   aom_film_grain_table_t film_grain_table;
371*77c1e3ccSAndroid Build Coastguard Worker   aom_film_grain_t film_grain;
372*77c1e3ccSAndroid Build Coastguard Worker   struct aom_internal_error_info error_info;
373*77c1e3ccSAndroid Build Coastguard Worker   memset(&photon_noise_args, 0, sizeof(photon_noise_args));
374*77c1e3ccSAndroid Build Coastguard Worker   memset(&film_grain_table, 0, sizeof(film_grain_table));
375*77c1e3ccSAndroid Build Coastguard Worker   memset(&film_grain, 0, sizeof(film_grain));
376*77c1e3ccSAndroid Build Coastguard Worker   memset(&error_info, 0, sizeof(error_info));
377*77c1e3ccSAndroid Build Coastguard Worker 
378*77c1e3ccSAndroid Build Coastguard Worker   exec_name = argv[0];
379*77c1e3ccSAndroid Build Coastguard Worker   parse_args(argc, argv, &photon_noise_args);
380*77c1e3ccSAndroid Build Coastguard Worker 
381*77c1e3ccSAndroid Build Coastguard Worker   generate_photon_noise(&photon_noise_args, &film_grain);
382*77c1e3ccSAndroid Build Coastguard Worker   aom_film_grain_table_append(&film_grain_table, 0, 9223372036854775807ull,
383*77c1e3ccSAndroid Build Coastguard Worker                               &film_grain);
384*77c1e3ccSAndroid Build Coastguard Worker   if (aom_film_grain_table_write(&film_grain_table,
385*77c1e3ccSAndroid Build Coastguard Worker                                  photon_noise_args.output_filename,
386*77c1e3ccSAndroid Build Coastguard Worker                                  &error_info) != AOM_CODEC_OK) {
387*77c1e3ccSAndroid Build Coastguard Worker     aom_film_grain_table_free(&film_grain_table);
388*77c1e3ccSAndroid Build Coastguard Worker     fprintf(stderr, "Failed to write film grain table");
389*77c1e3ccSAndroid Build Coastguard Worker     if (error_info.has_detail) {
390*77c1e3ccSAndroid Build Coastguard Worker       fprintf(stderr, ": %s", error_info.detail);
391*77c1e3ccSAndroid Build Coastguard Worker     }
392*77c1e3ccSAndroid Build Coastguard Worker     fprintf(stderr, "\n");
393*77c1e3ccSAndroid Build Coastguard Worker     return EXIT_FAILURE;
394*77c1e3ccSAndroid Build Coastguard Worker   }
395*77c1e3ccSAndroid Build Coastguard Worker   aom_film_grain_table_free(&film_grain_table);
396*77c1e3ccSAndroid Build Coastguard Worker 
397*77c1e3ccSAndroid Build Coastguard Worker   return EXIT_SUCCESS;
398*77c1e3ccSAndroid Build Coastguard Worker }
399