feat: add star sparkle brush preset and enhance star brush parameters
This commit is contained in:
+151
-50
@@ -111,7 +111,22 @@ pub fn generate_brush_stamp(tip: &BrushTip) -> Vec<u8> {
|
||||
match tip.style {
|
||||
BrushStyle::Round => generate_round_stamp(d, center, r, tip.hardness),
|
||||
BrushStyle::Square => generate_square_stamp(d, center, r, tip.hardness),
|
||||
BrushStyle::Star => generate_star_stamp(d, center, r, tip.hardness),
|
||||
BrushStyle::Star => generate_star_stamp(
|
||||
d,
|
||||
center,
|
||||
r,
|
||||
tip.hardness,
|
||||
if tip.roundness > 0.0 {
|
||||
tip.roundness
|
||||
} else {
|
||||
0.22
|
||||
},
|
||||
if tip.density > 0.0 {
|
||||
tip.density
|
||||
} else {
|
||||
0.08
|
||||
},
|
||||
),
|
||||
BrushStyle::SoftRound => generate_round_stamp(d, center, r, 0.0),
|
||||
BrushStyle::HardRound => generate_round_stamp(d, center, r, 1.0),
|
||||
BrushStyle::Bitmap => {
|
||||
@@ -200,10 +215,19 @@ fn generate_square_stamp(d: usize, center: f32, r: f32, hardness: f32) -> Vec<u8
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_star_stamp(d: usize, center: f32, r: f32, hardness: f32) -> Vec<u8> {
|
||||
fn generate_star_stamp(
|
||||
d: usize,
|
||||
center: f32,
|
||||
r: f32,
|
||||
hardness: f32,
|
||||
arm_width_ratio: f32,
|
||||
inner_r_ratio: f32,
|
||||
) -> Vec<u8> {
|
||||
let arm_count = 4u32;
|
||||
let arm_width = r * 0.22;
|
||||
let inner_r = r * 0.08;
|
||||
let arm_width = arm_width_ratio.clamp(0.08, 0.35);
|
||||
let inner_r = r * inner_r_ratio.clamp(0.04, 0.12);
|
||||
let hardness = hardness.clamp(0.0, 1.0);
|
||||
let point_sharpness = 1.2 + (0.35 - arm_width) / 0.27 * 2.8;
|
||||
(0..d * d)
|
||||
.map(|i| {
|
||||
let x = (i % d) as f32 - center;
|
||||
@@ -212,21 +236,19 @@ fn generate_star_stamp(d: usize, center: f32, r: f32, hardness: f32) -> Vec<u8>
|
||||
let dist = (x * x + y * y).sqrt();
|
||||
let point_angle = std::f32::consts::PI * 2.0 / arm_count as f32;
|
||||
let a = angle.rem_euclid(point_angle);
|
||||
let arm_center = point_angle / 2.0;
|
||||
let arm_dist = (a - arm_center).abs();
|
||||
let max_arm_dist = arm_width / r;
|
||||
if dist > r {
|
||||
let arm_dist = a.min(point_angle - a);
|
||||
let angular_distance = (arm_dist / (point_angle * 0.5)).clamp(0.0, 1.0);
|
||||
let point_profile = (1.0 - angular_distance).powf(point_sharpness);
|
||||
let edge = inner_r + (r - inner_r) * point_profile;
|
||||
let soft_width = (r * (1.0 - hardness) * 0.35).max(0.75);
|
||||
let hard_edge = (edge - soft_width).max(0.0);
|
||||
|
||||
if dist >= edge {
|
||||
0
|
||||
} else if arm_dist < max_arm_dist && dist > inner_r {
|
||||
let arm_falloff = 1.0 - (arm_dist / max_arm_dist);
|
||||
let radial = ((dist - inner_r) / (r - inner_r)).clamp(0.0, 1.0);
|
||||
let alpha = arm_falloff * (1.0 - radial.powf(1.0 - hardness.clamp(0.0, 0.99)));
|
||||
(alpha * 255.0).round() as u8
|
||||
} else if dist <= inner_r {
|
||||
let alpha = (1.0 - dist / inner_r).max(0.5);
|
||||
(alpha * 255.0).round() as u8
|
||||
} else if dist <= hard_edge {
|
||||
255
|
||||
} else {
|
||||
0
|
||||
(((edge - dist) / soft_width).clamp(0.0, 1.0) * 255.0).round() as u8
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -650,6 +672,10 @@ fn draw_brush_style_dab(
|
||||
brush_hardness,
|
||||
is_eraser,
|
||||
mask,
|
||||
angle,
|
||||
rotation_random,
|
||||
roundness,
|
||||
density,
|
||||
),
|
||||
BrushStyle::Rock => draw_rock_brush(
|
||||
pixels,
|
||||
@@ -1621,59 +1647,134 @@ fn draw_star_brush(
|
||||
hardness: f32,
|
||||
is_eraser: bool,
|
||||
mask: Option<&[u8]>,
|
||||
angle: f32,
|
||||
rotation_random: f32,
|
||||
roundness: f32,
|
||||
density: f32,
|
||||
) {
|
||||
let effective_size = size * pressure;
|
||||
let seed = cx.to_bits().wrapping_mul(73_856_093)
|
||||
^ cy.to_bits().wrapping_mul(19_349_663);
|
||||
|
||||
let size_variance = roundness;
|
||||
let size_scale = if size_variance > 0.0 {
|
||||
let r = ((seed & 0xFFFF) as f32 / 65536.0 - 0.5) * 2.0 * size_variance;
|
||||
(1.0 + r).clamp(0.3, 2.0)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let effective_size = size * pressure * size_scale;
|
||||
|
||||
let angle_variance = rotation_random;
|
||||
let dab_angle = if angle_variance > 0.0 {
|
||||
let r = ((seed >> 16) as f32 / 65536.0 - 0.5) * 2.0 * angle_variance;
|
||||
angle + r * std::f32::consts::PI
|
||||
} else {
|
||||
angle
|
||||
};
|
||||
|
||||
let arm_width_ratio = if roundness > 0.0 { roundness } else { 0.22 };
|
||||
let inner_r_ratio = if density > 0.0 { density } else { 0.08 };
|
||||
|
||||
let tip = BrushTip {
|
||||
style: BrushStyle::Star,
|
||||
size: effective_size * 0.5,
|
||||
hardness,
|
||||
roundness: arm_width_ratio,
|
||||
density: inner_r_ratio,
|
||||
..BrushTip::default()
|
||||
};
|
||||
let stamp = generate_brush_stamp(&tip);
|
||||
draw_dab_with_stamp(
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
cx,
|
||||
cy,
|
||||
effective_size,
|
||||
hardness,
|
||||
color,
|
||||
opacity * pressure,
|
||||
is_eraser,
|
||||
mask,
|
||||
Some(&stamp),
|
||||
);
|
||||
let sparkle_count = ((effective_size * 0.15) as u32).clamp(2, 8);
|
||||
let sparkle_r = effective_size * 0.12;
|
||||
let tip_sm = BrushTip {
|
||||
style: BrushStyle::Star,
|
||||
size: sparkle_r,
|
||||
hardness,
|
||||
..BrushTip::default()
|
||||
|
||||
let cos_a = dab_angle.cos();
|
||||
let sin_a = dab_angle.sin();
|
||||
let stamp_rotated = if dab_angle.abs() > f32::EPSILON {
|
||||
let stamp_d = (effective_size * 0.5 * 2.0).ceil() as usize;
|
||||
let stamp_center = stamp_d as f32 * 0.5;
|
||||
let d = stamp_d;
|
||||
let center = d as f32 * 0.5;
|
||||
let mut rotated = vec![0u8; d * d];
|
||||
for y in 0..d {
|
||||
for x in 0..d {
|
||||
let px = x as f32 - center;
|
||||
let py = y as f32 - center;
|
||||
let rx = px * cos_a + py * sin_a;
|
||||
let ry = -px * sin_a + py * cos_a;
|
||||
let sx = rx + stamp_center;
|
||||
let sy = ry + stamp_center;
|
||||
let ix = sx.floor() as i32;
|
||||
let iy = sy.floor() as i32;
|
||||
if ix >= 0 && iy >= 0 && ix < stamp_d as i32 - 1 && iy < stamp_d as i32 - 1 {
|
||||
let fx = sx - ix as f32;
|
||||
let fy = sy - iy as f32;
|
||||
let d00 = stamp[iy as usize * stamp_d + ix as usize] as f32;
|
||||
let d10 = stamp[iy as usize * stamp_d + ix as usize + 1] as f32;
|
||||
let d01 = stamp[(iy as usize + 1) * stamp_d + ix as usize] as f32;
|
||||
let d11 = stamp[(iy as usize + 1) * stamp_d + ix as usize + 1] as f32;
|
||||
let v = d00 * (1.0 - fx) * (1.0 - fy)
|
||||
+ d10 * fx * (1.0 - fy)
|
||||
+ d01 * (1.0 - fx) * fy
|
||||
+ d11 * fx * fy;
|
||||
rotated[y * d + x] = v.round() as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
rotated
|
||||
} else {
|
||||
stamp
|
||||
};
|
||||
let stamp_sm = generate_brush_stamp(&tip_sm);
|
||||
let seed = cx.to_bits().wrapping_mul(73_856_093)
|
||||
^ cy.to_bits().wrapping_mul(19_349_663);
|
||||
|
||||
let sparkle_count = if density > 0.0 {
|
||||
(effective_size * 0.08 * density).clamp(2.0, 6.0) as u32
|
||||
} else {
|
||||
((effective_size * 0.05) as u32).clamp(2, 4)
|
||||
};
|
||||
let base_star_size = effective_size * 0.24;
|
||||
let footprint_radius = effective_size * 0.5;
|
||||
let phase = (seed & 0xFFFF) as f32 / 65536.0 * std::f32::consts::TAU;
|
||||
const GOLDEN_ANGLE: f32 = 2.399_963_1;
|
||||
|
||||
for i in 0..sparkle_count {
|
||||
let hash_i = seed.wrapping_add(i.wrapping_mul(668_265_263));
|
||||
let angle = (hash_i & 0xFFFF) as f32 / 65536.0 * std::f32::consts::TAU;
|
||||
let dist = ((hash_i >> 16) & 0xFFFF) as f32 / 65536.0 * effective_size * 0.6;
|
||||
let sx = cx + angle.cos() * dist;
|
||||
let sy = cy + angle.sin() * dist;
|
||||
let spark_size_var = if size_variance > 0.0 {
|
||||
let rv = ((hash_i.wrapping_add(31337) & 0xFFFF) as f32 / 65536.0 - 0.5)
|
||||
* 2.0
|
||||
* size_variance;
|
||||
(1.0 + rv).clamp(0.3, 2.0)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let star_size = base_star_size * spark_size_var;
|
||||
let max_center_radius = (footprint_radius - star_size * 0.5).max(0.0);
|
||||
let radial_fraction = ((i as f32 + 0.5) / sparkle_count as f32).sqrt();
|
||||
let spark_angle = phase + i as f32 * GOLDEN_ANGLE;
|
||||
let dist = radial_fraction * max_center_radius;
|
||||
let sx = cx + spark_angle.cos() * dist;
|
||||
let sy = cy + spark_angle.sin() * dist;
|
||||
|
||||
let spark_color = if color[3] > 0 {
|
||||
let hue_shift = ((hash_i.wrapping_add(7919) & 0xFF) as f32 / 255.0 - 0.5) * 30.0;
|
||||
let [r, g, b, a] = color;
|
||||
let (h, s, l) = rgb_to_hsl(r, g, b);
|
||||
let h = (h + hue_shift).rem_euclid(360.0);
|
||||
let (rr, gg, bb) = hsl_to_rgb(h, s, l);
|
||||
[rr, gg, bb, a]
|
||||
} else {
|
||||
color
|
||||
};
|
||||
|
||||
draw_dab_with_stamp(
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
sx,
|
||||
sy,
|
||||
sparkle_r * 2.0,
|
||||
star_size,
|
||||
hardness,
|
||||
color,
|
||||
opacity * pressure * 0.5,
|
||||
spark_color,
|
||||
opacity * pressure * 0.9,
|
||||
is_eraser,
|
||||
mask,
|
||||
Some(&stamp_sm),
|
||||
Some(&stamp_rotated),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user