Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
crystgrain.c File Reference
#include "widgets/bauhaus.h"
#include "common/imagebuf.h"
#include "common/opencl.h"
#include "develop/iop_profile.h"
#include "math/math.h"
#include "system/macros.h"
#include "system/openmp.h"
#include "system/target_clones.h"
#include "system/mem_alloc.h"
#include "common/module_versioning.h"
#include "develop/imageop.h"
#include "develop/imageop_gui.h"
#include "iop/noise_generator.h"
#include "gui/presets.h"
#include "iop/iop_api.h"
#include <float.h>
#include <gtk/gtk.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
+ Include dependency graph for crystgrain.c:

Go to the source code of this file.

Data Structures

struct  dt_iop_crystgrain_params_t
 
struct  dt_iop_crystgrain_gui_data_t
 
struct  dt_iop_crystgrain_data_t
 
struct  dt_iop_crystgrain_kernel_t
 
struct  dt_iop_crystgrain_layer_kernel_t
 
struct  dt_iop_crystgrain_runtime_t
 
struct  dt_iop_crystgrain_color_state_t
 
struct  dt_iop_crystgrain_global_data_t
 

Macros

#define DT_CRYSTGRAIN_LAYER_KERNELS   16
 
#define DT_CRYSTGRAIN_MIN_RADIUS   1e-3f
 
#define DT_CRYSTGRAIN_SUBSAMPLES   8
 
#define DT_CRYSTGRAIN_CL_PROGRAM   36
 
#define DT_CRYSTGRAIN_REDUCESIZE   64
 

Typedefs

typedef enum dt_iop_crystgrain_mode_t dt_iop_crystgrain_mode_t
 
typedef struct dt_iop_crystgrain_params_t dt_iop_crystgrain_params_t
 
typedef struct dt_iop_crystgrain_gui_data_t dt_iop_crystgrain_gui_data_t
 
typedef struct dt_iop_crystgrain_data_t dt_iop_crystgrain_data_t
 
typedef struct dt_iop_crystgrain_kernel_t dt_iop_crystgrain_kernel_t
 
typedef struct dt_iop_crystgrain_layer_kernel_t dt_iop_crystgrain_layer_kernel_t
 
typedef struct dt_iop_crystgrain_runtime_t dt_iop_crystgrain_runtime_t
 
typedef struct dt_iop_crystgrain_color_state_t dt_iop_crystgrain_color_state_t
 
typedef struct dt_iop_crystgrain_global_data_t dt_iop_crystgrain_global_data_t
 

Enumerations

enum  dt_iop_crystgrain_mode_t {
  DT_CRYSTGRAIN_MONO = 0 ,
  DT_CRYSTGRAIN_COLOR = 1
}
 

Functions

const char * name ()
 
const char ** description (struct dt_iop_module_t *self)
 
int flags ()
 
int default_group ()
 
