Ctrl K
ComunidadGuías…

data/kernels/toneequal.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/>.
*/

#include "common.h"
#include "bilinear.h"

// keep in sync with src/common/luminance_mask.h and src/iop/toneequal.c
#define TONEEQ_MIN_FLOAT 0x1p-16f     // exp2f(-16.0f)
#define TONEEQ_MIN_EV (-8.0f)
#define TONEEQ_MAX_EV (0.0f)

// dt_iop_luminance_mask_method_t
#define TONEEQ_MEAN 0
#define TONEEQ_LIGHTNESS 1
#define TONEEQ_VALUE 2
#define TONEEQ_NORM_1 3
#define TONEEQ_NORM_2 4
#define TONEEQ_NORM_POWER 5
#define TONEEQ_GEOMEAN 6

// dt_iop_guided_filter_blending_t
#define TONEEQ_BLENDING_LINEAR 0
#define TONEEQ_BLENDING_GEOMEAN 1


static inline float _linear_contrast(const float pixel,
                                     const float fulcrum,
                                     const float contrast)
{
  // Increase the slope of the value around a fulcrum value
  return fmax((pixel - fulcrum) * contrast + fulcrum, TONEEQ_MIN_FLOAT);
}


/* Flatten an RGB image into a luminance map, applying exposure and contrast
   compensation on the fly. Mirrors luminance_mask() from common/luminance_mask.h */
kernel void toneequal_luminance_mask(read_only image2d_t in,
                                     global float *const luminance,
                                     const int width,
                                     const int height,
                                     const int method,
                                     const float exposure_boost,
                                     const float fulcrum,
                                     const float contrast_boost)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const float4 pixel = read_imagef(in, samplerA, (int2)(x, y));
  float lum;

  switch(method)
  {
    case TONEEQ_LIGHTNESS:
    {
      // (max(RGB) + min(RGB)) / 2 is equivalent to HSL lightness
      const float max_rgb = fmax(fmax(pixel.x, pixel.y), pixel.z);
      const float min_rgb = fmin(fmin(pixel.x, pixel.y), pixel.z);
      lum = exposure_boost * (max_rgb + min_rgb) / 2.0f;
      break;
    }

    case TONEEQ_VALUE:
      // max(RGB) is equivalent to HSV value
      lum = exposure_boost * fmax(fmax(pixel.x, pixel.y), pixel.z);
      break;

    case TONEEQ_NORM_1:
      // vector norm L1
      lum = exposure_boost * (fabs(pixel.x) + fabs(pixel.y) + fabs(pixel.z));
      break;

    case TONEEQ_NORM_2:
      // vector norm L2 : euclidean norm
      lum = exposure_boost
        * dtcl_sqrt(pixel.x * pixel.x + pixel.y * pixel.y + pixel.z * pixel.z);
      break;

    case TONEEQ_NORM_POWER:
    {
      // weird norm sort of perceptual. This is black magic really, but it looks good.
      const float4 value = fabs(pixel);
      const float4 RGB_square = value * value;
      const float4 RGB_cubic = RGB_square * value;
      const float numerator = RGB_cubic.x + RGB_cubic.y + RGB_cubic.z;
      const float denominator = RGB_square.x + RGB_square.y + RGB_square.z;
      lum = exposure_boost * numerator / denominator;
      break;
    }

    case TONEEQ_GEOMEAN:
    {
      // geometric_mean(RGB)
      const float product = fabs(pixel.x) * fabs(pixel.y) * fabs(pixel.z);
      lum = exposure_boost * dtcl_pow(product, 1.0f / 3.0f);
      break;
    }

    case TONEEQ_MEAN:
    default:
      // mean(RGB) is the intensity
      lum = exposure_boost * (pixel.x + pixel.y + pixel.z) / 3.0f;
      break;
  }

  luminance[mad24(y, width, x)] = _linear_contrast(lum, fulcrum, contrast_boost);
}


/* Build the pixel correction as the sum of the contribution of each luminance
   channel, weighted by the gaussian of the radial distance in EV.
   This is the non-LUT reference implementation of apply_toneequalizer(): on GPU
   the transcendentals are cheaper than a 320 kB lookup table upload. */
