Ctrl K
ComunidadGuías…

data/kernels/demosaic_lmmse.cl

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
    This file is part of darktable,
    Copyright (C) 2026 darktable developers.

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/

// LMMSE demosaic (src/iop/demosaicing/lmmse.c) as whole-image passes.
//
// The CPU code works on 136x136 tiles, the image plus a 4 pixel border.
// Here every buffer covers the image plus that border (pw x ph, the image at
// an offset of 4), so that the passes see the same neighbors as a tile at
// the image edges, and keep the same margins to the padded edge. The border
// holds zeros where the CPU tile buffer is never written.
//
// None of the CPU loops reads a value that the same loop writes, so each one
// is a single pass. The only difference to the CPU is at the seams between
// its tiles, where the CPU passes run into the unwritten tile border; here
// they read the neighboring image data.

#include "common.h"
#pragma OPENCL FP_CONTRACT ON

bool _area(int col, int row, int m, int pw, int ph)
{
  return col >= m && col < pw - m && row >= m && row < ph - m;
}

// the CPU gamma tables of 65536 entries, which clip to 0 and 1
float _gamma_in(float x)
{
  if(x < 0.0f) return 0.0f;
  if(x * 65535.0f > 65534.99f) return 1.0f;
  return x <= 0.001867f ? x * 17.0f : 1.044445f * pow(x, 1.0f / 2.4f) - 0.044445f;
}

float _gamma_out(float x)
{
  if(x < 0.0f) return 0.0f;
  if(x * 65535.0f > 65534.99f) return 1.0f;
  return x <= 0.031746f ? fp32_div(x, 17.0f) : pow(fp32_div(x + 0.044445f, 1.044445f), 2.4f);
}

float _median3(float x0, float x1, float x2)
{
  return max(min(x0, x1), min(x2, max(x0, x1)));
}

void _cas(float *a, float *b)
{
  const float x = *a;
  const bool c = *a > *b;
  *a = c ? *b : *a;
  *b = c ? x : *b;
}

float _median9(float *s)
{
  _cas(&s[1], &s[2]);
  _cas(&s[4], &s[5]);
  _cas(&s[7], &s[8]);
  _cas(&s[0], &s[1]);
  _cas(&s[3], &s[4]);
  _cas(&s[6], &s[7]);
  _cas(&s[1], &s[2]);
  _cas(&s[4], &s[5]);
  _cas(&s[7], &s[8]);
  _cas(&s[0], &s[3]);
  _cas(&s[5], &s[8]);
  _cas(&s[4], &s[7]);
  _cas(&s[3], &s[6]);
  _cas(&s[1], &s[4]);
  _cas(&s[2], &s[5]);
  _cas(&s[4], &s[7]);
  _cas(&s[4], &s[2]);
  _cas(&s[6], &s[4]);
  _cas(&s[4], &s[2]);
  return s[4];
}

// the gamma corrected raw data
kernel void lmmse_init(
  read_only image2d_t in, global float *cfa,
                const int width, const int height, const int pw, const int ph,
                const float revscaler)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(col >= pw || row >= ph) return;
  const int x = col - 4;
  const int y = row - 4;
  const bool inside = x >= 0 && x < width && y >= 0 && y < height;
  cfa[row * pw + col] = inside ? _gamma_in(revscaler * Areadsingle(in, x, y)) : 0.0f;
}