int default_colorspace (dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
 
void init_presets (dt_iop_module_so_t *self)
 
int legacy_params (dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
 
static __DT_CLONE_TARGETS__ unsigned int _hash_string (const char *s)
 Hash a string into a stable 32-bit seed.
 
static float _uniform_random (const uint64_t seed)
 Turn a 64-bit seed into a uniform random number in [0; 1).
 
static float _gaussian_random (const uint64_t seed_a, const uint64_t seed_b)
 Turn 2 seeds into one gaussian deviate.
 
static int _reflect_index (int i, const int max)
 Mirror indices outside the current buffer like scipy ‘boundary='symm’`.
 
static float _seed_intensity (const float filling, const float crystal_area)
 Map the requested filling ratio to the germ intensity used to plant seeds, in crystals per grid pixel.
 
static int _poisson_random (const uint64_t seed, const float mu)
 Draw one Poisson deviate of mean mu.
 
static float _polygon_area (const float radius_f, const float vertices)
 Analytic surface of one crystal, in current grid pixels squared.
 
static float _polygon_radius (const float theta, const float radius_f, const float vertices, const float rotation)
 Polar radius of one crystal boundary at angle theta.
 
static float _crystal_coverage (const int dx, const int dy, const float radius_f, const float vertices, const float rotation)
 Estimate the surface of one pixel covered by one crystal.
 
static __DT_CLONE_TARGETS__ int _create_crystal_kernel (dt_iop_crystgrain_kernel_t *const kernel, const float radius_f, const float vertices, const float rotation)
 Build one partially-occluding crystal footprint for a layer.
 
static __attribute__ ((always_inline))
 Release one crystal kernel.
 
static __DT_CLONE_TARGETS__ int _pick_layer_kernel (dt_iop_crystgrain_layer_kernel_t *const entry, const dt_iop_crystgrain_runtime_t *const rt, const uint64_t seed)
 Pick one crystal geometry for one bank entry.
 
static float _average_grain_surface (const dt_iop_crystgrain_runtime_t *const rt)
 Estimate the reference grain surface used to normalize layer capture.
 
static int _build_layer_kernel_bank (dt_iop_crystgrain_layer_kernel_t *const bank, const dt_iop_crystgrain_runtime_t *const rt, const uint64_t layer_seed)
 Build the crystal bank for one layer.
 
static void _free_layer_kernel_bank (dt_iop_crystgrain_layer_kernel_t *const bank)
 Release all crystal footprints from one layer bank.
 
static __DT_CLONE_TARGETS__ float _average_discrete_grain_surface (const dt_iop_crystgrain_runtime_t *const rt)
 Estimate the actual rasterized grain surface at the current scale.
 
static float _flat_field_capture (const float remaining, const float cap, const float mass)
 Deplete a flat light field with a continuous germ mass.
 
static float _coincident_capture (const float remaining, const float cap, const float alpha, const int count)
 Deplete one pixel with count coincident crystals.
 
static __DT_CLONE_TARGETS__ float _predict_layer_capture (const dt_iop_crystgrain_layer_kernel_t *const bank, const float layer_scale, const float remaining_fraction)
 Predict the mean captured energy of one flat-field layer.
 
static float _predict_stack_exposure (const float remaining_fraction)
 Predict the exposure compensation of one monochrome grain stack.
 
static size_t _rgb_index (const size_t pixel, const int channel)
 
static __DT_CLONE_TARGETS__ int _simulate_channel (const dt_iop_crystgrain_runtime_t *const rt, const float *const image, float *const result, float *const remaining, float *const exposure)
 Simulate one monochrome grain field from one scalar image.
 
static __DT_CLONE_TARGETS__ int _simulate_color (const dt_iop_crystgrain_runtime_t *const rt, const dt_iop_crystgrain_color_state_t *const state, float *const exposure)
 Simulate one color grain stack with shared crystal geometry.
 
static __DT_CLONE_TARGETS__ void _extract_luminance_kernel (const float *const restrict in, float *const restrict image, const int width, const int height, const dt_iop_order_iccprofile_info_t *const work_profile)
 Extract a luminance image from the RGB input buffer.
 
static __DT_CLONE_TARGETS__ void _extract_rgb_kernels (const float *const restrict in, float *const restrict image, const int width, const int height)
 Extract the three RGB light channels as scalar images.
 
static __DT_CLONE_TARGETS__ void _apply_mono_grain_kernel (const float *const restrict in, float *const restrict out, const float *const restrict image, const float *const restrict result, const int width, const int height, const float exposure)
 Apply one monochrome grain field back onto the RGB image.
 
static __DT_CLONE_TARGETS__ void _finalize_color_grain_kernel (const float *const restrict in, float *const restrict out, const float *const restrict image, const float *const restrict result, const int width, const int height, const float exposure_r, const float exposure_g, const float exposure_b, const float colorfulness)
 Finalize the three color grain channels in one pass.
 
static cl_int _upload_layer_bank (const int devid, const dt_iop_crystgrain_layer_kernel_t *const bank, cl_mem *const dev_params, cl_mem *const dev_alpha)
 Upload one layer bank to the device.
 
static int _simulate_channel_cl (const int devid, dt_iop_crystgrain_global_data_t *const gd, const dt_iop_crystgrain_runtime_t *const rt, cl_mem dev_image, cl_mem dev_result, cl_mem dev_remaining, float *const exposure)
 Simulate one grain field entirely on the OpenCL device.
 
void init_global (dt_iop_module_so_t *module)
 
void cleanup_global (dt_iop_module_so_t *module)
 
int process_cl (struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
 
void commit_params (dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
 
void init_pipe (dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
 
void cleanup_pipe (dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
 
int process (struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
 
void gui_update (struct dt_iop_module_t *self)
 
static void _mode_changed (GtkWidget *widget, dt_iop_module_t *self)
 
void gui_init (struct dt_iop_module_t *self)
 

Macro Definition Documentation

◆ DT_CRYSTGRAIN_CL_PROGRAM

#define DT_CRYSTGRAIN_CL_PROGRAM   36

Definition at line 1219 of file crystgrain.c.

◆ DT_CRYSTGRAIN_LAYER_KERNELS

#define DT_CRYSTGRAIN_LAYER_KERNELS   16

Definition at line 48 of file crystgrain.c.

◆ DT_CRYSTGRAIN_MIN_RADIUS

#define DT_CRYSTGRAIN_MIN_RADIUS   1e-3f

Definition at line 53 of file crystgrain.c.

◆ DT_CRYSTGRAIN_REDUCESIZE

#define DT_CRYSTGRAIN_REDUCESIZE   64

Definition at line 1220 of file crystgrain.c.

◆ DT_CRYSTGRAIN_SUBSAMPLES

#define DT_CRYSTGRAIN_SUBSAMPLES   8

Definition at line 366 of file crystgrain.c.

Typedef Documentation

◆ dt_iop_crystgrain_color_state_t

◆ dt_iop_crystgrain_data_t

◆ dt_iop_crystgrain_global_data_t

◆ dt_iop_crystgrain_gui_data_t

◆ dt_iop_crystgrain_kernel_t

◆ dt_iop_crystgrain_layer_kernel_t

◆ dt_iop_crystgrain_mode_t

◆ dt_iop_crystgrain_params_t

◆ dt_iop_crystgrain_runtime_t

Enumeration Type Documentation

◆ dt_iop_crystgrain_mode_t

Enumerator
DT_CRYSTGRAIN_MONO 
DT_CRYSTGRAIN_COLOR 

Definition at line 55 of file crystgrain.c.

Function Documentation

◆ __attribute__()

static __attribute__ ( (always_inline)  )
inlinestatic

Release one crystal kernel.

Definition at line 534 of file crystgrain.c.

References kernel().

◆ _apply_mono_grain_kernel()

static __DT_CLONE_TARGETS__ void _apply_mono_grain_kernel ( const float *const restrict  in,
float *const restrict  out,
const float *const restrict  image,
const float *const restrict  result,
const int  width,
const int  height,
const float  exposure 
)
static

Apply one monochrome grain field back onto the RGB image.

We loop over rows, looking for the ratio between the original and grainy luminance, then rescale RGB together so hue stays unchanged. The fully synthesized grain field is applied directly after exposure normalization, with no extra transparency stage. Since the module now lives before filmic RGB, we preserve scene-referred highlights and only clamp negative values away.

Definition at line 1158 of file crystgrain.c.

References __OMP_PARALLEL_FOR__, height, k, out, row, width, and x.

Referenced by process().

◆ _average_discrete_grain_surface()

static __DT_CLONE_TARGETS__ float _average_discrete_grain_surface ( const dt_iop_crystgrain_runtime_t *const  rt)
static

Estimate the actual rasterized grain surface at the current scale.

The grain size slider lives in continuous preview pixels, but the simulation ultimately grows integer odd-width kernels that get discretized on the raster grid. That quantization is exactly what changes the look at small preview scales, so we normalize layer capture against the average discrete footprint area sampled from a few layer banks instead of against a noisier variance proxy.

Definition at line 622 of file crystgrain.c.

References _average_grain_surface(), _build_layer_kernel_bank(), _free_layer_kernel_bank(), dt_iop_crystgrain_runtime_t::base_seed, DT_CRYSTGRAIN_LAYER_KERNELS, i, dt_iop_crystgrain_runtime_t::layers, and MIN.

Referenced by process(), and process_cl().

◆ _average_grain_surface()

static float _average_grain_surface ( const dt_iop_crystgrain_runtime_t *const  rt)
inlinestatic

Estimate the reference grain surface used to normalize layer capture.

The user-facing layer capture is expressed against the average grain size control, not against the exact randomized footprint drawn for each seed. We therefore normalize it by the area of a disc built from the average grain radius at the current preview scale. This is only the fallback used when no bank could be sampled.

Definition at line 601 of file crystgrain.c.

References DT_CRYSTGRAIN_MIN_RADIUS, dt_iop_crystgrain_runtime_t::grain_size, dt_iop_crystgrain_runtime_t::kernel_scale, M_PI_F, and MAX.

Referenced by _average_discrete_grain_surface().

◆ _build_layer_kernel_bank()

static __DT_CLONE_TARGETS__ int _build_layer_kernel_bank ( dt_iop_crystgrain_layer_kernel_t *const  bank,
const dt_iop_crystgrain_runtime_t *const  rt,
const uint64_t  layer_seed 
)
static

Build the crystal bank for one layer.

We precompute several crystal footprints for the current layer so each accepted seed can randomly pick one geometry without paying the kernel construction cost inside the hot pixel loop.

Definition at line 658 of file crystgrain.c.

References _pick_layer_kernel(), DT_CRYSTGRAIN_LAYER_KERNELS, i, and k.

Referenced by _average_discrete_grain_surface(), _simulate_channel(), _simulate_channel_cl(), _simulate_color(), and process_cl().

◆ _coincident_capture()

static float _coincident_capture ( const float  remaining,
const float  cap,
const float  alpha,
const int  count 
)
inlinestatic

Deplete one pixel with count coincident crystals.

Exact closed form of the recurrence r <- r - alpha * min(r, cap) iterated count times, which is what the simulation would do if it looped over each of the crystals a sub-pixel germ process dropped on that single pixel. Same two regimes as the continuous version above, but counted in whole crystals: steps iterations while the ceiling binds, then a geometric decay by (1 - alpha) per crystal.

Evaluating it in closed form rather than looping is what bounds the cost: the crystal count per pixel grows as the square of the downscaling factor, so a thumbnail would otherwise pay the full-resolution grain count on a fraction of the pixels.

Definition at line 736 of file crystgrain.c.

Referenced by _simulate_channel(), and _simulate_color().

◆ _create_crystal_kernel()

static __DT_CLONE_TARGETS__ int _create_crystal_kernel ( dt_iop_crystgrain_kernel_t *const  kernel,
const float  radius_f,
const float  vertices,
const float  rotation 
)
static

Build one partially-occluding crystal footprint for a layer.

Each bank entry keeps one crystal size, shape and orientation, then the stochastic look comes from stacking many layers and randomly picking between several bank entries at each seed position. The support window is rasterized to integer pixels, but each tap stores the fraction of that pixel the crystal actually covers, so a crystal that no longer spans a whole pixel degrades into one tap of fractional weight instead of collapsing onto a binary 1-pixel dot.

The rasterized weights are then rescaled so their sum matches the analytic crystal surface exactly. The subsampling quantum is 1 / 64 of a pixel, and that residual is not cosmetic: the seed intensity, the per-crystal capture cap and the flat-field exposure prediction are all expressed against this surface, so letting it drift with the rasterization would make the three of them disagree at the scales where the crystal is only a few subsamples wide. The weights are deliberately not clamped back to 1 afterwards — they are energy weights, and clamping would silently break that sum.

kernel->radius is the tight support radius, so a sub-pixel crystal reports radius 0 and a single tap, which is the fast path the simulation keys on.

Definition at line 435 of file crystgrain.c.

References _crystal_coverage(), _polygon_area(), IS_NULL_PTR, k, kernel(), MAX, and x.

Referenced by _pick_layer_kernel().

◆ _crystal_coverage()

static float _crystal_coverage ( const int  dx,
const int  dy,
const float  radius_f,
const float  vertices,
const float  rotation 
)
inlinestatic

Estimate the surface of one pixel covered by one crystal.

This is a box-filtered (area-exact) rasterization, not a signed-distance ramp: the returned weight is the fraction of the unit square centred on (dx, dy) that lies inside the crystal. That distinction is what makes the model survive downscaling. A ramp saturates at 0.5 for a crystal shrinking to a point, so a sub-pixel crystal would keep printing half a pixel worth of density and the grain would never average out; the true covered area goes to zero with the crystal surface, which is the behaviour a physical emulsion has when it is scanned at a lower resolution.

Pixels entirely inside the inradius or entirely outside the circumradius are settled by two comparisons against the half-diagonal of the pixel square, so only the boundary band — O(radius) pixels out of O(radius^2) — pays for the subsampling.

Definition at line 385 of file crystgrain.c.

References _polygon_radius(), DT_CRYSTGRAIN_SUBSAMPLES, and M_PI_F.

Referenced by _create_crystal_kernel().

◆ _extract_luminance_kernel()

static __DT_CLONE_TARGETS__ void _extract_luminance_kernel ( const float *const restrict  in,
float *const restrict  image,
const int  width,
const int  height,
const dt_iop_order_iccprofile_info_t *const  work_profile 
)
static

Extract a luminance image from the RGB input buffer.

We loop over rows so each OpenMP worker owns whole scanlines and writes to disjoint cache lines in the destination buffer.

Definition at line 1095 of file crystgrain.c.

References __OMP_PARALLEL_FOR__, dt_camera_rgb_luminance(), height, k, luminance, dt_iop_order_iccprofile_info_t::lut_in, dt_iop_order_iccprofile_info_t::lutsize, dt_iop_order_iccprofile_info_t::matrix_in, dt_iop_order_iccprofile_info_t::nonlinearlut, row, width, and x.

Referenced by process().

◆ _extract_rgb_kernels()

static __DT_CLONE_TARGETS__ void _extract_rgb_kernels ( const float *const restrict  in,
float *const restrict  image,
const int  width,
const int  height 
)
static

Extract the three RGB light channels as scalar images.

The grain model works on per-channel light fields, so we extract them together in one pass to keep the input image hot in cache and avoid three independent full-frame reads before color synthesis starts.

Definition at line 1125 of file crystgrain.c.

References __OMP_PARALLEL_FOR__, _rgb_index(), height, k, row, width, and x.

Referenced by process().

◆ _finalize_color_grain_kernel()

static __DT_CLONE_TARGETS__ void _finalize_color_grain_kernel ( const float *const restrict  in,
float *const restrict  out,
const float *const restrict  image,
const float *const restrict  result,
const int  width,
const int  height,
const float  exposure_r,
const float  exposure_g,
const float  exposure_b,
const float  colorfulness 
)
static

Finalize the three color grain channels in one pass.

The color path only needs one final RGB pass once synthesis is done. We therefore restore each channel exposure, extract the RGB grain residual around the original image, mute only its chromatic excursion, and write the final RGBA output without staging intermediate normalized buffers.

Definition at line 1188 of file crystgrain.c.

References __OMP_PARALLEL_FOR__, _rgb_index(), height, k, out, row, width, and x.

Referenced by process().

◆ _flat_field_capture()

static float _flat_field_capture ( const float  remaining,
const float  cap,
const float  mass 
)
inlinestatic

Deplete a flat light field with a continuous germ mass.

Mean-field integration of the crystal stack over one layer. One pixel receives, from every germ whose footprint overlaps it, a share alpha of a flat tone min(r, cap), where cap is the per-crystal capture ceiling and r the light still available. Summing those shares over the germ process, the total weight reaching a pixel is intensity * area, so the depletion obeys dr/dt = -min(r, cap) integrated over t in [0, mass], t counting accumulated coverage weight rather than crystals.

That ODE is piecewise-closed: r first falls linearly at rate cap while it is above the ceiling, then decays exponentially once the ceiling stops binding. Integrating instead of taking the first-order product is what keeps the prediction usable when a single pixel is swept by many sub-pixel crystals — the first-order form silently over-predicts there, and it is the exposure normalization that would drift with resolution.

Definition at line 703 of file crystgrain.c.

References weight().

Referenced by _predict_layer_capture().

◆ _free_layer_kernel_bank()

static __DT_CLONE_TARGETS__ void _free_layer_kernel_bank ( dt_iop_crystgrain_layer_kernel_t *const  bank)
static

Release all crystal footprints from one layer bank.

Definition at line 680 of file crystgrain.c.

References DT_CRYSTGRAIN_LAYER_KERNELS, and i.

Referenced by _average_discrete_grain_surface(), _simulate_channel(), _simulate_channel_cl(), _simulate_color(), and process_cl().

◆ _gaussian_random()

static float _gaussian_random ( const uint64_t  seed_a,
const uint64_t  seed_b 
)
inlinestatic

Turn 2 seeds into one gaussian deviate.

We only need gaussian draws to pick crystal size and vertex count for one whole layer, so Box-Muller is enough and keeps the implementation local to this module.

Definition at line 249 of file crystgrain.c.

References _uniform_random(), M_PI_F, and u2.

Referenced by _pick_layer_kernel(), and _poisson_random().

◆ _hash_string()

static __DT_CLONE_TARGETS__ unsigned int _hash_string ( const char *  s)
static

Hash a string into a stable 32-bit seed.

Definition at line 227 of file crystgrain.c.

Referenced by process(), and process_cl().

◆ _mode_changed()

static void _mode_changed ( GtkWidget widget,
dt_iop_module_t self 
)
static

Definition at line 1778 of file crystgrain.c.

References gui_update().

Referenced by gui_init().

◆ _pick_layer_kernel()

◆ _poisson_random()

static int _poisson_random ( const uint64_t  seed,
const float  mu 
)
inlinestatic

Draw one Poisson deviate of mean mu.

Small means use Knuth's product method, which costs O(mu) random draws. Large means (deep zoom-outs, where each grid pixel holds many sub-pixel crystals) switch to the gaussian approximation with a continuity correction, so the per-pixel cost stays O(1) no matter how far the image is downscaled. The switch sits at mu = 12, where the relative error of the gaussian approximation is already below the stochastic spread of the grain itself.

Definition at line 315 of file crystgrain.c.

References _gaussian_random(), _uniform_random(), MAX, and mu.

Referenced by _simulate_channel(), and _simulate_color().

◆ _polygon_area()

static float _polygon_area ( const float  radius_f,
const float  vertices 
)
inlinestatic

Analytic surface of one crystal, in current grid pixels squared.

The polar envelope below draws a regular polygon of vertices sides whose circumradius is radius_f, so its surface is the textbook n R^2 sin(2 pi / n) / 2. We need that surface analytically — and not only as the sum of the rasterized weights — because it is the reference the rasterization gets normalized against, and because it stays exact when the whole crystal is smaller than one pixel.

Definition at line 349 of file crystgrain.c.

References M_PI_F.

Referenced by _create_crystal_kernel().

◆ _polygon_radius()

static float _polygon_radius ( const float  theta,
const float  radius_f,
const float  vertices,
const float  rotation 
)
inlinestatic

Polar radius of one crystal boundary at angle theta.

Definition at line 357 of file crystgrain.c.

References envelope(), and M_PI_F.

Referenced by _crystal_coverage().

◆ _predict_layer_capture()

static __DT_CLONE_TARGETS__ float _predict_layer_capture ( const dt_iop_crystgrain_layer_kernel_t *const  bank,
const float  layer_scale,
const float  remaining_fraction 
)
static

Predict the mean captured energy of one flat-field layer.

The output normalization only needs the average exposure loss of the stochastic crystal stack, so we run the mean-field depletion above on a unit flat field, once per bank entry, and average the results.

The germ mass one pixel receives from an entry is intensity * area, which by construction equals -ln(1 - filling) for every entry — the invariant that makes the model scale-free. The per-crystal ceiling area * layer_scale is what still differentiates the entries, since the layer sensitivity is expressed per unit of grain surface.

Definition at line 770 of file crystgrain.c.

References _flat_field_capture(), dt_iop_crystgrain_kernel_t::area, DT_CRYSTGRAIN_LAYER_KERNELS, dt_iop_crystgrain_layer_kernel_t::footprint, i, and MAX.

Referenced by _simulate_channel(), _simulate_channel_cl(), _simulate_color(), and process_cl().

◆ _predict_stack_exposure()

static float _predict_stack_exposure ( const float  remaining_fraction)
inlinestatic

Predict the exposure compensation of one monochrome grain stack.

We reuse the exact layer bank sampled for the synthesis and update a flat-field remaining-light fraction alongside the real image simulation. If r_l is the remaining light fraction before layer l, the recurrence is

r_(l+1) = max(r_l - mean_i(E_i(r_l)), 0)

with r_0 = 1. The synthesized stack therefore transmits on average 1 - r_L, so the final global exposure correction is simply

exposure = 1 / (1 - r_L).

This keeps the output normalization tied to the current grain size, filling ratio and layer sensitivity without measuring any image averages.

Definition at line 801 of file crystgrain.c.

Referenced by _simulate_channel(), _simulate_channel_cl(), _simulate_color(), and process_cl().

◆ _reflect_index()

static int _reflect_index ( int  i,
const int  max 
)
inlinestatic

Mirror indices outside the current buffer like scipy ‘boundary='symm’`.

Definition at line 260 of file crystgrain.c.

References i, and max.

Referenced by _simulate_channel(), and _simulate_color().

◆ _rgb_index()

static size_t _rgb_index ( const size_t  pixel,
const int  channel 
)
inlinestatic

◆ _seed_intensity()

static float _seed_intensity ( const float  filling,
const float  crystal_area 
)
inlinestatic

Map the requested filling ratio to the germ intensity used to plant seeds, in crystals per grid pixel.

The crystal population is a boolean (germ-grain) model: germs are a Poisson point process of intensity lambda germs per unit surface, and every germ grows one crystal of surface A. The probability that a given location is left uncovered is exp(-lambda * A), so matching a requested filling ratio f amounts to solving 1 - f = exp(-lambda * A), that is lambda = -ln(1 - f) / A.

We express that intensity per pixel of the current grid, so A is the crystal surface measured in current grid pixels. Note the invariant that makes the whole model scale-free: the germ mass reaching one pixel is lambda * A = -ln(1 - f), independent of the crystal surface and therefore of the resolution the pipeline happens to run at.

Unlike a per-pixel Bernoulli draw, this intensity is not capped at one crystal per pixel. Below 100% zoom a crystal eventually becomes smaller than one grid pixel and several of them land in the same pixel; that multiplicity is exactly what averages the grain out at low resolution, so it must be sampled, not truncated.

Definition at line 298 of file crystgrain.c.

References CLAMPS, and MAX.

Referenced by _pick_layer_kernel().

◆ _simulate_channel()

static __DT_CLONE_TARGETS__ int _simulate_channel ( const dt_iop_crystgrain_runtime_t *const  rt,
const float *const  image,
float *const  result,
float *const  remaining,
float *const  exposure 
)
static

Simulate one monochrome grain field from one scalar image.

We loop over seed candidates, looking for pixels that still have photons left to capture on the current layer. Each seed first picks one crystal footprint from the precomputed layer bank, then averages the local layer energy over that footprint so one whole crystal prints one uniform tone. The crystal is finally grown over that footprint while capping the accumulated capture by the local layer capacity so the growth stays energy-conserving. Most pixels live away from image borders, so we keep a fast path there with direct indexing and only fall back to reflected coordinates near the edges.

Definition at line 826 of file crystgrain.c.

References _build_layer_kernel_bank(), _coincident_capture(), _free_layer_kernel_bank(), _poisson_random(), _predict_layer_capture(), _predict_stack_exposure(), _reflect_index(), dt_iop_crystgrain_runtime_t::base_seed, DT_CRYSTGRAIN_LAYER_KERNELS, dt_iop_crystgrain_layer_kernel_t::footprint, height, dt_iop_crystgrain_runtime_t::height, dt_iop_crystgrain_layer_kernel_t::intensity, dt_iop_crystgrain_runtime_t::inv_scale, kernel(), dt_iop_crystgrain_runtime_t::layer_scale, dt_iop_crystgrain_runtime_t::layers, dt_iop_crystgrain_kernel_t::radius, dt_iop_crystgrain_runtime_t::roi_x, dt_iop_crystgrain_runtime_t::roi_y, splitmix32(), width, dt_iop_crystgrain_runtime_t::width, and x.

Referenced by process().

◆ _simulate_channel_cl()

static int _simulate_channel_cl ( const int  devid,
dt_iop_crystgrain_global_data_t *const  gd,
const dt_iop_crystgrain_runtime_t *const  rt,
cl_mem  dev_image,
cl_mem  dev_result,
cl_mem  dev_remaining,
float *const  exposure 
)
static

Simulate one grain field entirely on the OpenCL device.

The host precomputes a small bank of crystal geometries for the current layer and uploads only their analytic parameters. Each layer is then dispatched once on the GPU: every pixel decides whether it spawns a crystal, averages the input and current light field over that footprint, then updates the output and remaining light in place through atomic writes.

Definition at line 1301 of file crystgrain.c.

References _build_layer_kernel_bank(), _free_layer_kernel_bank(), _predict_layer_capture(), _predict_stack_exposure(), _upload_layer_bank(), dt_iop_crystgrain_runtime_t::base_seed, DT_CRYSTGRAIN_LAYER_KERNELS, dt_opencl_enqueue_copy_buffer_to_buffer(), dt_opencl_enqueue_kernel_2d(), dt_opencl_release_mem_object(), dt_opencl_set_kernel_arg(), height, dt_iop_crystgrain_runtime_t::height, dt_iop_crystgrain_runtime_t::inv_scale, dt_iop_crystgrain_global_data_t::kernel_simulate_layer, dt_iop_crystgrain_global_data_t::kernel_zero_scalar, dt_iop_crystgrain_runtime_t::layer_scale, dt_iop_crystgrain_runtime_t::layers, dt_iop_crystgrain_runtime_t::roi_x, dt_iop_crystgrain_runtime_t::roi_y, ROUNDUP, width, and dt_iop_crystgrain_runtime_t::width.

Referenced by process_cl().

◆ _simulate_color()

static __DT_CLONE_TARGETS__ int _simulate_color ( const dt_iop_crystgrain_runtime_t *const  rt,
const dt_iop_crystgrain_color_state_t *const  state,
float *const  exposure 
)
static

Simulate one color grain stack with shared crystal geometry.

Real color film is not achromatic either: it stacks blue-, green- and red-sensitive monochrome emulsions in depth, each with its own crystal population. This routine therefore keeps one sequential remaining-light model, but assigns each layer to one spectral sub-stack in blue/green/red order. That keeps the physical "light goes through upper layers first" behavior while avoiding the over-correlated all-channels-at-once look.

Definition at line 961 of file crystgrain.c.

References _build_layer_kernel_bank(), _coincident_capture(), _free_layer_kernel_bank(), _poisson_random(), _predict_layer_capture(), _predict_stack_exposure(), _reflect_index(), _rgb_index(), _uniform_random(), dt_iop_crystgrain_runtime_t::base_seed, dt_iop_crystgrain_runtime_t::channel_correlation, DT_CRYSTGRAIN_LAYER_KERNELS, dt_iop_crystgrain_layer_kernel_t::footprint, height, dt_iop_crystgrain_runtime_t::height, dt_iop_crystgrain_layer_kernel_t::intensity, dt_iop_crystgrain_runtime_t::inv_scale, kernel(), dt_iop_crystgrain_runtime_t::layer_scale, dt_iop_crystgrain_runtime_t::layers, dt_iop_crystgrain_kernel_t::radius, dt_iop_crystgrain_runtime_t::roi_x, dt_iop_crystgrain_runtime_t::roi_y, splitmix32(), state, width, dt_iop_crystgrain_runtime_t::width, and x.

Referenced by process().

◆ _uniform_random()

static float _uniform_random ( const uint64_t  seed)
inlinestatic

Turn a 64-bit seed into a uniform random number in [0; 1).

Definition at line 237 of file crystgrain.c.

References splitmix32().

Referenced by _gaussian_random(), _pick_layer_kernel(), _poisson_random(), and _simulate_color().

◆ _upload_layer_bank()

static cl_int _upload_layer_bank ( const int  devid,
const dt_iop_crystgrain_layer_kernel_t *const  bank,
cl_mem *const  dev_params,
cl_mem *const  dev_alpha 
)
static

Upload one layer bank to the device.

The device used to re-derive every tap's coverage analytically from (radius, vertices, rotation), twice per crystal and once per tap. It now reads the very same weights the CPU path rasterizes, uploaded as one dense (2 * radius + 1)^2 map per bank entry: cheaper in the inner loop, and it removes a standing CPU/GPU divergence — the two coverage functions had already drifted apart, the device one short-circuiting any crystal with 5 vertices or more to a disc.

params carries what the kernel still needs per entry: the germ intensity, the crystal surface, the support radius (0 for a sub-pixel crystal, which is the fast path) and the offset of its weight map.

Definition at line 1237 of file crystgrain.c.

References dt_iop_crystgrain_kernel_t::alpha, dt_iop_crystgrain_kernel_t::area, dt_iop_crystgrain_kernel_t::count, DT_CRYSTGRAIN_LAYER_KERNELS, dt_opencl_alloc_device_buffer(), dt_opencl_copy_host_to_device_constant(), dt_opencl_release_mem_object(), dt_opencl_write_buffer_to_device(), dt_iop_crystgrain_kernel_t::dx, dt_iop_crystgrain_kernel_t::dy, dt_iop_crystgrain_layer_kernel_t::footprint, i, dt_iop_crystgrain_layer_kernel_t::intensity, IS_NULL_PTR, dt_iop_crystgrain_kernel_t::radius, and TRUE.

Referenced by _simulate_channel_cl(), and process_cl().

◆ cleanup_global()

◆ cleanup_pipe()

void cleanup_pipe ( dt_iop_module_t self,
dt_dev_pixelpipe_t pipe,
dt_dev_pixelpipe_iop_t piece 
)

Definition at line 1634 of file crystgrain.c.

References dt_dev_pixelpipe_iop_t::data, and dt_free_align.

◆ commit_params()

void commit_params ( dt_iop_module_t self,
dt_iop_params_t p1,
dt_dev_pixelpipe_t pipe,
dt_dev_pixelpipe_iop_t piece 
)

Definition at line 1612 of file crystgrain.c.

References d, dt_dev_pixelpipe_iop_t::data, and p.

◆ default_colorspace()

int default_colorspace ( dt_iop_module_t self,
dt_dev_pixelpipe_t pipe,
const dt_dev_pixelpipe_iop_t piece 
)

Definition at line 179 of file crystgrain.c.

References IOP_CS_RGB.

◆ default_group()

int default_group ( )

Definition at line 174 of file crystgrain.c.

References IOP_GROUP_EFFECTS.

◆ description()

const char ** description ( struct dt_iop_module_t self)

Definition at line 160 of file crystgrain.c.

References dt_iop_set_description().

◆ flags()

int flags ( )

Definition at line 169 of file crystgrain.c.

References IOP_FLAGS_INCLUDE_IN_STYLES, and IOP_FLAGS_SUPPORTS_BLENDING.

◆ gui_init()

◆ gui_update()

void gui_update ( struct dt_iop_module_t self)
Todo:
check why needed
Todo:
by hand

Definition at line 1768 of file crystgrain.c.

References DT_CRYSTGRAIN_COLOR, dt_iop_gui_data(), g, p, and dt_iop_module_t::params.

Referenced by _mode_changed(), and gui_init().

◆ init_global()

◆ init_pipe()

void init_pipe ( dt_iop_module_t self,
dt_dev_pixelpipe_t pipe,
dt_dev_pixelpipe_iop_t piece 
)

◆ init_presets()

◆ legacy_params()

int legacy_params ( dt_iop_module_t self,
const void *const  old_params,
const int  old_version,
void new_params,
const int  new_version 
)

Definition at line 209 of file crystgrain.c.

References n.

◆ name()

const char * name ( )

Definition at line 155 of file crystgrain.c.

◆ process()

◆ process_cl()

int process_cl ( struct dt_iop_module_t self,
const dt_dev_pixelpipe_t pipe,
const dt_dev_pixelpipe_iop_t piece,
cl_mem  dev_in,
cl_mem  dev_out 
)

Definition at line 1398 of file crystgrain.c.

References _average_discrete_grain_surface(), _build_layer_kernel_bank(), _free_layer_kernel_bank(), _hash_string(), _predict_layer_capture(), _predict_stack_exposure(), _simulate_channel_cl(), _upload_layer_bank(), dt_iop_crystgrain_runtime_t::base_seed, dt_iop_crystgrain_runtime_t::channel_correlation, CLAMPS, d, dt_dev_pixelpipe_iop_t::data, dt_dev_pixelpipe_t::dev, dt_dev_pixelpipe_t::devid, DT_CRYSTGRAIN_LAYER_KERNELS, DT_CRYSTGRAIN_MONO, dt_dev_get_module_scale(), dt_ioppr_build_iccprofile_params_cl(), dt_ioppr_free_iccprofile_params_cl(), dt_ioppr_get_pipe_work_profile_info(), dt_opencl_alloc_device_buffer(), dt_opencl_enqueue_copy_buffer_to_buffer(), dt_opencl_enqueue_copy_image(), dt_opencl_enqueue_kernel_2d(), dt_opencl_release_mem_object(), dt_opencl_set_kernel_arg(), error(), FALSE, dt_image_t::filename, dt_iop_module_t::global_data, height, dt_iop_roi_t::height, dt_develop_t::image_storage, dt_iop_crystgrain_runtime_t::inv_scale, IS_NULL_PTR, dt_iop_crystgrain_global_data_t::kernel_apply_mono, dt_iop_crystgrain_global_data_t::kernel_extract_luminance, dt_iop_crystgrain_global_data_t::kernel_extract_rgb, dt_iop_crystgrain_global_data_t::kernel_finalize_color, dt_iop_crystgrain_global_data_t::kernel_simulate_layer_color, dt_iop_crystgrain_global_data_t::kernel_zero_rgb, dt_iop_crystgrain_runtime_t::layer_scale, dt_iop_crystgrain_runtime_t::layers, MAX, dt_dev_pixelpipe_iop_t::roi_in, dt_dev_pixelpipe_iop_t::roi_out, dt_iop_crystgrain_runtime_t::roi_x, dt_iop_crystgrain_runtime_t::roi_y, ROUNDUP, TRUE, width, dt_iop_crystgrain_runtime_t::width, dt_iop_roi_t::width, dt_iop_roi_t::x, and dt_iop_roi_t::y.