static inline float _pixel_correction(const float exposure,
                                      const float4 factors_low,
                                      const float4 factors_high,
                                      const float gauss_denom)
{
  // radial distances of the exposure octaves, see centers_ops[] in toneequal.c
  const float4 centers_low = { -56.0f / 7.0f, -48.0f / 7.0f,
                               -40.0f / 7.0f, -32.0f / 7.0f };
  const float4 centers_high = { -24.0f / 7.0f, -16.0f / 7.0f,
                                 -8.0f / 7.0f,   0.0f / 7.0f };

  const float4 radius_low = exposure - centers_low;
  const float4 radius_high = exposure - centers_high;

  const float4 gauss_low =
    dtcl_exp(-radius_low * radius_low / gauss_denom) * factors_low;
  const float4 gauss_high =
    dtcl_exp(-radius_high * radius_high / gauss_denom) * factors_high;

  const float4 result = gauss_low + gauss_high;

  // the user-set correction is expected in [-2;+2] EV, so is the interpolated one
  return clamp(result.x + result.y + result.z + result.w, 0.25f, 4.0f);
}


kernel void toneequal_apply(read_only image2d_t in,
                            global const float *const luminance,
                            write_only image2d_t out,
                            const int width,
                            const int height,
                            const int in_width,
                            const int offset_x,
                            const int offset_y,
                            const float4 factors_low,
                            const float4 factors_high,
                            const float gauss_denom)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int xin = x + offset_x;
  const int yin = y + offset_y;

  // The radial-basis interpolation is valid in [-8; 0] EV and can quickly
  // diverge outside
  const float exposure = clamp(dtcl_log2(luminance[mad24(yin, in_width, xin)]),
                               TONEEQ_MIN_EV, TONEEQ_MAX_EV);
  const float correction =
    _pixel_correction(exposure, factors_low, factors_high, gauss_denom);

  const float4 pixel = read_imagef(in, samplerA, (int2)(xin, yin));
  write_imagef(out, (int2)(x, y), correction * pixel);
}


kernel void toneequal_display_mask(read_only image2d_t in,
                                   global const float *const luminance,
                                   write_only image2d_t out,
                                   const int width,
                                   const int height,
                                   const int in_width,
                                   const int offset_x,
                                   const int offset_y)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int xin = x + offset_x;
  const int yin = y + offset_y;

  // normalize the mask intensity between -8 EV and 0 EV for clarity,
  // and add a "gamma" 2.0 for better legibility in shadows
  const float lum = luminance[mad24(yin, in_width, xin)];
  const float intensity =
    dtcl_sqrt(fmin(fmax(lum - 0.00390625f, 0.0f) / 0.99609375f, 1.0f));

  // set gray level for the mask, copy alpha channel
  const float4 pixel = read_imagef(in, samplerA, (int2)(xin, yin));
  write_imagef(out, (int2)(x, y),
               (float4)(intensity, intensity, intensity, pixel.w));
}


/* Quantize in exposure levels evenly spaced in log by sampling.
   Mirrors quantize() from common/fast_guided_filter.h */
kernel void toneequal_quantize(global const float *const in,
                               global float *const out,
                               const int width,
                               const int height,
                               const float sampling,
                               const float clip_min,
                               const float clip_max)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float value = in[k];

  if(sampling == 0.0f)
    out[k] = value;                      // no-op
  else if(sampling == 1.0f)              // fast track
    out[k] = clamp(dtcl_exp2(floor(dtcl_log2(value))), clip_min, clip_max);
  else                                   // slow track
    out[k] = clamp(dtcl_exp2(floor(dtcl_log2(value) / sampling) * sampling),
                   clip_min, clip_max);
}


/* Separable box average over a window of size 2*radius + 1, mirroring
   dt_box_mean() from common/box_filters.cc. The window is clipped at the
   image borders and the average is taken over the samples actually seen,
   so input and output buffers must be distinct. */
#define TONEEQ_BOX_MEAN_LINE(TYPE, LENGTH, STRIDE)                       \
  TYPE sum = (TYPE)0.0f;                                                 \
  int hits = 0;                                                          \
  int i;                                                                 \
                                                                         \
  /* add up the left half of the window */                               \
  for(i = 0; i < min(radius, LENGTH); i++)                               \
  {                                                                      \
    sum += in[(size_t)i * STRIDE];                                       \
    hits++;                                                              \
  }                                                                      \
                                                                         \
  /* up to the point where we start removing values from the average */  \
  for(i = 0; i <= radius && i + radius < LENGTH; i++)                    \
  {                                                                      \
    sum += in[(size_t)(i + radius) * STRIDE];                            \
    hits++;                                                              \
    out[(size_t)i * STRIDE] = sum / (float)hits;                         \
  }                                                                      \
                                                                         \
  /* if radius > LENGTH/2 we can neither add nor remove values */        \
  for(; i <= radius && i < LENGTH; i++)                                  \
    out[(size_t)i * STRIDE] = sum / (float)hits;                         \
                                                                         \
  /* the bulk of the line */                                             \
  for(; i + radius < LENGTH; i++)                                        \
  {                                                                      \
    sum -= in[(size_t)(i - radius - 1) * STRIDE];                        \
    sum += in[(size_t)(i + radius) * STRIDE];                            \
    out[(size_t)i * STRIDE] = sum / (float)hits;                         \
  }                                                                      \
                                                                         \
  /* the end of the line, no more values to add to the average */        \
  for(; i < LENGTH; i++)                                                 \
  {                                                                      \
    sum -= in[(size_t)(i - radius - 1) * STRIDE];                        \
    hits--;                                                              \
    out[(size_t)i * STRIDE] = sum / (float)hits;                         \
  }