// G-R(B), horizontal and vertical
kernel void lmmse_diff(
  global float *cfa, global float *hdiff, global float *vdiff,
                const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(col >= pw || row >= ph) return;
  const int i = row * pw + col;
  if(!_area(col, row, 2, pw, ph))
  {
    hdiff[i] = 0.0f;
    vdiff[i] = 0.0f;
    return;
  }
  const int w1 = pw, w2 = 2 * pw;
  const float c0 = cfa[i];
  float h, v;
  if((FC(row, col, filters) & 1) == 0)
  { // at R(B)
    const float v0 = 0.0625f * (cfa[i - w1 - 1] + cfa[i - w1 + 1] + cfa[i + w1 - 1] + cfa[i + w1 + 1]) + 0.25f * c0;
    h = -0.25f * (cfa[i - 2] + cfa[i + 2]) + 0.5f * (cfa[i - 1] + c0 + cfa[i + 1]);
    const float Y0 = v0 + 0.5f * h;
    h = (c0 > 1.75f * Y0) ? _median3(h, cfa[i - 1], cfa[i + 1]) : clamp(h, 0.0f, 1.0f);
    h -= c0;

    v = -0.25f * (cfa[i - w2] + cfa[i + w2]) + 0.5f * (cfa[i - w1] + c0 + cfa[i + w1]);
    const float Y1 = v0 + 0.5f * v;
    v = (c0 > 1.75f * Y1) ? _median3(v, cfa[i - w1], cfa[i + w1]) : clamp(v, 0.0f, 1.0f);
    v -= c0;
  }
  else
  { // at G
    h = 0.25f * (cfa[i - 2] + cfa[i + 2]) - 0.5f * (cfa[i - 1] + c0 + cfa[i + 1]);
    v = 0.25f * (cfa[i - w2] + cfa[i + w2]) - 0.5f * (cfa[i - w1] + c0 + cfa[i + w1]);
    h = clamp(h, -1.0f, 0.0f) + c0;
    v = clamp(v, -1.0f, 0.0f) + c0;
  }
  hdiff[i] = h;
  vdiff[i] = v;
}

// low pass filter on the color differences
kernel void lmmse_lowpass(
  global float *hdiff, global float *vdiff, global float *hlp, global float *vlp,
                   const int pw, const int ph,
                   const float h0, const float h1, const float h2, const float h3, const float h4)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(col >= pw || row >= ph) return;
  const int i = row * pw + col;
  if(!_area(col, row, 4, pw, ph))
  {
    hlp[i] = 0.0f;
    vlp[i] = 0.0f;
    return;
  }
  const int w1 = pw, w2 = 2 * pw, w3 = 3 * pw, w4 = 4 * pw;
  hlp[i] = h0 * hdiff[i] + h1 * (hdiff[i - 1] + hdiff[i + 1]) + h2 * (hdiff[i - 2] + hdiff[i + 2])
         + h3 * (hdiff[i - 3] + hdiff[i + 3]) + h4 * (hdiff[i - 4] + hdiff[i + 4]);
  vlp[i] = h0 * vdiff[i] + h1 * (vdiff[i - w1] + vdiff[i + w1]) + h2 * (vdiff[i - w2] + vdiff[i + w2])
         + h3 * (vdiff[i - w3] + vdiff[i + w3]) + h4 * (vdiff[i - w4] + vdiff[i + w4]);
}

// the LMMSE estimate along one direction, d is 1 or the row pitch
void _estimate(global float *diff, global float *lp, int i, int d, float *x, float *var)
{
  float p[9];
  float mu = 0.0f;
  for(int k = 0; k < 9; k++)
  {
    p[k] = lp[i + (k - 4) * d];
    mu += p[k];
  }
  mu = fp32_div(mu, 9.0f);
  float vx = 1e-7f;
  for(int k = 0; k < 9; k++) vx += fsquare(p[k] - mu);
  float vn = 1e-7f;
  for(int k = 0; k < 9; k++) vn += fsquare(p[k] - diff[i + (k - 4) * d]);
  *x = fp32_div(diff[i] * vx + lp[i] * vn, vx + vn);
  *var = fp32_div(vx * vn, vx + vn);
}

