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"
21#endif
22
23#include "widgets/bauhaus.h"
24#include "common/imagebuf.h"
25#include "common/opencl.h"
26#include "develop/iop_profile.h"
27#include "math/math.h"
28#include "system/macros.h"
29#include "system/openmp.h"
31#include "system/mem_alloc.h"
33#include "develop/imageop.h"
34#include "develop/imageop_gui.h"
35#include "iop/noise_generator.h"
36#include "gui/presets.h"
37#include "iop/iop_api.h"
38
39#include <float.h>
40#include <gtk/gtk.h>
41#include <math.h>
42#include <stdint.h>
43#include <stdlib.h>
44#include <string.h>
45
47
48#define DT_CRYSTGRAIN_LAYER_KERNELS 16
49
51{
52 DT_CRYSTGRAIN_MONO = 0, // $DESCRIPTION: "B&W"
53 DT_CRYSTGRAIN_COLOR = 1 // $DESCRIPTION: "color"
55
57{
58 dt_iop_crystgrain_mode_t mode; // $DEFAULT: DT_CRYSTGRAIN_MONO $DESCRIPTION: "mode"
59 float filling; // $MIN: 0.0 $MAX: 95.0 $DEFAULT: 25.0 $DESCRIPTION: "Average layer filling"
60 float grain_size; // $MIN: 1.0 $MAX: 31.0 $DEFAULT: 4.0 $DESCRIPTION: "Crystals average size"
61 int layers; // $MIN: 1 $MAX: 64 $DEFAULT: 30 $DESCRIPTION: "Crystals layers"
62 float size_stddev; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.25 $DESCRIPTION: "Crystals size variability"
63 float layer_capture; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "Layer sensitivity"
64 float channel_correlation; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 67.0 $DESCRIPTION: "Inter-channel grain correlation"
65 float colorspace_saturation; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 67.0 $DESCRIPTION: "Grain colorfulness"
67
79
91
102
111
128
135
136#ifdef HAVE_OPENCL
148#endif
149
150
151const char *name()
152{
153 return _("Photographic grain");
154}
155
156const char **description(struct dt_iop_module_t *self)
157{
158 return dt_iop_set_description(self, _("simulate photographic grain from stacked silver-halide crystal layers"),
159 _("creative"),
160 _("non-linear, RGB, scene-referred"),
161 _("non-linear, RGB"),
162 _("non-linear, RGB, scene-referred"));
163}
164
169
171{
172 return IOP_GROUP_EFFECTS;
173}
174
176{
177 return IOP_CS_RGB;
178}
179
181{
183
185 p.filling = 85.0f;
186 p.grain_size = 4.0f;
187 p.layers = 30;
188 p.size_stddev = 0.25f;
189 p.layer_capture = 0.0f;
190 p.channel_correlation = 67.0f;
191 p.colorspace_saturation = 67.0f;
192 dt_gui_presets_add_generic(_("color grain"), self->op, self->version(), &p, sizeof(p), 1,
194
195 p.mode = DT_CRYSTGRAIN_MONO;
196 p.filling = 25.0f;
197 p.grain_size = 4.0f;
198 p.layers = 30;
199 p.size_stddev = 0.25f;
200 p.layer_capture = 0.0f;
201 p.channel_correlation = 67.0f;
202 p.colorspace_saturation = 67.0f;
203 dt_gui_presets_add_generic(_("B&W grain"), self->op, self->version(), &p, sizeof(p), 1,
205}
206
207int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
208 const int new_version)
209{
210 if((old_version == 1 || old_version == 8) && new_version == 9)
211 {
212 const dt_iop_crystgrain_params_t *o = old_params;
213 dt_iop_crystgrain_params_t *n = new_params;
214 *n = *o;
215 return 0;
216 }
217
218 return 1;
219}
220
225static unsigned int _hash_string(const char *s)
226{
227 unsigned int h = 0;
228 while(*s) h = 33 * h ^ (unsigned int)*s++;
229 return h;
230}
231
235static inline float _uniform_random(const uint64_t seed)
236{
237 return splitmix32(seed) * 0x1.0p-32f;
238}
239
247static inline float _gaussian_random(const uint64_t seed_a, const uint64_t seed_b)
248{
249 const float u1 = fmaxf(_uniform_random(seed_a), FLT_MIN);
250 const float u2 = _uniform_random(seed_b);
251 return sqrtf(-2.0f * logf(u1)) * cosf(2.0f * M_PI_F * u2);
252}
253
258static inline int _reflect_index(int i, const int max)
259{
260 if(max <= 1) return 0;
261
262 while(i < 0 || i >= max)
263 {
264 if(i < 0)
265 i = -i - 1;
266 else
267 i = 2 * max - i - 1;
268 }
269
270 return i;
271}
272
285static inline float _seed_probability(const float filling, const float crystal_area)
286{
287 const float clamped_filling = CLAMPS(filling, 0.0f, 0.9999f);
288 if(crystal_area <= 1.0f) return clamped_filling;
289 return 1.0f - powf(1.0f - clamped_filling, 1.0f / crystal_area);
290}
291
301static inline float _crystal_coverage(const int dx, const int dy, const float radius_f, const float vertices,
302 const float rotation)
303{
304 const float local_radius = hypotf((float)dx, (float)dy);
305 float signed_distance = 0.0f;
306 const float theta = atan2f((float)dy, (float)dx);
307 const float envelope = cosf(M_PI_F / vertices)
308 / cosf((2.0f * asinf(cosf(vertices * (theta + rotation))) + M_PI_F)
309 / (2.0f * vertices));
310 const float polygon_radius = radius_f * envelope;
311 signed_distance = polygon_radius - local_radius;
312 return CLAMPS(signed_distance + 0.5f, 0.0f, 1.0f);
313}
314
325static int _create_crystal_kernel(dt_iop_crystgrain_kernel_t *const kernel, const float radius_f,
326 const float vertices, const float rotation)
327{
328 memset(kernel, 0, sizeof(*kernel));
329
330 const int radius = MAX((int)ceilf(radius_f + 0.5f), 1);
331 const int width = 2 * radius + 1;
332 int count = 0;
333 float area = 0.0f;
334
335 for(int y = 0; y < width; y++)
336 {
337 for(int x = 0; x < width; x++)
338 {
339 const float alpha = _crystal_coverage(x - radius, y - radius, radius_f, vertices, rotation);
340 if(alpha > FLT_EPSILON)
341 {
342 count++;
343 area += alpha;
344 }
345 }
346 }
347
348 if(count <= 0 || area <= FLT_EPSILON) return 1;
349
350 kernel->dx = malloc(sizeof(int) * count);
351 kernel->dy = malloc(sizeof(int) * count);
352 kernel->alpha = malloc(sizeof(float) * count);
353 if(IS_NULL_PTR(kernel->dx) || IS_NULL_PTR(kernel->dy) || IS_NULL_PTR(kernel->alpha))
354 {
355 free(kernel->dx);
356 free(kernel->dy);
357 free(kernel->alpha);
358 memset(kernel, 0, sizeof(*kernel));
359 return 1;
360 }
361
362 kernel->count = count;
363 kernel->radius = radius;
364 kernel->radius_f = radius_f;
365 kernel->area = area;
366
367 int k = 0;
368 for(int y = 0; y < width; y++)
369 {
370 for(int x = 0; x < width; x++)
371 {
372 const float alpha = _crystal_coverage(x - radius, y - radius, radius_f, vertices, rotation);
373 if(alpha > FLT_EPSILON)
374 {
375 kernel->dx[k] = x - radius;
376 kernel->dy[k] = y - radius;
377 kernel->alpha[k] = alpha;
378 k++;
379 }
380 }
381 }
382
383 return 0;
384}
385
389static inline __attribute__((always_inline)) void _free_crystal_kernel(dt_iop_crystgrain_kernel_t *const kernel)
390{
391 free(kernel->dx);
392 free(kernel->dy);
393 free(kernel->alpha);
394 memset(kernel, 0, sizeof(*kernel));
395}
396
402 const dt_iop_crystgrain_runtime_t *const rt, const uint64_t seed)
403{
404 memset(entry, 0, sizeof(*entry));
405
406 // Let the grain follow the preview scaling below 100% so zoomed-out views
407 // stay visually coherent, but clamp at 100% to avoid inventing larger
408 // crystals when the user zooms in past the native image scale.
409 const float mean_size = MAX(rt->grain_size * rt->kernel_scale, 1.0f);
410 const float max_size = MAX(3.0f * mean_size, 1.0f);
411
412 for(int attempt = 0; attempt < 8; attempt++)
413 {
414 const float vertices = CLAMPS(6.0f + 1.5f * _gaussian_random(seed + 17u + attempt * 31u,
415 seed + 23u + attempt * 37u),
416 3.0f, 10.0f);
417 const float rotation = 2.0f * M_PI_F * _uniform_random(seed + 101u + attempt * 43u);
418 const float log_size = logf(mean_size) + rt->size_stddev * _gaussian_random(seed + 151u + attempt * 47u,
419 seed + 181u + attempt * 53u);
420 const float random_size = CLAMPS(expf(log_size), 1.0f, max_size);
421 const float radius_f = MAX(0.5f * (random_size - 1.0f), 0.5f);
422
423 if(_create_crystal_kernel(&entry->footprint, radius_f, vertices, rotation) == 0)
424 {
425 entry->probability = _seed_probability(rt->filling, entry->footprint.area);
426 entry->vertices = vertices;
427 entry->rotation = rotation;
428 entry->width = 2 * entry->footprint.radius + 1;
429 return 0;
430 }
431 }
432
433 if(_create_crystal_kernel(&entry->footprint, 0.5f, 4.0f, 0.0f) != 0) return 1;
434
435 entry->probability = _seed_probability(rt->filling, entry->footprint.area);
436 entry->vertices = 4.0f;
437 entry->rotation = 0.0f;
438 entry->width = 1;
439 return 0;
440}
441
450static inline float _average_grain_surface(const dt_iop_crystgrain_runtime_t *const rt)
451{
452 const float mean_size = MAX(rt->grain_size * rt->kernel_scale, 1.0f);
453 const float mean_radius = MAX(0.5f * (mean_size - 1.0f), 0.5f);
454 return M_PI_F * mean_radius * mean_radius;
455}
456
458 const dt_iop_crystgrain_runtime_t *const rt, const uint64_t layer_seed);
460
473{
474 const int sampled_layers = MIN(rt->layers, 4);
475 if(sampled_layers <= 0) return _average_grain_surface(rt);
476
477 float total_area = 0.0f;
478 int total_kernels = 0;
479
480 for(int layer = 0; layer < sampled_layers; layer++)
481 {
483 const uint64_t layer_seed = rt->base_seed + layer * 4099u;
484
485 if(_build_layer_kernel_bank(bank, rt, layer_seed) != 0)
486 return _average_grain_surface(rt);
487
488 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
489 total_area += bank[i].footprint.area;
490
491 total_kernels += DT_CRYSTGRAIN_LAYER_KERNELS;
493 }
494
495 return (total_area > FLT_EPSILON && total_kernels > 0)
496 ? total_area / total_kernels
498}
499
509 const dt_iop_crystgrain_runtime_t *const rt, const uint64_t layer_seed)
510{
512
513 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
514 {
515 const uint64_t kernel_seed = layer_seed ^ ((uint64_t)(i + 1) * 0xd1342543de82ef95ull);
516 if(_pick_layer_kernel(&bank[i], rt, kernel_seed) != 0)
517 {
518 for(int k = 0; k < i; k++) _free_crystal_kernel(&bank[k].footprint);
519 return 1;
520 }
521 }
522
523 return 0;
524}
525
531{
532 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++) _free_crystal_kernel(&bank[i].footprint);
533}
534
555static float _predict_layer_capture(const dt_iop_crystgrain_layer_kernel_t *const bank, const float layer_scale,
556 const float remaining_fraction)
557{
558 double capture = 0.0;
559
560 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
561 {
562 const float area = bank[i].footprint.area;
563 const float captured = fminf(remaining_fraction, area * layer_scale);
564 capture += bank[i].probability * area * captured;
565 }
566
567 return MAX((float)(capture / DT_CRYSTGRAIN_LAYER_KERNELS), 0.0f);
568}
569
587static inline float _predict_stack_exposure(const float remaining_fraction)
588{
589 const float transmitted = 1.0f - remaining_fraction;
590 return (transmitted > FLT_EPSILON) ? 1.0f / transmitted : 1.0f;
591}
592
593static inline size_t _rgb_index(const size_t pixel, const int channel)
594{
595 return 4 * pixel + channel;
596}
597
612static int _simulate_channel(const dt_iop_crystgrain_runtime_t *const rt, const float *const image, float *const result,
613 float *const remaining, float *const exposure)
614{
615 const int width = rt->width;
616 const int height = rt->height;
617 const size_t npixels = (size_t)width * height;
618 float predicted_remaining = 1.0f;
619 memset(result, 0, sizeof(float) * npixels);
620 memcpy(remaining, image, sizeof(float) * npixels);
621
622 for(int layer = 0; layer < rt->layers; layer++)
623 {
625 const uint64_t layer_seed = rt->base_seed + layer * 4099u;
626 if(_build_layer_kernel_bank(kernel_bank, rt, layer_seed) != 0) return 1;
627 predicted_remaining = fmaxf(predicted_remaining
628 - _predict_layer_capture(kernel_bank, rt->layer_scale, predicted_remaining),
629 0.0f);
630
631 for(int y = 0; y < rt->height; y++)
632 {
633 const int world_y = (int)((rt->roi_y + y) * rt->inv_scale);
634 for(int x = 0; x < rt->width; x++)
635 {
636 const size_t index = (size_t)y * rt->width + x;
637 if(remaining[index] <= 0.0f) continue;
638
639 const int world_x = (int)((rt->roi_x + x) * rt->inv_scale);
640 const uint64_t pixel_seed = rt->base_seed
641 ^ ((uint64_t)(uint32_t)world_x << 32)
642 ^ (uint32_t)world_y
643 ^ (uint64_t)(layer + 1) * 0x9e3779b97f4a7c15ull;
644 const int kernel_index = splitmix32(pixel_seed ^ 0x94d049bb133111ebull) & (DT_CRYSTGRAIN_LAYER_KERNELS - 1);
645 const dt_iop_crystgrain_layer_kernel_t *const entry = &kernel_bank[kernel_index];
646 const dt_iop_crystgrain_kernel_t *const kernel = &entry->footprint;
647 const int radius = kernel->radius;
648 const int interior = (y >= radius && y < rt->height - radius && x >= radius && x < rt->width - radius);
649 float seed_energy = 0.0f;
650 float original_energy = 0.0f;
651
652 // The seed tests the light field that is still available after all
653 // previous grains and layers have already depleted their share.
654 if(_uniform_random(pixel_seed ^ 0xda942042e4dd58b5ull) >= entry->probability) continue;
655
656 // Like the OpenCL path, each pixel either exits immediately or sweeps
657 // only its own crystal footprint to print one flat tone into the
658 // reconstruction while depleting the remaining light field in place.
659 for(int tap = 0; tap < kernel->count; tap++)
660 {
661 int xx = x + kernel->dx[tap];
662 int yy = y + kernel->dy[tap];
663 if(!interior)
664 {
665 xx = _reflect_index(xx, width);
666 yy = _reflect_index(yy, height);
667 }
668
669 const size_t dst = (size_t)yy * width + xx;
670 // A crystal prints one flat tone from the average of the current
671 // light field and of the immutable input over the whole grain
672 // surface, so no detail finer than the grain survives inside it.
673 seed_energy += remaining[dst] * kernel->alpha[tap];
674 original_energy += image[dst] * kernel->alpha[tap];
675 }
676 seed_energy /= kernel->area;
677 // The user layer scale now applies to the whole grain surface, so the
678 // per-pixel flat tone cap must scale with the grain area too.
679 original_energy *= rt->layer_scale;
680 seed_energy = fminf(seed_energy, original_energy);
681 if(seed_energy <= 0.0f) continue;
682
683 for(int tap = 0; tap < kernel->count; tap++)
684 {
685 int xx = x + kernel->dx[tap];
686 int yy = y + kernel->dy[tap];
687 if(!interior)
688 {
689 xx = _reflect_index(xx, width);
690 yy = _reflect_index(yy, height);
691 }
692
693 const size_t dst = (size_t)yy * width + xx;
694 // Write the flat crystal tone back to the output and subtract the
695 // same quantity from the light field that will feed deeper layers.
696 const float deposited = seed_energy * kernel->alpha[tap];
697 result[dst] += deposited;
698 remaining[dst] = fmaxf(remaining[dst] - deposited, 0.0f);
699 }
700 }
701 }
702
703 _free_layer_kernel_bank(kernel_bank);
704 }
705
706 *exposure = _predict_stack_exposure(predicted_remaining);
707 return 0;
708}
709
723 float *const exposure)
724{
725 const int width = rt->width;
726 const int height = rt->height;
727 const size_t npixels = (size_t)width * height;
728 const int blue_layers = (rt->layers + 2) / 3;
729 const int green_layers = (rt->layers + 1) / 3;
730 float predicted_remaining[3] = { 1.0f, 1.0f, 1.0f };
731 const uint64_t channel_salt[3] = {
732 0xa24baed4963ee407ull,
733 0x9fb21c651e98df25ull,
734 0xc13fa9a902a6328full
735 };
736
737 memset(state->result, 0, sizeof(float) * npixels * 4);
738 memcpy(state->remaining, state->image, sizeof(float) * npixels * 4);
739
740 for(int layer = 0; layer < rt->layers; layer++)
741 {
743 const int c = (layer < blue_layers) ? 2 : ((layer < blue_layers + green_layers) ? 1 : 0);
744 const int sublayer = (c == 2) ? layer : ((c == 1) ? layer - blue_layers : layer - blue_layers - green_layers);
745 const uint64_t layer_seed = rt->base_seed + (uint64_t)(sublayer + 1) * 4099u;
746 if(_build_layer_kernel_bank(kernel_bank, rt, layer_seed) != 0) return 1;
747 predicted_remaining[c] = fmaxf(predicted_remaining[c]
748 - _predict_layer_capture(kernel_bank, rt->layer_scale, predicted_remaining[c]),
749 0.0f);
750
751 for(int y = 0; y < height; y++)
752 {
753 const int world_y = (int)((rt->roi_y + y) * rt->inv_scale);
754 for(int x = 0; x < width; x++)
755 {
756 const size_t index = (size_t)y * width + x;
757 const float remaining_total = state->remaining[_rgb_index(index, 0)]
758 + state->remaining[_rgb_index(index, 1)]
759 + state->remaining[_rgb_index(index, 2)];
760 if(remaining_total <= 0.0f) continue;
761
762 const int world_x = (int)((rt->roi_x + x) * rt->inv_scale);
763 const uint64_t shared_seed = rt->base_seed
764 ^ ((uint64_t)(uint32_t)world_x << 32)
765 ^ (uint32_t)world_y
766 ^ (uint64_t)(sublayer + 1) * 0x9e3779b97f4a7c15ull;
767 const uint64_t channel_seed = shared_seed ^ channel_salt[c];
768 const int use_shared = _uniform_random(channel_seed ^ 0x4f1bbcdc6762f96bull) < rt->channel_correlation;
769 const uint64_t pixel_seed = use_shared ? shared_seed : channel_seed;
770 const int kernel_index = splitmix32(pixel_seed ^ 0x94d049bb133111ebull) & (DT_CRYSTGRAIN_LAYER_KERNELS - 1);
771 const dt_iop_crystgrain_layer_kernel_t *const entry = &kernel_bank[kernel_index];
772 const dt_iop_crystgrain_kernel_t *const kernel = &entry->footprint;
773 const int radius = kernel->radius;
774 const int interior = (y >= radius && y < height - radius && x >= radius && x < width - radius);
775 float seed_energy = 0.0f;
776 float original_energy = 0.0f;
777
778 if(_uniform_random(pixel_seed ^ 0xda942042e4dd58b5ull) >= entry->probability) continue;
779
780 for(int tap = 0; tap < kernel->count; tap++)
781 {
782 int xx = x + kernel->dx[tap];
783 int yy = y + kernel->dy[tap];
784 if(!interior)
785 {
786 xx = _reflect_index(xx, width);
787 yy = _reflect_index(yy, height);
788 }
789
790 const size_t dst = (size_t)yy * width + xx;
791 // Each depth layer belongs to one spectral emulsion only, so it
792 // prints one flat tone from that channel and leaves the others to
793 // deeper layers.
794 seed_energy += state->remaining[_rgb_index(dst, c)] * kernel->alpha[tap];
795 original_energy += state->image[_rgb_index(dst, c)] * kernel->alpha[tap];
796 }
797
798 seed_energy /= kernel->area;
799 original_energy *= rt->layer_scale;
800 const float captured = fminf(seed_energy, original_energy);
801 if(captured <= 0.0f) continue;
802
803 for(int tap = 0; tap < kernel->count; tap++)
804 {
805 int xx = x + kernel->dx[tap];
806 int yy = y + kernel->dy[tap];
807 if(!interior)
808 {
809 xx = _reflect_index(xx, width);
810 yy = _reflect_index(yy, height);
811 }
812
813 const size_t dst = (size_t)yy * width + xx;
814 const float deposited = captured * kernel->alpha[tap];
815 state->result[_rgb_index(dst, c)] += deposited;
816 state->remaining[_rgb_index(dst, c)]
817 = fmaxf(state->remaining[_rgb_index(dst, c)] - deposited, 0.0f);
818 }
819 }
820 }
821
822 _free_layer_kernel_bank(kernel_bank);
823 }
824
825 for(int c = 0; c < 3; c++) exposure[c] = _predict_stack_exposure(predicted_remaining[c]);
826 return 0;
827}
828
836static void _extract_luminance_kernel(const float *const restrict in, float *const restrict image,
837 const int width, const int height,
838 const dt_iop_order_iccprofile_info_t *const work_profile)
839{
841 for(int y = 0; y < height; y++)
842 {
843 const size_t row = (size_t)y * width;
844 for(int x = 0; x < width; x++)
845 {
846 const size_t k = row + x;
847 const float luminance = (work_profile)
848 ? dt_ioppr_get_rgb_matrix_luminance(in + 4 * k, work_profile->matrix_in, work_profile->lut_in,
849 work_profile->unbounded_coeffs_in, work_profile->lutsize,
850 work_profile->nonlinearlut)
851 : dt_camera_rgb_luminance(in + 4 * k);
852
853 image[k] = fmaxf(luminance, 0.0f);
854 }
855 }
856}
857
866static void _extract_rgb_kernels(const float *const restrict in, float *const restrict image,
867 const int width, const int height)
868{
870 for(int y = 0; y < height; y++)
871 {
872 const size_t row = (size_t)y * width;
873 for(int x = 0; x < width; x++)
874 {
875 const size_t k = row + x;
876 const float red = fmaxf(in[4 * k + 0], 0.0f);
877 const float green = fmaxf(in[4 * k + 1], 0.0f);
878 const float blue = fmaxf(in[4 * k + 2], 0.0f);
879
880 image[_rgb_index(k, 0)] = red;
881 image[_rgb_index(k, 1)] = green;
882 image[_rgb_index(k, 2)] = blue;
883 image[_rgb_index(k, 3)] = 0.0f;
884 }
885 }
886}
887
899static void _apply_mono_grain_kernel(const float *const restrict in, float *const restrict out,
900 const float *const restrict image, const float *const restrict result,
901 const int width, const int height, const float exposure)
902{
904 for(int y = 0; y < height; y++)
905 {
906 const size_t row = (size_t)y * width;
907 for(int x = 0; x < width; x++)
908 {
909 const size_t k = row + x;
910 const float grainy = fmaxf(result[k] * exposure, 0.0f);
911 const float ratio = (image[k] > 1e-6f) ? grainy / image[k] : 0.0f;
912
913 out[4 * k + 0] = fmaxf(in[4 * k + 0] * ratio, 0.0f);
914 out[4 * k + 1] = fmaxf(in[4 * k + 1] * ratio, 0.0f);
915 out[4 * k + 2] = fmaxf(in[4 * k + 2] * ratio, 0.0f);
916 }
917 }
918}
919
929static void _finalize_color_grain_kernel(const float *const restrict in, float *const restrict out,
930 const float *const restrict image, const float *const restrict result,
931 const int width, const int height, const float exposure_r,
932 const float exposure_g, const float exposure_b, const float colorfulness)
933{
935 for(int y = 0; y < height; y++)
936 {
937 const size_t row = (size_t)y * width;
938 for(int x = 0; x < width; x++)
939 {
940 const size_t k = row + x;
941 const float image_r = image[_rgb_index(k, 0)];
942 const float image_g = image[_rgb_index(k, 1)];
943 const float image_b = image[_rgb_index(k, 2)];
944 const float grain_r = (exposure_r > 0.0f) ? fmaxf(result[_rgb_index(k, 0)] * exposure_r, 0.0f) : image_r;
945 const float grain_g = (exposure_g > 0.0f) ? fmaxf(result[_rgb_index(k, 1)] * exposure_g, 0.0f) : image_g;
946 const float grain_b = (exposure_b > 0.0f) ? fmaxf(result[_rgb_index(k, 2)] * exposure_b, 0.0f) : image_b;
947 const float residual_r = grain_r - image_r;
948 const float residual_g = grain_g - image_g;
949 const float residual_b = grain_b - image_b;
950 const float mean = (residual_r + residual_g + residual_b) / 3.0f;
951
952 out[4 * k + 0] = in[4 * k + 0] + mean + (residual_r - mean) * colorfulness;
953 out[4 * k + 1] = in[4 * k + 1] + mean + (residual_g - mean) * colorfulness;
954 out[4 * k + 2] = in[4 * k + 2] + mean + (residual_b - mean) * colorfulness;
955 }
956 }
957}
958
959#ifdef HAVE_OPENCL
960#define DT_CRYSTGRAIN_CL_PROGRAM 36
961#define DT_CRYSTGRAIN_REDUCESIZE 64
962
972static int _simulate_channel_cl(const int devid, dt_iop_crystgrain_global_data_t *const gd,
973 const dt_iop_crystgrain_runtime_t *const rt, cl_mem dev_image, cl_mem dev_result,
974 cl_mem dev_remaining, float *const exposure)
975{
976 cl_int err = CL_SUCCESS;
977 const int width = rt->width;
978 const int height = rt->height;
979 size_t sizes[3] = { ROUNDUP((size_t)width, (size_t)16), ROUNDUP((size_t)height, (size_t)16), 1 };
980 const size_t buffer_size = sizeof(float) * (size_t)width * height;
981 float predicted_remaining = 1.0f;
982
983 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_scalar, 0, sizeof(cl_mem), &dev_result);
984 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_scalar, 1, sizeof(int), &width);
985 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_scalar, 2, sizeof(int), &height);
986 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_zero_scalar, sizes);
987 if(err != CL_SUCCESS) return err;
988
989 err = dt_opencl_enqueue_copy_buffer_to_buffer(devid, dev_image, dev_remaining, 0, 0, buffer_size);
990 if(err != CL_SUCCESS) return err;
991
992 for(int layer = 0; layer < rt->layers; layer++)
993 {
995 float kernel_bank_cl[DT_CRYSTGRAIN_LAYER_KERNELS][4];
996 cl_mem dev_kernel_bank = NULL;
997 const float layer_scale = rt->layer_scale;
998 const int roi_x = rt->roi_x;
999 const int roi_y = rt->roi_y;
1000 const float inv_scale = rt->inv_scale;
1001 const cl_ulong base_seed = (cl_ulong)rt->base_seed;
1002
1003 if(_build_layer_kernel_bank(kernel_bank, rt, rt->base_seed + layer * 4099u) != 0)
1004 {
1005 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1006 return err;
1007 }
1008 predicted_remaining = fmaxf(predicted_remaining
1009 - _predict_layer_capture(kernel_bank, rt->layer_scale, predicted_remaining),
1010 0.0f);
1011
1012 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
1013 {
1014 kernel_bank_cl[i][0] = kernel_bank[i].vertices;
1015 kernel_bank_cl[i][1] = kernel_bank[i].rotation;
1016 kernel_bank_cl[i][2] = kernel_bank[i].probability;
1017 kernel_bank_cl[i][3] = kernel_bank[i].footprint.radius_f;
1018 }
1019
1020 dev_kernel_bank = dt_opencl_copy_host_to_device_constant(devid, sizeof(kernel_bank_cl), kernel_bank_cl);
1021 _free_layer_kernel_bank(kernel_bank);
1022 if(IS_NULL_PTR(dev_kernel_bank)) return CL_MEM_OBJECT_ALLOCATION_FAILURE;
1023
1024 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 0, sizeof(cl_mem), &dev_image);
1025 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 1, sizeof(cl_mem), &dev_remaining);
1026 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 2, sizeof(cl_mem), &dev_result);
1027 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 3, sizeof(cl_mem), &dev_kernel_bank);
1028 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 4, sizeof(int), &width);
1029 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 5, sizeof(int), &height);
1030 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 6, sizeof(int), &roi_x);
1031 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 7, sizeof(int), &roi_y);
1032 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 8, sizeof(float), &inv_scale);
1033 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 9, sizeof(cl_ulong), &base_seed);
1034 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 10, sizeof(int), &layer);
1035 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer, 11, sizeof(float), &layer_scale);
1036 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_simulate_layer, sizes);
1037 dt_opencl_release_mem_object(dev_kernel_bank);
1038 if(err != CL_SUCCESS) return err;
1039 }
1040
1041 *exposure = _predict_stack_exposure(predicted_remaining);
1042 return err;
1043}
1044
1046{
1048 module->data = gd;
1049 const int program = DT_CRYSTGRAIN_CL_PROGRAM;
1050 gd->kernel_zero_scalar = dt_opencl_create_kernel(program, "crystgrain_zero_scalar");
1051 gd->kernel_zero_rgb = dt_opencl_create_kernel(program, "crystgrain_zero_rgb");
1052 gd->kernel_extract_luminance = dt_opencl_create_kernel(program, "crystgrain_extract_luminance");
1053 gd->kernel_extract_rgb = dt_opencl_create_kernel(program, "crystgrain_extract_rgb");
1054 gd->kernel_simulate_layer = dt_opencl_create_kernel(program, "crystgrain_simulate_layer");
1055 gd->kernel_simulate_layer_color = dt_opencl_create_kernel(program, "crystgrain_simulate_layer_color");
1056 gd->kernel_apply_mono = dt_opencl_create_kernel(program, "crystgrain_apply_mono");
1057 gd->kernel_finalize_color = dt_opencl_create_kernel(program, "crystgrain_finalize_color");
1058}
1059
1074
1075int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1076 cl_mem dev_in, cl_mem dev_out)
1077{
1078 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1079 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1080 const dt_iop_crystgrain_data_t *const d = (const dt_iop_crystgrain_data_t *)piece->data;
1083 const int devid = pipe->devid;
1084 const int width = roi_out->width;
1085 const int height = roi_out->height;
1086 // Grain size is authored in full-resolution output pixels at 100% zoom.
1087 // The current processing grid may already be downsampled twice:
1088 // 1. by the ROI zoom factor used for the current preview/export,
1089 // 2. by the mipmap level chosen before the pipe even starts.
1090 const float kernel_scale = MAX(1.0f / dt_dev_get_module_scale(pipe, roi_in), 1e-6f);
1091 cl_int err = CL_SUCCESS;
1092 float exposure[3] = { 1.0f, 1.0f, 1.0f };
1093
1094 if(width <= 0 || height <= 0 || d->layers <= 0 || d->filling <= 0.0f)
1095 {
1096 size_t origin[] = { 0, 0, 0 };
1097 size_t region[] = { (size_t)width, (size_t)height, 1 };
1098 return dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, origin, origin, region);
1099 }
1100
1101 cl_mem dev_image = NULL;
1102 cl_mem dev_result = NULL;
1103 cl_mem dev_remaining = NULL;
1104 cl_mem dev_image_rgb = NULL;
1105 cl_mem dev_result_rgb = NULL;
1106 cl_mem dev_remaining_rgb = NULL;
1107 dt_colorspaces_iccprofile_info_cl_t *profile_info_cl = NULL;
1108 cl_float *profile_lut_cl = NULL;
1109 cl_mem dev_profile_info = NULL;
1110 cl_mem dev_profile_lut = NULL;
1111
1112 dev_image = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height);
1113 dev_result = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height);
1114 dev_remaining = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height);
1115 if(IS_NULL_PTR(dev_image) || IS_NULL_PTR(dev_result) || IS_NULL_PTR(dev_remaining))
1116 {
1117 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1118 goto error;
1119 }
1120
1122 .width = width,
1123 .height = height,
1124 .roi_x = roi_out->x,
1125 .roi_y = roi_out->y,
1126 .layers = d->layers,
1127 .layer_scale = 0.0f,
1128 .filling = d->filling,
1129 .grain_size = d->grain_size,
1130 .size_stddev = d->size_stddev,
1131 .kernel_scale = kernel_scale,
1132 .inv_scale = 1.0f / kernel_scale,
1133 .channel_correlation = d->channel_correlation,
1134 .base_seed = ((uint64_t)_hash_string(pipe->dev->image_storage.filename) << 32)
1135 ^ ((uint64_t)width << 16) ^ (uint64_t)height
1136 };
1137 const float current_surface = _average_discrete_grain_surface(&rt);
1138 // Neutral layer capture is defined as 1/layers of the input energy for a
1139 // grain of average rasterized surface. Since each sampled bank entry can
1140 // have a different discrete area A_i, the flat-field recurrence uses
1141 // min(r_l, A_i * layer_scale) per crystal, with the current_surface term
1142 // keeping the user-facing EV control centered on that neutral 1/layers
1143 // behaviour across preview scales.
1144 rt.layer_scale = d->layer_capture / MAX((float)d->layers, 1.0f) / MAX(current_surface, FLT_EPSILON);
1145 const int blue_layers = (rt.layers + 2) / 3;
1146 const int green_layers = (rt.layers + 1) / 3;
1147
1148 size_t sizes[3] = { ROUNDUP((size_t)width, (size_t)16), ROUNDUP((size_t)height, (size_t)16), 1 };
1149
1150 if(d->mode == DT_CRYSTGRAIN_MONO)
1151 {
1152 err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
1153 &dev_profile_info, &dev_profile_lut);
1154 if(err != CL_SUCCESS) goto error;
1155
1156 const int use_work_profile = (!IS_NULL_PTR(work_profile)) ? 1 : 0;
1157 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 0, sizeof(cl_mem), &dev_in);
1158 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 1, sizeof(cl_mem), &dev_image);
1159 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 2, sizeof(int), &width);
1160 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 3, sizeof(int), &height);
1161 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 4, sizeof(cl_mem), &dev_profile_info);
1162 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 5, sizeof(cl_mem), &dev_profile_lut);
1163 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_luminance, 6, sizeof(int), &use_work_profile);
1165 if(err != CL_SUCCESS) goto error;
1166
1167 err = _simulate_channel_cl(devid, gd, &rt, dev_image, dev_result, dev_remaining, &exposure[0]);
1168 if(err != CL_SUCCESS) goto error;
1169
1170 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 0, sizeof(cl_mem), &dev_in);
1171 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 1, sizeof(cl_mem), &dev_image);
1172 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 2, sizeof(cl_mem), &dev_result);
1173 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 3, sizeof(cl_mem), &dev_out);
1174 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 4, sizeof(int), &width);
1175 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 5, sizeof(int), &height);
1176 dt_opencl_set_kernel_arg(devid, gd->kernel_apply_mono, 6, sizeof(float), &exposure[0]);
1177 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_apply_mono, sizes);
1178 goto error;
1179 }
1180
1181 dev_image_rgb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height * 4);
1182 dev_result_rgb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height * 4);
1183 dev_remaining_rgb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * (size_t)width * height * 4);
1184 if(IS_NULL_PTR(dev_image_rgb) || IS_NULL_PTR(dev_result_rgb) || IS_NULL_PTR(dev_remaining_rgb))
1185 {
1186 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1187 goto error;
1188 }
1189
1190 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 0, sizeof(cl_mem), &dev_in);
1191 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 1, sizeof(cl_mem), &dev_image_rgb);
1192 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 2, sizeof(int), &width);
1193 dt_opencl_set_kernel_arg(devid, gd->kernel_extract_rgb, 3, sizeof(int), &height);
1194 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_extract_rgb, sizes);
1195 if(err != CL_SUCCESS) goto error;
1196
1197 const size_t color_buffer_size = sizeof(float) * (size_t)width * height * 4;
1198 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_rgb, 0, sizeof(cl_mem), &dev_result_rgb);
1199 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_rgb, 1, sizeof(int), &width);
1200 dt_opencl_set_kernel_arg(devid, gd->kernel_zero_rgb, 2, sizeof(int), &height);
1201 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_zero_rgb, sizes);
1202 if(err != CL_SUCCESS) goto error;
1203
1204 err = dt_opencl_enqueue_copy_buffer_to_buffer(devid, dev_image_rgb, dev_remaining_rgb, 0, 0, color_buffer_size);
1205 if(err != CL_SUCCESS) goto error;
1206
1207 float predicted_remaining[3] = { 1.0f, 1.0f, 1.0f };
1208 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 0, sizeof(cl_mem), &dev_image_rgb);
1209 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 1, sizeof(cl_mem), &dev_remaining_rgb);
1210 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 2, sizeof(cl_mem), &dev_result_rgb);
1211 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 3, sizeof(int), &width);
1212 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 4, sizeof(int), &height);
1213 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 5, sizeof(int), &rt.roi_x);
1214 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 6, sizeof(int), &rt.roi_y);
1215 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 7, sizeof(float), &rt.inv_scale);
1216 {
1217 const cl_ulong base_seed = (cl_ulong)rt.base_seed;
1218 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 8, sizeof(cl_ulong), &base_seed);
1219 }
1220 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 10, sizeof(float), &rt.layer_scale);
1222
1223 for(int layer = 0; layer < rt.layers; layer++)
1224 {
1226 float kernel_bank_cl[DT_CRYSTGRAIN_LAYER_KERNELS][4];
1227 cl_mem dev_kernel_bank = NULL;
1228 const int active_channel = (layer < blue_layers) ? 2 : ((layer < blue_layers + green_layers) ? 1 : 0);
1229 const int sublayer = (active_channel == 2)
1230 ? layer
1231 : ((active_channel == 1) ? layer - blue_layers : layer - blue_layers - green_layers);
1232 if(_build_layer_kernel_bank(kernel_bank, &rt, rt.base_seed + (uint64_t)(sublayer + 1) * 4099u) != 0)
1233 {
1234 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1235 goto error;
1236 }
1237 predicted_remaining[active_channel]
1238 = fmaxf(predicted_remaining[active_channel]
1239 - _predict_layer_capture(kernel_bank, rt.layer_scale, predicted_remaining[active_channel]),
1240 0.0f);
1241
1242 for(int i = 0; i < DT_CRYSTGRAIN_LAYER_KERNELS; i++)
1243 {
1244 kernel_bank_cl[i][0] = kernel_bank[i].vertices;
1245 kernel_bank_cl[i][1] = kernel_bank[i].rotation;
1246 kernel_bank_cl[i][2] = kernel_bank[i].probability;
1247 kernel_bank_cl[i][3] = kernel_bank[i].footprint.radius_f;
1248 }
1249
1250 dev_kernel_bank = dt_opencl_copy_host_to_device_constant(devid, sizeof(kernel_bank_cl), kernel_bank_cl);
1251 _free_layer_kernel_bank(kernel_bank);
1252 if(IS_NULL_PTR(dev_kernel_bank))
1253 {
1254 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1255 goto error;
1256 }
1257
1258 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 9, sizeof(cl_mem), &dev_kernel_bank);
1259 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 11, sizeof(int), &sublayer);
1260 dt_opencl_set_kernel_arg(devid, gd->kernel_simulate_layer_color, 12, sizeof(int), &active_channel);
1262 dt_opencl_release_mem_object(dev_kernel_bank);
1263 if(err != CL_SUCCESS) goto error;
1264 }
1265
1266 for(int c = 0; c < 3; c++) exposure[c] = _predict_stack_exposure(predicted_remaining[c]);
1267
1268 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 0, sizeof(cl_mem), &dev_in);
1269 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 1, sizeof(cl_mem), &dev_image_rgb);
1270 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 2, sizeof(cl_mem), &dev_result_rgb);
1271 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 3, sizeof(cl_mem), &dev_out);
1272 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 4, sizeof(int), &width);
1273 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 5, sizeof(int), &height);
1274 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 6, sizeof(float), &exposure[0]);
1275 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 7, sizeof(float), &exposure[1]);
1276 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 8, sizeof(float), &exposure[2]);
1277 dt_opencl_set_kernel_arg(devid, gd->kernel_finalize_color, 9, sizeof(float), &d->colorspace_saturation);
1278 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_finalize_color, sizes);
1279
1280error:
1282 dt_opencl_release_mem_object(dev_result);
1283 dt_opencl_release_mem_object(dev_remaining);
1284 dt_opencl_release_mem_object(dev_image_rgb);
1285 dt_opencl_release_mem_object(dev_result_rgb);
1286 dt_opencl_release_mem_object(dev_remaining_rgb);
1287 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1288 return (err == CL_SUCCESS) ? TRUE : FALSE;
1289}
1290#endif
1291
1294{
1297
1298 d->mode = p->mode;
1299 d->filling = p->filling * 0.01f;
1300 d->grain_size = p->grain_size;
1301 d->layers = p->layers;
1302 d->size_stddev = p->size_stddev;
1303 d->layer_capture = exp2f(p->layer_capture);
1304 d->channel_correlation = p->channel_correlation * 0.01f;
1305 d->colorspace_saturation = p->colorspace_saturation * 0.01f;
1306}
1307
1313
1315{
1316 dt_free_align(piece->data);
1317 piece->data = NULL;
1318}
1319
1320int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1321 const void *const ivoid, void *const ovoid)
1322{
1323 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1324 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1325 const dt_iop_crystgrain_data_t *const d = (const dt_iop_crystgrain_data_t *)piece->data;
1327 const float *const restrict in = (const float *const)ivoid;
1328 float *const restrict out = (float *const)ovoid;
1329 const int width = roi_out->width;
1330 const int height = roi_out->height;
1331 // Grain size is authored in full-resolution output pixels at 100% zoom.
1332 const float kernel_scale = MAX(1.0f / dt_dev_get_module_scale(pipe, roi_in), 1e-6f);
1333
1334 if(width <= 0 || height <= 0 || d->layers <= 0 || d->filling <= 0.0f)
1335 {
1336 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1337 return 0;
1338 }
1339
1340 float *image = NULL;
1341 float *result = NULL;
1342 float *remaining = NULL;
1343 float *image_rgb = NULL;
1344 float *result_rgb = NULL;
1345 float *remaining_rgb = NULL;
1346 if(dt_iop_alloc_image_buffers(self, roi_in, roi_out,
1347 1, &image,
1348 1 | DT_IMGSZ_CLEARBUF, &result,
1349 1, &remaining,
1350 4, &image_rgb,
1351 4 | DT_IMGSZ_CLEARBUF, &result_rgb,
1352 4, &remaining_rgb,
1353 0))
1354 {
1355 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1356 return 1;
1357 }
1358
1360
1362 .width = width,
1363 .height = height,
1364 .roi_x = roi_out->x,
1365 .roi_y = roi_out->y,
1366 .layers = d->layers,
1367 .layer_scale = 0.0f,
1368 .filling = d->filling,
1369 .grain_size = d->grain_size,
1370 .size_stddev = d->size_stddev,
1371 .kernel_scale = kernel_scale,
1372 .inv_scale = 1.0f / kernel_scale,
1373 .channel_correlation = d->channel_correlation,
1374 .base_seed = ((uint64_t)_hash_string(pipe->dev->image_storage.filename) << 32)
1375 ^ ((uint64_t)width << 16) ^ (uint64_t)height
1376 };
1377 const float current_surface = _average_discrete_grain_surface(&rt);
1378 // Neutral layer capture is defined as 1/layers of the input energy for a
1379 // grain of average rasterized surface. Since each sampled bank entry can
1380 // have a different discrete area A_i, the flat-field recurrence uses
1381 // min(r_l, A_i * layer_scale) per crystal, with the current_surface term
1382 // keeping the user-facing EV control centered on that neutral 1/layers
1383 // behaviour across preview scales.
1384 rt.layer_scale = d->layer_capture / MAX((float)d->layers, 1.0f) / MAX(current_surface, FLT_EPSILON);
1385
1386 if(d->mode == DT_CRYSTGRAIN_MONO)
1387 {
1388 _extract_luminance_kernel(in, image, width, height, work_profile);
1389 float mono_exposure = 1.0f;
1390
1391 if(_simulate_channel(&rt, image, result, remaining, &mono_exposure) != 0)
1392 {
1396 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1397 return 1;
1398 }
1399
1400 _apply_mono_grain_kernel(in, out, image, result, width, height, mono_exposure);
1401 }
1402 else
1403 {
1404 // Color film layers share one crystal geometry stack. Keep the working
1405 // light fields interleaved as RGB tuples so extraction, simulation and
1406 // final write-back all walk one contiguous color buffer instead of three
1407 // independent scalar plates.
1408 _extract_rgb_kernels(in, image_rgb, width, height);
1409 float color_exposure[3] = { 1.0f, 1.0f, 1.0f };
1410 const dt_iop_crystgrain_color_state_t color_state = {
1411 .image = image_rgb,
1412 .result = result_rgb,
1413 .remaining = remaining_rgb
1414 };
1415
1416 if(_simulate_color(&rt, &color_state, color_exposure) != 0)
1417 {
1423 dt_pixelpipe_cache_free_align(remaining_rgb);
1424 dt_iop_copy_image_roi(out, in, 4, roi_in, roi_out, TRUE);
1425 return 1;
1426 }
1427
1428 _finalize_color_grain_kernel(in, out, image_rgb, result_rgb, width, height,
1429 color_exposure[0], color_exposure[1], color_exposure[2],
1430 d->colorspace_saturation);
1431 }
1432
1438 dt_pixelpipe_cache_free_align(remaining_rgb);
1439 return 0;
1440}
1441
1442void gui_update(struct dt_iop_module_t *self)
1443{
1446 const gboolean is_color = (p->mode == DT_CRYSTGRAIN_COLOR);
1447
1448 gtk_widget_set_visible(g->channel_correlation, is_color);
1449 gtk_widget_set_visible(g->colorspace_saturation, is_color);
1450}
1451
1452static void _mode_changed(GtkWidget *widget, dt_iop_module_t *self)
1453{
1454 gui_update(self);
1455}
1456
1457void gui_init(struct dt_iop_module_t *self)
1458{
1460
1461 g->mode = dt_bauhaus_combobox_from_params(self, "mode");
1462 gtk_widget_set_tooltip_text(g->mode, _("simulate one shared B&W grain field or one shared blue/green/red-sensitive color grain stack"));
1463 g_signal_connect(G_OBJECT(g->mode), "value-changed", G_CALLBACK(_mode_changed), self);
1464
1465 g->filling = dt_bauhaus_slider_from_params(self, "filling");
1466 dt_bauhaus_slider_set_format(g->filling, "%");
1467 gtk_widget_set_tooltip_text(g->filling, _("surface ratio occupied by silver-halide crystals in each layer"));
1468
1469 g->grain_size = dt_bauhaus_slider_from_params(self, "grain_size");
1470 dt_bauhaus_slider_set_digits(g->grain_size, 0);
1471 dt_bauhaus_slider_set_format(g->grain_size, " px");
1472 gtk_widget_set_tooltip_text(g->grain_size, _("average crystal footprint at 100% zoom, clamped so zooming in does not enlarge it further"));
1473
1474 g->layers = dt_bauhaus_slider_from_params(self, "layers");
1475 dt_bauhaus_slider_set_digits(g->layers, 0);
1476 gtk_widget_set_tooltip_text(g->layers, _("number of crystal layers stacked through the emulsion"));
1477
1478 g->layer_capture = dt_bauhaus_slider_from_params(self, "layer_capture");
1479 dt_bauhaus_slider_set_soft_range(g->layer_capture, -2.0f, 2.0f);
1480 dt_bauhaus_slider_set_format(g->layer_capture, _(" EV"));
1481 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"));
1482
1483 g->channel_correlation = dt_bauhaus_slider_from_params(self, "channel_correlation");
1484 dt_bauhaus_slider_set_format(g->channel_correlation, "%");
1485 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"));
1486
1487 g->colorspace_saturation = dt_bauhaus_slider_from_params(self, "colorspace_saturation");
1488 dt_bauhaus_slider_set_format(g->colorspace_saturation, "%");
1489 gtk_widget_set_tooltip_text(g->colorspace_saturation, _("scale only the chromatic amplitude of the RGB grain residual while keeping its achromatic strength unchanged"));
1490
1491 g->size_stddev = dt_bauhaus_slider_from_params(self, "size_stddev");
1492 gtk_widget_set_tooltip_text(g->size_stddev, _("log-normal standard deviation of crystal sizes"));
1493
1494 gui_update(self);
1495}
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:1498
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3338
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3402
@ DEVELOP_BLEND_CS_RGB_SCENE
Definition blend.h:60
static float dt_camera_rgb_luminance(const float4 rgb)
@ IOP_CS_RGB
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)
free parameters build with the previous function.
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)
build the required parameters for a kernel that uses a profile info.
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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:285
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:325
const char ** description(struct dt_iop_module_t *self)
Definition crystgrain.c:156
int default_group()
Definition crystgrain.c:170
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:555
#define DT_CRYSTGRAIN_CL_PROGRAM
Definition crystgrain.c:960
#define DT_CRYSTGRAIN_LAYER_KERNELS
Definition crystgrain.c:48
static float _uniform_random(const uint64_t seed)
Turn a 64-bit seed into a uniform random number in [0; 1).
Definition crystgrain.c:235
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:899
static int _reflect_index(int i, const int max)
Mirror indices outside the current buffer like scipy ‘boundary='symm’`.
Definition crystgrain.c:258
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:450
static size_t _rgb_index(const size_t pixel, const int channel)
Definition crystgrain.c:593
static __DT_CLONE_TARGETS__ unsigned int _hash_string(const char *s)
Hash a string into a stable 32-bit seed.
Definition crystgrain.c:225
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:530
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:401
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:836
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:866
const char * name()
Definition crystgrain.c:151
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:612
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:508
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:929
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:175
int flags()
Definition crystgrain.c:165
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:301
static float _predict_stack_exposure(const float remaining_fraction)
Predict the exposure compensation of one monochrome grain stack.
Definition crystgrain.c:587
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:972
void init_presets(dt_iop_module_so_t *self)
Definition crystgrain.c:180
static float _gaussian_random(const uint64_t seed_a, const uint64_t seed_b)
Turn 2 seeds into one gaussian deviate.
Definition crystgrain.c:247
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:51
@ DT_CRYSTGRAIN_COLOR
Definition crystgrain.c:53
@ DT_CRYSTGRAIN_MONO
Definition crystgrain.c:52
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:472
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:721
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:207
#define M_PI_F
static unsigned int splitmix32(const unsigned long seed)
void dt_iop_params_t
Definition dev_history.h:43
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
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:35
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:163
#define DT_IMGSZ_CLEARBUF
Definition imagebuf.h:62
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:91
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:3243
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:137
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:180
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:181
@ IOP_GROUP_EFFECTS
Definition imageop.h:156
#define IOP_GUI_ALLOC(module)
Definition imageop.h:605
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:79
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)
float *const restrict luminance
float *const restrict const size_t k
#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 macros.h:65
static const int max_size
Definition map.c:132
#define CLAMPS(A, L, H)
Definition math.h:78
#define dt_free_align(ptr)
Definition mem_alloc.h:122
static void * dt_calloc_align(size_t size)
Definition mem_alloc.h:129
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2276
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2692
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2170
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2472
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:2401
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2213
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:2267
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:2436
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2527
#define ROUNDUP(a, n)
Definition opencl.h:79
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:60
#define dt_pixelpipe_cache_free_align(mem)
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float uint32_t state[4]
const float u2
unsigned __int64 uint64_t
Definition strptime.c:75
The device-side view of a dt_iop_order_iccprofile_info_t: the scalar fields only.
struct dt_iop_module_t *void * data
struct dt_develop_t * dev
dt_image_t image_storage
Definition develop.h:261
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:362
dt_iop_crystgrain_mode_t mode
Definition crystgrain.c:82
GtkWidget * colorspace_saturation
Definition crystgrain.c:77
dt_iop_crystgrain_kernel_t footprint
Definition crystgrain.c:105
dt_iop_crystgrain_mode_t mode
Definition crystgrain.c:58
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:318
dt_iop_global_data_t * global_data
Definition imageop.h:321
dt_iop_params_t * params
Definition imageop.h:314
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
dt_colormatrix_t matrix_in
RGB -> XYZ (D50), row-major. matrix_in[1][*] is the luminance row. NaN in [0][0] marks the whole prof...
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29