Paint: Add color jitter support

Add ability to randomize color of each stroke for or use perlin noise
randomization during the stroke. Works similarly to grease pencil stroke
color randomization. This feature is available for all mesh color
painting modes (Sculpt Paint brush, Texture Paint and Vertex Paint)

This setting is available both as a per-brush setting and a unified
setting on the scene level. Additionally, the equivalent Grease Pencil
options have been migrated to these more generic flags and values.

Based on this [1] RCS request.

[1]: https://blender.community/c/rightclickselect/mwgbbc/

Pull Request: https://projects.blender.org/blender/blender/pulls/128953
This commit is contained in:
Charles S
2025-05-31 23:48:59 +02:00
committed by Sean Kim
parent 2fd5b39a1b
commit 96e549c092
23 changed files with 751 additions and 128 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -10,6 +10,8 @@
* General operations for brushes.
*/
#include <optional>
#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<BrushColorJitterSettings> 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);

View File

@@ -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();

View File

@@ -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<BrushGpencilSettings>(
__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<BrushColorJitterSettings> 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)

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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<BrushColorJitterSettings> 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));

View File

@@ -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<blender::float3> 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<BrushPainter>(__func__);
BrushPainter *painter = MEM_new<BrushPainter>(__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);

View File

@@ -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<blender::float3> 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_SIZE<decltype(ps_handle->ps_views)>()] = {0};
ps_handle = MEM_callocN<ProjStrokeHandle>("ProjStrokeHandle");
ps_handle = MEM_new<ProjStrokeHandle>("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;
}

View File

@@ -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);

View File

@@ -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<typename Color, typename Traits>
static Color vpaint_blend_stroke(const VPaint &vp,
MutableSpan<Color> prev_vertex_colors,
MutableSpan<Color> vertex_colors,
MutableSpan<Color> 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<Color, Traits>(
IMB_BlendMode::IMB_BLEND_MIX, stroke_buffer[index], brush_mark_color, brush_mark_alpha);
result = vpaint_blend<Color, Traits>(vp,
prev_vertex_colors[index],
prev_vertex_colors[index],
stroke_buffer[index],
stroke_buffer[index].a,
Traits::range * brush_strength);
}
else {
result = vpaint_blend<Color, Traits>(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<VPaintData> 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<BrushColorJitterSettings> 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<float3> 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<bool>(".select_poly", bke::AttrDomain::Face);
}
const blender::float3 brush_color = get_brush_color(
&scene, &brush, &vp.paint, cache, vpd.paintcol);
struct LocalData {
Vector<float> factors;
Vector<float> distances;
@@ -1742,7 +1829,10 @@ static void vpaint_do_draw(const bContext *C,
using Traits = blender::color::Traits<Color>;
MutableSpan<Color> colors = attribute.typed<T>().template cast<Color>();
MutableSpan<Color> previous_color = g_previous_color.typed<T>().template cast<Color>();
Color color_final = fromFloat<Color>(vpd.paintcol);
MutableSpan<Color> stroke_buffer = g_stroke_buffer.typed<T>().template cast<Color>();
Color color_final = fromFloat<Color>(
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<Color>(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<Color, Traits>(vp,
colors[vert],
color_orig,
color_final,
final_alpha,
Traits::range * brush_strength);
colors[vert] = vpaint_blend_stroke<Color, Traits>(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<Color, Traits>(vp,
colors[corner],
color_orig,
color_final,
final_alpha,
Traits::range * brush_strength);
colors[corner] = vpaint_blend_stroke<Color, Traits>(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];

View File

@@ -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;

View File

@@ -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<blender::float3> initial_hsv_jitter;
/* Beginning of stroke may do some things special. */
bool first_time = false;

View File

@@ -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<BrushColorJitterSettings> 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<float4> orig_colors = orig_color_data_get_mesh(object, node);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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(

View File

@@ -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);