// interpolated G-R(B) at R(B)
kernel void lmmse_interp(
  global float *hdiff, global float *vdiff, global float *hlp, global float *vlp, global float *interp,
                  const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(col >= pw || row >= ph) return;
  const int i = row * pw + col;
  if(!_area(col, row, 4, pw, ph) || (FC(row, col, filters) & 1) != 0)
  {
    interp[i] = 0.0f;
    return;
  }
  float xh, vh, xv, vv;
  _estimate(hdiff, hlp, i, 1, &xh, &vh);
  _estimate(vdiff, vlp, i, pw, &xv, &vv);
  interp[i] = fp32_div(xh * vv + xv * vh, vh + vv);
}

// the CFA values into their planes, and green at R/B. The planes take the
// buffers of hdiff, vdiff and hlp, which hold zeros at the padded edge
kernel void lmmse_planes(
  global float *cfa, global float *interp, global float *red, global float *green, global float *blue,
                  const int width, const int height, const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(col >= pw || row >= ph) return;
  const int i = row * pw + col;
  const int x = col - 4;
  const int y = row - 4;
  const bool inside = x >= 0 && x < width && y >= 0 && y < height;
  const int c = FC(row, col, filters);
  const float v = inside ? cfa[i] : 0.0f;
  if(c == 1)
    green[i] = v;
  else
  {
    green[i] = inside ? v + interp[i] : 0.0f;
    if(c == 0) red[i] = v;
    else blue[i] = v;
  }
}

// bilinear R/B at G: h from its row, v from its column
void _rb_at_g(global float *h, global float *v, global float *green, int i, int w1)
{
  const float g = green[i];
  h[i] = g + 0.5f * (h[i - 1] - green[i - 1] + h[i + 1] - green[i + 1]);
  v[i] = g + 0.5f * (v[i - w1] - green[i - w1] + v[i + w1] - green[i + w1]);
}

kernel void lmmse_rb_at_g(
  global float *red, global float *green, global float *blue,
                   const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 1, pw, ph) || (FC(row, col, filters) & 1) == 0) return;
  const int i = row * pw + col;
  if(FC(row, col + 1, filters) == 0)
    _rb_at_g(red, blue, green, i, pw);
  else
    _rb_at_g(blue, red, green, i, pw);
}

// bilinear B at R and R at B
float _rb_at_rb(global float *o, global float *green, int i, int w1)
{
  return green[i] + 0.25f * (o[i - w1] - green[i - w1] + o[i - 1] - green[i - 1]
                             + o[i + 1] - green[i + 1] + o[i + w1] - green[i + w1]);
}

kernel void lmmse_rb_at_rb(
  global float *red, global float *green, global float *blue,
                    const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 1, pw, ph)) return;
  const int c = FC(row, col, filters);
  if((c & 1) != 0) return;
  const int i = row * pw + col;
  if(c == 0) blue[i] = _rb_at_rb(blue, green, i, pw);
  else red[i] = _rb_at_rb(red, green, i, pw);
}

// 3x3 medians of R-G and B-G
kernel void lmmse_median(
  global float *red, global float *green, global float *blue, global float *corr_r, global float *corr_b,
                  const int pw, const int ph)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 1, pw, ph)) return;
  const int i = row * pw + col;
  float r[9], b[9];
  for(int dy = -1, k = 0; dy <= 1; dy++)
    for(int dx = -1; dx <= 1; dx++, k++)
    {
      const int j = i + dy * pw + dx;
      r[k] = red[j] - green[j];
      b[k] = blue[j] - green[j];
    }
  corr_r[i] = _median9(r);
  corr_b[i] = _median9(b);
}

// the colors from the medians. The CPU loop stops one row early at the
// bottom, kept as is
kernel void lmmse_median_apply(
  global float *red, global float *green, global float *blue, global float *corr_r, global float *corr_b,
                        const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(col < 6 || col >= pw - 6 || row < 6 || row >= ph - 7) return;
  const int i = row * pw + col;
  const int c = FC(row, col, filters);
  const float g = green[i];
  if((c & 1) != 0)
  {
    red[i] = g + corr_r[i];
    blue[i] = g + corr_b[i];
  }
  else if(c == 0)
  {
    const float b = g + corr_b[i];
    blue[i] = b;
    green[i] = 0.5f * (red[i] - corr_r[i] + b - corr_b[i]);
  }
  else
  {
    const float r = g + corr_r[i];
    red[i] = r;
    green[i] = 0.5f * (r - corr_r[i] + blue[i] - corr_b[i]);
  }
}

