diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 9f1110249d2..2100ed68660 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 11 +#define BLENDER_FILE_SUBVERSION 12 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_colortools.hh b/source/blender/blenkernel/BKE_colortools.hh index a12c0098927..e086209d67c 100644 --- a/source/blender/blenkernel/BKE_colortools.hh +++ b/source/blender/blenkernel/BKE_colortools.hh @@ -168,6 +168,7 @@ void BKE_curvemapping_premultiply(CurveMapping *cumap, bool restore); void BKE_curvemapping_blend_write(BlendWriter *writer, const CurveMapping *cumap); void BKE_curvemapping_curves_blend_write(BlendWriter *writer, const CurveMapping *cumap); + /** * \note `cumap` itself has been read already. */ diff --git a/source/blender/blenkernel/intern/colortools.cc b/source/blender/blenkernel/intern/colortools.cc index 7122ca1d6c5..593c8f70054 100644 --- a/source/blender/blenkernel/intern/colortools.cc +++ b/source/blender/blenkernel/intern/colortools.cc @@ -294,8 +294,8 @@ void BKE_curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope case CURVE_PRESET_MAX: cuma->totpoint = 2; break; - case CURVE_PRESET_MID9: - cuma->totpoint = 9; + case CURVE_PRESET_MID8: + cuma->totpoint = 8; break; case CURVE_PRESET_ROUND: cuma->totpoint = 4; @@ -363,9 +363,9 @@ void BKE_curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope cuma->curve[1].x = 1; cuma->curve[1].y = 1; break; - case CURVE_PRESET_MID9: { + case CURVE_PRESET_MID8: { for (int i = 0; i < cuma->totpoint; i++) { - cuma->curve[i].x = i / (float(cuma->totpoint) - 1); + cuma->curve[i].x = i / (float(cuma->totpoint)); cuma->curve[i].y = 0.5; } break; @@ -637,6 +637,17 @@ static float curvemap_calc_extend(const CurveMapping *cumap, return 0.0f; } +/* Evaluates CM_RESOL number of points on the Bezier segment defined by the given start and end + * Bezier triples, writing the output to the points array. */ +static void curve_eval_bezier_point(float start[3][3], float end[3][3], float *point) +{ + BKE_curve_correct_bezpart(start[1], start[2], end[0], end[1]); + BKE_curve_forward_diff_bezier( + start[1][0], start[2][0], end[0][0], end[1][0], point, CM_RESOL - 1, sizeof(float[2])); + BKE_curve_forward_diff_bezier( + start[1][1], start[2][1], end[0][1], end[1][1], point + 1, CM_RESOL - 1, sizeof(float[2])); +} + /* only creates a table for a single channel in CurveMapping */ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) { @@ -644,6 +655,13 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) CurveMapPoint *cmp = cuma->curve; BezTriple *bezt; + /* Wrapping ensures that the heights of the first and last points are the same. It adds two + * virtual points, which are copies of the first and last points, and moves them to the opposite + * side of the curve offset by the table range. The handles of these points are calculated, as if + * they were between the last and first real points. */ + + const bool use_wrapping = cumap->flag & CUMA_USE_WRAPPING; + if (cuma->curve == nullptr) { return; } @@ -651,6 +669,7 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) /* default rect also is table range */ cuma->mintable = clipr->xmin; cuma->maxtable = clipr->xmax; + float table_range = cuma->maxtable - cuma->mintable; /* Rely on Blender interpolation for bezier curves, support extra functionality here as well. */ bezt = static_cast(MEM_callocN(cuma->totpoint * sizeof(BezTriple), "beztarr")); @@ -671,16 +690,54 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) } } + const BezTriple *bezt_next = nullptr; const BezTriple *bezt_prev = nullptr; + + /* Create two extra points for wrapping curves. */ + BezTriple bezt_pre = bezt[cuma->totpoint - 1]; + BezTriple bezt_post = bezt[0]; + + BezTriple *bezt_post_ptr; + + if (use_wrapping) { + /* Handle location of pre and post points for wrapping curves. */ + bezt_pre.h1 = bezt_pre.h2 = bezt[cuma->totpoint - 1].h2; + bezt_pre.vec[1][0] = bezt[cuma->totpoint - 1].vec[1][0] - table_range; + bezt_pre.vec[1][1] = bezt[cuma->totpoint - 1].vec[1][1]; + + bezt_post.h1 = bezt_post.h2 = bezt[0].h1; + bezt_post.vec[1][0] = bezt[0].vec[1][0] + table_range; + bezt_post.vec[1][1] = bezt[0].vec[1][1]; + + bezt_prev = &bezt_pre; + bezt_post_ptr = &bezt_post; + } + else { + bezt_prev = nullptr; + bezt_post_ptr = nullptr; + } + + /* Process middle elements */ for (int a = 0; a < cuma->totpoint; a++) { - const BezTriple *bezt_next = (a != cuma->totpoint - 1) ? &bezt[a + 1] : nullptr; + bezt_next = (a != cuma->totpoint - 1) ? &bezt[a + 1] : bezt_post_ptr; calchandle_curvemap(&bezt[a], bezt_prev, bezt_next); bezt_prev = &bezt[a]; } + /* Correct handles of pre and post points for wrapping curves. */ + bezt_pre.vec[0][0] = bezt[cuma->totpoint - 1].vec[0][0] - table_range; + bezt_pre.vec[0][1] = bezt[cuma->totpoint - 1].vec[0][1]; + bezt_pre.vec[2][0] = bezt[cuma->totpoint - 1].vec[2][0] - table_range; + bezt_pre.vec[2][1] = bezt[cuma->totpoint - 1].vec[2][1]; + + bezt_post.vec[0][0] = bezt[0].vec[0][0] + table_range; + bezt_post.vec[0][1] = bezt[0].vec[0][1]; + bezt_post.vec[2][0] = bezt[0].vec[2][0] + table_range; + bezt_post.vec[2][1] = bezt[0].vec[2][1]; + /* first and last handle need correction, instead of pointing to center of next/prev, * we let it point to the closest handle */ - if (cuma->totpoint > 2) { + if (cuma->totpoint > 2 && !use_wrapping) { float hlen, nlen, vec[3]; if (bezt[0].h2 == HD_AUTO) { @@ -719,35 +776,34 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) } } } + /* make the bezier curve */ if (cuma->table) { MEM_freeN(cuma->table); } - int totpoint = (cuma->totpoint - 1) * CM_RESOL; + int totpoint = use_wrapping ? (cuma->totpoint + 1) * CM_RESOL : (cuma->totpoint - 1) * CM_RESOL; float *allpoints = static_cast(MEM_callocN(totpoint * 2 * sizeof(float), "table")); float *point = allpoints; - for (int a = 0; a < cuma->totpoint - 1; a++, point += 2 * CM_RESOL) { - BKE_curve_correct_bezpart( - bezt[a].vec[1], bezt[a].vec[2], bezt[a + 1].vec[0], bezt[a + 1].vec[1]); - BKE_curve_forward_diff_bezier(bezt[a].vec[1][0], - bezt[a].vec[2][0], - bezt[a + 1].vec[0][0], - bezt[a + 1].vec[1][0], - point, - CM_RESOL - 1, - sizeof(float[2])); - BKE_curve_forward_diff_bezier(bezt[a].vec[1][1], - bezt[a].vec[2][1], - bezt[a + 1].vec[0][1], - bezt[a + 1].vec[1][1], - point + 1, - CM_RESOL - 1, - sizeof(float[2])); + /* Handle pre point for wrapping */ + if (use_wrapping) { + curve_eval_bezier_point(bezt_pre.vec, bezt[0].vec, point); + point += 2 * CM_RESOL; } - /* store first and last handle for extrapolation, unit length */ + /* Process middle elements */ + for (int a = 0; a < cuma->totpoint - 1; a++, point += 2 * CM_RESOL) { + int b = a + 1; + curve_eval_bezier_point(bezt[a].vec, bezt[b].vec, point); + } + + if (use_wrapping) { + /* Handle post point for wrapping */ + curve_eval_bezier_point(bezt[cuma->totpoint - 1].vec, bezt_post.vec, point); + } + /* Store first and last handle for extrapolation, unit length. (Only relevant when not using + * wrapping.) */ cuma->ext_in[0] = bezt[0].vec[0][0] - bezt[0].vec[1][0]; cuma->ext_in[1] = bezt[0].vec[0][1] - bezt[0].vec[1][1]; float ext_in_range = sqrtf(cuma->ext_in[0] * cuma->ext_in[0] + @@ -766,7 +822,7 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) /* cleanup */ MEM_freeN(bezt); - float range = CM_TABLEDIV * (cuma->maxtable - cuma->mintable); + float range = CM_TABLEDIV * table_range; cuma->range = 1.0f / range; /* now make a table with CM_TABLE equal x distances */ @@ -785,9 +841,8 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) while (cur_x >= point[0] && point != lastpoint) { point += 2; } - /* Check if we are on or outside the start or end point. */ - if (point == firstpoint || (point == lastpoint && cur_x >= point[0])) { + if ((point == firstpoint || (point == lastpoint && cur_x >= point[0])) && !use_wrapping) { if (compare_ff(cur_x, point[0], 1e-6f)) { /* When on the point exactly, use the value directly to avoid precision * issues with extrapolation of extreme slopes. */ diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 913a8c5beb9..5a460ffc144 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -49,6 +49,7 @@ #include "BKE_animsys.h" #include "BKE_armature.hh" #include "BKE_attribute.hh" +#include "BKE_colortools.hh" #include "BKE_curve.hh" #include "BKE_effect.h" #include "BKE_grease_pencil.hh" @@ -1998,6 +1999,31 @@ static void image_settings_avi_to_ffmpeg(Scene *scene) } } +static bool seq_hue_correct_set_wrapping(Sequence *seq, void * /*user_data*/) +{ + LISTBASE_FOREACH (SequenceModifierData *, smd, &seq->modifiers) { + if (smd->type == seqModifierType_HueCorrect) { + HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd; + CurveMapping *cumap = (CurveMapping *)&hcmd->curve_mapping; + cumap->flag |= CUMA_USE_WRAPPING; + } + } + return true; +} + +static void versioning_node_hue_correct_set_wrappng(bNodeTree *ntree) +{ + if (ntree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { + + if (node->type == CMP_NODE_HUECORRECT) { + CurveMapping *cumap = (CurveMapping *)node->storage; + cumap->flag |= CUMA_USE_WRAPPING; + } + } + } +} + void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 1)) { @@ -3070,6 +3096,19 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 12)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + versioning_node_hue_correct_set_wrappng(ntree); + } + FOREACH_NODETREE_END; + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->ed != nullptr) { + SEQ_for_each_callback(&scene->ed->seqbase, seq_hue_correct_set_wrapping, nullptr); + } + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/editors/interface/interface_templates.cc b/source/blender/editors/interface/interface_templates.cc index af6f8687aaa..6c4b2283af2 100644 --- a/source/blender/editors/interface/interface_templates.cc +++ b/source/blender/editors/interface/interface_templates.cc @@ -4275,7 +4275,7 @@ static uiBlock *curvemap_tools_func( }); } - if (show_extend) { + if (show_extend && !(cumap->flag & CUMA_USE_WRAPPING)) { { uiBut *but = uiDefIconTextBut(block, UI_BTYPE_BUT_MENU, diff --git a/source/blender/makesdna/DNA_color_types.h b/source/blender/makesdna/DNA_color_types.h index 80d062446f3..54ac42d220f 100644 --- a/source/blender/makesdna/DNA_color_types.h +++ b/source/blender/makesdna/DNA_color_types.h @@ -92,6 +92,7 @@ typedef enum eCurveMappingFlags { /** The curve is extended by extrapolation. When not set the curve is extended horizontally. */ CUMA_EXTEND_EXTRAPOLATE = (1 << 4), + CUMA_USE_WRAPPING = (1 << 5), } eCurveMappingFlags; /** #CurveMapping.preset */ @@ -100,7 +101,7 @@ typedef enum eCurveMappingPreset { CURVE_PRESET_SHARP = 1, CURVE_PRESET_SMOOTH = 2, CURVE_PRESET_MAX = 3, - CURVE_PRESET_MID9 = 4, + CURVE_PRESET_MID8 = 4, CURVE_PRESET_ROUND = 5, CURVE_PRESET_ROOT = 6, CURVE_PRESET_GAUSS = 7, diff --git a/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc index 1a61c76e74f..06dbded2c3f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc +++ b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc @@ -36,13 +36,14 @@ static void node_composit_init_huecorrect(bNodeTree * /*ntree*/, bNode *node) CurveMapping *cumapping = (CurveMapping *)node->storage; - cumapping->preset = CURVE_PRESET_MID9; + cumapping->preset = CURVE_PRESET_MID8; for (int c = 0; c < 3; c++) { CurveMap *cuma = &cumapping->cm[c]; BKE_curvemap_reset(cuma, &cumapping->clipr, cumapping->preset, CURVEMAP_SLOPE_POSITIVE); } - + /* use wrapping for all hue correct nodes */ + cumapping->flag |= CUMA_USE_WRAPPING; /* default to showing Saturation */ cumapping->cur = 1; } diff --git a/source/blender/sequencer/intern/modifier.cc b/source/blender/sequencer/intern/modifier.cc index b1f479f369c..07f353b5f76 100644 --- a/source/blender/sequencer/intern/modifier.cc +++ b/source/blender/sequencer/intern/modifier.cc @@ -861,15 +861,15 @@ static void hue_correct_init_data(SequenceModifierData *smd) int c; BKE_curvemapping_set_defaults(&hcmd->curve_mapping, 1, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO); - hcmd->curve_mapping.preset = CURVE_PRESET_MID9; + hcmd->curve_mapping.preset = CURVE_PRESET_MID8; for (c = 0; c < 3; c++) { CurveMap *cuma = &hcmd->curve_mapping.cm[c]; - BKE_curvemap_reset( cuma, &hcmd->curve_mapping.clipr, hcmd->curve_mapping.preset, CURVEMAP_SLOPE_POSITIVE); } - + /* use wrapping for all hue correct modifiers */ + hcmd->curve_mapping.flag |= CUMA_USE_WRAPPING; /* default to showing Saturation */ hcmd->curve_mapping.cur = 1; }