diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index 08a3a1d5078..bf154a1f38f 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -1178,6 +1178,43 @@ def brush_shared_settings(layout, context, brush, popover=False): layout.row().prop(brush, "direction", expand=True) +def color_jitter_panel(layout, context, brush): + mode = UnifiedPaintPanel.get_brush_mode(context) + ups = context.scene.tool_settings.unified_paint_settings + + is_sculpt_paint_mode = mode == 'SCULPT' and brush.sculpt_capabilities.has_color + if mode in {'PAINT_TEXTURE', 'PAINT_2D', 'PAINT_VERTEX'} or is_sculpt_paint_mode: + prop_owner = ups if ups.use_unified_color else brush + layout.use_property_split = False + + header, panel = layout.panel("color_jitter_panel", default_closed=True) + header.prop(prop_owner, "use_color_jitter", text="Randomize Color") + if panel: + panel.use_property_split = True + panel.use_property_decorate = False + + col = panel.column(align=True) + col.use_property_split = True + + row = col.row(align=True) + row.enabled = prop_owner.use_color_jitter + row.prop(prop_owner, "hue_jitter", slider=True, text="Hue") + row.prop(prop_owner, "use_stroke_random_hue", text="", icon='GP_SELECT_STROKES') + row.prop(prop_owner, "use_random_press_hue", text="", icon='STYLUS_PRESSURE') + + row = col.row(align=True) + row.enabled = prop_owner.use_color_jitter + row.prop(prop_owner, "saturation_jitter", slider=True, text="Saturation") + row.prop(prop_owner, "use_stroke_random_sat", text="", icon='GP_SELECT_STROKES') + row.prop(prop_owner, "use_random_press_sat", text="", icon='STYLUS_PRESSURE') + + row = col.row(align=True) + row.enabled = prop_owner.use_color_jitter + row.prop(prop_owner, "value_jitter", slider=True, text="Value") + row.prop(prop_owner, "use_stroke_random_val", text="", icon='GP_SELECT_STROKES') + row.prop(prop_owner, "use_random_press_val", text="", icon='STYLUS_PRESSURE') + + def brush_settings_advanced(layout, context, settings, brush, popover=False): """Draw advanced brush settings for Sculpt, Texture/Vertex/Weight Paint modes.""" @@ -1357,6 +1394,9 @@ def brush_settings_advanced(layout, context, settings, brush, popover=False): if use_frontface: layout.prop(brush, "use_frontface", text="Front Faces Only") + if popover: + color_jitter_panel(layout, context, brush) + # Brush modes header, panel = layout.panel("modes", default_closed=True) header.label(text="Modes") @@ -1400,6 +1440,9 @@ def draw_color_settings(context, layout, brush, color_type=False): row.separator() row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False) row.prop(ups, "use_unified_color", text="", icon='BRUSHES_ALL') + + color_jitter_panel(layout, context, brush) + # Gradient elif brush.color_type == 'GRADIENT': layout.template_color_ramp(brush, "gradient", expand=True) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index f27d557c473..b4031dddb8f 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 83 +#define BLENDER_FILE_SUBVERSION 84 /* 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_brush.hh b/source/blender/blenkernel/BKE_brush.hh index d2edd331670..b45ca71bba1 100644 --- a/source/blender/blenkernel/BKE_brush.hh +++ b/source/blender/blenkernel/BKE_brush.hh @@ -10,6 +10,8 @@ * General operations for brushes. */ +#include + #include "BLI_span.hh" #include "DNA_brush_enums.h" @@ -164,7 +166,22 @@ ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool displa /* Unified strength size and color. */ +struct BrushColorJitterSettings { + int flag; + /** Jitter amounts */ + float hue; + float saturation; + float value; + + /** Jitter pressure curves. */ + struct CurveMapping *curve_hue_jitter; + struct CurveMapping *curve_sat_jitter; + struct CurveMapping *curve_val_jitter; +}; + const float *BKE_brush_color_get(const Scene *scene, const Paint *paint, const Brush *brush); +const std::optional BKE_brush_color_jitter_get_settings( + const Scene *scene, const Paint *paint, const Brush *brush); const float *BKE_brush_secondary_color_get(const Scene *scene, const Paint *paint, const Brush *brush); diff --git a/source/blender/blenkernel/BKE_paint.hh b/source/blender/blenkernel/BKE_paint.hh index fdfd3a1753e..ef52cf90a4e 100644 --- a/source/blender/blenkernel/BKE_paint.hh +++ b/source/blender/blenkernel/BKE_paint.hh @@ -31,6 +31,7 @@ struct BMesh; struct BlendDataReader; struct BlendWriter; struct Brush; +struct BrushColorJitterSettings; struct CurveMapping; struct Depsgraph; struct EnumPropertyItem; @@ -314,6 +315,10 @@ void BKE_paint_face_set_overlay_color_get(int face_set, int seed, uchar r_color[ /* Stroke related. */ +/* Random values are generated on each new stroke so each stroke + * gets a different starting point in the perlin noise. */ +blender::float3 seed_hsv_jitter(); + bool paint_calculate_rake_rotation(UnifiedPaintSettings &ups, const Brush &brush, const float mouse_pos[2], @@ -325,6 +330,12 @@ void paint_update_brush_rake_rotation(UnifiedPaintSettings &ups, void BKE_paint_stroke_get_average(const Scene *scene, const Object *ob, float stroke[3]); +blender::float3 BKE_paint_randomize_color(const BrushColorJitterSettings &color_jitter, + const blender::float3 &initial_hsv_jitter, + const float distance, + const float pressure, + const blender::float3 &color); + /* .blend I/O */ void BKE_paint_blend_write(BlendWriter *writer, Paint *paint); @@ -660,3 +671,4 @@ bool BKE_paint_canvas_image_get(PaintModeSettings *settings, int BKE_paint_canvas_uvmap_layer_index_get(const PaintModeSettings *settings, Object *ob); void BKE_sculpt_check_cavity_curves(Sculpt *sd); CurveMapping *BKE_sculpt_default_cavity_curve(); +CurveMapping *BKE_paint_default_curve(); diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index 7c8a996c946..534aa5da607 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -86,6 +86,10 @@ static void brush_copy_data(Main * /*bmain*/, brush_dst->curve = BKE_curvemapping_copy(brush_src->curve); brush_dst->automasking_cavity_curve = BKE_curvemapping_copy(brush_src->automasking_cavity_curve); + brush_dst->curve_rand_hue = BKE_curvemapping_copy(brush_src->curve_rand_hue); + brush_dst->curve_rand_saturation = BKE_curvemapping_copy(brush_src->curve_rand_saturation); + brush_dst->curve_rand_value = BKE_curvemapping_copy(brush_src->curve_rand_value); + if (brush_src->gpencil_settings != nullptr) { brush_dst->gpencil_settings = MEM_dupallocN( __func__, *(brush_src->gpencil_settings)); @@ -129,6 +133,10 @@ static void brush_free_data(ID *id) BKE_curvemapping_free(brush->curve); BKE_curvemapping_free(brush->automasking_cavity_curve); + BKE_curvemapping_free(brush->curve_rand_hue); + BKE_curvemapping_free(brush->curve_rand_saturation); + BKE_curvemapping_free(brush->curve_rand_value); + if (brush->gpencil_settings != nullptr) { BKE_curvemapping_free(brush->gpencil_settings->curve_sensitivity); BKE_curvemapping_free(brush->gpencil_settings->curve_strength); @@ -229,6 +237,16 @@ static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_addres BKE_curvemapping_blend_write(writer, brush->automasking_cavity_curve); } + if (brush->curve_rand_hue) { + BKE_curvemapping_blend_write(writer, brush->curve_rand_hue); + } + if (brush->curve_rand_saturation) { + BKE_curvemapping_blend_write(writer, brush->curve_rand_saturation); + } + if (brush->curve_rand_value) { + BKE_curvemapping_blend_write(writer, brush->curve_rand_value); + } + if (brush->gpencil_settings) { BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings); @@ -295,6 +313,30 @@ static void brush_blend_read_data(BlendDataReader *reader, ID *id) brush->automasking_cavity_curve = BKE_sculpt_default_cavity_curve(); } + BLO_read_struct(reader, CurveMapping, &brush->curve_rand_hue); + if (brush->curve_rand_hue) { + BKE_curvemapping_blend_read(reader, brush->curve_rand_hue); + } + else { + brush->curve_rand_hue = BKE_paint_default_curve(); + } + + BLO_read_struct(reader, CurveMapping, &brush->curve_rand_saturation); + if (brush->curve_rand_saturation) { + BKE_curvemapping_blend_read(reader, brush->curve_rand_saturation); + } + else { + brush->curve_rand_saturation = BKE_paint_default_curve(); + } + + BLO_read_struct(reader, CurveMapping, &brush->curve_rand_value); + if (brush->curve_rand_value) { + BKE_curvemapping_blend_read(reader, brush->curve_rand_value); + } + else { + brush->curve_rand_value = BKE_paint_default_curve(); + } + /* grease pencil */ BLO_read_struct(reader, BrushGpencilSettings, &brush->gpencil_settings); if (brush->gpencil_settings != nullptr) { @@ -1084,6 +1126,42 @@ const float *BKE_brush_color_get(const Scene *scene, const Paint *paint, const B return brush->rgb; } +/** Get color jitter settings if enabled. */ +const std::optional BKE_brush_color_jitter_get_settings( + const Scene *scene, const Paint *paint, const Brush *brush) +{ + if (BKE_paint_use_unified_color(scene->toolsettings, paint)) { + if ((scene->toolsettings->unified_paint_settings.flag & UNIFIED_PAINT_COLOR_JITTER) == 0) { + return std::nullopt; + } + + const UnifiedPaintSettings settings = scene->toolsettings->unified_paint_settings; + return BrushColorJitterSettings{ + settings.color_jitter_flag, + settings.hsv_jitter[0], + settings.hsv_jitter[1], + settings.hsv_jitter[2], + settings.curve_rand_hue, + settings.curve_rand_saturation, + settings.curve_rand_value, + }; + } + + if ((brush->flag2 & BRUSH_JITTER_COLOR) == 0) { + return std::nullopt; + } + + return BrushColorJitterSettings{ + brush->color_jitter_flag, + brush->hsv_jitter[0], + brush->hsv_jitter[1], + brush->hsv_jitter[2], + brush->curve_rand_hue, + brush->curve_rand_saturation, + brush->curve_rand_value, + }; +} + const float *BKE_brush_secondary_color_get(const Scene *scene, const Paint *paint, const Brush *brush) diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index 5fe4d83b87b..7b1dfde2387 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -32,6 +32,7 @@ #include "BLI_math_color.h" #include "BLI_math_matrix.hh" #include "BLI_math_vector.h" +#include "BLI_noise.hh" #include "BLI_string.h" #include "BLI_utildefines.h" #include "BLI_vector.hh" @@ -1848,6 +1849,62 @@ void BKE_paint_stroke_get_average(const Scene *scene, const Object *ob, float st } } +blender::float3 BKE_paint_randomize_color(const BrushColorJitterSettings &color_jitter, + const blender::float3 &initial_hsv_jitter, + const float distance, + const float pressure, + const blender::float3 &color) +{ + constexpr float noise_scale = 1 / 20.0f; + + const float random_hue = (color_jitter.flag & BRUSH_COLOR_JITTER_USE_HUE_AT_STROKE) ? + initial_hsv_jitter[0] : + blender::noise::perlin(blender::float2( + distance * noise_scale, initial_hsv_jitter[0] * 100)); + + const float random_sat = (color_jitter.flag & BRUSH_COLOR_JITTER_USE_SAT_AT_STROKE) ? + initial_hsv_jitter[1] : + blender::noise::perlin(blender::float2( + distance * noise_scale, initial_hsv_jitter[1] * 100)); + + const float random_val = (color_jitter.flag & BRUSH_COLOR_JITTER_USE_VAL_AT_STROKE) ? + initial_hsv_jitter[2] : + blender::noise::perlin(blender::float2( + distance * noise_scale, initial_hsv_jitter[2] * 100)); + + float hue_jitter_scale = color_jitter.hue; + if ((color_jitter.flag & BRUSH_COLOR_JITTER_USE_HUE_RAND_PRESS)) { + hue_jitter_scale *= BKE_curvemapping_evaluateF(color_jitter.curve_hue_jitter, 0, pressure); + } + float sat_jitter_scale = color_jitter.saturation; + if ((color_jitter.flag & BRUSH_COLOR_JITTER_USE_SAT_RAND_PRESS)) { + sat_jitter_scale *= BKE_curvemapping_evaluateF(color_jitter.curve_sat_jitter, 0, pressure); + } + float val_jitter_scale = color_jitter.value; + if ((color_jitter.flag & BRUSH_COLOR_JITTER_USE_VAL_RAND_PRESS)) { + val_jitter_scale *= BKE_curvemapping_evaluateF(color_jitter.curve_val_jitter, 0, pressure); + } + + blender::float3 hsv; + rgb_to_hsv_v(color, hsv); + + hsv[0] += blender::math::interpolate(0.5f, random_hue, hue_jitter_scale) - 0.5f; + /* Wrap hue. */ + if (hsv[0] > 1.0f) { + hsv[0] -= 1.0f; + } + else if (hsv[0] < 0.0f) { + hsv[0] += 1.0f; + } + + hsv[1] *= blender::math::interpolate(1.0f, random_sat * 2.0f, sat_jitter_scale); + hsv[2] *= blender::math::interpolate(1.0f, random_val * 2.0f, val_jitter_scale); + + blender::float3 random_color; + hsv_to_rgb_v(hsv, random_color); + return random_color; +} + void BKE_paint_blend_write(BlendWriter *writer, Paint *paint) { if (paint->cavity_curve) { diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index b14a1de9a74..4ec132603cc 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -137,6 +137,15 @@ CurveMapping *BKE_sculpt_default_cavity_curve() return cumap; } +CurveMapping *BKE_paint_default_curve() +{ + CurveMapping *cumap = BKE_curvemapping_add(1, 0, 0, 1, 1); + BKE_curvemap_reset(cumap->cm, &cumap->clipr, CURVE_PRESET_LINE, CURVEMAP_SLOPE_POSITIVE); + BKE_curvemapping_init(cumap); + + return cumap; +} + void BKE_sculpt_check_cavity_curves(Sculpt *sd) { if (!sd->automasking_cavity_curve) { @@ -173,6 +182,10 @@ static void scene_init_data(ID *id) scene->toolsettings->autokey_mode = uchar(U.autokey_mode); + scene->toolsettings->unified_paint_settings.curve_rand_hue = BKE_paint_default_curve(); + scene->toolsettings->unified_paint_settings.curve_rand_saturation = BKE_paint_default_curve(); + scene->toolsettings->unified_paint_settings.curve_rand_value = BKE_paint_default_curve(); + /* Grease pencil multi-frame falloff curve. */ scene->toolsettings->gp_sculpt.cur_falloff = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); CurveMapping *gp_falloff_curve = scene->toolsettings->gp_sculpt.cur_falloff; @@ -1013,6 +1026,22 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres /* direct data */ ToolSettings *tos = sce->toolsettings; BLO_write_struct(writer, ToolSettings, tos); + + BLO_write_struct(writer, CurveMapping, &tos->unified_paint_settings.curve_rand_hue); + if (tos->unified_paint_settings.curve_rand_hue) { + BKE_curvemapping_blend_write(writer, tos->unified_paint_settings.curve_rand_hue); + } + + BLO_write_struct(writer, CurveMapping, &tos->unified_paint_settings.curve_rand_saturation); + if (tos->unified_paint_settings.curve_rand_saturation) { + BKE_curvemapping_blend_write(writer, tos->unified_paint_settings.curve_rand_saturation); + } + + BLO_write_struct(writer, CurveMapping, &tos->unified_paint_settings.curve_rand_value); + if (tos->unified_paint_settings.curve_rand_value) { + BKE_curvemapping_blend_write(writer, tos->unified_paint_settings.curve_rand_value); + } + if (tos->vpaint) { BLO_write_struct(writer, VPaint, tos->vpaint); BKE_paint_blend_write(writer, &tos->vpaint->paint); @@ -1209,6 +1238,24 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id) zero_v3(ups->last_location); ups->last_hit = 0; + BLO_read_struct(reader, CurveMapping, &ups->curve_rand_hue); + if (ups->curve_rand_hue) { + BKE_curvemapping_blend_read(reader, ups->curve_rand_hue); + BKE_curvemapping_init(ups->curve_rand_hue); + } + + BLO_read_struct(reader, CurveMapping, &ups->curve_rand_saturation); + if (ups->curve_rand_saturation) { + BKE_curvemapping_blend_read(reader, ups->curve_rand_saturation); + BKE_curvemapping_init(ups->curve_rand_saturation); + } + + BLO_read_struct(reader, CurveMapping, &ups->curve_rand_value); + if (ups->curve_rand_value) { + BKE_curvemapping_blend_read(reader, ups->curve_rand_value); + BKE_curvemapping_init(ups->curve_rand_value); + } + direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->sculpt); direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->vpaint); direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->wpaint); @@ -1657,6 +1704,14 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag) BKE_paint_copy(&ts->curves_sculpt->paint, &ts->curves_sculpt->paint, flag); } + /* Color jitter curves in unified paint settings. */ + ts->unified_paint_settings.curve_rand_hue = BKE_curvemapping_copy( + ts->unified_paint_settings.curve_rand_hue); + ts->unified_paint_settings.curve_rand_saturation = BKE_curvemapping_copy( + ts->unified_paint_settings.curve_rand_saturation); + ts->unified_paint_settings.curve_rand_value = BKE_curvemapping_copy( + ts->unified_paint_settings.curve_rand_value); + BKE_paint_copy(&ts->imapaint.paint, &ts->imapaint.paint, flag); ts->particle.paintcursor = nullptr; ts->particle.scene = nullptr; @@ -1723,6 +1778,17 @@ void BKE_toolsettings_free(ToolSettings *toolsettings) } BKE_paint_free(&toolsettings->imapaint.paint); + /* Color jitter curves in unified paint settings. */ + if (toolsettings->unified_paint_settings.curve_rand_hue) { + BKE_curvemapping_free(toolsettings->unified_paint_settings.curve_rand_hue); + } + if (toolsettings->unified_paint_settings.curve_rand_saturation) { + BKE_curvemapping_free(toolsettings->unified_paint_settings.curve_rand_saturation); + } + if (toolsettings->unified_paint_settings.curve_rand_value) { + BKE_curvemapping_free(toolsettings->unified_paint_settings.curve_rand_value); + } + /* free Grease Pencil interpolation curve */ if (toolsettings->gp_interpolate.custom_ipo) { BKE_curvemapping_free(toolsettings->gp_interpolate.custom_ipo); diff --git a/source/blender/blenloader/intern/versioning_450.cc b/source/blender/blenloader/intern/versioning_450.cc index 6851001ce8c..616da032224 100644 --- a/source/blender/blenloader/intern/versioning_450.cc +++ b/source/blender/blenloader/intern/versioning_450.cc @@ -35,6 +35,7 @@ #include "BKE_node.hh" #include "BKE_node_legacy_types.hh" #include "BKE_node_runtime.hh" +#include "BKE_paint.hh" #include "SEQ_iterator.hh" #include "SEQ_sequencer.hh" @@ -4265,6 +4266,45 @@ static void do_version_blur_node_options_to_inputs_animation(bNodeTree *node_tre }); } +/* Unified paint settings need a default curve for the color jitter options. */ +static void do_init_default_jitter_curves_in_unified_paint_settings(ToolSettings *ts) +{ + if (ts->unified_paint_settings.curve_rand_hue == nullptr) { + ts->unified_paint_settings.curve_rand_hue = BKE_paint_default_curve(); + } + if (ts->unified_paint_settings.curve_rand_saturation == nullptr) { + ts->unified_paint_settings.curve_rand_saturation = BKE_paint_default_curve(); + } + if (ts->unified_paint_settings.curve_rand_value == nullptr) { + ts->unified_paint_settings.curve_rand_value = BKE_paint_default_curve(); + } +} + +/* GP_BRUSH_* settings in gpencil_settings->flag2 were deprecated and replaced with + * brush->color_jitter_flag. */ +static void do_convert_gp_jitter_flags(Brush *brush) +{ + BrushGpencilSettings *settings = brush->gpencil_settings; + if (settings->flag2 & GP_BRUSH_USE_HUE_AT_STROKE) { + brush->color_jitter_flag |= BRUSH_COLOR_JITTER_USE_HUE_AT_STROKE; + } + if (settings->flag2 & GP_BRUSH_USE_SAT_AT_STROKE) { + brush->color_jitter_flag |= BRUSH_COLOR_JITTER_USE_SAT_AT_STROKE; + } + if (settings->flag2 & GP_BRUSH_USE_VAL_AT_STROKE) { + brush->color_jitter_flag |= BRUSH_COLOR_JITTER_USE_VAL_AT_STROKE; + } + if (settings->flag2 & GP_BRUSH_USE_HUE_RAND_PRESS) { + brush->color_jitter_flag |= BRUSH_COLOR_JITTER_USE_HUE_RAND_PRESS; + } + if (settings->flag2 & GP_BRUSH_USE_SAT_RAND_PRESS) { + brush->color_jitter_flag |= BRUSH_COLOR_JITTER_USE_SAT_RAND_PRESS; + } + if (settings->flag2 & GP_BRUSH_USE_VAL_RAND_PRESS) { + brush->color_jitter_flag |= BRUSH_COLOR_JITTER_USE_VAL_RAND_PRESS; + } +} + void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 8)) { @@ -4881,6 +4921,18 @@ void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 84)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + do_init_default_jitter_curves_in_unified_paint_settings(scene->toolsettings); + } + + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->gpencil_settings) { + do_convert_gp_jitter_flags(brush); + } + } + } + /** * 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/blenloader/intern/versioning_defaults.cc b/source/blender/blenloader/intern/versioning_defaults.cc index 3bdc91942cb..55ca62ceb93 100644 --- a/source/blender/blenloader/intern/versioning_defaults.cc +++ b/source/blender/blenloader/intern/versioning_defaults.cc @@ -452,6 +452,16 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene) ts->unified_paint_settings.flag = default_ups.flag; copy_v3_v3(ts->unified_paint_settings.rgb, default_ups.rgb); copy_v3_v3(ts->unified_paint_settings.secondary_rgb, default_ups.secondary_rgb); + + if (ts->unified_paint_settings.curve_rand_hue == nullptr) { + ts->unified_paint_settings.curve_rand_hue = BKE_paint_default_curve(); + } + if (ts->unified_paint_settings.curve_rand_saturation == nullptr) { + ts->unified_paint_settings.curve_rand_saturation = BKE_paint_default_curve(); + } + if (ts->unified_paint_settings.curve_rand_value == nullptr) { + ts->unified_paint_settings.curve_rand_value = BKE_paint_default_curve(); + } } void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_randomize.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_randomize.cc index fb723c25b7e..ec7fbb8590c 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_randomize.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_randomize.cc @@ -6,6 +6,8 @@ * \ingroup edgreasepencil */ +#include "BKE_brush.hh" +#include "BKE_paint.hh" #include "BLI_noise.hh" #include "BLI_rand.hh" @@ -141,82 +143,25 @@ ColorGeometry4f randomize_color(const BrushGpencilSettings &settings, { return color; } - /* TODO: This should be exposed as a setting to scale the noise along the stroke. */ - constexpr float noise_scale = 1 / 20.0f; - float random_hue = 0.0f; - if ((settings.flag2 & GP_BRUSH_USE_HUE_AT_STROKE) == 0) { - random_hue = noise::perlin_signed(float2(distance * noise_scale, stroke_hue_factor)); - } - else { - random_hue = stroke_hue_factor; - } + BrushColorJitterSettings jitter_settings = { + settings.color_jitter_flag, + settings.random_hue, + settings.random_saturation, + settings.random_value, - float random_saturation = 0.0f; - if ((settings.flag2 & GP_BRUSH_USE_SAT_AT_STROKE) == 0) { - random_saturation = noise::perlin_signed( - float2(distance * noise_scale, stroke_saturation_factor)); - } - else { - random_saturation = stroke_saturation_factor; - } + settings.curve_rand_hue, + settings.curve_rand_saturation, + settings.curve_rand_value, + }; - float random_value = 0.0f; - if ((settings.flag2 & GP_BRUSH_USE_VAL_AT_STROKE) == 0) { - random_value = noise::perlin_signed(float2(distance * noise_scale, stroke_value_factor)); - } - else { - random_value = stroke_value_factor; - } + blender::float3 initial_hsv_jitter = { + stroke_hue_factor, stroke_saturation_factor, stroke_value_factor}; - if ((settings.flag2 & GP_BRUSH_USE_HUE_RAND_PRESS) != 0) { - random_hue *= BKE_curvemapping_evaluateF(settings.curve_rand_hue, 0, pressure); - } - if ((settings.flag2 & GP_BRUSH_USE_SAT_RAND_PRESS) != 0) { - random_saturation *= BKE_curvemapping_evaluateF(settings.curve_rand_saturation, 0, pressure); - } - if ((settings.flag2 & GP_BRUSH_USE_VAL_RAND_PRESS) != 0) { - random_value *= BKE_curvemapping_evaluateF(settings.curve_rand_value, 0, pressure); - } + blender::float3 jittered = BKE_paint_randomize_color( + jitter_settings, initial_hsv_jitter, distance, pressure, {color.r, color.g, color.b}); - float3 hsv; - linearrgb_to_srgb_v3_v3(hsv, color); - rgb_to_hsv_v(hsv, hsv); - - hsv[0] += random_hue * settings.random_hue; - - const float absolute_brightness = hsv[2] + random_value * settings.random_value; - - /* - * To match relative brightness we want the ratio of the original to modified Value to not - * depend on the brightness of the input Value, Exp is used because we need a function that - * is positive for all 'x' and has the property that 'f(-x) = 1/f(x)' this is so that - * we make the Value on average growth and shrink by the same amount. To detriment the rate - * of the Exponential we set slope to match addition for small random values at an arbitrary - * Base Value. - */ - constexpr float base_value = 0.5f; - const float relative_brightness = hsv[2] * - math::exp(random_value * settings.random_value / base_value); - - /* Use the fourth power. */ - const float blend_factor = math::pow(settings.random_value, 4.0f); - /* Use relative brightness at low randomness, and switch to absolute at high. */ - hsv[2] = math::interpolate(relative_brightness, absolute_brightness, blend_factor); - - /* Multiply by the current saturation to prevent grays from becoming red. */ - hsv[1] += random_saturation * settings.random_saturation * 2.0f * hsv[1]; - - /* Wrap hue, clamp saturation and value. */ - hsv[0] = math::fract(hsv[0]); - hsv[1] = math::clamp(hsv[1], 0.0f, 1.0f); - hsv[2] = math::clamp(hsv[2], 0.0f, 1.0f); - - ColorGeometry4f random_color; - hsv_to_rgb_v(hsv, random_color); - srgb_to_linearrgb_v3_v3(random_color, random_color); - random_color.a = color.a; - return random_color; + return {jittered[0], jittered[1], jittered[2], 1}; } } // namespace blender::ed::greasepencil diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index 4f7c5aea8a7..765b17a70bc 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -16,6 +16,8 @@ #include "BLI_listbase.h" #include "BLI_math_vector.hh" +#include "BLI_noise.hh" +#include "BLI_rand.hh" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -33,6 +35,7 @@ #include "BKE_brush.hh" #include "BKE_colorband.hh" +#include "BKE_colortools.hh" #include "BKE_context.hh" #include "BKE_curves.hh" #include "BKE_grease_pencil.hh" @@ -349,7 +352,7 @@ static bool image_paint_2d_clone_poll(bContext *C) /** \name Paint Operator * \{ */ -bool paint_use_opacity_masking(Brush *brush) +bool paint_use_opacity_masking(const Scene *scene, const Paint *paint, const Brush *brush) { return ((brush->flag & BRUSH_AIRBRUSH) || (brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED) || @@ -358,6 +361,7 @@ bool paint_use_opacity_masking(Brush *brush) IMAGE_PAINT_BRUSH_TYPE_SOFTEN) || (brush->image_brush_type == IMAGE_PAINT_BRUSH_TYPE_FILL) || (brush->flag & BRUSH_USE_GRADIENT) || + (BKE_brush_color_jitter_get_settings(scene, paint, brush)) || (brush->mtex.tex && !ELEM(brush->mtex.brush_map_mode, MTEX_MAP_MODE_TILED, MTEX_MAP_MODE_STENCIL, @@ -369,6 +373,7 @@ bool paint_use_opacity_masking(Brush *brush) void paint_brush_color_get(Scene *scene, const Paint *paint, Brush *br, + blender::float3 &initial_hsv_jitter, bool color_correction, bool invert, float distance, @@ -380,6 +385,8 @@ void paint_brush_color_get(Scene *scene, copy_v3_v3(r_color, BKE_brush_secondary_color_get(scene, paint, br)); } else { + const std::optional color_jitter_settings = + BKE_brush_color_jitter_get_settings(scene, paint, br); if (br->flag & BRUSH_USE_GRADIENT) { float color_gr[4]; switch (br->gradient_stroke_mode) { @@ -400,6 +407,14 @@ void paint_brush_color_get(Scene *scene, * Brush colors are expected to be in sRGB though. */ IMB_colormanagement_scene_linear_to_srgb_v3(r_color, color_gr); } + else if (color_jitter_settings) { + copy_v3_v3(r_color, + BKE_paint_randomize_color(*color_jitter_settings, + initial_hsv_jitter, + distance, + pressure, + BKE_brush_color_get(scene, paint, br))); + } else { copy_v3_v3(r_color, BKE_brush_color_get(scene, paint, br)); } @@ -1142,6 +1157,12 @@ static bool texture_paint_poll(bContext *C) return false; } +blender::float3 seed_hsv_jitter() +{ + blender::RandomNumberGenerator rng = blender::RandomNumberGenerator::from_random_seed(); + return blender::float3{rng.get_float(), rng.get_float(), rng.get_float()}; +} + bool image_texture_paint_poll(bContext *C) { return (texture_paint_poll(C) || ED_image_tools_paint_poll(C)); diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.cc b/source/blender/editors/sculpt_paint/paint_image_2d.cc index 28b50efda91..9d3bf0f6b3a 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.cc +++ b/source/blender/editors/sculpt_paint/paint_image_2d.cc @@ -12,6 +12,7 @@ #include "MEM_guardedalloc.h" #include "DNA_brush_types.h" +#include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_space_types.h" @@ -78,6 +79,10 @@ struct BrushPainter { const Paint *paint; Brush *brush; + /* Store initial starting points for perlin noise on the beginning of each stroke when using + * color jitter. */ + std::optional initial_hsv_jitter; + bool firsttouch; /* first paint op */ ImagePool *pool; /* image pool */ @@ -140,11 +145,14 @@ static BrushPainter *brush_painter_2d_new(Scene *scene, Brush *brush, bool invert) { - BrushPainter *painter = MEM_callocN(__func__); + BrushPainter *painter = MEM_new(__func__); painter->brush = brush; painter->scene = scene; painter->paint = paint; + if (BKE_brush_color_jitter_get_settings(scene, paint, brush)) { + painter->initial_hsv_jitter = seed_hsv_jitter(); + } painter->firsttouch = true; painter->cache_invert = invert; @@ -397,6 +405,7 @@ static ImBuf *brush_painter_imbuf_new( paint_brush_color_get(scene, paint, brush, + *painter->initial_hsv_jitter, use_color_correction, cache->invert, distance, @@ -486,8 +495,16 @@ static void brush_painter_imbuf_update(BrushPainter *painter, /* get brush color */ if (brush->image_brush_type == IMAGE_PAINT_BRUSH_TYPE_DRAW) { - paint_brush_color_get( - scene, paint, brush, use_color_correction, cache->invert, 0.0f, 1.0f, display, brush_rgb); + paint_brush_color_get(scene, + paint, + brush, + *painter->initial_hsv_jitter, + use_color_correction, + cache->invert, + 0.0f, + 1.0f, + display, + brush_rgb); } else { brush_rgb[0] = 1.0f; @@ -710,10 +727,12 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s, bool do_random = false; bool do_partial_update = false; - bool update_color = ((brush->flag & BRUSH_USE_GRADIENT) && (ELEM(brush->gradient_stroke_mode, - BRUSH_GRADIENT_SPACING_REPEAT, - BRUSH_GRADIENT_SPACING_CLAMP) || - (cache->last_pressure != pressure))); + bool update_color = ((brush->flag & BRUSH_USE_GRADIENT) && + (ELEM(brush->gradient_stroke_mode, + BRUSH_GRADIENT_SPACING_REPEAT, + BRUSH_GRADIENT_SPACING_CLAMP) || + (cache->last_pressure != pressure))) || + BKE_brush_color_jitter_get_settings(scene, painter->paint, brush); float tex_rotation = -brush->mtex.rot; float mask_rotation = -brush->mask_mtex.rot; @@ -1419,7 +1438,7 @@ static int paint_2d_op(void *state, return 1; } -static int paint_2d_canvas_set(ImagePaintState *s) +static int paint_2d_canvas_set(ImagePaintState *s, const Paint *paint) { /* set clone canvas */ if (s->brush_type == IMAGE_PAINT_BRUSH_TYPE_CLONE) { @@ -1444,7 +1463,7 @@ static int paint_2d_canvas_set(ImagePaintState *s) } /* set masking */ - s->do_masking = paint_use_opacity_masking(s->brush); + s->do_masking = paint_use_opacity_masking(s->scene, paint, s->brush); return 1; } @@ -1647,7 +1666,7 @@ void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode) s->tiles[tile_idx].uv_origin[1] = ((tile->tile_number - 1001) / 10); } - if (!paint_2d_canvas_set(s)) { + if (!paint_2d_canvas_set(s, paint)) { MEM_freeN(s->tiles); MEM_freeN(s); @@ -1713,7 +1732,7 @@ void paint_2d_stroke_done(void *ps) for (int i = 0; i < s->num_tiles; i++) { brush_painter_cache_2d_free(&s->tiles[i].cache); } - MEM_freeN(s->painter); + MEM_delete(s->painter); MEM_freeN(s->tiles); paint_brush_exit_tex(s->brush); diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.cc b/source/blender/editors/sculpt_paint/paint_image_proj.cc index 6ad32345a16..1c3f6f44b95 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.cc +++ b/source/blender/editors/sculpt_paint/paint_image_proj.cc @@ -233,6 +233,10 @@ struct ProjStrokeHandle { * we can assume at least the first is set while painting. */ ProjPaintState *ps_views[8]; + /* Store initial starting points for perlin noise on the beginning of each stroke when using + * color jitter. */ + std::optional initial_hsv_jitter; + int ps_views_tot; int symmetry_flags; @@ -5769,6 +5773,7 @@ static void paint_proj_stroke_ps(const bContext * /*C*/, paint_brush_color_get(scene, paint, brush, + *ps_handle->initial_hsv_jitter, false, ps->mode == BRUSH_STROKE_INVERT, distance, @@ -5864,7 +5869,7 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int } /* disable for 3d mapping also because painting on mirrored mesh can create "stripes" */ - ps->do_masking = paint_use_opacity_masking(brush); + ps->do_masking = paint_use_opacity_masking(scene, ps->paint, brush); ps->is_texbrush = (brush->mtex.tex && ps->brush_type == IMAGE_PAINT_BRUSH_TYPE_DRAW) ? true : false; ps->is_maskbrush = (brush->mask_mtex.tex) ? true : false; @@ -5955,10 +5960,14 @@ void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int m ToolSettings *settings = scene->toolsettings; char symmetry_flag_views[BOUNDED_ARRAY_TYPE_SIZEps_views)>()] = {0}; - ps_handle = MEM_callocN("ProjStrokeHandle"); + ps_handle = MEM_new("ProjStrokeHandle"); ps_handle->scene = scene; ps_handle->brush = BKE_paint_brush(&settings->imapaint.paint); + if (BKE_brush_color_jitter_get_settings(scene, &settings->imapaint.paint, ps_handle->brush)) { + ps_handle->initial_hsv_jitter = seed_hsv_jitter(); + } + if (mode == BRUSH_STROKE_INVERT) { /* Bypass regular stroke logic. */ if (ps_handle->brush->image_brush_type == IMAGE_PAINT_BRUSH_TYPE_CLONE) { @@ -6045,7 +6054,7 @@ fail: for (int i = 0; i < ps_handle->ps_views_tot; i++) { MEM_delete(ps_handle->ps_views[i]); } - MEM_freeN(ps_handle); + MEM_delete(ps_handle); return nullptr; } @@ -6075,7 +6084,7 @@ void paint_proj_stroke_done(void *ps_handle_p) Scene *scene = ps_handle->scene; if (ps_handle->is_clone_cursor_pick) { - MEM_freeN(ps_handle); + MEM_delete(ps_handle); return; } diff --git a/source/blender/editors/sculpt_paint/paint_intern.hh b/source/blender/editors/sculpt_paint/paint_intern.hh index caec56fcff1..40f60b90652 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.hh +++ b/source/blender/editors/sculpt_paint/paint_intern.hh @@ -8,9 +8,14 @@ #pragma once +#include "BLI_array.hh" +#include "BLI_compiler_compat.h" +#include "BLI_function_ref.hh" #include "BLI_index_mask_fwd.hh" #include "BLI_math_vector_types.hh" +#include "BLI_set.hh" #include "BLI_span.hh" +#include "BLI_vector.hh" #include "DNA_object_enums.h" #include "DNA_scene_enums.h" @@ -345,13 +350,14 @@ void paint_proj_stroke_done(void *ps_handle_p); void paint_brush_color_get(Scene *scene, const Paint *paint, Brush *br, + blender::float3 &initial_hsv_jitter, bool color_correction, bool invert, float distance, float pressure, const ColorManagedDisplay *display, float r_color[3]); -bool paint_use_opacity_masking(Brush *brush); +bool paint_use_opacity_masking(const Scene *scene, const Paint *paint, const Brush *brush); void paint_brush_init_tex(Brush *brush); void paint_brush_exit_tex(Brush *brush); diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index 4713b316c6f..fc9bc8f5788 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -18,6 +18,8 @@ #include "BLI_color.hh" #include "BLI_color_mix.hh" #include "BLI_enumerable_thread_specific.hh" +#include "BLI_listbase.h" +#include "BLI_math_color.h" #include "BLI_math_geom.h" #include "BLI_math_matrix.hh" #include "BLI_math_rotation.h" @@ -493,6 +495,10 @@ void update_cache_invariants( cache->is_last_valid = false; cache->accum = true; + + if (BKE_brush_color_jitter_get_settings(scene, &vp.paint, brush)) { + cache->initial_hsv_jitter = seed_hsv_jitter(); + } } void update_cache_variants(bContext *C, VPaint &vp, Object &ob, PointerRNA *ptr) @@ -710,6 +716,55 @@ static Color vpaint_blend(const VPaint &vp, return color_blend; } +/* If in accumulate mode, blend brush mark directly onto mesh, else blend into temporary + * stroke_buffer and blend the stroke onto the mesh. */ +template +static Color vpaint_blend_stroke(const VPaint &vp, + MutableSpan prev_vertex_colors, + MutableSpan vertex_colors, + MutableSpan stroke_buffer, + Color brush_mark_color, + float brush_mark_alpha, + float brush_strength, + int index) +{ + Color result; + if (!vwpaint::brush_use_accumulate(vp)) { + BLI_assert(!stroke_buffer.is_empty()); + BLI_assert(!prev_vertex_colors.is_empty()); + + if (isZero(prev_vertex_colors[index])) { + prev_vertex_colors[index] = vertex_colors[index]; + } + + /* Mix with mesh color under the stroke (a bit easier than trying to premultiply + * byte Color types */ + if (isZero(stroke_buffer[index])) { + stroke_buffer[index] = vertex_colors[index]; + stroke_buffer[index].a = 0; + } + + stroke_buffer[index] = BLI_mix_colors( + IMB_BlendMode::IMB_BLEND_MIX, stroke_buffer[index], brush_mark_color, brush_mark_alpha); + + result = vpaint_blend(vp, + prev_vertex_colors[index], + prev_vertex_colors[index], + stroke_buffer[index], + stroke_buffer[index].a, + Traits::range * brush_strength); + } + else { + result = vpaint_blend(vp, + vertex_colors[index], + Color() /* unused in accumulate mode */, + brush_mark_color, + brush_mark_alpha, + Traits::range * brush_strength); + } + return result; +} + static void paint_and_tex_color_alpha_intern(const VPaint &vp, const ViewContext *vc, const float co[3], @@ -906,6 +961,7 @@ struct VPaintData : public PaintModeData { /* For brushes that don't use accumulation, a temporary holding array */ GArray<> prev_colors; + GArray<> stroke_buffer; ~VPaintData() override { @@ -964,9 +1020,16 @@ static std::unique_ptr vpaint_init_vpaint(bContext *C, vpd->prev_colors = GArray(attribute.type(), attribute.size()); attribute.type().value_initialize_n(vpd->prev_colors.data(), vpd->prev_colors.size()); } + + if (vpd->stroke_buffer.is_empty()) { + const GVArray attribute = *mesh.attributes().lookup(mesh.active_color_attribute); + vpd->stroke_buffer = GArray(attribute.type(), attribute.size()); + attribute.type().value_initialize_n(vpd->stroke_buffer.data(), vpd->stroke_buffer.size()); + } } else { vpd->prev_colors = {}; + vpd->stroke_buffer = {}; } return vpd; @@ -1650,6 +1713,26 @@ static float paint_and_tex_color_alpha(const VPaint &vp, return rgba[3]; } +/* Compute brush color, using jitter if it's enabled */ +static blender::float3 get_brush_color(const Scene *scene, + const Brush *brush, + const Paint *paint, + const StrokeCache &cache, + const ColorPaint4f &paint_color) +{ + blender::float3 brush_color = blender::float3(paint_color.r, paint_color.g, paint_color.b); + const std::optional color_jitter_settings = + BKE_brush_color_jitter_get_settings(scene, paint, brush); + if (color_jitter_settings) { + brush_color = BKE_paint_randomize_color(*color_jitter_settings, + *cache.initial_hsv_jitter, + cache.stroke_distance, + cache.pressure, + brush_color); + } + return brush_color; +} + static void vpaint_do_draw(const bContext *C, const VPaint &vp, VPaintData &vpd, @@ -1677,6 +1760,7 @@ static void vpaint_do_draw(const bContext *C, ss, brush.falloff_shape); GMutableSpan g_previous_color = vpd.prev_colors; + GMutableSpan g_stroke_buffer = vpd.stroke_buffer; const Span vert_positions = bke::pbvh::vert_positions_eval(depsgraph, ob); const OffsetIndices faces = mesh.faces(); @@ -1694,6 +1778,9 @@ static void vpaint_do_draw(const bContext *C, select_poly = *attributes.lookup(".select_poly", bke::AttrDomain::Face); } + const blender::float3 brush_color = get_brush_color( + &scene, &brush, &vp.paint, cache, vpd.paintcol); + struct LocalData { Vector factors; Vector distances; @@ -1742,7 +1829,10 @@ static void vpaint_do_draw(const bContext *C, using Traits = blender::color::Traits; MutableSpan colors = attribute.typed().template cast(); MutableSpan previous_color = g_previous_color.typed().template cast(); - Color color_final = fromFloat(vpd.paintcol); + MutableSpan stroke_buffer = g_stroke_buffer.typed().template cast(); + + Color color_final = fromFloat( + ColorPaint4f(brush_color[0], brush_color[1], brush_color[2], 1.0)); /* If we're painting with a texture, sample the texture color and alpha. */ float tex_alpha = 1.0; @@ -1764,24 +1854,18 @@ static void vpaint_do_draw(const bContext *C, tex_alpha = paint_and_tex_color_alpha(vp, vpd, symm_point, &color_final); } - Color color_orig(0, 0, 0, 0); + const float final_alpha = Traits::frange * brush_fade * brush_strength * tex_alpha * + brush_alpha_pressure; if (vpd.domain == AttrDomain::Point) { - if (!previous_color.is_empty()) { - if (isZero(previous_color[vert])) { - previous_color[vert] = colors[vert]; - } - color_orig = previous_color[vert]; - } - const float final_alpha = Traits::frange * brush_fade * brush_strength * tex_alpha * - brush_alpha_pressure; - - colors[vert] = vpaint_blend(vp, - colors[vert], - color_orig, - color_final, - final_alpha, - Traits::range * brush_strength); + colors[vert] = vpaint_blend_stroke(vp, + previous_color, + colors, + stroke_buffer, + color_final, + final_alpha, + brush_strength, + vert); } else { /* For each face owning this vert, paint each loop belonging to this vert. */ @@ -1791,23 +1875,14 @@ static void vpaint_do_draw(const bContext *C, if (!select_poly.is_empty() && !select_poly[face]) { continue; } - Color color_orig = Color(0, 0, 0, 0); /* unused when array is nullptr */ - - if (!previous_color.is_empty()) { - if (isZero(previous_color[corner])) { - previous_color[corner] = colors[corner]; - } - color_orig = previous_color[corner]; - } - const float final_alpha = Traits::frange * brush_fade * brush_strength * tex_alpha * - brush_alpha_pressure; - - colors[corner] = vpaint_blend(vp, - colors[corner], - color_orig, - color_final, - final_alpha, - Traits::range * brush_strength); + colors[corner] = vpaint_blend_stroke(vp, + previous_color, + colors, + stroke_buffer, + color_final, + final_alpha, + brush_strength, + corner); } } }); @@ -1974,6 +2049,8 @@ static void vpaint_stroke_update_step(bContext *C, Object &ob = *vc.obact; SculptSession &ss = *ob.sculpt; + ss.cache->stroke_distance = paint_stroke_distance_get(stroke); + vwpaint::update_cache_variants(C, vp, ob, itemptr); float mat[4][4]; diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index c18632c63d4..a99412a052b 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -4039,6 +4039,9 @@ static void sculpt_update_cache_invariants( cache->accum = true; } + if (BKE_brush_color_jitter_get_settings(CTX_data_scene(C), &sd.paint, brush)) { + cache->initial_hsv_jitter = seed_hsv_jitter(); + } cache->first_time = true; cache->plane_brush.first_time = true; diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index 47d0b018e5b..ede36150c7e 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -230,6 +230,9 @@ struct StrokeCache { /* The rest is temporary storage that isn't saved as a property */ + /* Store initial starting points for perlin noise on the beginning of each stroke when using + * color jitter. */ + std::optional initial_hsv_jitter; /* Beginning of stroke may do some things special. */ bool first_time = false; diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.cc b/source/blender/editors/sculpt_paint/sculpt_paint_color.cc index 501ba32e01a..ac25f1ffb75 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.cc +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.cc @@ -428,11 +428,23 @@ static void do_paint_brush_task(const Scene &scene, } } - const float3 brush_color_rgb = ss.cache->invert ? - BKE_brush_secondary_color_get(&scene, &paint, &brush) : - BKE_brush_color_get(&scene, &paint, &brush); + float3 brush_color_rgb = ss.cache->invert ? + BKE_brush_secondary_color_get(&scene, &paint, &brush) : + BKE_brush_color_get(&scene, &paint, &brush); + + IMB_colormanagement_srgb_to_scene_linear_v3(brush_color_rgb, brush_color_rgb); + + const std::optional color_jitter_settings = + BKE_brush_color_jitter_get_settings(&scene, &paint, &brush); + if (color_jitter_settings) { + brush_color_rgb = BKE_paint_randomize_color(*color_jitter_settings, + *ss.cache->initial_hsv_jitter, + ss.cache->stroke_distance, + ss.cache->pressure, + brush_color_rgb); + } + float4 brush_color(brush_color_rgb, 1.0f); - IMB_colormanagement_srgb_to_scene_linear_v3(brush_color, brush_color); const Span orig_colors = orig_color_data_get_mesh(object, node); diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index d63725bc83e..c81a1927520 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -104,10 +104,13 @@ typedef enum eGPDbrush_Flag { } eGPDbrush_Flag; typedef enum eGPDbrush_Flag2 { + /* DEPRECATED: replaced with BRUSH_COLOR_JITTER_USE_HUE_AT_STROKE */ /* Brush use random Hue at stroke level */ GP_BRUSH_USE_HUE_AT_STROKE = (1 << 0), + /* DEPRECATED: replaced with BRUSH_COLOR_JITTER_USE_SAT_AT_STROKE */ /* Brush use random Saturation at stroke level */ GP_BRUSH_USE_SAT_AT_STROKE = (1 << 1), + /* DEPRECATED: replaced with BRUSH_COLOR_JITTER_USE_VAL_AT_STROKE */ /* Brush use random Value at stroke level */ GP_BRUSH_USE_VAL_AT_STROKE = (1 << 2), /* Brush use random Pressure at stroke level */ @@ -116,10 +119,13 @@ typedef enum eGPDbrush_Flag2 { GP_BRUSH_USE_STRENGTH_AT_STROKE = (1 << 4), /* Brush use random UV at stroke level */ GP_BRUSH_USE_UV_AT_STROKE = (1 << 5), + /* DEPRECATED: replaced with BRUSH_COLOR_JITTER_USE_HUE_RAND_PRESS */ /* Brush use Hue random pressure */ GP_BRUSH_USE_HUE_RAND_PRESS = (1 << 6), + /* DEPRECATED: replaced with BRUSH_COLOR_JITTER_USE_SAT_RAND_PRESS */ /* Brush use Saturation random pressure */ GP_BRUSH_USE_SAT_RAND_PRESS = (1 << 7), + /* DEPRECATED: replaced with BRUSH_COLOR_JITTER_USE_VAL_RAND_PRESS */ /* Brush use Value random pressure */ GP_BRUSH_USE_VAL_RAND_PRESS = (1 << 8), /* Brush use Pressure random pressure */ @@ -398,6 +404,7 @@ typedef enum eBrushFlags2 { BRUSH_AREA_RADIUS_PRESSURE = (1 << 7), BRUSH_GRAB_SILHOUETTE = (1 << 8), BRUSH_USE_COLOR_AS_DISPLACEMENT = (1 << 9), + BRUSH_JITTER_COLOR = (1 << 10), } eBrushFlags2; typedef enum { @@ -585,4 +592,13 @@ typedef enum eBrushCurvesSculptDensityMode { BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE = 2, } eBrushCurvesSculptDensityMode; +typedef enum eBrushColorJitterSettings_Flag { + BRUSH_COLOR_JITTER_USE_HUE_AT_STROKE = (1 << 0), + BRUSH_COLOR_JITTER_USE_SAT_AT_STROKE = (1 << 1), + BRUSH_COLOR_JITTER_USE_VAL_AT_STROKE = (1 << 2), + BRUSH_COLOR_JITTER_USE_HUE_RAND_PRESS = (1 << 3), + BRUSH_COLOR_JITTER_USE_SAT_RAND_PRESS = (1 << 4), + BRUSH_COLOR_JITTER_USE_VAL_RAND_PRESS = (1 << 5), +} eBrushColorJitterSettings_Flag; + #define MAX_BRUSH_PIXEL_RADIUS 500 diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 6cf2ba178cc..1b98c81e621 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -104,6 +104,9 @@ typedef struct BrushGpencilSettings { /** Randomness for Value. */ float random_value; + int color_jitter_flag; + char _pad1[4]; + /** Factor to extend stroke extremes using fill tool. */ float fill_extend_fac; /** Number of pixels to dilate fill area. */ @@ -223,6 +226,14 @@ typedef struct Brush { /** Color. */ float rgb[3]; + int color_jitter_flag; + float hsv_jitter[3]; + + /** Color jitter pressure curves. */ + struct CurveMapping *curve_rand_hue; + struct CurveMapping *curve_rand_saturation; + struct CurveMapping *curve_rand_value; + /** Opacity. */ float alpha; /** Hardness */ @@ -291,7 +302,7 @@ typedef struct Brush { char gpencil_weight_brush_type; /** Active curves sculpt brush type (#eBrushCurvesSculptType). */ char curves_sculpt_brush_type; - char _pad1[2]; + char _pad1[10]; float autosmooth_factor; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 82cb5aa2f92..47adce5d58a 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1426,6 +1426,15 @@ typedef struct UnifiedPaintSettings { /** Unified brush secondary color. */ float secondary_rgb[3]; + /** Unified color jitter settings */ + int color_jitter_flag; + float hsv_jitter[3]; + + /** Color jitter pressure curves. */ + struct CurveMapping *curve_rand_hue; + struct CurveMapping *curve_rand_saturation; + struct CurveMapping *curve_rand_value; + /** Unified brush stroke input samples. */ int input_samples; @@ -1508,6 +1517,7 @@ typedef enum { UNIFIED_PAINT_WEIGHT = (1 << 5), UNIFIED_PAINT_COLOR = (1 << 6), UNIFIED_PAINT_INPUT_SAMPLES = (1 << 7), + UNIFIED_PAINT_COLOR_JITTER = (1 << 8), } eUnifiedPaintSettingsFlags; diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index 4bbbee76953..18582a6fe6b 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -2749,6 +2749,96 @@ static void rna_def_brush(BlenderRNA *brna) prop, "Gradient Spacing", "Spacing before brush gradient goes full circle"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_color_jitter", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_JITTER_COLOR); + RNA_def_property_ui_text(prop, "Use Color Jitter", "Jitter brush color"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "hue_jitter", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "hsv_jitter[0]"); + RNA_def_property_range(prop, 0, 1.0f); + RNA_def_property_ui_range(prop, 0, 1, 0.05, 2); + RNA_def_property_ui_text(prop, "Hue Jitter", "Color jitter effect on hue"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "saturation_jitter", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "hsv_jitter[1]"); + RNA_def_property_range(prop, 0, 1.0f); + RNA_def_property_ui_range(prop, 0, 1, 0.05, 2); + RNA_def_property_ui_text(prop, "Saturation Jitter", "Color jitter effect on saturation"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "value_jitter", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "hsv_jitter[2]"); + RNA_def_property_range(prop, 0, 1.0f); + RNA_def_property_ui_range(prop, 0, 1, 0.05, 2); + RNA_def_property_ui_text(prop, "Value Jitter", "Color jitter effect on value"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "use_stroke_random_hue", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_HUE_AT_STROKE); + RNA_def_property_ui_icon(prop, ICON_GP_SELECT_STROKES, 0); + RNA_def_property_ui_text(prop, "Stroke Random", "Use randomness at stroke level"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "use_stroke_random_sat", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_SAT_AT_STROKE); + RNA_def_property_ui_icon(prop, ICON_GP_SELECT_STROKES, 0); + RNA_def_property_ui_text(prop, "Stroke Random", "Use randomness at stroke level"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "use_stroke_random_val", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_VAL_AT_STROKE); + RNA_def_property_ui_icon(prop, ICON_GP_SELECT_STROKES, 0); + RNA_def_property_ui_text(prop, "Stroke Random", "Use randomness at stroke level"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "use_random_press_hue", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_HUE_RAND_PRESS); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Use Pressure", "Use pressure to modulate randomness"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "use_random_press_sat", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_SAT_RAND_PRESS); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Use Pressure", "Use pressure to modulate randomness"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "use_random_press_val", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_VAL_RAND_PRESS); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Use Pressure", "Use pressure to modulate randomness"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "curve_random_hue", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, nullptr, "curve_rand_hue"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Random Curve", "Curve used for modulating effect"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "curve_random_saturation", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, nullptr, "curve_rand_saturation"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Random Curve", "Curve used for modulating effect"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "curve_random_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, nullptr, "curve_rand_value"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Random Curve", "Curve used for modulating effect"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "smooth_stroke_radius", PROP_INT, PROP_PIXEL); RNA_def_property_range(prop, 10, 200); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 8d7fc58ee84..c8398ac8909 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -4454,6 +4454,72 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Secondary Color", ""); RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + prop = RNA_def_property(srna, "use_color_jitter", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", UNIFIED_PAINT_COLOR_JITTER); + RNA_def_property_ui_text(prop, "Use Color Jitter", "Jitter brush color"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "hue_jitter", PROP_FLOAT, PROP_NONE); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_float_sdna(prop, nullptr, "hsv_jitter[0]"); + RNA_def_property_range(prop, 0, 1.0f); + RNA_def_property_ui_range(prop, 0, 1, 0.05, 2); + RNA_def_property_ui_text(prop, "Hue Jitter", "Color jitter effect on hue"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "saturation_jitter", PROP_FLOAT, PROP_NONE); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_float_sdna(prop, nullptr, "hsv_jitter[1]"); + RNA_def_property_range(prop, 0, 1.0f); + RNA_def_property_ui_range(prop, 0, 1, 0.05, 2); + RNA_def_property_ui_text(prop, "Saturation Jitter", "Color jitter effect on saturation"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "value_jitter", PROP_FLOAT, PROP_NONE); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_float_sdna(prop, nullptr, "hsv_jitter[2]"); + RNA_def_property_range(prop, 0, 1.0f); + RNA_def_property_ui_range(prop, 0, 1, 0.05, 2); + RNA_def_property_ui_text(prop, "Value Jitter", "Color jitter effect on value"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "use_stroke_random_hue", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_HUE_AT_STROKE); + RNA_def_property_ui_icon(prop, ICON_GP_SELECT_STROKES, 0); + RNA_def_property_ui_text(prop, "Stroke Random", "Use randomness at stroke level"); + + prop = RNA_def_property(srna, "use_stroke_random_sat", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_SAT_AT_STROKE); + RNA_def_property_ui_icon(prop, ICON_GP_SELECT_STROKES, 0); + RNA_def_property_ui_text(prop, "Stroke Random", "Use randomness at stroke level"); + + prop = RNA_def_property(srna, "use_stroke_random_val", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_VAL_AT_STROKE); + RNA_def_property_ui_icon(prop, ICON_GP_SELECT_STROKES, 0); + RNA_def_property_ui_text(prop, "Stroke Random", "Use randomness at stroke level"); + + prop = RNA_def_property(srna, "use_random_press_hue", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_HUE_RAND_PRESS); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Use Pressure", "Use pressure to modulate randomness"); + + prop = RNA_def_property(srna, "use_random_press_sat", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_SAT_RAND_PRESS); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Use Pressure", "Use pressure to modulate randomness"); + + prop = RNA_def_property(srna, "use_random_press_val", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "color_jitter_flag", BRUSH_COLOR_JITTER_USE_VAL_RAND_PRESS); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Use Pressure", "Use pressure to modulate randomness"); + prop = RNA_def_property(srna, "input_samples", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_sdna(prop, nullptr, "input_samples"); RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);