// the CFA values back into their planes
kernel void lmmse_fill(
  global float *cfa, global float *red, global float *green, global float *blue,
                const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 4, pw, ph)) return;
  const int i = row * pw + col;
  const int c = FC(row, col, filters);
  if(c == 0) red[i] = cfa[i];
  else if(c == 2) blue[i] = cfa[i];
  else green[i] = cfa[i];
}

// the refinement: the weights from the gradients of a at i-2..i+2 and of b
// at i-1..i+1, horizontally and vertically, applied to p - green
float _refine(global float *p, global float *a, global float *b, global float *green, int i, int w1)
{
  const int w2 = 2 * w1;
  const float dL = fp32_div(1.0f, 1.0f + fabs(a[i - 2] - a[i]) + fabs(b[i + 1] - b[i - 1]));
  const float dR = fp32_div(1.0f, 1.0f + fabs(a[i + 2] - a[i]) + fabs(b[i + 1] - b[i - 1]));
  const float dU = fp32_div(1.0f, 1.0f + fabs(a[i - w2] - a[i]) + fabs(b[i + w1] - b[i - w1]));
  const float dD = fp32_div(1.0f, 1.0f + fabs(a[i + w2] - a[i]) + fabs(b[i + w1] - b[i - w1]));
  return fp32_div((green[i - 1] - p[i - 1]) * dL + (green[i + 1] - p[i + 1]) * dR
                  + (green[i - w1] - p[i - w1]) * dU + (green[i + w1] - p[i + w1]) * dD,
                  dL + dR + dU + dD);
}

// green at R/B
kernel void lmmse_refine_g(
  global float *red, global float *green, global float *blue,
                    const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 8, pw, ph)) return;
  const int c = FC(row, col, filters);
  if((c & 1) != 0) return;
  const int i = row * pw + col;
  if(c == 0) green[i] = red[i] + _refine(red, red, green, green, i, pw);
  else green[i] = blue[i] + _refine(blue, blue, green, green, i, pw);
}

// R and B at G
kernel void lmmse_refine_rb_at_g(
  global float *red, global float *green, global float *blue,
                          const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 8, pw, ph) || (FC(row, col, filters) & 1) == 0) return;
  const int i = row * pw + col;
  const float g = green[i];
  const float r = g - _refine(red, green, red, green, i, pw);
  const float b = g - _refine(blue, green, blue, green, i, pw);
  red[i] = r;
  blue[i] = b;
}

// B at R and R at B
kernel void lmmse_refine_rb_at_rb(
  global float *red, global float *green, global float *blue,
                           const int pw, const int ph, const unsigned int filters)
{
  const int col = get_global_id(0);
  const int row = get_global_id(1);
  if(!_area(col, row, 8, pw, ph)) return;
  const int c = FC(row, col, filters);
  if((c & 1) != 0) return;
  const int i = row * pw + col;
  if(c == 0) blue[i] = green[i] - _refine(blue, red, green, green, i, pw);
  else red[i] = green[i] - _refine(red, blue, green, green, i, pw);
}

// gamma back and the output, inside the 4 pixel border that PPG fills
kernel void lmmse_output(
  global float *red, global float *green, global float *blue, write_only image2d_t out,
                  const int width, const int height, const int pw, const float scaler)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x < 4 || x >= width - 4 || y < 4 || y >= height - 4) return;
  const int i = (y + 4) * pw + x + 4;
  write_imagef(out, (int2)(x, y), (float4)(max(0.0f, scaler * _gamma_out(red[i])),
                               max(0.0f, scaler * _gamma_out(green[i])),
                               max(0.0f, scaler * _gamma_out(blue[i])), 0.0f));
}