Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
crystgrain.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
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 Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18#ifdef HAVE_CONFIG_H
19#include "config.h"
20#endif
21
22#include "bauhaus/bauhaus.h"
23#include "common/imagebuf.h"
24#include "common/opencl.h"
25#include "common/iop_profile.h"
26#include "common/math.h"
27#include "develop/imageop.h"
28#include "develop/imageop_gui.h"
30#include "gui/presets.h"
31#include "gui/gtk.h"
32#include "iop/iop_api.h"
33
34#include <float.h>
35#include <gtk/gtk.h>
36#include <math.h>
37#include <stdint.h>
38#include <stdlib.h>
39#include <string.h>
40
42
43#define DT_CRYSTGRAIN_LAYER_KERNELS 16
44
46{
47 DT_CRYSTGRAIN_MONO = 0, // $DESCRIPTION: "B&W"
48 DT_CRYSTGRAIN_COLOR = 1 // $DESCRIPTION: "color"
50
52{
53 dt_iop_crystgrain_mode_t mode; // $DEFAULT: DT_CRYSTGRAIN_MONO $DESCRIPTION: "mode"
54 float filling; // $MIN: 0.0 $MAX: 95.0 $DEFAULT: 25.0 $DESCRIPTION: "Average layer filling"
55 float grain_size; // $MIN: 1.0 $MAX: 31.0 $DEFAULT: 4.0 $DESCRIPTION: "Crystals average size"
56 int layers; // $MIN: 1 $MAX: 64 $DEFAULT: 30 $DESCRIPTION: "Crystals layers"
57 float size_stddev; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.25 $DESCRIPTION: "Crystals size variability"
58 float layer_capture; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "Layer sensitivity"
59 float channel_correlation; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 67.0 $DESCRIPTION: "Inter-channel grain correlation"
60 float colorspace_saturation; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 67.0 $DESCRIPTION: "Grain colorfulness"
62
74
86
88{
89 int count;
90 int radius;
91 float radius_f;
92 float area;
93 int *dx;
94 int *dy;
95 float *alpha;
97
106
123
130
131#ifdef HAVE_OPENCL
143#endif
144
145
146const char *name()
147{
148 return _("Photographic grain");
149}
150
151const char **description(struct dt_iop_module_t *self)
152{
153 return dt_iop_set_description(self, _("simulate photographic grain from stacked silver-halide crystal layers"),
154 _("creative"),
155 _("non-linear, RGB, scene-referred"),
156 _("non-linear, RGB"),
157 _("non-linear, RGB, scene-referred"));
158}
159
164
166{
167 return IOP_GROUP_EFFECTS;
168}
169
171{
172 return IOP_CS_RGB;
173}
174
176{
178
180 p.filling = 85.0f;
181 p.grain_size = 4.0f;
182 p.layers = 30;
183 p.size_stddev = 0.25f;
184 p.layer_capture = 0.0f;
185 p.channel_correlation = 67.0f;
186 p.colorspace_saturation = 67.0f;
187 dt_gui_presets_add_generic(_("color grain"), self->op, self->version(), &p, sizeof(p), 1,
189
190 p.mode = DT_CRYSTGRAIN_MONO;
191 p.filling = 25.0f;
192 p.grain_size = 4.0f;
193 p.layers = 30;
194 p.size_stddev = 0.25f;
195 p.layer_capture = 0.0f;
196 p.channel_correlation = 67.0f;
197 p.colorspace_saturation = 67.0f;
198 dt_gui_presets_add_generic(_("B&W grain"), self->op, self->version(), &p, sizeof(p), 1,
200}
201
202int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
203 const int new_version)
204{
205 if((old_version == 1 || old_version == 8) && new_version == 9)
206 {
207 const dt_iop_crystgrain_params_t *o = old_params;
208 dt_iop_crystgrain_params_t *n = new_params;
209 *n = *o;
210 return 0;
211 }
212
213 return 1;
214}
215
220static unsigned int _hash_string(const char *s)
221{
222 unsigned int h = 0;
223 while(*s) h = 33 * h ^ (unsigned int)*s++;
224 return h;
225}
226
230static inline float _uniform_random(const uint64_t seed)
231{
232 return splitmix32(seed) * 0x1.0p-32f;
233}
234
242static inline float _gaussian_random(const uint64_t seed_a, const uint64_t seed_b)
243{
244 const float u1 = fmaxf(_uniform_random(seed_a), FLT_MIN);
245 const float u2 = _uniform_random(seed_b);
246 return sqrtf(-2.0f * logf(u1)) * cosf(2.0f * M_PI_F * u2);
247}
248
253static inline int _reflect_index(int i, const int max)
254{
255 if(max <= 1) return 0;
256
257 while(i < 0 || i >= max)
258 {
259 if(i < 0)
260 i = -i - 1;
261 else
262 i = 2 * max - i - 1;
263 }
264
265 return i;
266}
267
280static inline float _seed_probability(const float filling, const float crystal_area)
281{
282 const float clamped_filling = CLAMPS(filling, 0.0f, 0.9999f);
283 if(crystal_area <= 1.0f) return clamped_filling;
284 return 1.0f - powf(1.0f - clamped_filling, 1.0f / crystal_area);
285}
286
296static inline float _crystal_coverage(const int dx, const int dy, const float radius_f, const float vertices,
297 const float rotation)
298{
299 const float local_radius = hypotf((float)dx, (float)dy);
300 float signed_distance = 0.0f;
301 const float theta = atan2f((float)dy, (float)dx);
302 const float envelope = cosf(M_PI_F / vertices)
303 / cosf((2.0f * asinf(cosf(vertices * (theta + rotation))) + M_PI_F)
304 / (2.0f * vertices));
305 const float polygon_radius = radius_f * envelope;
306 signed_distance = polygon_radius - local_radius;
307 return CLAMPS(signed_distance + 0.5f, 0.0f, 1.0f);
308}
309
320static int _create_crystal_kernel(dt_iop_crystgrain_kernel_t *const kernel, const float radius_f,
321 const float vertices, const float rotation)
322{
323 memset(kernel, 0, sizeof(*kernel));
324
325 const int radius = MAX((int)ceilf(radius_f + 0.5f), 1);
326 const int width = 2 * radius + 1;
327 int count = 0;
328 float area = 0.0f;
329
330 for(int y = 0; y < width; y++)
331 {
332 for(int x = 0; x < width; x++)
333 {
334 const float alpha = _crystal_coverage(x - radius, y - radius, radius_f, vertices, rotation);
335 if(alpha > FLT_EPSILON)
336 {
337 count++;
338 area += alpha;
339 }
340 }
341 }
342
343 if(count <= 0 || area <= FLT_EPSILON) return 1;
344
345 kernel->dx = malloc(sizeof(int) * count);
346 kernel->dy = malloc(sizeof(int) * count);
347 kernel->alpha = malloc(sizeof(float) * count);
348 if(IS_NULL_PTR(kernel->dx) || IS_NULL_PTR(kernel->dy) || IS_NULL_PTR(kernel->alpha))
349 {
350 free(kernel->dx);
351 free(kernel->dy);
352 free(kernel->alpha);
353 memset(kernel, 0, sizeof(*kernel));
354 return 1;
355 }
356
357 kernel->count = count;
358 kernel->radius = radius;
359 kernel->radius_f = radius_f;
360 kernel->area = area;
361
362 int k = 0;
363 for(int y = 0; y < width; y++)
364 {
365 for(int x = 0; x < width; x++)
366 {
367 const float alpha = _crystal_coverage(x - radius, y - radius, radius_f, vertices, rotation);
368 if(alpha > FLT_EPSILON)
369 {
370 kernel->dx[k] = x - radius;
371 kernel->dy[k] = y - radius;
372 kernel->alpha[k] = alpha;
373 k++;
374 }
375 }
376 }
377
378 return 0;
379}
380
384static inline __attribute__((always_inline)) void _free_crystal_kernel(dt_iop_crystgrain_kernel_t *const kernel)
385{
386 free(kernel->dx);
387 free(kernel->dy);
388 free(kernel->alpha);
389 memset(kernel, 0, sizeof(*kernel));
390}
391
397 const dt_iop_crystgrain_runtime_t *const rt, const uint64_t seed)
398{
399 memset(entry, 0, sizeof(*entry));
400
401 // Let the grain follow the preview scaling below 100% so zoomed-out views
402 // stay visually coherent, but clamp at 100% to avoid inventing larger
403 // crystals when the user zooms in past the native image scale.
404 const float mean_size = MAX(rt->grain_size * rt->kernel_scale, 1.0f);
405 const float max_size = MAX(3.0f * mean_size, 1.0f);
406
407 for(int attempt = 0; attempt < 8; attempt++)
408 {
409 const float vertices = CLAMPS(6.0f + 1.5f * _gaussian_random(seed + 17u + attempt * 31u,
410 seed + 23u + attempt * 37u),
411 3.0f, 10.0f);
412 const float rotation = 2.0f * M_PI_F * _uniform_random(seed + 101u + attempt * 43u);
413 const float log_size = logf(mean_size) + rt->size_stddev * _gaussian_random(seed + 151u + attempt * 47u,
414 seed + 181u + attempt * 53u);
415 const float random_size = CLAMPS(expf(log_size), 1.0f, max_size);
416 const float radius_f = MAX(0.5f * (random_size - 1.0f), 0.5f);
417
418 if(_create_crystal_kernel(&entry->footprint, radius_f, vertices, rotation) == 0)
419 {
420 entry->probability = _seed_probability(rt->filling, entry->footprint.area);
421 entry->vertices = vertices;
422 entry->rotation = rotation;
423 entry->width = 2 * entry->footprint.radius + 1;
424 return 0;
425 }
426 }
427
428 if(_create_crystal_kernel(&entry->footprint, 0.5f, 4.0f, 0.0f) != 0) return 1;
429
430 entry->probability = _seed_probability(rt->filling, entry->footprint.area);
431 entry->vertices = 4.0f;
432 entry->rotation = 0.0f;
433 entry->width = 1;
434 return 0;
435}
436
445static inline float _average_grain_surface(const dt_iop_crystgrain_runtime_t *const rt)
446{
447 const float mean_size = MAX(rt->grain_size * rt->kernel_scale, 1.0f);
448 const float mean_radius = MAX(0.5f * (mean_size - 1.0f), 0.5f);
449 return M_PI_F * mean_radius * mean_radius;
450}
451
453 const dt_iop_crystgrain_runtime_t *const rt, const uint64_t layer_seed);
455
468{
469 const int sampled_layers = MIN(rt->layers, 4);
470 if(sampled_layers <= 0) return _average_grain_surface(rt);
471
472 float total_area = 0.0f;
473 int total_kernels = 0;
474
475 for(int layer = 0; layer < sampled_layers; layer++)
476 {
478 const uint64_t layer_seed = rt->base_seed + layer * 4099u;
479
480 if(_build_layer_kernel_bank(bank, rt, layer_seed) != 0)
481 return _average_grain_surface(rt);
482
483 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
484 total_area += bank[i].footprint.area;
485
486 total_kernels += DT_CRYSTGRAIN_LAYER_KERNELS;
488 }
489
490 return (total_area > FLT_EPSILON && total_kernels > 0)
491 ? total_area / total_kernels
493}
494
504 const dt_iop_crystgrain_runtime_t *const rt, const uint64_t layer_seed)
505{
507
508 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
509 {
510 const uint64_t kernel_seed = layer_seed ^ ((uint64_t)(i + 1) * 0xd1342543de82ef95ull);
511 if(_pick_layer_kernel(&bank[i], rt, kernel_seed) != 0)
512 {
513 for(int k = 0; k < i; k++) _free_crystal_kernel(&bank[k].footprint);
514 return 1;
515 }
516 }
517
518 return 0;
519}
520
526{
527 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++) _free_crystal_kernel(&bank[i].footprint);
528}
529
550static float _predict_layer_capture(const dt_iop_crystgrain_layer_kernel_t *const bank, const float layer_scale,
551 const float remaining_fraction)
552{
553 double capture = 0.0;
554
555 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
556 {
557 const float area = bank[i].footprint.area;
558 const float captured = fminf(remaining_fraction, area * layer_scale);
559 capture += bank[i].probability * area * captured;
560 }
561
562 return MAX((float)(capture / DT_CRYSTGRAIN_LAYER_KERNELS), 0.0f);
563}
564
582static inline float _predict_stack_exposure(const float remaining_fraction)
583{
584 const float transmitted = 1.0f - remaining_fraction;
585 return (transmitted > FLT_EPSILON) ? 1.0f / transmitted : 1.0f;
586}
587
588static inline size_t _rgb_index(const size_t pixel, const int channel)
589{
590 return 4 * pixel + channel;
591}
592
607static int _simulate_channel(const dt_iop_crystgrain_runtime_t *const rt, const float *const image, float *const result,
608 float *const remaining, float *const exposure)
609{
610 const int width = rt->width;
611 const int height = rt->height;
612 const size_t npixels = (size_t)width * height;
613 float predicted_remaining = 1.0f;
614 memset(result, 0, sizeof(float) * npixels);
615 memcpy(remaining, image, sizeof(float) * npixels);
616
617 for(int layer = 0; layer < rt->layers; layer++)
618 {
620 const uint64_t layer_seed = rt->base_seed + layer * 4099u;
621 if(_build_layer_kernel_bank(kernel_bank, rt, layer_seed) != 0) return 1;
622 predicted_remaining = fmaxf(predicted_remaining
623 - _predict_layer_capture(kernel_bank, rt->layer_scale, predicted_remaining),
624 0.0f);
625
626 for(int y = 0; y < rt->height; y++)
627 {
628 const int world_y = (int)((rt->roi_y + y) * rt->inv_scale);
629 for(int x = 0; x < rt->width; x++)
630 {
631 const size_t index = (size_t)y * rt->width + x;
632 if(remaining[index] <= 0.0f) continue;
633
634 const int world_x = (int)((rt->roi_x + x) * rt->inv_scale);
635 const uint64_t pixel_seed = rt->base_seed
636 ^ ((uint64_t)(uint32_t)world_x << 32)
637 ^ (uint32_t)world_y
638 ^ (uint64_t)(layer + 1) * 0x9e3779b97f4a7c15ull;
639 const int kernel_index = splitmix32(pixel_seed ^ 0x94d049bb133111ebull) & (DT_CRYSTGRAIN_LAYER_KERNELS - 1);
640 const dt_iop_crystgrain_layer_kernel_t *const entry = &kernel_bank[kernel_index];
641 const dt_iop_crystgrain_kernel_t *const kernel = &entry->footprint;
642 const int radius = kernel->radius;
643 const int interior = (y >= radius && y < rt->height - radius && x >= radius && x < rt->width - radius);
644 float seed_energy = 0.0f;
645 float original_energy = 0.0f;
646
647 // The seed tests the light field that is still available after all
648 // previous grains and layers have already depleted their share.
649 if(_uniform_random(pixel_seed ^ 0xda942042e4dd58b5ull) >= entry->probability) continue;
650
651 // Like the OpenCL path, each pixel either exits immediately or sweeps
652 // only its own crystal footprint to print one flat tone into the
653 // reconstruction while depleting the remaining light field in place.
654 for(int tap = 0; tap < kernel->count; tap++)
655 {
656 int xx = x + kernel->dx[tap];
657 int yy = y + kernel->dy[tap];
658 if(!interior)
659 {
660 xx = _reflect_index(xx, width);
661 yy = _reflect_index(yy, height);
662 }
663
664 const size_t dst = (size_t)yy * width + xx;
665 // A crystal prints one flat tone from the average of the current
666 // light field and of the immutable input over the whole grain
667 // surface, so no detail finer than the grain survives inside it.
668 seed_energy += remaining[dst] * kernel->alpha[tap];
669 original_energy += image[dst] * kernel->alpha[tap];
670 }
671 seed_energy /= kernel->area;
672 // The user layer scale now applies to the whole grain surface, so the
673 // per-pixel flat tone cap must scale with the grain area too.
674 original_energy *= rt->layer_scale;
675 seed_energy = fminf(seed_energy, original_energy);
676 if(seed_energy <= 0.0f) continue;
677
678 for(int tap = 0; tap < kernel->count; tap++)
679 {
680 int xx = x + kernel->dx[tap];
681 int yy = y + kernel->dy[tap];
682 if(!interior)
683 {
684 xx = _reflect_index(xx, width);
685 yy = _reflect_index(yy, height);
686 }
687
688 const size_t dst = (size_t)yy * width + xx;
689 // Write the flat crystal tone back to the output and subtract the
690 // same quantity from the light field that will feed deeper layers.
691 const float deposited = seed_energy * kernel->alpha[tap];
692 result[dst] += deposited;
693 remaining[dst] = fmaxf(remaining[dst] - deposited, 0.0f);
694 }
695 }
696 }
697
698 _free_layer_kernel_bank(kernel_bank);
699 }
700
701 *exposure = _predict_stack_exposure(predicted_remaining);
702 return 0;
703}
704
718 float *const exposure)
719{
720 const int width = rt->width;
721 const int height = rt->height;
722 const size_t npixels = (size_t)width * height;
723 const int blue_layers = (rt->layers + 2) / 3;
724 const int green_layers = (rt->layers + 1) / 3;
725 float predicted_remaining[3] = { 1.0f, 1.0f, 1.0f };
726 const uint64_t channel_salt[3] = {
727 0xa24baed4963ee407ull,
728 0x9fb21c651e98df25ull,
729 0xc13fa9a902a6328full
730 };
731
732 memset(state->result, 0, sizeof(float) * npixels * 4);
733 memcpy(state->remaining, state->image, sizeof(float) * npixels * 4);
734
735 for(int layer = 0; layer < rt->layers; layer++)
736 {
738 const int c = (layer < blue_layers) ? 2 : ((layer < blue_layers + green_layers) ? 1 : 0);
739 const int sublayer = (c == 2) ? layer : ((c == 1) ? layer - blue_layers : layer - blue_layers - green_layers);
740 const uint64_t layer_seed = rt->base_seed + (uint64_t)(sublayer + 1) * 4099u;
741 if(_build_layer_kernel_bank(kernel_bank, rt, layer_seed) != 0) return 1;
742 predicted_remaining[c] = fmaxf(predicted_remaining[c]
743 - _predict_layer_capture(kernel_bank, rt->layer_scale, predicted_remaining[c]),
744 0.0f);
745
746 for(int y = 0; y < height; y++)
747 {
748 const int world_y = (int)((rt->roi_y + y) * rt->inv_scale);
749 for(int x = 0; x < width; x++)
750 {
751 const size_t index = (size_t)y * width + x;
752 const float remaining_total = state->remaining[_rgb_index(index, 0)]
753 + state->remaining[_rgb_index(index, 1)]
754 + state->remaining[_rgb_index(index, 2)];
755 if(remaining_total <= 0.0f) continue;
756
757 const int world_x = (int)((rt->roi_x + x) * rt->inv_scale);
758 const uint64_t shared_seed = rt->base_seed
759 ^ ((uint64_t)(uint32_t)world_x << 32)
760 ^ (uint32_t)world_y
761 ^ (uint64_t)(sublayer + 1) * 0x9e3779b97f4a7c15ull;
762 const uint64_t channel_seed = shared_seed ^ channel_salt[c];
763 const int use_shared = _uniform_random(channel_seed ^ 0x4f1bbcdc6762f96bull) < rt->channel_correlation;
764 const uint64_t pixel_seed = use_shared ? shared_seed : channel_seed;
765 const int kernel_index = splitmix32(pixel_seed ^ 0x94d049bb133111ebull) & (DT_CRYSTGRAIN_LAYER_KERNELS - 1);
766 const dt_iop_crystgrain_layer_kernel_t *const entry = &kernel_bank[kernel_index];
767 const dt_iop_crystgrain_kernel_t *const kernel = &entry->footprint;
768 const int radius = kernel->radius;
769 const int interior = (y >= radius && y < height - radius && x >= radius && x < width - radius);
770 float seed_energy = 0.0f;
771 float original_energy = 0.0f;
772
773 if(_uniform_random(pixel_seed ^ 0xda942042e4dd58b5ull) >= entry->probability) continue;
774
775 for(int tap = 0; tap < kernel->count; tap++)
776 {
777 int xx = x + kernel->dx[tap];
778 int yy = y + kernel->dy[tap];
779 if(!interior)
780 {
781 xx = _reflect_index(xx, width);
782 yy = _reflect_index(yy, height);
783 }
784
785 const size_t dst = (size_t)yy * width + xx;
786 // Each depth layer belongs to one spectral emulsion only, so it
787 // prints one flat tone from that channel and leaves the others to
788 // deeper layers.
789 seed_energy += state->remaining[_rgb_index(dst, c)] * kernel->alpha[tap];
790 original_energy += state->image[_rgb_index(dst, c)] * kernel->alpha[tap];
791 }
792
793 seed_energy /= kernel->area;
794 original_energy *= rt->layer_scale;
795 const float captured = fminf(seed_energy, original_energy);
796 if(captured <= 0.0f) continue;
797
798 for(int tap = 0; tap < kernel->count; tap++)
799 {
800 int xx = x + kernel->dx[tap];
801 int yy = y + kernel->dy[tap];
802 if(!interior)
803 {
804 xx = _reflect_index(xx, width);
805 yy = _reflect_index(yy, height);
806 }
807
808 const size_t dst = (size_t)yy * width + xx;
809 const float deposited = captured * kernel->alpha[tap];
810 state->result[_rgb_index(dst, c)] += deposited;
811 state->remaining[_rgb_index(dst, c)]
812 = fmaxf(state->remaining[_rgb_index(dst, c)] - deposited, 0.0f);
813 }
814 }
815 }
816
817 _free_layer_kernel_bank(kernel_bank);
818 }
819
820 for(int c = 0; c < 3; c++) exposure[c] = _predict_stack_exposure(predicted_remaining[c]);
821 return 0;
822}
823
831static void _extract_luminance_kernel(const float *const restrict in, float *const restrict image,
832 const int width, const int height,
833 const dt_iop_order_iccprofile_info_t *const work_profile)
834{
836 for(int y = 0; y < height; y++)
837 {
838 const size_t row = (size_t)y * width;
839 for(int x = 0; x < width; x++)
840 {
841 const size_t k = row + x;
842 const float luminance = (work_profile)
843 ? dt_ioppr_get_rgb_matrix_luminance(in + 4 * k, work_profile->matrix_in, work_profile->lut_in,
844 work_profile->unbounded_coeffs_in, work_profile->lutsize,
845 work_profile->nonlinearlut)
846 : dt_camera_rgb_luminance(in + 4 * k);
847
848 image[k] = fmaxf(luminance, 0.0f);
849 }
850 }
851}
852
861static void _extract_rgb_kernels(const float *const restrict in, float *const restrict image,
862 const int width, const int height)
863{
865 for(int y = 0; y < height; y++)
866 {
867 const size_t row = (size_t)y * width;
868 for(int x = 0; x < width; x++)
869 {
870 const size_t k = row + x;
871 const float red = fmaxf(in[4 * k + 0], 0.0f);
872 const float green = fmaxf(in[4 * k + 1], 0.0f);
873 const float blue = fmaxf(in[4 * k + 2], 0.0f);
874
875 image[_rgb_index(k, 0)] = red;
876 image[_rgb_index(k, 1)] = green;
877 image[_rgb_index(k, 2)] = blue;
878 image[_rgb_index(k, 3)] = 0.0f;
879 }
880 }
881}
882
894static void _apply_mono_grain_kernel(const float *const restrict in, float *const restrict out,
895 const float *const restrict image, const float *const restrict result,
896 const int width, const int height, const float exposure)
897{
899 for(int y = 0; y < height; y++)
900 {
901 const size_t row = (size_t)y * width;
902 for(int x = 0; x < width; x++)
903 {
904 const size_t k = row + x;
905 const float grainy = fmaxf(result[k] * exposure, 0.0f);
906 const float ratio = (image[k] > 1e-6f) ? grainy / image[k] : 0.0f;
907
908 out[4 * k + 0] = fmaxf(in[4 * k + 0] * ratio, 0.0f);
909 out[4 * k + 1] = fmaxf(in[4 * k + 1] * ratio, 0.0f);
910 out[4 * k + 2] = fmaxf(in[4 * k + 2] * ratio, 0.0f);
911 }
912 }
913}
914
924static void _finalize_color_grain_kernel(const float *const restrict in, float *const restrict out,
925 const float *const restrict image, const float *const restrict result,
926 const int width, const int height, const float exposure_r,
927 const float exposure_g, const float exposure_b, const float colorfulness)
928{
930 for(int y = 0; y < height; y++)
931 {
932 const size_t row = (size_t)y * width;
933 for(int x = 0; x < width; x++)
934 {
935 const size_t k = row + x;
936 const float image_r = image[_rgb_index(k, 0)];
937 const float image_g = image[_rgb_index(k, 1)];
938 const float image_b = image[_rgb_index(k, 2)];
939 const float grain_r = (exposure_r > 0.0f) ? fmaxf(result[_rgb_index(k, 0)] * exposure_r, 0.0f) : image_r;
940 const float grain_g = (exposure_g > 0.0f) ? fmaxf(result[_rgb_index(k, 1)] * exposure_g, 0.0f) : image_g;
941 const float grain_b = (exposure_b > 0.0f) ? fmaxf(result[_rgb_index(k, 2)] * exposure_b, 0.0f) : image_b;
942 const float residual_r = grain_r - image_r;
943 const float residual_g = grain_g - image_g;
944 const float residual_b = grain_b - image_b;
945 const float mean = (residual_r + residual_g + residual_b) / 3.0f;
946
947 out[4 * k + 0] = in[4 * k + 0] + mean + (residual_r - mean) * colorfulness;
948 out[4 * k + 1] = in[4 * k + 1] + mean + (residual_g - mean) * colorfulness;
949 out[4 * k + 2] = in[4 * k + 2] + mean + (residual_b - mean) * colorfulness;
950 }
951 }
952}
953
954#ifdef HAVE_OPENCL
955#define DT_CRYSTGRAIN_CL_PROGRAM 36
956#define DT_CRYSTGRAIN_REDUCESIZE 64
957
967static int _simulate_channel_cl(const int devid, dt_iop_crystgrain_global_data_t *const gd,
968 const dt_iop_crystgrain_runtime_t *const rt, cl_mem dev_image, cl_mem dev_result,
969 cl_mem dev_remaining, float *const exposure)
970{
971 cl_int err = CL_SUCCESS;
972 const int width = rt->width;
973 const int height = rt->height;
974 size_t sizes[3] = { ROUNDUP((size_t)width, (size_t)16), ROUNDUP((size_t)height, (size_t)16), 1 };
975 const size_t buffer_size = sizeof(float) * (size_t)width * height;
976 float predicted_remaining = 1.0f;
977
978 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_scalar, 0, sizeof(cl_mem), &dev_result);
979 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_scalar, 1, sizeof(int), &width);
980 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_scalar, 2, sizeof(int), &height);
981 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_zero_scalar, sizes);
982 if(err != CL_SUCCESS) return err;
983
984 err = dt_opencl_enqueue_copy_buffer_to_buffer(devid, dev_image, dev_remaining, 0, 0, buffer_size);
985 if(err != CL_SUCCESS) return err;
986
987 for(int layer = 0; layer < rt->layers; layer++)
988 {
990 float kernel_bank_cl[DT_CRYSTGRAIN_LAYER_KERNELS][4];
991 cl_mem dev_kernel_bank = NULL;
992 const float layer_scale = rt->layer_scale;
993 const int roi_x = rt->roi_x;
994 const int roi_y = rt->roi_y;
995 const float inv_scale = rt->inv_scale;
996 const cl_ulong base_seed = (cl_ulong)rt->base_seed;
997
998 if(_build_layer_kernel_bank(kernel_bank, rt, rt->base_seed + layer * 4099u) != 0)
999 {
1000 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1001 return err;
1002 }
1003 predicted_remaining = fmaxf(predicted_remaining
1004 - _predict_layer_capture(kernel_bank, rt->layer_scale, predicted_remaining),
1005 0.0f);
1006
1007 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
1008 {
1009 kernel_bank_cl[i][0] = kernel_bank[i].vertices;
1010 kernel_bank_cl[i][1] = kernel_bank[i].rotation;
1011 kernel_bank_cl[i][2] = kernel_bank[i].probability;
1012 kernel_bank_cl[i][3] = kernel_bank[i].footprint.radius_f;
1013 }
1014
1015 dev_kernel_bank = dt_opencl_copy_host_to_device_constant(devid, sizeof(kernel_bank_cl), kernel_bank_cl);
1016 _free_layer_kernel_bank(kernel_bank);
1017 if(IS_NULL_PTR(dev_kernel_bank)) return CL_MEM_OBJECT_ALLOCATION_FAILURE;
1018
1019 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 0, sizeof(cl_mem), &dev_image);
1020 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 1, sizeof(cl_mem), &dev_remaining);
1021 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 2, sizeof(cl_mem), &dev_result);
1022 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 3, sizeof(cl_mem), &dev_kernel_bank);
1023 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 4, sizeof(int), &width);
1024 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 5, sizeof(int), &height);
1025 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 6, sizeof(int), &roi_x);
1026 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 7, sizeof(int), &roi_y);
1027 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 8, sizeof(float), &inv_scale);
1028 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 9, sizeof(cl_ulong), &base_seed);
1029 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 10, sizeof(int), &layer);
1030 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 11, sizeof(float), &layer_scale);
1031 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_simulate_layer, sizes);
1032 dt_opencl_release_mem_object(dev_kernel_bank);
1033 if(err != CL_SUCCESS) return err;
1034 }
1035
1036 *exposure = _predict_stack_exposure(predicted_remaining);
1037 return err;
1038}
1039
1041{
1043 module->data = gd;
1044 const int program = DT_CRYSTGRAIN_CL_PROGRAM;
1045 gd->kernel_zero_scalar = dt_opencl_create_kernel(program, "crystgrain_zero_scalar");
1046 gd->kernel_zero_rgb = dt_opencl_create_kernel(program, "crystgrain_zero_rgb");
1047 gd->kernel_extract_luminance = dt_opencl_create_kernel(program, "crystgrain_extract_luminance");
1048 gd->kernel_extract_rgb = dt_opencl_create_kernel(program, "crystgrain_extract_rgb");
1049 gd->kernel_simulate_layer = dt_opencl_create_kernel(program, "crystgrain_simulate_layer");
1050 gd->kernel_simulate_layer_color = dt_opencl_create_kernel(program, "crystgrain_simulate_layer_color");
1051 gd->kernel_apply_mono = dt_opencl_create_kernel(program, "crystgrain_apply_mono");
1052 gd->kernel_finalize_color = dt_opencl_create_kernel(program, "crystgrain_finalize_color");
1053}
1054
1069
1070int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1071 cl_mem dev_in, cl_mem dev_out)
1072{
1073 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1074 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1075 const dt_iop_crystgrain_data_t *const d = (const dt_iop_crystgrain_data_t *)piece->data;
1078 const int devid = pipe->devid;
1079 const int width = roi_out->width;
1080 const int height = roi_out->height;
1081 // Grain size is authored in full-resolution output pixels at 100% zoom.
1082 // The current processing grid may already be downsampled twice:
1083 // 1. by the ROI zoom factor used for the current preview/export,
1084 // 2. by the mipmap level chosen before the pipe even starts.
1085 const float kernel_scale = MAX(1.0f / dt_dev_get_module_scale(pipe, roi_in), 1e-6f);
1086 cl_int err = CL_SUCCESS;
1087 float exposure[3] = { 1.0f, 1.0f, 1.0f };
1088
1089 if(width <= 0 || height <= 0 || d->layers <= 0 || d->filling <= 0.0f)
1090 {
1091 size_t origin[] = { 0, 0, 0 };
1092 size_t region[] = { (size_t)width, (size_t)height, 1 };
1093 return dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, origin, origin, region);
1094 }
1095
1096 cl_mem dev_image = NULL;
1097 cl_mem dev_result = NULL;
1098 cl_mem dev_remaining = NULL;
1099 cl_mem dev_image_rgb = NULL;
1100 cl_mem dev_result_rgb = NULL;
1101 cl_mem dev_remaining_rgb = NULL;
1102 dt_colorspaces_iccprofile_info_cl_t *profile_info_cl = NULL;
1103 cl_float *profile_lut_cl = NULL;
1104 cl_mem dev_profile_info = NULL;
1105 cl_mem dev_profile_lut = NULL;
1106
1107 dev_image = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height);
1108 dev_result = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height);
1109 dev_remaining = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height);
1110 if(IS_NULL_PTR(dev_image) || IS_NULL_PTR(dev_result) || IS_NULL_PTR(dev_remaining))
1111 {
1112 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1113 goto error;
1114 }
1115
1117 .width = width,
1118 .height = height,
1119 .roi_x = roi_out->x,
1120 .roi_y = roi_out->y,
1121 .layers = d->layers,
1122 .layer_scale = 0.0f,
1123 .filling = d->filling,
1124 .grain_size = d->grain_size,
1125 .size_stddev = d->size_stddev,
1126 .kernel_scale = kernel_scale,
1127 .inv_scale = 1.0f / kernel_scale,
1128 .channel_correlation = d->channel_correlation,
1129 .base_seed = ((uint64_t)_hash_string(pipe->dev->image_storage.filename) << 32)
1130 ^ ((uint64_t)width << 16) ^ (uint64_t)height
1131 };
1132 const float current_surface = _average_discrete_grain_surface(&rt);
1133 // Neutral layer capture is defined as 1/layers of the input energy for a
1134 // grain of average rasterized surface. Since each sampled bank entry can
1135 // have a different discrete area A_i, the flat-field recurrence uses
1136 // min(r_l, A_i * layer_scale) per crystal, with the current_surface term
1137 // keeping the user-facing EV control centered on that neutral 1/layers
1138 // behaviour across preview scales.
1139 rt.layer_scale = d->layer_capture / MAX((float)d->layers, 1.0f) / MAX(current_surface, FLT_EPSILON);
1140 const int blue_layers = (rt.layers + 2) / 3;
1141 const int green_layers = (rt.layers + 1) / 3;
1142
1143 size_t sizes[3] = { ROUNDUP((size_t)width, (size_t)16), ROUNDUP((size_t)height, (size_t)16), 1 };
1144
1145 if(d->mode == DT_CRYSTGRAIN_MONO)
1146 {
1147 err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
1148 &dev_profile_info, &dev_profile_lut);
1149 if(err != CL_SUCCESS) goto error;
1150
1151 const int use_work_profile = (!IS_NULL_PTR(work_profile)) ? 1 : 0;
1152 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 0, sizeof(cl_mem), &dev_in);
1153 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 1, sizeof(cl_mem), &dev_image);
1154 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 2, sizeof(int), &width);
1155 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 3, sizeof(int), &height);
1156 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 4, sizeof(cl_mem), &dev_profile_info);
1157 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 5, sizeof(cl_mem), &dev_profile_lut);
1158 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 6, sizeof(int), &use_work_profile);
1160 if(err != CL_SUCCESS) goto error;
1161
1162 err = _simulate_channel_cl(devid, gd, &rt, dev_image, dev_result, dev_remaining, &exposure[0]);
1163 if(err != CL_SUCCESS) goto error;
1164
1165 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 0, sizeof(cl_mem), &dev_in);
1166 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 1, sizeof(cl_mem), &dev_image);
1167 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 2, sizeof(cl_mem), &dev_result);
1168 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 3, sizeof(cl_mem), &dev_out);
1169 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 4, sizeof(int), &width);
1170 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 5, sizeof(int), &height);
1171 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 6, sizeof(float), &exposure[0]);
1172 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_apply_mono, sizes);
1173 goto error;
1174 }
1175
1176 dev_image_rgb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height * 4);
1177 dev_result_rgb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height * 4);
1178 dev_remaining_rgb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height * 4);
1179 if(IS_NULL_PTR(dev_image_rgb) || IS_NULL_PTR(dev_result_rgb) || IS_NULL_PTR(dev_remaining_rgb))
1180 {
1181 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1182 goto error;
1183 }
1184
1185 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 0, sizeof(cl_mem), &dev_in);
1186 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 1, sizeof(cl_mem), &dev_image_rgb);
1187 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 2, sizeof(int), &width);
1188 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 3, sizeof(int), &height);
1189 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_extract_rgb, sizes);
1190 if(err != CL_SUCCESS) goto error;
1191
1192 const size_t color_buffer_size = sizeof(float) * (size_t)width * height * 4;
1193 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_rgb, 0, sizeof(cl_mem), &dev_result_rgb);
1194 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_rgb, 1, sizeof(int), &width);
1195 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_rgb, 2, sizeof(int), &height);
1196 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_zero_rgb, sizes);
1197 if(err != CL_SUCCESS) goto error;
1198
1199 err = dt_opencl_enqueue_copy_buffer_to_buffer(devid, dev_image_rgb, dev_remaining_rgb, 0, 0, color_buffer_size);
1200 if(err != CL_SUCCESS) goto error;
1201
1202 float predicted_remaining[3] = { 1.0f, 1.0f, 1.0f };
1203 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 0, sizeof(cl_mem), &dev_image_rgb);
1204 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 1, sizeof(cl_mem), &dev_remaining_rgb);
1205 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 2, sizeof(cl_mem), &dev_result_rgb);
1206 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 3, sizeof(int), &width);
1207 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 4, sizeof(int), &height);
1208 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 5, sizeof(int), &rt.roi_x);
1209 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 6, sizeof(int), &rt.roi_y);
1210 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 7, sizeof(float), &rt.inv_scale);
1211 {
1212 const cl_ulong base_seed = (cl_ulong)rt.base_seed;
1213 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 8, sizeof(cl_ulong), &base_seed);
1214 }
1215 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 10, sizeof(float), &rt.layer_scale);
1217
1218 for(int layer = 0; layer < rt.layers; layer++)
1219 {
1221 float kernel_bank_cl[DT_CRYSTGRAIN_LAYER_KERNELS][4];
1222 cl_mem dev_kernel_bank = NULL;
1223 const int active_channel = (layer < blue_layers) ? 2 : ((layer < blue_layers + green_layers) ? 1 : 0);
1224 const int sublayer = (active_channel == 2)
1225 ? layer
1226 : ((active_channel == 1) ? layer - blue_layers : layer - blue_layers - green_layers);
1227 if(_build_layer_kernel_bank(kernel_bank, &rt, rt.base_seed + (uint64_t)(sublayer + 1) * 4099u) != 0)
1228 {
1229 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1230 goto error;
1231 }
1232 predicted_remaining[active_channel]
1233 = fmaxf(predicted_remaining[active_channel]
1234 - _predict_layer_capture(kernel_bank, rt.layer_scale, predicted_remaining[active_channel]),
1235 0.0f);
1236
1237 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
1238 {
1239 kernel_bank_cl[i][0] = kernel_bank[i].vertices;
1240 kernel_bank_cl[i][1] = kernel_bank[i].rotation;
1241 kernel_bank_cl[i][2] = kernel_bank[i].probability;
1242 kernel_bank_cl[i][3] = kernel_bank[i].footprint.radius_f;
1243 }
1244
1245 dev_kernel_bank = dt_opencl_copy_host_to_device_constant(devid, sizeof(kernel_bank_cl), kernel_bank_cl);
1246 _free_layer_kernel_bank(kernel_bank);
1247 if(IS_NULL_PTR(dev_kernel_bank))
1248 {
1249 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1250 goto error;
1251 }
1252
1253 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 9, sizeof(cl_mem), &dev_kernel_bank);
1254 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 11, sizeof(int), &sublayer);
1255 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 12, sizeof(int), &active_channel);
1257 dt_opencl_release_mem_object(dev_kernel_bank);
1258 if(err != CL_SUCCESS) goto error;
1259 }
1260
1261 for(int c = 0; c < 3; c++) exposure[c] = _predict_stack_exposure(predicted_remaining[c]);
1262
1263 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 0, sizeof(cl_mem), &dev_in);
1264 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 1, sizeof(cl_mem), &dev_image_rgb);
1265 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 2, sizeof(cl_mem), &dev_result_rgb);
1266 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 3, sizeof(cl_mem), &dev_out);
1267 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 4, sizeof(int), &width);
1268 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 5, sizeof(int), &height);
1269 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 6, sizeof(float), &exposure[0]);
1270 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 7, sizeof(float), &exposure[1]);
1271 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 8, sizeof(float), &exposure[2]);
1272 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 9, sizeof(float), &d->colorspace_saturation);
1273 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_finalize_color, sizes);
1274
1275error:
1277 dt_opencl_release_mem_object(dev_result);
1278 dt_opencl_release_mem_object(dev_remaining);
1279 dt_opencl_release_mem_object(dev_image_rgb);
1280 dt_opencl_release_mem_object(dev_result_rgb);
1281 dt_opencl_release_mem_object(dev_remaining_rgb);
1282 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1283 return (err == CL_SUCCESS) ? TRUE : FALSE;
1284}
1285#endif
1286
1289{
1292
1293 d->mode = p->mode;
1294 d->filling = p->filling * 0.01f;
1295 d->grain_size = p->grain_size;
1296 d->layers = p->layers;
1297 d->size_stddev = p->size_stddev;
1298 d->layer_capture = exp2f(p->layer_capture);
1299 d->channel_correlation = p->channel_correlation * 0.01f;
1300 d->colorspace_saturation = p->colorspace_saturation * 0.01f;
1301}
1302
1308
1310{
1311 dt_free_align(piece->data);
1312 piece->data = NULL;
1313}
1314
1315int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1316 const void *const ivoid, void *const ovoid)
1317{
1318 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1319 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1320 const dt_iop_crystgrain_data_t *const d = (const dt_iop_crystgrain_data_t *)piece->data;
1322 const float *const restrict in = (const float *const)ivoid;
1323 float *const restrict out = (float *const)ovoid;
1324 const int width = roi_out->width;
1325 const int height = roi_out->height;
1326 // Grain size is authored in full-resolution output pixels at 100% zoom.
1327 const float kernel_scale = MAX(1.0f / dt_dev_get_module_scale(pipe, roi_in), 1e-6f);
1328
1329 if(width <= 0 || height <= 0 || d->layers <= 0 || d->filling <= 0.0f)
1330 {
1331 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1332 return 0;
1333 }
1334
1335 float *image = NULL;
1336 float *result = NULL;
1337 float *remaining = NULL;
1338 float *image_rgb = NULL;
1339 float *result_rgb = NULL;
1340 float *remaining_rgb = NULL;
1341 if(dt_iop_alloc_image_buffers(self, roi_in, roi_out,
1342 1, &image,
1343 1 | DT_IMGSZ_CLEARBUF, &result,
1344 1, &remaining,
1345 4, &image_rgb,
1346 4 | DT_IMGSZ_CLEARBUF, &result_rgb,
1347 4, &remaining_rgb,
1348 0))
1349 {
1350 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1351 return 1;
1352 }
1353
1355
1357 .width = width,
1358 .height = height,
1359 .roi_x = roi_out->x,
1360 .roi_y = roi_out->y,
1361 .layers = d->layers,
1362 .layer_scale = 0.0f,
1363 .filling = d->filling,
1364 .grain_size = d->grain_size,
1365 .size_stddev = d->size_stddev,
1366 .kernel_scale = kernel_scale,
1367 .inv_scale = 1.0f / kernel_scale,
1368 .channel_correlation = d->channel_correlation,
1369 .base_seed = ((uint64_t)_hash_string(pipe->dev->image_storage.filename) << 32)
1370 ^ ((uint64_t)width << 16) ^ (uint64_t)height
1371 };
1372 const float current_surface = _average_discrete_grain_surface(&rt);
1373 // Neutral layer capture is defined as 1/layers of the input energy for a
1374 // grain of average rasterized surface. Since each sampled bank entry can
1375 // have a different discrete area A_i, the flat-field recurrence uses
1376 // min(r_l, A_i * layer_scale) per crystal, with the current_surface term
1377 // keeping the user-facing EV control centered on that neutral 1/layers
1378 // behaviour across preview scales.
1379 rt.layer_scale = d->layer_capture / MAX((float)d->layers, 1.0f) / MAX(current_surface, FLT_EPSILON);
1380
1381 if(d->mode == DT_CRYSTGRAIN_MONO)
1382 {
1383 _extract_luminance_kernel(in, image, width, height, work_profile);
1384 float mono_exposure = 1.0f;
1385
1386 if(_simulate_channel(&rt, image, result, remaining, &mono_exposure) != 0)
1387 {
1391 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1392 return 1;
1393 }
1394
1395 _apply_mono_grain_kernel(in, out, image, result, width, height, mono_exposure);
1396 }
1397 else
1398 {
1399 // Color film layers share one crystal geometry stack. Keep the working
1400 // light fields interleaved as RGB tuples so extraction, simulation and
1401 // final write-back all walk one contiguous color buffer instead of three
1402 // independent scalar plates.
1403 _extract_rgb_kernels(in, image_rgb, width, height);
1404 float color_exposure[3] = { 1.0f, 1.0f, 1.0f };
1405 const dt_iop_crystgrain_color_state_t color_state = {
1406 .image = image_rgb,
1407 .result = result_rgb,
1408 .remaining = remaining_rgb
1409 };
1410
1411 if(_simulate_color(&rt, &color_state, color_exposure) != 0)
1412 {
1418 dt_pixelpipe_cache_free_align(remaining_rgb);
1419 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1420 return 1;
1421 }
1422
1423 _finalize_color_grain_kernel(in, out, image_rgb, result_rgb, width, height,
1424 color_exposure[0], color_exposure[1], color_exposure[2],
1425 d->colorspace_saturation);
1426 }
1427
1433 dt_pixelpipe_cache_free_align(remaining_rgb);
1434 return 0;
1435}
1436
1437void gui_update(struct dt_iop_module_t *self)
1438{
1441 const gboolean is_color = (p->mode == DT_CRYSTGRAIN_COLOR);
1442
1443 gtk_widget_set_visible(g->channel_correlation, is_color);
1444 gtk_widget_set_visible(g->colorspace_saturation, is_color);
1445}
1446
1447static void _mode_changed(GtkWidget *widget, dt_iop_module_t *self)
1448{
1449 gui_update(self);
1450}
1451
1452void gui_init(struct dt_iop_module_t *self)
1453{
1455
1456 g->mode = dt_bauhaus_combobox_from_params(self, "mode");
1457 gtk_widget_set_tooltip_text(g->mode, _("simulate one shared B&W grain field or one shared blue/green/red-sensitive color grain stack"));
1458 g_signal_connect(G_OBJECT(g->mode), "value-changed", G_CALLBACK(_mode_changed), self);
1459
1460 g->filling = dt_bauhaus_slider_from_params(self, "filling");
1461 dt_bauhaus_slider_set_format(g->filling, "%");
1462 gtk_widget_set_tooltip_text(g->filling, _("surface ratio occupied by silver-halide crystals in each layer"));
1463
1464 g->grain_size = dt_bauhaus_slider_from_params(self, "grain_size");
1465 dt_bauhaus_slider_set_digits(g->grain_size, 0);
1466 dt_bauhaus_slider_set_format(g->grain_size, " px");
1467 gtk_widget_set_tooltip_text(g->grain_size, _("average crystal footprint at 100% zoom, clamped so zooming in does not enlarge it further"));
1468
1469 g->layers = dt_bauhaus_slider_from_params(self, "layers");
1470 dt_bauhaus_slider_set_digits(g->layers, 0);
1471 gtk_widget_set_tooltip_text(g->layers, _("number of crystal layers stacked through the emulsion"));
1472
1473 g->layer_capture = dt_bauhaus_slider_from_params(self, "layer_capture");
1474 dt_bauhaus_slider_set_soft_range(g->layer_capture, -2.0f, 2.0f);
1475 dt_bauhaus_slider_set_format(g->layer_capture, _(" EV"));
1476 gtk_widget_set_tooltip_text(g->layer_capture, _("0 EV means one layer captures its neutral 1/layers share after normalization by the rasterized grain surface; positive values increase that capture and negative values decrease it"));
1477
1478 g->channel_correlation = dt_bauhaus_slider_from_params(self, "channel_correlation");
1479 dt_bauhaus_slider_set_format(g->channel_correlation, "%");
1480 gtk_widget_set_tooltip_text(g->channel_correlation, _("probability that blue-, green- and red-sensitive sub-layers reuse the same crystal births and shapes at matching depths"));
1481
1482 g->colorspace_saturation = dt_bauhaus_slider_from_params(self, "colorspace_saturation");
1483 dt_bauhaus_slider_set_format(g->colorspace_saturation, "%");
1484 gtk_widget_set_tooltip_text(g->colorspace_saturation, _("scale only the chromatic amplitude of the RGB grain residual while keeping its achromatic strength unchanged"));
1485
1486 g->size_stddev = dt_bauhaus_slider_from_params(self, "size_stddev");
1487 gtk_widget_set_tooltip_text(g->size_stddev, _("log-normal standard deviation of crystal sizes"));
1488
1489 gui_update(self);
1490}
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DEVELOP_BLEND_CS_RGB_SCENE
Definition blend.h:60
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static float dt_camera_rgb_luminance(const float4 rgb)
@ IOP_CS_RGB
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
static float envelope(const float xx)
static float _seed_probability(const float filling, const float crystal_area)
Map the requested filling ratio to the Bernoulli probability used to plant seeds.
Definition crystgrain.c:280
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.
Definition crystgrain.c:320
const char ** description(struct dt_iop_module_t *self)
Definition crystgrain.c:151
int default_group()
Definition crystgrain.c:165
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.
Definition crystgrain.c:550
#define DT_CRYSTGRAIN_CL_PROGRAM
Definition crystgrain.c:955
#define DT_CRYSTGRAIN_LAYER_KERNELS
Definition crystgrain.c:43
static float _uniform_random(const uint64_t seed)
Turn a 64-bit seed into a uniform random number in [0; 1).
Definition crystgrain.c:230
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.
Definition crystgrain.c:894
static int _reflect_index(int i, const int max)
Mirror indices outside the current buffer like scipy ‘boundary='symm’`.
Definition crystgrain.c:253
static float _average_grain_surface(const dt_iop_crystgrain_runtime_t *const rt)
Estimate the reference grain surface used to normalize layer capture.
Definition crystgrain.c:445
static size_t _rgb_index(const size_t pixel, const int channel)
Definition crystgrain.c:588
static __DT_CLONE_TARGETS__ unsigned int _hash_string(const char *s)
Hash a string into a stable 32-bit seed.
Definition crystgrain.c:220
static void _free_layer_kernel_bank(dt_iop_crystgrain_layer_kernel_t *const bank)
Release all crystal footprints from one layer bank.
Definition crystgrain.c:525
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.
Definition crystgrain.c:396
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.
Definition crystgrain.c:831
static void _mode_changed(GtkWidget *widget, dt_iop_module_t *self)
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.
Definition crystgrain.c:861
const char * name()
Definition crystgrain.c:146
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.
Definition crystgrain.c:607
void gui_update(struct dt_iop_module_t *self)
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.
Definition crystgrain.c:503
void gui_init(struct dt_iop_module_t *self)
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
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.
Definition crystgrain.c:924
void cleanup_global(dt_iop_module_so_t *module)
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition crystgrain.c:170
int flags()
Definition crystgrain.c:160
static float _crystal_coverage(const int dx, const int dy, const float radius_f, const float vertices, const float rotation)
Estimate the partial coverage of one pixel by one crystal boundary.
Definition crystgrain.c:296
static float _predict_stack_exposure(const float remaining_fraction)
Predict the exposure compensation of one monochrome grain stack.
Definition crystgrain.c:582
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.
Definition crystgrain.c:967
void init_presets(dt_iop_module_so_t *self)
Definition crystgrain.c:175
static float _gaussian_random(const uint64_t seed_a, const uint64_t seed_b)
Turn 2 seeds into one gaussian deviate.
Definition crystgrain.c:242
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 init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
dt_iop_crystgrain_mode_t
Definition crystgrain.c:46
@ DT_CRYSTGRAIN_COLOR
Definition crystgrain.c:48
@ DT_CRYSTGRAIN_MONO
Definition crystgrain.c:47
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.
Definition crystgrain.c:467
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.
Definition crystgrain.c:716
void init_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)
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 crystgrain.c:202
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define M_PI_F
static unsigned int splitmix32(const unsigned long seed)
void dt_iop_params_t
Definition dev_history.h:41
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled, const dt_develop_blend_colorspace_t blend_cst)
int dt_iop_alloc_image_buffers(struct dt_iop_module_t *const module, const struct dt_iop_roi_t *const roi_in, const struct dt_iop_roi_t *const roi_out,...)
Definition imagebuf.c:31
void dt_iop_copy_image_roi(float *const __restrict__ out, const float *const __restrict__ in, const size_t ch, const dt_iop_roi_t *const __restrict__ roi_in, const dt_iop_roi_t *const __restrict__ roi_out, const int zero_pad)
Definition imagebuf.c:159
#define DT_IMGSZ_CLEARBUF
Definition imagebuf.h:58
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:87
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:131
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_GROUP_EFFECTS
Definition imageop.h:142
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
static float kernel(const float *x, const float *y)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
void dt_ioppr_free_iccprofile_params_cl(dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
cl_int dt_ioppr_build_iccprofile_params_cl(const dt_iop_order_iccprofile_info_t *const profile_info, const int devid, dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
static const float x
float *const restrict luminance
float *const restrict const size_t k
static const int max_size
Definition map.c:130
#define CLAMPS(A, L, H)
Definition math.h:76
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2136
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2544
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2261
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2127
int dt_opencl_enqueue_copy_buffer_to_buffer(const int devid, cl_mem src_buffer, cl_mem dst_buffer, size_t srcoffset, size_t dstoffset, size_t size)
Definition opencl.c:2296
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#define ROUNDUP(a, n)
Definition opencl.h:78
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float u2
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_iop_module_t *void * data
struct dt_develop_t * dev
dt_image_t image_storage
Definition develop.h:259
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
dt_iop_crystgrain_mode_t mode
Definition crystgrain.c:77
GtkWidget * colorspace_saturation
Definition crystgrain.c:72
dt_iop_crystgrain_kernel_t footprint
Definition crystgrain.c:100
dt_iop_crystgrain_mode_t mode
Definition crystgrain.c:53
GModule *dt_dev_operation_t op
Definition imageop.h:230
dt_iop_global_data_t * data
Definition imageop.h:233
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29