diff --git a/hcie-brush-engine/src/lib.rs b/hcie-brush-engine/src/lib.rs index 08d66e0..b3a7990 100644 --- a/hcie-brush-engine/src/lib.rs +++ b/hcie-brush-engine/src/lib.rs @@ -201,26 +201,32 @@ fn generate_square_stamp(d: usize, center: f32, r: f32, hardness: f32) -> Vec Vec { - let points = 5u32; - let inner_r = r * 0.4; + let arm_count = 4u32; + let arm_width = r * 0.22; + let inner_r = r * 0.08; (0..d * d) .map(|i| { let x = (i % d) as f32 - center; let y = (i / d) as f32 - center; let angle = y.atan2(x); let dist = (x * x + y * y).sqrt(); - let point_angle = std::f32::consts::PI * 2.0 / points as f32; + let point_angle = std::f32::consts::PI * 2.0 / arm_count as f32; let a = angle.rem_euclid(point_angle); - let edge = if a < point_angle / 2.0 { - inner_r + (r - inner_r) * (a / (point_angle / 2.0)) - } else { - r - (r - inner_r) * ((a - point_angle / 2.0) / (point_angle / 2.0)) - }; - if dist >= edge { + let arm_center = point_angle / 2.0; + let arm_dist = (a - arm_center).abs(); + let max_arm_dist = arm_width / r; + if dist > r { 0 - } else { - let alpha = 1.0 - (dist / edge).powf(1.0 - hardness.clamp(0.0, 0.99)); + } 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 { + 0 } }) .collect() @@ -1374,6 +1380,11 @@ fn draw_grain_brush( let x1 = (cx + radius).ceil().min(width.saturating_sub(1) as f32) as u32; let y0 = (cy - radius).floor().max(0.0) as u32; let y1 = (cy + radius).ceil().min(height.saturating_sub(1) as f32) as u32; + + let seed_x = (cx.to_bits().wrapping_mul(73_856_093) + ^ cy.to_bits().wrapping_mul(19_349_663)) as u32; + let seed_y = seed_x.wrapping_mul(668_265_263); + for y in y0..=y1 { for x in x0..=x1 { let dx = x as f32 - cx; @@ -1382,16 +1393,22 @@ fn draw_grain_brush( if distance > 1.0 { continue; } + let ox = x.wrapping_add(seed_x); + let oy = y.wrapping_add(seed_y); let hash = - ((x.wrapping_mul(73_856_093) ^ y.wrapping_mul(19_349_663)) & 1023) as f32 / 1023.0; + ((ox.wrapping_mul(73_856_093) ^ oy.wrapping_mul(19_349_663)) & 1023) as f32 / 1023.0; let texture = if woven { - 0.55 + 0.45 * ((x as f32 * 0.42).sin() * (y as f32 * 0.31).cos()).abs() + 0.55 + 0.45 * ((x as f32 * 0.42 + seed_x as f32 * 0.001).sin() + * (y as f32 * 0.31 + seed_y as f32 * 0.001).cos()) + .abs() } else { hash }; - if texture < threshold { - continue; - } + let grain_alpha = if texture < threshold { + (texture / threshold).clamp(0.0, 1.0) + } else { + 0.55 + 0.45 * texture + }; let falloff = if distance <= hardness { 1.0 } else { @@ -1407,7 +1424,7 @@ fn draw_grain_brush( * pressure * falloff * mask_alpha - * (0.55 + 0.45 * texture) + * grain_alpha * (color[3] as f32 / 255.0); let offset = index * 4; if is_eraser { @@ -1627,6 +1644,38 @@ fn draw_star_brush( 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 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); + 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; + draw_dab_with_stamp( + pixels, + width, + height, + sx, + sy, + sparkle_r * 2.0, + hardness, + color, + opacity * pressure * 0.5, + is_eraser, + mask, + Some(&stamp_sm), + ); + } } /// Apply oil paint brush - coherent, directional bristle tracks with pigment pickup. @@ -2964,6 +3013,10 @@ pub fn draw_crayon_brush( let [fr, fg, fb, _] = color; + let seed_x = (cx.to_bits().wrapping_mul(73_856_093) + ^ cy.to_bits().wrapping_mul(19_349_663)) as u32; + let seed_y = seed_x.wrapping_mul(668_265_263); + for py in y_min..=y_max { for px in x_min..=x_max { let dx = px as f32 - cx; @@ -2990,20 +3043,22 @@ pub fn draw_crayon_brush( (1.0 - (d_sq - hard_sq) * soft_range_inv).clamp(0.0, 1.0) }; - // Stable canvas-space paper tooth keeps pores aligned across consecutive dabs. - let noise = ((px as u32).wrapping_mul(73_856_093) - ^ (py as u32).wrapping_mul(19_349_663)) - & 1023; - let grain = noise as f32 / 1023.0; - if grain < 0.18 * (1.0 - pressure) + 0.08 { - continue; - } + let ox = (px as u32).wrapping_add(seed_x); + let oy = (py as u32).wrapping_add(seed_y); + let noise = ox.wrapping_mul(73_856_093) ^ oy.wrapping_mul(19_349_663); + let grain = (noise & 1023) as f32 / 1023.0; + let threshold = 0.18 * (1.0 - pressure) + 0.08; + let grain_alpha = if grain < threshold { + (grain / threshold).clamp(0.0, 1.0) + } else { + 0.35 + 0.65 * grain + }; let brush_alpha = (alpha_factor * opacity * pressure * (color[3] as f32 / 255.0) - * (0.35 + 0.65 * grain) + * grain_alpha * (mask_val as f32 / 255.0) * 255.0) .round() as u8; diff --git a/hcie-egui-app/crates/hcie-gui-egui/src/app/tools/brushes_panel.rs b/hcie-egui-app/crates/hcie-gui-egui/src/app/tools/brushes_panel.rs index 432807b..d0ad76b 100644 --- a/hcie-egui-app/crates/hcie-gui-egui/src/app/tools/brushes_panel.rs +++ b/hcie-egui-app/crates/hcie-gui-egui/src/app/tools/brushes_panel.rs @@ -1718,7 +1718,7 @@ pub fn show_brushes_ui(app: &mut HcieApp, ui: &mut egui::Ui) { (hcie_engine_api::tools::BrushStyle::Marker, "Marker"), (hcie_engine_api::tools::BrushStyle::Sketch, "Sketch"), (hcie_engine_api::tools::BrushStyle::Hatch, "Hatch"), - (hcie_engine_api::tools::BrushStyle::Star, "Stipple Dots"), + (hcie_engine_api::tools::BrushStyle::Star, "Star Sparkle"), (hcie_engine_api::tools::BrushStyle::Glow, "Glow"), (hcie_engine_api::tools::BrushStyle::Airbrush, "Airbrush"), (hcie_engine_api::tools::BrushStyle::Pencil, "Pencil"),