Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
chroma.h
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21// Anisotropic (divergence-form) chrominance-coherence stage (CPU + OpenCL).
22// Public API of this highlights harmonic-transposition module (a compiled TU). Include
23// this header to call into the module; internals are static in the .c. See common.h.
24
25#include "common/opencl.h"
27#include <math.h>
28#include <stdint.h>
29
30void _aniso_tensor(const float *const restrict luminance, float *const restrict tensor_xx,
31 float *const restrict tensor_xy, float *const restrict tensor_yy, float *const restrict scratch,
32 const int region_w, const int region_h);
33
34// Obstacle-projected variant (stage A of the core-shelf fix): the saturation floors are
35// information, not just output clamps -- a clipped channel's ratio can never fall below
36// clip0_c / L. Projecting after EVERY smoothing step (u = max(u, obs)) turns the diffusion
37// into a monotone obstacle-problem relaxation: the bound's influence spreads smoothly through
38// the field instead of leaving an exactly-flat floor-clamped shelf at the reassembly.
39//
40// MATHS BRIDGE -- Step 8 explicit trace-form relaxation under the obstacle (article §"The update
41// rules", the r_c <- max(r_c + 0.18(D_xx d_xx r + 2 D_xy d_xy r + D_yy d_yy r), c0/L) update, and
42// §"The saturation floors, as obstacles"): one explicit Euler step of dr/dt = tr(D Hess r) (the
43// trace form of the steered diffusion) with time step 0.18, followed by the projection
44// r <- max(r, obstacle). Every neighbour weight is nonnegative, so the projected scheme is
45// monotone and converges to the variational-inequality solution of
46// min int grad(r)^T D grad(r) s.t. r >= c0/L. This is the coarse-to-fine ladder's per-level solver
47// and, for the large-core (pyramid) path, the primary Step-8 estimator.
48void _aniso_iterate_obs(float *const restrict field, const float *const restrict obstacle,
49 const uint8_t *const restrict hole, const float *const restrict tensor_xx,
50 const float *const restrict tensor_xy, const float *const restrict tensor_yy,
51 float *const restrict tmp, const int region_w, const int region_h, const int iters,
52 const int box_x_lo, const int box_y_lo, const int box_x_hi, const int box_y_hi);
53
54static inline float _aniso_edge_w(const float *const restrict tensor_xx, const float *const restrict tensor_xy,
55 const float *const restrict tensor_yy, const size_t i, const size_t j,
56 const int offset_x, const int offset_y)
57{
58 const float avg_xx = 0.5f * (tensor_xx[i] + tensor_xx[j]); // a = D_xx averaged across the edge
59 const float avg_yy = 0.5f * (tensor_yy[i] + tensor_yy[j]); // c = D_yy averaged across the edge
60 const float limit = fminf(avg_xx, avg_yy);
61 const float cross
62 = CLAMP(0.5f * (tensor_xy[i] + tensor_xy[j]), -limit, limit); // b clamped to +-min(a,c) >= 0 guarantee
63
64 if(offset_y == 0) return fmaxf(avg_xx - fabsf(cross), 1e-4f); // axis x: a - |b|
65 if(offset_x == 0) return fmaxf(avg_yy - fabsf(cross), 1e-4f); // axis y: c - |b|
66 if(offset_x == offset_y) return fmaxf(cross, 0.f); // diagonal (+,+) / (-,-): +b/2 share
67 return fmaxf(-cross, 0.f); // diagonal (+,-) / (-,+): -b/2 share
68}
69
70// Divergence-form direct solve: fills the three ratio planes of s1 (rn*4 layout) over the
71// pixels where vld_an < 0.5 (identical hole for the three channels in the coefficient-field
72// mode: the all-clip core). `planes` is rn*4 float scratch (tensor + scratch). Returns 1 on
73// success, 0 to fall back to the explicit path.
74//
75// MATHS BRIDGE -- Step 8 PRIMARY estimator (article §"The update rules", "divergence-form exact
76// solve"): the exact steady state div(D grad r) = 0 (no obstacle in the matrix; the floor is
77// applied by the polish pass afterward), r|dOmega = r_valid Dirichlet. Assembles the Weickert
78// nonnegativity graph Laplacian (diagonal = sum of the 8 edge weights _aniso_edge_w, off-diagonals
79// = -w_ij, Dirichlet neighbours eliminated into the RHS), then ONE sparse Cholesky factorization
80// serves the three channel right-hand sides. Used when the core fits DT_HL_SPARSE_MAX unknowns;
81// larger cores take _aniso_iterate_obs on the coarse-to-fine pyramid instead.
82int _aniso_div_solve(float *const restrict ratios, const float *const restrict valid,
83 const float *const restrict luminance, float *const restrict scratch_planes,
84 const int region_w, const int region_h, const dt_dev_pixelpipe_t *pipe);
85
86void _aniso_chroma(_hl_region_ctx_t *const ctx);
87
88#if defined(HAVE_OPENCL) && DT_HL_SPARSE_SOLVE && (DT_HL_ANISO_SOLVER == 2)
89// Divergence-form structure-steered chroma diffusion on the device (smooth the colour ratios
90// along image edges, never across them), mirroring the DT_HL_ANISO_CHROMA production block
91// with _aniso_div_solve: the structure tensor is computed on the GPU from the recovered
92// brightness, and the host downloads only the all-clip mask (the sparse symbolic analysis
93// needs it) plus the COMPACT per-unknown 8-edge weight list -- the matrix values -- for the
94// exact CPU assembly; the three right-hand sides are built on-device from the same weight
95// buffer, so no full-res float plane crosses the bus. Any change here must be mirrored in
96// _aniso_div_solve (CPU) and re-validated with the HL_ANISOCL_TEST self-test
97// (_aniso_stage_cl_selftest).
98//
99// MATHS BRIDGE -- Step 8 chrominance coherence (article §"Chrominance coherence"): the
100// divergence-form exact solve of div(D grad r) = 0, r|dOmega = r_valid, restricted to the all-clip
101// hole (partial-clip pixels are Dirichlet anchors). Weickert nonnegativity graph Laplacian from D
102// (edge weights = hl_aniso_weights), ONE Cholesky factor for the three channel RHS, then a full-res
103// obstacle-projected polish (r >= c0/L) since a direct factorization cannot project mid-solve.
104// Cores above DT_HL_SPARSE_MAX fall to _aniso_pyramid_cl. Reassembly RGB = L_sum * r.
105cl_int _aniso_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem clip0,
106 const int region_w, const int region_h, const float radius, const dt_dev_pixelpipe_t *pipe);
107#endif
int _aniso_div_solve(float *const restrict ratios, const float *const restrict valid, const float *const restrict luminance, float *const restrict scratch_planes, const int region_w, const int region_h, const dt_dev_pixelpipe_t *pipe)
Definition chroma.c:155
void _aniso_iterate_obs(float *const restrict field, const float *const restrict obstacle, const uint8_t *const restrict hole, const float *const restrict tensor_xx, const float *const restrict tensor_xy, const float *const restrict tensor_yy, float *const restrict tmp, const int region_w, const int region_h, const int iters, const int box_x_lo, const int box_y_lo, const int box_x_hi, const int box_y_hi)
Definition chroma.c:102
void _aniso_tensor(const float *const restrict luminance, float *const restrict tensor_xx, float *const restrict tensor_xy, float *const restrict tensor_yy, float *const restrict scratch, const int region_w, const int region_h)
Definition chroma.c:31
void _aniso_chroma(_hl_region_ctx_t *const ctx)
Definition chroma.c:322
static float _aniso_edge_w(const float *const restrict tensor_xx, const float *const restrict tensor_xy, const float *const restrict tensor_yy, const size_t i, const size_t j, const int offset_x, const int offset_y)
Definition chroma.h:54
float *const restrict luminance