So, I worked a bit into the darktable code and I managed to bring out something for adding the grain with the LUT…

Here are some output directly from darktable, using 6400 ISO and 100% strength.

On the left the old darktable output and on the right the modified one. There are three versions with 0, 0.5 and 1 contrast applied from the contrast-lightness-saturation module.

Here is the added code into the grain.c file.

```
#define LUT_SIZE 128
#define MAX_DELTA 2
#define MIN_DELTA 0.005
...
float paper_resp(float exposure, float mb, float gp)
{
float density;
float delta = - (MAX_DELTA - MIN_DELTA) * mb + MAX_DELTA;
density = (1 + 2 * delta) / (1 + exp( (4 * gp * (0.5 - exposure)) / (1 + 2 * delta) )) - delta;
return density;
}
float paper_resp_inverse(float density, float mb, float gp)
{
float exposure;
float delta = - (MAX_DELTA - MIN_DELTA) * mb + MAX_DELTA;
exposure = -log((1 + 2 * delta) / (density + delta) - 1) * (1 + 2 * delta) / (4 * gp) + 0.5;
return exposure;
}
static float midtone_bias = 1.0;
static float gamma_paper = 1.0;
static float grain_lut[LUT_SIZE*LUT_SIZE];
static void evaluate_grain_lut(const float mb, const float gp)
{
for(int i = 0; i < LUT_SIZE; i++)
{
for(int j = 0; j < LUT_SIZE; j++)
{
float gu = (double)i / (LUT_SIZE - 1) - 0.5;
float l = (double)j / (LUT_SIZE - 1);
grain_lut[j * LUT_SIZE + i]= paper_resp(gu + paper_resp_inverse(l, mb, gp), mb, gp) - l;
}
}
}
float dt_lut_lookup_2d_1c(const float x, const float y)
{
const float _x = CLAMPS((x + 0.5) * (LUT_SIZE - 1), 0, LUT_SIZE - 1);
const float _y = CLAMPS(y * (LUT_SIZE - 1), 0, LUT_SIZE - 1);
const int _x0 = _x < LUT_SIZE - 2 ? _x : LUT_SIZE - 2;
const int _y0 = _y < LUT_SIZE - 2 ? _y : LUT_SIZE - 2;
const int _x1 = _x0 + 1;
const int _y1 = _y0 + 1;
const float x_diff = _x - _x0;
const float y_diff = _y - _y0;
const float l00 = grain_lut[_y0 * LUT_SIZE + _x0];
const float l01 = grain_lut[_y0 * LUT_SIZE + _x1];
const float l10 = grain_lut[_y1 * LUT_SIZE + _x0];
const float l11 = grain_lut[_y1 * LUT_SIZE + _x1];
const float xy0 = (1.0 - y_diff) * l00 + l10 * y_diff;
const float xy1 = (1.0 - y_diff) * l01 + l11 * y_diff;
return xy0 * (1.0f - x_diff) + xy1 * x_diff;
}
```

and into the process function:

```
evaluate_grain_lut(midtone_bias, gamma_paper);
...
out[0] = in[0] + 100 * dt_lut_lookup_2d_1c(noise * strength * GRAIN_LIGHTNESS_STRENGTH_SCALE, in[0] / 100);
...
```