#define TONEEQ_BOX_MEAN_X(TYPE)                                          \
  const int y = get_global_id(0);                                        \
  if(y >= height) return;                                                \
                                                                         \
  global const TYPE *const in = input + (size_t)y * width;               \
  global TYPE *const out = output + (size_t)y * width;                   \
  TONEEQ_BOX_MEAN_LINE(TYPE, width, 1)

#define TONEEQ_BOX_MEAN_Y(TYPE)                                          \
  const int x = get_global_id(0);                                        \
  if(x >= width) return;                                                 \
                                                                         \
  global const TYPE *const in = input + x;                               \
  global TYPE *const out = output + x;                                   \
  TONEEQ_BOX_MEAN_LINE(TYPE, height, width)

kernel void toneequal_box_mean_x_2c(global const float2 *const input,
                                    global float2 *const output,
                                    const int width,
                                    const int height,
                                    const int radius)
{
  TONEEQ_BOX_MEAN_X(float2)
}

kernel void toneequal_box_mean_y_2c(global const float2 *const input,
                                    global float2 *const output,
                                    const int width,
                                    const int height,
                                    const int radius)
{
  TONEEQ_BOX_MEAN_Y(float2)
}

kernel void toneequal_box_mean_x_4c(global const float4 *const input,
                                    global float4 *const output,
                                    const int width,
                                    const int height,
                                    const int radius)
{
  TONEEQ_BOX_MEAN_X(float4)
}

kernel void toneequal_box_mean_y_4c(global const float4 *const input,
                                    global float4 *const output,
                                    const int width,
                                    const int height,
                                    const int radius)
{
  TONEEQ_BOX_MEAN_Y(float4)
}


/* Guided filter: pack guide and mask into an array of 4×1 vectors so that the
   box average of all four terms can be done in a single pass.
   Mirrors the first loop of variance_analyse() in common/fast_guided_filter.h */
kernel void toneequal_gf_pack(global const float *const guide,
                              global const float *const mask,
                              global float4 *const packed,
                              const int width,
                              const int height)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float g = guide[k];
  const float m = mask[k];

  packed[k] = (float4)(g, m, g * g, g * m);
}


/* Guided filter: turn the averaged terms into the linear blending parameters
   a and b such that mask = a * I + b */
kernel void toneequal_gf_ab(global const float4 *const packed,
                            global float2 *const ab,
                            const int width,
                            const int height,
                            const float feathering)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float4 avg = packed[k];

  // avoid division by 0
  const float d = fmax((avg.z - avg.x * avg.x) + feathering, 1e-15f);
  const float a = (avg.w - avg.x * avg.y) / d;
  const float b = avg.y - a * avg.x;

  ab[k] = (float2)(a, b);
}


/* Guided filter: blend one pixel with its a and b parameters.
   Mirrors apply_linear_blending[_w_geomean]() in common/fast_guided_filter.h */
static inline float _gf_blend(const float pixel,
                              const float2 blend,
                              const int filter)
{
  // Note : image[k] is positive at the outside of the luminance mask
  const float blended = fmax(pixel * blend.x + blend.y, TONEEQ_MIN_FLOAT);

  return (filter == TONEEQ_BLENDING_GEOMEAN)
    ? dtcl_sqrt(pixel * blended)
    : blended;
}


/* Guided filter: blend the intermediate iterations, where the parameters
   are at the same (downscaled) size as the image */
kernel void toneequal_gf_blend(global float *const image,
                               global const float2 *const ab,
                               const int width,
                               const int height,
                               const int filter)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  image[k] = _gf_blend(image[k], ab[k], filter);
}


/* Guided filter: final blend of the full size image, sampling the a and b
   parameters at the downscaled size rather than upsampling them into a
   full size buffer first */
