From 52f07ad7248b4c6e947153aea87be3e6b917dd17 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jan 2016 15:53:43 +0100 Subject: Sequencer: Implement Tone Map strip modifier Behaves same exact way as compositor node, but could be applied in the sequencer and used as a grading tool. Requested by the Nieve project artists. --- source/blender/blenkernel/intern/seqmodifier.c | 221 +++++++++++++++++++++++++ source/blender/makesdna/DNA_sequence_types.h | 14 ++ source/blender/makesrna/intern/rna_sequencer.c | 63 +++++++ 3 files changed, 298 insertions(+) (limited to 'source/blender') diff --git a/source/blender/blenkernel/intern/seqmodifier.c b/source/blender/blenkernel/intern/seqmodifier.c index 6e9cbbfff0c..f44f4fa3291 100644 --- a/source/blender/blenkernel/intern/seqmodifier.c +++ b/source/blender/blenkernel/intern/seqmodifier.c @@ -635,6 +635,226 @@ static SequenceModifierTypeInfo seqModifier_Mask = { maskmodifier_apply /* apply */ }; +/* **** Tonemap Modifier **** */ + +typedef struct AvgLogLum { + SequencerTonemapModifierData *tmmd; + struct ColorSpace *colorspace; + float al; + float auto_key; + float lav; + float cav[4]; + float igm; +} AvgLogLum; + +static void tonemapmodifier_init_data(SequenceModifierData *smd) +{ + SequencerTonemapModifierData *tmmd = (SequencerTonemapModifierData *) smd; + /* Same as tonemap compositor node. */ + tmmd->type = SEQ_TONEMAP_RD_PHOTORECEPTOR; + tmmd->key = 0.18f; + tmmd->offset = 1.0f; + tmmd->gamma = 1.0f; + tmmd->intensity = 0.0f; + tmmd->contrast = 0.0f; + tmmd->adaptation = 1.0f; + tmmd->correction = 0.0f; +} + +static void tonemapmodifier_apply_threaded_simple(int width, + int height, + unsigned char *rect, + float *rect_float, + unsigned char *mask_rect, + float *mask_rect_float, + void *data_v) +{ + AvgLogLum *avg = (AvgLogLum *)data_v; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel_index = (y * width + x) * 4; + float input[4], output[4], mask[3] = {1.0f, 1.0f, 1.0f}; + /* Get input value. */ + if (rect_float) { + copy_v4_v4(input, &rect_float[pixel_index]); + } + else { + straight_uchar_to_premul_float(input, &rect[pixel_index]); + } + IMB_colormanagement_colorspace_to_scene_linear_v3(input, avg->colorspace); + copy_v4_v4(output, input); + /* Get mask value. */ + if (mask_rect_float) { + copy_v3_v3(mask, mask_rect_float + pixel_index); + } + else if (mask_rect) { + rgb_uchar_to_float(mask, mask_rect + pixel_index); + } + /* Apply correction. */ + mul_v3_fl(output, avg->al); + float dr = output[0] + avg->tmmd->offset; + float dg = output[1] + avg->tmmd->offset; + float db = output[2] + avg->tmmd->offset; + output[0] /= ((dr == 0.0f) ? 1.0f : dr); + output[1] /= ((dg == 0.0f) ? 1.0f : dg); + output[2] /= ((db == 0.0f) ? 1.0f : db); + const float igm = avg->igm; + if (igm != 0.0f) { + output[0] = powf(max_ff(output[0], 0.0f), igm); + output[1] = powf(max_ff(output[1], 0.0f), igm); + output[2] = powf(max_ff(output[2], 0.0f), igm); + } + /* Apply mask. */ + output[0] = input[0] * (1.0f - mask[0]) + output[0] * mask[0]; + output[1] = input[1] * (1.0f - mask[1]) + output[1] * mask[1]; + output[2] = input[2] * (1.0f - mask[2]) + output[2] * mask[2]; + /* Copy result back. */ + IMB_colormanagement_scene_linear_to_colorspace_v3(output, avg->colorspace); + if (rect_float) { + copy_v4_v4(&rect_float[pixel_index], output); + } + else { + premul_float_to_straight_uchar(&rect[pixel_index], output); + } + } + } +} + +static void tonemapmodifier_apply_threaded_photoreceptor(int width, + int height, + unsigned char *rect, + float *rect_float, + unsigned char *mask_rect, + float *mask_rect_float, + void *data_v) +{ + AvgLogLum *avg = (AvgLogLum *)data_v; + const float f = expf(-avg->tmmd->intensity); + const float m = (avg->tmmd->contrast > 0.0f) ? avg->tmmd->contrast : (0.3f + 0.7f * powf(avg->auto_key, 1.4f)); + const float ic = 1.0f - avg->tmmd->correction, ia = 1.0f - avg->tmmd->adaptation; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel_index = (y * width + x) * 4; + float input[4], output[4], mask[3] = {1.0f, 1.0f, 1.0f}; + /* Get input value. */ + if (rect_float) { + copy_v4_v4(input, &rect_float[pixel_index]); + } + else { + straight_uchar_to_premul_float(input, &rect[pixel_index]); + } + IMB_colormanagement_colorspace_to_scene_linear_v3(input, avg->colorspace); + copy_v4_v4(output, input); + /* Get mask value. */ + if (mask_rect_float) { + copy_v3_v3(mask, mask_rect_float + pixel_index); + } + else if (mask_rect) { + rgb_uchar_to_float(mask, mask_rect + pixel_index); + } + /* Apply correction. */ + const float L = IMB_colormanagement_get_luminance(output); + float I_l = output[0] + ic * (L - output[0]); + float I_g = avg->cav[0] + ic * (avg->lav - avg->cav[0]); + float I_a = I_l + ia * (I_g - I_l); + output[0] /= (output[0] + powf(f * I_a, m)); + I_l = output[1] + ic * (L - output[1]); + I_g = avg->cav[1] + ic * (avg->lav - avg->cav[1]); + I_a = I_l + ia * (I_g - I_l); + output[1] /= (output[1] + powf(f * I_a, m)); + I_l = output[2] + ic * (L - output[2]); + I_g = avg->cav[2] + ic * (avg->lav - avg->cav[2]); + I_a = I_l + ia * (I_g - I_l); + output[2] /= (output[2] + powf(f * I_a, m)); + /* Apply mask. */ + output[0] = input[0] * (1.0f - mask[0]) + output[0] * mask[0]; + output[1] = input[1] * (1.0f - mask[1]) + output[1] * mask[1]; + output[2] = input[2] * (1.0f - mask[2]) + output[2] * mask[2]; + /* Copy result back. */ + IMB_colormanagement_scene_linear_to_colorspace_v3(output, avg->colorspace); + if (rect_float) { + copy_v4_v4(&rect_float[pixel_index], output); + } + else { + premul_float_to_straight_uchar(&rect[pixel_index], output); + } + } + } +} + +static void tonemapmodifier_apply(struct SequenceModifierData *smd, + ImBuf *ibuf, + ImBuf *mask) +{ + SequencerTonemapModifierData *tmmd = (SequencerTonemapModifierData *) smd; + AvgLogLum data; + data.tmmd = tmmd; + data.colorspace = (ibuf->rect_float != NULL) + ? ibuf->float_colorspace + : ibuf->rect_colorspace; + float lsum = 0.0f; + int p = ibuf->x * ibuf->y; + float *fp = ibuf->rect_float; + unsigned char *cp = (unsigned char *)ibuf->rect; + float avl, maxl = -FLT_MAX, minl = FLT_MAX; + const float sc = 1.0f / p; + float Lav = 0.f; + float cav[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + while (p--) { + float pixel[4]; + if (fp != NULL) { + copy_v4_v4(pixel, fp); + } + else { + straight_uchar_to_premul_float(pixel, cp); + } + IMB_colormanagement_colorspace_to_scene_linear_v3(pixel, data.colorspace); + float L = IMB_colormanagement_get_luminance(pixel); + Lav += L; + add_v3_v3(cav, pixel); + lsum += logf(max_ff(L, 0.0f) + 1e-5f); + maxl = (L > maxl) ? L : maxl; + minl = (L < minl) ? L : minl; + if (fp != NULL) { + fp += 4; + } else { + cp += 4; + } + } + data.lav = Lav * sc; + mul_v3_v3fl(data.cav, cav, sc); + maxl = logf(maxl + 1e-5f); + minl = logf(minl + 1e-5f); + avl = lsum * sc; + data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f; + float al = expf(avl); + data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al); + data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma); + + if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) { + modifier_apply_threaded(ibuf, + mask, + tonemapmodifier_apply_threaded_photoreceptor, + &data); + } + else /* if (tmmd->type == SEQ_TONEMAP_RD_SIMPLE) */ { + modifier_apply_threaded(ibuf, + mask, + tonemapmodifier_apply_threaded_simple, + &data); + } +} + +static SequenceModifierTypeInfo seqModifier_Tonemap = { + CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Tonemap"), /* name */ + "SequencerTonemapModifierData", /* struct_name */ + sizeof(SequencerTonemapModifierData), /* struct_size */ + tonemapmodifier_init_data, /* init_data */ + NULL, /* free_data */ + NULL, /* copy_data */ + tonemapmodifier_apply /* apply */ +}; + /*********************** Modifier functions *************************/ static void sequence_modifier_type_info_init(void) @@ -647,6 +867,7 @@ static void sequence_modifier_type_info_init(void) INIT_TYPE(BrightContrast); INIT_TYPE(Mask); INIT_TYPE(WhiteBalance); + INIT_TYPE(Tonemap); #undef INIT_TYPE } diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index bb49419b2ac..0236f41c5b9 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -352,6 +352,19 @@ typedef struct WhiteBalanceModifierData { float pad; } WhiteBalanceModifierData; +typedef struct SequencerTonemapModifierData { + SequenceModifierData modifier; + + float key, offset, gamma; + float intensity, contrast, adaptation, correction; + int type; +} SequencerTonemapModifierData; + +enum { + SEQ_TONEMAP_RH_SIMPLE = 0, + SEQ_TONEMAP_RD_PHOTORECEPTOR = 1, +}; + /* ***************** Scopes ****************** */ typedef struct SequencerScopes { @@ -528,6 +541,7 @@ enum { seqModifierType_BrightContrast = 4, seqModifierType_Mask = 5, seqModifierType_WhiteBalance = 6, + seqModifierType_Tonemap = 7, NUM_SEQUENCE_MODIFIER_TYPES }; diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index fd9a6f3dbdb..36eb82c2f25 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -67,6 +67,7 @@ EnumPropertyItem rna_enum_sequence_modifier_type_items[] = { {seqModifierType_BrightContrast, "BRIGHT_CONTRAST", ICON_NONE, "Bright/Contrast", ""}, {seqModifierType_Mask, "MASK", ICON_NONE, "Mask", ""}, {seqModifierType_WhiteBalance, "WHITE_BALANCE", ICON_NONE, "White Balance", ""}, + {seqModifierType_Tonemap, "TONEMAP", ICON_NONE, "Tone Map", ""}, {0, NULL, 0, NULL, NULL} }; @@ -953,6 +954,8 @@ static StructRNA *rna_SequenceModifier_refine(struct PointerRNA *ptr) return &RNA_BrightContrastModifier; case seqModifierType_WhiteBalance: return &RNA_WhiteBalanceModifier; + case seqModifierType_Tonemap: + return &RNA_SequencerTonemapModifierData; default: return &RNA_SequenceModifier; } @@ -2559,6 +2562,65 @@ static void rna_def_brightcontrast_modifier(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); } +static void rna_def_tonemap_modifier(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static EnumPropertyItem type_items[] = { + {SEQ_TONEMAP_RD_PHOTORECEPTOR, "RD_PHOTORECEPTOR", 0, "R/D Photoreceptor", ""}, + {SEQ_TONEMAP_RH_SIMPLE, "RH_SIMPLE", 0, "Rh Simple", ""}, + {0, NULL, 0, NULL, NULL} + }; + + srna = RNA_def_struct(brna, "SequencerTonemapModifierData", "SequenceModifier"); + RNA_def_struct_sdna(srna, "SequencerTonemapModifierData"); + RNA_def_struct_ui_text(srna, "SequencerTonemapModifierData", + "Tone mapping modifier"); + + prop = RNA_def_property(srna, "tonemap_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "type"); + RNA_def_property_enum_items(prop, type_items); + RNA_def_property_ui_text(prop, "Tonemap Type", ""); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "key", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Key", "The value the average luminance is mapped to"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, 0.001f, 10.0f); + RNA_def_property_ui_text(prop, "Offset", + "Normally always 1, but can be used as an extra control to alter the brightness curve"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "gamma", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, 0.001f, 3.0f); + RNA_def_property_ui_text(prop, "Gamma", "If not used, set to 1"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "intensity", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, -8.0f, 8.0f); + RNA_def_property_ui_text(prop, "Intensity", "If less than zero, darkens image; otherwise, makes it brighter"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "contrast", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Contrast", "Set to 0 to use estimate from input image"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "adaptation", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Adaptation", "If 0, global; if 1, based on pixel intensity"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); + + prop = RNA_def_property(srna, "correction", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Color Correction", "If 0, same for all channels; if 1, each independent"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceModifier_update"); +} + static void rna_def_modifiers(BlenderRNA *brna) { rna_def_modifier(brna); @@ -2568,6 +2630,7 @@ static void rna_def_modifiers(BlenderRNA *brna) rna_def_hue_modifier(brna); rna_def_brightcontrast_modifier(brna); rna_def_whitebalance_modifier(brna); + rna_def_tonemap_modifier(brna); } void RNA_def_sequencer(BlenderRNA *brna) -- cgit v1.2.3