Blender V5.0
MOD_tonemap.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_array.hh"
10
11#include "BLT_translation.hh"
12
13#include "DNA_sequence_types.h"
14
16
17#include "SEQ_modifier.hh"
18
19#include "UI_interface.hh"
21
22#include "RNA_access.hh"
23
24#include "modifier.hh"
25#include "render.hh"
26
27namespace blender::seq {
28
29struct AvgLogLum {
31 float al;
32 float auto_key;
33 float lav;
35 float igm;
36};
37
39{
41 /* Same as tone-map compositor node. */
43 tmmd->key = 0.18f;
44 tmmd->offset = 1.0f;
45 tmmd->gamma = 1.0f;
46 tmmd->intensity = 0.0f;
47 tmmd->contrast = 0.0f;
48 tmmd->adaptation = 1.0f;
49 tmmd->correction = 0.0f;
50}
51
52/* Convert chunk of float image pixels to scene linear space, in-place. */
54 float4 *pixels,
56{
58 (float *)(pixels), int(count), 1, 4, colorspace, false);
59}
60
61/* Convert chunk of byte image pixels to scene linear space, into a destination array. */
63 const uchar *pixels,
64 float4 *dst,
66{
67 const uchar *bptr = pixels;
68 float4 *dst_ptr = dst;
69 for (int64_t i = 0; i < count; i++) {
70 straight_uchar_to_premul_float(*dst_ptr, bptr);
71 bptr += 4;
72 dst_ptr++;
73 }
75 (float *)dst, int(count), 1, 4, colorspace, false);
76}
77
79{
82 (float *)src, int(range.size()), 1, 4, colorspace);
83 const float4 *src_ptr = src;
84 uchar *bptr = ibuf->byte_buffer.data;
85 for (const int64_t idx : range) {
86 premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
87 src_ptr++;
88 }
89}
90
93 double sum = 0.0f;
94 float3 color_sum = {0, 0, 0};
95 double log_sum = 0.0;
96 float min = FLT_MAX;
97 float max = -FLT_MAX;
98};
99
101{
103 float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
105 (float *)(fptr + range.first()), int(range.size()), 1, 4, colorspace);
106}
107
108template<typename MaskSampler>
109static void tonemap_simple(
110 float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
111{
112 for (int64_t y : y_range) {
113 mask.begin_row(y);
114 for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
115 float4 input = *scene_linear;
116
117 /* Apply correction. */
118 float3 pixel = input.xyz() * avg.al;
119 float3 d = pixel + avg.tmmd->offset;
120 pixel.x /= (d.x == 0.0f) ? 1.0f : d.x;
121 pixel.y /= (d.y == 0.0f) ? 1.0f : d.y;
122 pixel.z /= (d.z == 0.0f) ? 1.0f : d.z;
123 const float igm = avg.igm;
124 if (igm != 0.0f) {
125 pixel.x = powf(math::max(pixel.x, 0.0f), igm);
126 pixel.y = powf(math::max(pixel.y, 0.0f), igm);
127 pixel.z = powf(math::max(pixel.z, 0.0f), igm);
128 }
129
130 /* Apply mask. */
131 float4 result(pixel.x, pixel.y, pixel.z, input.w);
132 mask.apply_mask(input, result);
133 *scene_linear = result;
134 scene_linear++;
135 }
136 }
137}
138
139template<typename MaskSampler>
141 float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
142{
143 const float f = expf(-avg.tmmd->intensity);
144 const float m = (avg.tmmd->contrast > 0.0f) ? avg.tmmd->contrast :
145 (0.3f + 0.7f * powf(avg.auto_key, 1.4f));
146 const float ic = 1.0f - avg.tmmd->correction, ia = 1.0f - avg.tmmd->adaptation;
147
148 for (int64_t y : y_range) {
149 mask.begin_row(y);
150 for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
151 float4 input = *scene_linear;
152
153 /* Apply correction. */
154 float3 pixel = input.xyz();
155 const float L = IMB_colormanagement_get_luminance(pixel);
156 float I_l = pixel.x + ic * (L - pixel.x);
157 float I_g = avg.cav.x + ic * (avg.lav - avg.cav.x);
158 float I_a = I_l + ia * (I_g - I_l);
159 pixel.x /= std::max(pixel.x + powf(f * I_a, m), 1.0e-30f);
160 I_l = pixel.y + ic * (L - pixel.y);
161 I_g = avg.cav.y + ic * (avg.lav - avg.cav.y);
162 I_a = I_l + ia * (I_g - I_l);
163 pixel.y /= std::max(pixel.y + powf(f * I_a, m), 1.0e-30f);
164 I_l = pixel.z + ic * (L - pixel.z);
165 I_g = avg.cav.z + ic * (avg.lav - avg.cav.z);
166 I_a = I_l + ia * (I_g - I_l);
167 pixel.z /= std::max(pixel.z + powf(f * I_a, m), 1.0e-30f);
168
169 /* Apply mask. */
170 float4 result(pixel.x, pixel.y, pixel.z, input.w);
171 mask.apply_mask(input, result);
172 *scene_linear = result;
173 scene_linear++;
174 }
175 }
176}
177
183
184 template<typename ImageT, typename MaskSampler>
185 void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
186 {
187 const IndexRange pixel_range(y_range.first() * image_x, y_range.size() * image_x);
188 if constexpr (std::is_same_v<ImageT, float>) {
189 /* Float pixels: no need for temporary storage. Luminance calculation already converted
190 * data to scene linear. */
191 float4 *pixels = (float4 *)(image + y_range.first() * image_x * 4);
192 if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
193 tonemap_rd_photoreceptor(pixels, mask, image_x, y_range, data);
194 }
195 else {
196 BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE);
197 tonemap_simple(pixels, mask, image_x, y_range, data);
198 }
199 scene_linear_to_image_chunk_float(this->ibuf, pixel_range);
200 }
201 else {
202 /* Byte pixels: temporary storage for scene linear pixel values. */
203 Array<float4> scene_linear(pixel_range.size());
204 pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
205 ibuf->byte_buffer.data + pixel_range.first() * 4,
206 scene_linear.data(),
207 pixel_range.size());
208 if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
209 tonemap_rd_photoreceptor(scene_linear.data(), mask, image_x, y_range, data);
210 }
211 else {
212 BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE);
213 tonemap_simple(scene_linear.data(), mask, image_x, y_range, data);
214 }
215 scene_linear_to_image_chunk_byte(scene_linear.data(), this->ibuf, pixel_range);
216 }
217 }
218};
219
220static void tonemap_calc_chunk_luminance(const int width,
221 const IndexRange y_range,
222 const float4 *scene_linear,
223 AreaLuminance &r_lum)
224{
225 for ([[maybe_unused]] const int y : y_range) {
226 for (int x = 0; x < width; x++) {
227 float4 pixel = *scene_linear;
228 r_lum.pixel_count++;
230 r_lum.sum += L;
231 r_lum.color_sum.x += pixel.x;
232 r_lum.color_sum.y += pixel.y;
233 r_lum.color_sum.z += pixel.z;
234 r_lum.log_sum += logf(math::max(L, 0.0f) + 1e-5f);
235 r_lum.max = math::max(r_lum.max, L);
236 r_lum.min = math::min(r_lum.min, L);
237 scene_linear++;
238 }
239 }
240}
241
243{
244 AreaLuminance lum;
246 IndexRange(ibuf->y),
247 32,
248 lum,
249 /* Calculate luminance for a chunk. */
250 [&](const IndexRange y_range, const AreaLuminance &init) {
251 AreaLuminance lum = init;
252 const int64_t chunk_size = y_range.size() * ibuf->x;
253 /* For float images, convert to scene-linear in place. The rest
254 * of tone-mapper can then continue with scene-linear values. */
255 if (ibuf->float_buffer.data != nullptr) {
256 float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
257 fptr += y_range.first() * ibuf->x;
258 pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
259 tonemap_calc_chunk_luminance(ibuf->x, y_range, fptr, lum);
260 }
261 else {
262 const uchar *bptr = ibuf->byte_buffer.data + y_range.first() * ibuf->x * 4;
263 Array<float4> scene_linear(chunk_size);
264 pixels_to_scene_linear_byte(
265 ibuf->byte_buffer.colorspace, bptr, scene_linear.data(), chunk_size);
266 tonemap_calc_chunk_luminance(ibuf->x, y_range, scene_linear.data(), lum);
267 }
268 return lum;
269 },
270 /* Reduce luminance results. */
271 [&](const AreaLuminance &a, const AreaLuminance &b) {
272 AreaLuminance res;
273 res.pixel_count = a.pixel_count + b.pixel_count;
274 res.sum = a.sum + b.sum;
275 res.color_sum = a.color_sum + b.color_sum;
276 res.log_sum = a.log_sum + b.log_sum;
277 res.min = math::min(a.min, b.min);
278 res.max = math::max(a.max, b.max);
279 return res;
280 });
281 return lum;
282}
283
286 ImBuf *mask)
287{
289
291 op.type = eModTonemapType(tmmd->type);
292 op.ibuf = context.image;
293 op.lum = tonemap_calc_input_luminance(context.image);
294 if (op.lum.pixel_count == 0) {
295 return; /* Strip is zero size or off-screen. */
296 }
297
298 op.data.tmmd = tmmd;
299 op.data.lav = op.lum.sum / op.lum.pixel_count;
300 op.data.cav.x = op.lum.color_sum.x / op.lum.pixel_count;
301 op.data.cav.y = op.lum.color_sum.y / op.lum.pixel_count;
302 op.data.cav.z = op.lum.color_sum.z / op.lum.pixel_count;
303 float maxl = log(double(op.lum.max) + 1e-5f);
304 float minl = log(double(op.lum.min) + 1e-5f);
305 float avl = op.lum.log_sum / op.lum.pixel_count;
306 op.data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
307 float al = exp(double(avl));
308 op.data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
309 op.data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
310
311 apply_modifier_op(op, context.image, mask, context.transform);
312}
313
314static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)
315{
316 uiLayout *layout = panel->layout;
318
319 const int tonemap_type = RNA_enum_get(ptr, "tonemap_type");
320
321 layout->use_property_split_set(true);
322
323 uiLayout &col = layout->column(false);
324 col.prop(ptr, "tonemap_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
325 if (tonemap_type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
326 col.prop(ptr, "intensity", UI_ITEM_NONE, std::nullopt, ICON_NONE);
327 col.prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
328 col.prop(ptr, "adaptation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
329 col.prop(ptr, "correction", UI_ITEM_NONE, std::nullopt, ICON_NONE);
330 }
331 else if (tonemap_type == SEQ_TONEMAP_RH_SIMPLE) {
332 col.prop(ptr, "key", UI_ITEM_NONE, std::nullopt, ICON_NONE);
333 col.prop(ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
334 col.prop(ptr, "gamma", UI_ITEM_NONE, std::nullopt, ICON_NONE);
335 }
336 else {
338 }
339
340 if (uiLayout *mask_input_layout = layout->panel_prop(
341 C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
342 {
343 draw_mask_input_type_settings(C, mask_input_layout, ptr);
344 }
345}
346
351
353 /*idname*/ "Tonemap",
354 /*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Tonemap"),
355 /*struct_name*/ "SequencerTonemapModifierData",
356 /*struct_size*/ sizeof(SequencerTonemapModifierData),
357 /*init_data*/ tonemapmodifier_init_data,
358 /*free_data*/ nullptr,
359 /*copy_data*/ nullptr,
360 /*apply*/ tonemapmodifier_apply,
361 /*panel_register*/ tonemapmodifier_register,
362 /*blend_write*/ nullptr,
363 /*blend_read*/ nullptr,
364};
365
366}; // namespace blender::seq
blender::ocio::ColorSpace ColorSpace
Definition BLF_api.hh:38
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE void straight_uchar_to_premul_float(float result[4], const unsigned char color[4])
MINLINE void premul_float_to_straight_uchar(unsigned char *result, const float color[4])
unsigned char uchar
#define CTX_N_(context, msgid)
#define BLT_I18NCONTEXT_ID_SEQUENCE
#define IFACE_(msgid)
eModTonemapType
@ SEQ_TONEMAP_RD_PHOTORECEPTOR
@ SEQ_TONEMAP_RH_SIMPLE
@ eSeqModifierType_Tonemap
void IMB_colormanagement_colorspace_to_scene_linear(float *buffer, int width, int height, int channels, const ColorSpace *colorspace, bool predivide)
BLI_INLINE float IMB_colormanagement_get_luminance(const float rgb[3])
void IMB_colormanagement_scene_linear_to_colorspace(float *buffer, int width, int height, int channels, const ColorSpace *colorspace)
PanelType * modifier_panel_register(ARegionType *region_type, ModifierType type, PanelDrawFn draw)
#define C
Definition RandGen.cpp:29
PointerRNA * UI_panel_custom_data_get(const Panel *panel)
#define UI_ITEM_NONE
long long int int64_t
const T * data() const
Definition BLI_array.hh:312
constexpr int64_t first() const
constexpr int64_t size() const
#define logf(x)
#define expf(x)
#define powf(x, y)
uint col
#define input
#define log
#define exp
int count
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
#define L
T min(const T &a, const T &b)
T max(const T &a, const T &b)
static void scene_linear_to_image_chunk_float(ImBuf *ibuf, IndexRange range)
static void tonemap_calc_chunk_luminance(const int width, const IndexRange y_range, const float4 *scene_linear, AreaLuminance &r_lum)
static AreaLuminance tonemap_calc_input_luminance(const ImBuf *ibuf)
void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr)
void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask, const float3x3 &mask_transform)
Definition modifier.hh:263
static void tonemapmodifier_init_data(StripModifierData *smd)
static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)
StripModifierTypeInfo seqModifierType_Tonemap
static void tonemapmodifier_apply(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask)
static void tonemap_simple(float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
static void pixels_to_scene_linear_float(const ColorSpace *colorspace, float4 *pixels, int64_t count)
static void tonemapmodifier_register(ARegionType *region_type)
static void pixels_to_scene_linear_byte(const ColorSpace *colorspace, const uchar *pixels, float4 *dst, int64_t count)
static void scene_linear_to_image_chunk_byte(float4 *src, ImBuf *ibuf, IndexRange range)
static void tonemap_rd_photoreceptor(float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
Value parallel_reduce(IndexRange range, int64_t grain_size, const Value &identity, const Function &function, const Reduction &reduction)
Definition BLI_task.hh:151
VecBase< float, 4 > float4
VecBase< float, 3 > float3
static void init(bNodeTree *, bNode *node)
int RNA_enum_get(PointerRNA *ptr, const char *name)
#define FLT_MAX
Definition stdcycles.h:14
const ColorSpace * colorspace
const ColorSpace * colorspace
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
struct uiLayout * layout
const SequencerTonemapModifierData * tmmd
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
PanelLayout panel_prop(const bContext *C, PointerRNA *open_prop_owner, blender::StringRefNull open_prop_name)
uiLayout & column(bool align)
void use_property_split_set(bool value)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4238