kernel void toneequal_gf_blend_upsample(global float *const image,
                                        const int width,
                                        const int height,
                                        global const float2 *const ds_ab,
                                        const int ds_width,
                                        const int ds_height,
                                        const int filter)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float2 blend =
    dt_bilinear_sample2(ds_ab, ds_width, ds_height, x, y, width, height);
  image[k] = _gf_blend(image[k], blend, filter);
}


/* Exposure independent guided filter: pack guide and mask so that a single
   gaussian blur yields the averages of guide, guide², mask and mask × guide.
   Mirrors eigf_variance_analysis() in common/eigf.h */
kernel void toneequal_eigf_pack_4c(global const float *const guide,
                                   global const float *const mask,
                                   global float4 *const packed,
                                   const int width,
                                   const int height)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float g = guide[k];
  const float m = mask[k];

  packed[k] = (float4)(g, g * g, m, m * g);
}


kernel void toneequal_eigf_finish_4c(global float4 *const av,
                                     const int width,
                                     const int height)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float4 avg = av[k];

  // turn the averages of the squares into variance and covariance
  av[k] = (float4)(avg.x,
                   avg.y - avg.x * avg.x,
                   avg.z,
                   avg.w - avg.x * avg.z);
}


/* same as above, but specialized for the case where guide == mask */
kernel void toneequal_eigf_pack_2c(global const float *const guide,
                                   global float2 *const packed,
                                   const int width,
                                   const int height)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float g = guide[k];

  packed[k] = (float2)(g, g * g);
}


kernel void toneequal_eigf_finish_2c(global float2 *const av,
                                     const int width,
                                     const int height)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float2 avg = av[k];

  av[k] = (float2)(avg.x, avg.y - avg.x * avg.x);
}


/* Exposure independent guided filter: blending step, sampling the averages
   and variances at the downscaled size. Mirrors eigf_blending() in
   common/eigf.h */
static inline float _eigf_blend(const float pixel,
                                const float mask,
                                const float4 avg,
                                const int filter,
                                const float feathering)
{
  const float avg_g = avg.x;
  const float var_g = avg.y;
  const float avg_m = avg.z;
  const float covar_mg = avg.w;

  const float norm_g = fmax(avg_g * pixel, 1E-6f);
  const float norm_m = fmax(avg_m * mask, 1E-6f);
  const float normalized_var_guide = var_g / norm_g;
  const float normalized_covar = covar_mg / dtcl_sqrt(norm_g * norm_m);
  const float a = normalized_covar / (normalized_var_guide + feathering);
  const float b = avg_m - a * avg_g;

  const float blended = fmax(pixel * a + b, TONEEQ_MIN_FLOAT);

  return (filter == TONEEQ_BLENDING_GEOMEAN)
    ? dtcl_sqrt(pixel * blended)
    : blended;
}


kernel void toneequal_eigf_blend(global float *const image,
                                 global const float *const mask,
                                 const int width,
                                 const int height,
                                 global const float4 *const ds_av,
                                 const int ds_width,
                                 const int ds_height,
                                 const int filter,
                                 const float feathering)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float4 avg =
    dt_bilinear_sample4(ds_av, ds_width, ds_height, x, y, width, height);
  image[k] = _eigf_blend(image[k], mask[k], avg, filter, feathering);
}


/* same as above, but specialized for the case where guide == mask */
static inline float _eigf_blend_no_mask(const float pixel,
                                        const float2 avg,
                                        const int filter,
                                        const float feathering)
{
  const float avg_g = avg.x;
  const float var_g = avg.y;

  const float norm_g = fmax(avg_g * pixel, 1E-6f);
  const float normalized_var_guide = var_g / norm_g;
  const float a = normalized_var_guide / (normalized_var_guide + feathering);
  const float b = avg_g - a * avg_g;

  const float blended = fmax(pixel * a + b, TONEEQ_MIN_FLOAT);

  return (filter == TONEEQ_BLENDING_GEOMEAN)
    ? dtcl_sqrt(pixel * blended)
    : blended;
}


kernel void toneequal_eigf_blend_no_mask(global float *const image,
                                         const int width,
                                         const int height,
                                         global const float2 *const ds_av,
                                         const int ds_width,
                                         const int ds_height,
                                         const int filter,
                                         const float feathering)
{
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  if(x >= width || y >= height) return;

  const int k = mad24(y, width, x);
  const float2 avg =
    dt_bilinear_sample2(ds_av, ds_width, ds_height, x, y, width, height);
  image[k] = _eigf_blend_no_mask(image[k], avg, filter, feathering);
}