add brush library
This commit is contained in:
parent
63a8080e60
commit
292328bf87
|
|
@ -15,17 +15,12 @@
|
|||
//! The GPU compute shader (`brush_dab.wgsl`) is the authoritative implementation.
|
||||
//!
|
||||
//! ### Dab placement
|
||||
//! Dabs are placed along the stroke polyline at intervals of
|
||||
//! `spacing = radius * dabs_per_radius`. Fractional remainder is tracked across
|
||||
//! consecutive calls via `StrokeState`.
|
||||
//! Spacing = 1 / max(dabs_per_basic_radius/radius, dabs_per_actual_radius/actual_radius).
|
||||
//! Fractional remainder is tracked across consecutive calls via `StrokeState`.
|
||||
//!
|
||||
//! ### Blending
|
||||
//! Normal mode uses the standard "over" operator on premultiplied RGBA:
|
||||
//! ```text
|
||||
//! result_a = opa_a + (1 - opa_a) * bottom_a
|
||||
//! result_rgb = opa_a * top_rgb + (1 - opa_a) * bottom_rgb
|
||||
//! ```
|
||||
//! Erase mode: subtract `opa_a` from the destination alpha and premultiply.
|
||||
//! Normal mode uses the standard "over" operator on premultiplied RGBA.
|
||||
//! Erase mode subtracts from destination alpha.
|
||||
|
||||
use image::RgbaImage;
|
||||
use crate::raster_layer::{RasterBlendMode, StrokeRecord};
|
||||
|
|
@ -65,20 +60,46 @@ pub struct GpuDab {
|
|||
|
||||
/// Blend mode: 0 = Normal, 1 = Erase, 2 = Smudge
|
||||
pub blend_mode: u32,
|
||||
pub _pad0: u32,
|
||||
pub _pad1: u32,
|
||||
pub _pad2: u32,
|
||||
/// Elliptical dab aspect ratio (1.0 = circle)
|
||||
pub elliptical_dab_ratio: f32,
|
||||
/// Elliptical dab rotation angle in radians
|
||||
pub elliptical_dab_angle: f32,
|
||||
/// Lock alpha: 0.0 = modify alpha normally, 1.0 = don't modify destination alpha
|
||||
pub lock_alpha: f32,
|
||||
}
|
||||
|
||||
/// Transient brush stroke state (tracks partial dab position between segments)
|
||||
/// Transient brush stroke state (tracks position and randomness between segments)
|
||||
pub struct StrokeState {
|
||||
/// Distance along the path already "consumed" toward the next dab (in pixels)
|
||||
pub distance_since_last_dab: f32,
|
||||
/// Exponentially-smoothed cursor X for slow_tracking
|
||||
pub smooth_x: f32,
|
||||
/// Exponentially-smoothed cursor Y for slow_tracking
|
||||
pub smooth_y: f32,
|
||||
/// Whether smooth_x/y have been initialised yet
|
||||
pub smooth_initialized: bool,
|
||||
/// xorshift32 seed for jitter and radius variation
|
||||
pub rng_seed: u32,
|
||||
/// Accumulated per-dab hue shift
|
||||
pub color_h_phase: f32,
|
||||
/// Accumulated per-dab value shift
|
||||
pub color_v_phase: f32,
|
||||
/// Accumulated per-dab saturation shift
|
||||
pub color_s_phase: f32,
|
||||
}
|
||||
|
||||
impl StrokeState {
|
||||
pub fn new() -> Self {
|
||||
Self { distance_since_last_dab: 0.0 }
|
||||
Self {
|
||||
distance_since_last_dab: 0.0,
|
||||
smooth_x: 0.0,
|
||||
smooth_y: 0.0,
|
||||
smooth_initialized: false,
|
||||
rng_seed: 0xDEAD_BEEF,
|
||||
color_h_phase: 0.0,
|
||||
color_v_phase: 0.0,
|
||||
color_s_phase: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,37 +107,96 @@ impl Default for StrokeState {
|
|||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// xorshift32 — fast, no-alloc PRNG. Returns a value in [0, 1).
|
||||
#[inline]
|
||||
fn xorshift(seed: &mut u32) -> f32 {
|
||||
let mut s = *seed;
|
||||
s ^= s << 13;
|
||||
s ^= s >> 17;
|
||||
s ^= s << 5;
|
||||
*seed = s;
|
||||
(s as f32) / (u32::MAX as f32)
|
||||
}
|
||||
|
||||
/// Convert linear RGB (premultiplied, alpha already separated) to HSV.
|
||||
/// Input: r, g, b in [0, 1] (not premultiplied; caller divides by alpha first).
|
||||
fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let delta = max - min;
|
||||
let v = max;
|
||||
let s = if max > 1e-6 { delta / max } else { 0.0 };
|
||||
let h = if delta < 1e-6 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
((g - b) / delta).rem_euclid(6.0) / 6.0
|
||||
} else if max == g {
|
||||
((b - r) / delta + 2.0) / 6.0
|
||||
} else {
|
||||
((r - g) / delta + 4.0) / 6.0
|
||||
};
|
||||
(h, s, v)
|
||||
}
|
||||
|
||||
/// Convert HSV to linear RGB.
|
||||
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
|
||||
let h6 = h.rem_euclid(1.0) * 6.0;
|
||||
let i = h6.floor() as i32;
|
||||
let f = h6 - i as f32;
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - s * f);
|
||||
let t = v * (1.0 - s * (1.0 - f));
|
||||
match i % 6 {
|
||||
0 => (v, t, p),
|
||||
1 => (q, v, p),
|
||||
2 => (p, v, t),
|
||||
3 => (p, q, v),
|
||||
4 => (t, p, v),
|
||||
_ => (v, p, q),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure-Rust MyPaint-style Gaussian dab brush engine
|
||||
pub struct BrushEngine;
|
||||
|
||||
impl BrushEngine {
|
||||
/// Compute the list of GPU dabs for a stroke segment.
|
||||
///
|
||||
/// Uses the same dab-spacing logic as [`apply_stroke_with_state`] but produces
|
||||
/// [`GpuDab`] structs for upload to the GPU compute pipeline instead of painting
|
||||
/// into a pixel buffer.
|
||||
/// Uses the MyPaint dab-spacing formula and produces [`GpuDab`] structs for
|
||||
/// upload to the GPU compute pipeline.
|
||||
///
|
||||
/// Also returns the union bounding box of all dabs as `(x0, y0, x1, y1)` in
|
||||
/// integer canvas pixel coordinates (clamped to non-negative values; `x0==i32::MAX`
|
||||
/// when the returned Vec is empty).
|
||||
/// integer canvas pixel coordinates (`x0==i32::MAX` when the Vec is empty).
|
||||
pub fn compute_dabs(
|
||||
stroke: &StrokeRecord,
|
||||
state: &mut StrokeState,
|
||||
) -> (Vec<GpuDab>, (i32, i32, i32, i32)) {
|
||||
let mut dabs: Vec<GpuDab> = Vec::new();
|
||||
let mut bbox = (i32::MAX, i32::MAX, i32::MIN, i32::MIN);
|
||||
let bs = &stroke.brush_settings;
|
||||
|
||||
let blend_mode_u = match stroke.blend_mode {
|
||||
// Determine blend mode, allowing brush settings to override Normal
|
||||
let base_blend = match stroke.blend_mode {
|
||||
RasterBlendMode::Normal if bs.eraser > 0.5 => RasterBlendMode::Erase,
|
||||
RasterBlendMode::Normal if bs.smudge > 0.5 => RasterBlendMode::Smudge,
|
||||
other => other,
|
||||
};
|
||||
let blend_mode_u = match base_blend {
|
||||
RasterBlendMode::Normal => 0u32,
|
||||
RasterBlendMode::Erase => 1u32,
|
||||
RasterBlendMode::Smudge => 2u32,
|
||||
};
|
||||
|
||||
let push_dab = |dabs: &mut Vec<GpuDab>,
|
||||
bbox: &mut (i32, i32, i32, i32),
|
||||
x: f32, y: f32,
|
||||
radius: f32, opacity: f32,
|
||||
ndx: f32, ndy: f32, smudge_dist: f32| {
|
||||
bbox: &mut (i32, i32, i32, i32),
|
||||
x: f32, y: f32,
|
||||
radius: f32, opacity: f32,
|
||||
cr: f32, cg: f32, cb: f32,
|
||||
ndx: f32, ndy: f32, smudge_dist: f32| {
|
||||
let r_fringe = radius + 1.0;
|
||||
bbox.0 = bbox.0.min((x - r_fringe).floor() as i32);
|
||||
bbox.1 = bbox.1.min((y - r_fringe).floor() as i32);
|
||||
|
|
@ -124,26 +204,33 @@ impl BrushEngine {
|
|||
bbox.3 = bbox.3.max((y + r_fringe).ceil() as i32);
|
||||
dabs.push(GpuDab {
|
||||
x, y, radius,
|
||||
hardness: stroke.brush_settings.hardness,
|
||||
hardness: bs.hardness,
|
||||
opacity,
|
||||
color_r: stroke.color[0],
|
||||
color_g: stroke.color[1],
|
||||
color_b: stroke.color[2],
|
||||
color_r: cr,
|
||||
color_g: cg,
|
||||
color_b: cb,
|
||||
color_a: stroke.color[3],
|
||||
ndx, ndy, smudge_dist,
|
||||
blend_mode: blend_mode_u,
|
||||
_pad0: 0, _pad1: 0, _pad2: 0,
|
||||
elliptical_dab_ratio: bs.elliptical_dab_ratio.max(1.0),
|
||||
elliptical_dab_angle: bs.elliptical_dab_angle.to_radians(),
|
||||
lock_alpha: bs.lock_alpha,
|
||||
});
|
||||
};
|
||||
|
||||
if stroke.points.len() < 2 {
|
||||
if let Some(pt) = stroke.points.first() {
|
||||
let r = stroke.brush_settings.radius_at_pressure(pt.pressure);
|
||||
let raw_o = stroke.brush_settings.opacity_at_pressure(pt.pressure);
|
||||
let o = 1.0 - (1.0 - raw_o).powf(stroke.brush_settings.dabs_per_radius * 0.5);
|
||||
// Single-tap smudge has no direction — skip (same as CPU engine)
|
||||
if !matches!(stroke.blend_mode, RasterBlendMode::Smudge) {
|
||||
push_dab(&mut dabs, &mut bbox, pt.x, pt.y, r, o, 0.0, 0.0, 0.0);
|
||||
let r = bs.radius_at_pressure(pt.pressure);
|
||||
// Default dpr for a single tap: prefer actual_radius spacing
|
||||
let dpr = if bs.dabs_per_radius > 0.0 { bs.dabs_per_radius }
|
||||
else { bs.dabs_per_actual_radius.max(0.01) };
|
||||
let raw_o = bs.opacity_at_pressure(pt.pressure);
|
||||
let o = (1.0 - (1.0 - raw_o).powf(dpr * 0.5)
|
||||
* (1.0 + bs.opaque_multiply)).clamp(0.0, 1.0);
|
||||
if !matches!(base_blend, RasterBlendMode::Smudge) {
|
||||
let (cr, cg, cb) = (stroke.color[0], stroke.color[1], stroke.color[2]);
|
||||
push_dab(&mut dabs, &mut bbox, pt.x, pt.y, r, o, cr, cg, cb,
|
||||
0.0, 0.0, 0.0);
|
||||
}
|
||||
state.distance_since_last_dab = 0.0;
|
||||
}
|
||||
|
|
@ -162,8 +249,18 @@ impl BrushEngine {
|
|||
let mut t = 0.0f32;
|
||||
while t < 1.0 {
|
||||
let pressure = p0.pressure + t * (p1.pressure - p0.pressure);
|
||||
let radius = stroke.brush_settings.radius_at_pressure(pressure);
|
||||
let spacing = (radius * stroke.brush_settings.dabs_per_radius).max(0.5);
|
||||
let radius2 = bs.radius_at_pressure(pressure);
|
||||
|
||||
// Spacing: densest wins between basic-radius and actual-radius methods.
|
||||
// dabs_per_basic_radius = N dabs per basic_radius pixels → spacing = basic_r / N
|
||||
// dabs_per_actual_radius = N dabs per actual_radius pixels → spacing = actual_r / N
|
||||
let spacing_basic = if bs.dabs_per_radius > 0.0 {
|
||||
radius2 / bs.dabs_per_radius
|
||||
} else { f32::MAX };
|
||||
let spacing_actual = if bs.dabs_per_actual_radius > 0.0 {
|
||||
radius2 / bs.dabs_per_actual_radius
|
||||
} else { f32::MAX };
|
||||
let spacing = spacing_basic.min(spacing_actual).max(0.5);
|
||||
|
||||
let dist_to_next = spacing - state.distance_since_last_dab;
|
||||
let seg_t_to_next = (dist_to_next / seg_len).max(0.0);
|
||||
|
|
@ -174,27 +271,87 @@ impl BrushEngine {
|
|||
}
|
||||
|
||||
t += seg_t_to_next;
|
||||
let pressure2 = p0.pressure + t * (p1.pressure - p0.pressure);
|
||||
|
||||
// Stroke threshold gating
|
||||
if pressure2 < bs.stroke_threshold {
|
||||
state.distance_since_last_dab = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut radius2 = bs.radius_at_pressure(pressure2);
|
||||
|
||||
// Opacity — normalised so dense dabs don't saturate faster than sparse ones
|
||||
let dpr = radius2 / spacing; // effective dabs per radius
|
||||
let raw_opacity = bs.opacity_at_pressure(pressure2);
|
||||
let mut opacity2 = 1.0 - (1.0 - raw_opacity).powf(dpr * 0.5);
|
||||
opacity2 = (opacity2 * (1.0 + bs.opaque_multiply)).clamp(0.0, 1.0);
|
||||
|
||||
// Slow tracking: exponential position smoothing
|
||||
let x2 = p0.x + t * dx;
|
||||
let y2 = p0.y + t * dy;
|
||||
let pressure2 = p0.pressure + t * (p1.pressure - p0.pressure);
|
||||
let radius2 = stroke.brush_settings.radius_at_pressure(pressure2);
|
||||
let raw_opacity = stroke.brush_settings.opacity_at_pressure(pressure2);
|
||||
// Normalize per-dab opacity so dense dabs don't saturate faster than sparse ones.
|
||||
// Formula: per_dab = 1 − (1 − raw)^(dabs_per_radius / 2)
|
||||
// Derivation: N = 2/dabs_per_radius dabs cover one full diameter at the centre;
|
||||
// accumulated = 1 − (1 − per_dab)^N = raw → per_dab = 1 − (1−raw)^(dabs_per_radius/2)
|
||||
let opacity2 = 1.0 - (1.0 - raw_opacity).powf(stroke.brush_settings.dabs_per_radius * 0.5);
|
||||
if !state.smooth_initialized {
|
||||
state.smooth_x = x2; state.smooth_y = y2;
|
||||
state.smooth_initialized = true;
|
||||
}
|
||||
let k = if bs.slow_tracking > 0.0 {
|
||||
(-spacing / bs.slow_tracking.max(0.1)).exp()
|
||||
} else { 0.0 };
|
||||
state.smooth_x = state.smooth_x * k + x2 * (1.0 - k);
|
||||
state.smooth_y = state.smooth_y * k + y2 * (1.0 - k);
|
||||
let mut ex = state.smooth_x;
|
||||
let mut ey = state.smooth_y;
|
||||
|
||||
if matches!(stroke.blend_mode, RasterBlendMode::Smudge) {
|
||||
// Radius jitter (log-scale)
|
||||
if bs.radius_by_random != 0.0 {
|
||||
let r_rng = xorshift(&mut state.rng_seed) * 2.0 - 1.0;
|
||||
radius2 = (radius2 * (bs.radius_by_random * r_rng).exp()).clamp(0.5, 500.0);
|
||||
}
|
||||
|
||||
// Position jitter + fixed offset
|
||||
if bs.offset_by_random != 0.0 || bs.offset_x != 0.0 || bs.offset_y != 0.0 {
|
||||
let jitter = bs.offset_by_random * radius2;
|
||||
ex += (xorshift(&mut state.rng_seed) * 2.0 - 1.0) * jitter
|
||||
+ bs.offset_x * radius2;
|
||||
ey += (xorshift(&mut state.rng_seed) * 2.0 - 1.0) * jitter
|
||||
+ bs.offset_y * radius2;
|
||||
}
|
||||
|
||||
// Per-dab color phase shifts
|
||||
state.color_h_phase += bs.change_color_h;
|
||||
state.color_v_phase += bs.change_color_v;
|
||||
state.color_s_phase += bs.change_color_hsv_s;
|
||||
|
||||
let (mut cr, mut cg, mut cb) = (
|
||||
stroke.color[0], stroke.color[1], stroke.color[2],
|
||||
);
|
||||
let ca = stroke.color[3];
|
||||
if ca > 1e-6 {
|
||||
// un-premultiply for HSV conversion
|
||||
let (ur, ug, ub) = (cr / ca, cg / ca, cb / ca);
|
||||
let (mut h, mut s, mut v) = rgb_to_hsv(ur, ug, ub);
|
||||
if bs.change_color_h != 0.0 || bs.change_color_v != 0.0
|
||||
|| bs.change_color_hsv_s != 0.0 {
|
||||
h = (h + state.color_h_phase).rem_euclid(1.0);
|
||||
v = (v + state.color_v_phase).clamp(0.0, 1.0);
|
||||
s = (s + state.color_s_phase).clamp(0.0, 1.0);
|
||||
let (r2, g2, b2) = hsv_to_rgb(h, s, v);
|
||||
cr = r2 * ca; cg = g2 * ca; cb = b2 * ca;
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(base_blend, RasterBlendMode::Smudge) {
|
||||
let ndx = dx / seg_len;
|
||||
let ndy = dy / seg_len;
|
||||
let smudge_dist =
|
||||
(radius2 * stroke.brush_settings.dabs_per_radius).max(1.0);
|
||||
(radius2 * dpr).max(1.0) * bs.smudge_radius_log.exp();
|
||||
push_dab(&mut dabs, &mut bbox,
|
||||
x2, y2, radius2, opacity2, ndx, ndy, smudge_dist);
|
||||
ex, ey, radius2, opacity2, cr, cg, cb,
|
||||
ndx, ndy, smudge_dist);
|
||||
} else {
|
||||
push_dab(&mut dabs, &mut bbox,
|
||||
x2, y2, radius2, opacity2, 0.0, 0.0, 0.0);
|
||||
ex, ey, radius2, opacity2, cr, cg, cb,
|
||||
0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
state.distance_since_last_dab = 0.0;
|
||||
|
|
|
|||
|
|
@ -1,63 +1,215 @@
|
|||
//! Brush settings for the raster paint engine
|
||||
//!
|
||||
//! Settings that describe the appearance and behavior of a paint brush.
|
||||
//! Compatible with MyPaint .myb brush file format (subset).
|
||||
//! Compatible with MyPaint .myb brush file format.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Settings for a paint brush
|
||||
/// Settings for a paint brush — mirrors the MyPaint .myb settings schema.
|
||||
///
|
||||
/// All fields correspond directly to MyPaint JSON keys. Fields marked
|
||||
/// "parse-only" are stored so that .myb files round-trip cleanly; they will
|
||||
/// be used when the dynamic-input system is wired up in a future task.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BrushSettings {
|
||||
// ── Core shape ──────────────────────────────────────────────────────────
|
||||
/// log(radius) base value; actual radius = exp(radius_log)
|
||||
pub radius_log: f32,
|
||||
/// Edge hardness 0.0 (fully soft/gaussian) to 1.0 (hard edge)
|
||||
pub hardness: f32,
|
||||
/// Base opacity 0.0–1.0
|
||||
pub opaque: f32,
|
||||
/// Dab spacing as fraction of radius (smaller = denser strokes)
|
||||
/// Additional opacity multiplier (opaque_multiply)
|
||||
pub opaque_multiply: f32,
|
||||
/// Dabs per basic_radius distance (MyPaint: dabs_per_basic_radius)
|
||||
pub dabs_per_radius: f32,
|
||||
/// Dabs per actual (pressure-modified) radius distance
|
||||
pub dabs_per_actual_radius: f32,
|
||||
|
||||
// ── Elliptical dab ──────────────────────────────────────────────────────
|
||||
/// Dab aspect ratio ≥ 1.0 (1.0 = circle, 3.0 = 3:1 ellipse)
|
||||
pub elliptical_dab_ratio: f32,
|
||||
/// Elliptical dab rotation angle in degrees (0–180)
|
||||
pub elliptical_dab_angle: f32,
|
||||
|
||||
// ── Jitter / offset ─────────────────────────────────────────────────────
|
||||
/// Random radius variation (log-scale, 0 = none)
|
||||
pub radius_by_random: f32,
|
||||
/// Random positional jitter in units of radius
|
||||
pub offset_by_random: f32,
|
||||
/// Fixed X offset in units of radius
|
||||
pub offset_x: f32,
|
||||
/// Fixed Y offset in units of radius
|
||||
pub offset_y: f32,
|
||||
|
||||
// ── Position tracking ───────────────────────────────────────────────────
|
||||
/// Slow position tracking — higher = brush lags behind cursor more
|
||||
pub slow_tracking: f32,
|
||||
/// Per-dab position tracking smoothing
|
||||
pub slow_tracking_per_dab: f32,
|
||||
|
||||
// ── Color ───────────────────────────────────────────────────────────────
|
||||
/// HSV hue (0.0–1.0); usually overridden by stroke color
|
||||
pub color_h: f32,
|
||||
/// HSV saturation (0.0–1.0)
|
||||
pub color_s: f32,
|
||||
/// HSV value (0.0–1.0)
|
||||
pub color_v: f32,
|
||||
/// Per-dab hue shift (accumulates over the stroke)
|
||||
pub change_color_h: f32,
|
||||
/// Per-dab HSV value shift
|
||||
pub change_color_v: f32,
|
||||
/// Per-dab HSV saturation shift
|
||||
pub change_color_hsv_s: f32,
|
||||
/// Per-dab HSL lightness shift
|
||||
pub change_color_l: f32,
|
||||
/// Per-dab HSL saturation shift
|
||||
pub change_color_hsl_s: f32,
|
||||
|
||||
// ── Blend ───────────────────────────────────────────────────────────────
|
||||
/// Lock alpha channel (0 = off, 1 = on — don't modify destination alpha)
|
||||
pub lock_alpha: f32,
|
||||
/// Eraser strength (>0.5 activates erase blend when tool mode is Normal)
|
||||
pub eraser: f32,
|
||||
|
||||
// ── Smudge ──────────────────────────────────────────────────────────────
|
||||
/// Smudge amount (>0.5 activates smudge blend when tool mode is Normal)
|
||||
pub smudge: f32,
|
||||
/// How quickly the smudge color updates (0 = instant, 1 = slow)
|
||||
pub smudge_length: f32,
|
||||
/// Smudge pickup radius offset (log-scale added to radius_log)
|
||||
pub smudge_radius_log: f32,
|
||||
|
||||
// ── Stroke gating ───────────────────────────────────────────────────────
|
||||
/// Minimum pressure required to emit dabs (0 = always emit)
|
||||
pub stroke_threshold: f32,
|
||||
|
||||
// ── Pressure dynamics ───────────────────────────────────────────────────
|
||||
/// How much pressure increases/decreases radius
|
||||
/// Final radius = exp(radius_log + pressure_radius_gain * pressure)
|
||||
pub pressure_radius_gain: f32,
|
||||
/// How much pressure increases/decreases opacity
|
||||
/// Final opacity = opaque * (1 + pressure_opacity_gain * (pressure - 0.5))
|
||||
pub pressure_opacity_gain: f32,
|
||||
|
||||
// ── Parse-only: future input curve system ───────────────────────────────
|
||||
pub opaque_linearize: f32,
|
||||
pub anti_aliasing: f32,
|
||||
pub dabs_per_second: f32,
|
||||
pub offset_by_speed: f32,
|
||||
pub offset_by_speed_slowness: f32,
|
||||
pub speed1_slowness: f32,
|
||||
pub speed2_slowness: f32,
|
||||
pub speed1_gamma: f32,
|
||||
pub speed2_gamma: f32,
|
||||
pub direction_filter: f32,
|
||||
pub stroke_duration_log: f32,
|
||||
pub stroke_holdtime: f32,
|
||||
pub pressure_gain_log: f32,
|
||||
pub smudge_transparency: f32,
|
||||
pub smudge_length_log: f32,
|
||||
pub smudge_bucket: f32,
|
||||
pub paint_mode: f32,
|
||||
pub colorize: f32,
|
||||
pub posterize: f32,
|
||||
pub posterize_num: f32,
|
||||
pub snap_to_pixel: f32,
|
||||
pub custom_input: f32,
|
||||
pub custom_input_slowness: f32,
|
||||
pub gridmap_scale: f32,
|
||||
pub gridmap_scale_x: f32,
|
||||
pub gridmap_scale_y: f32,
|
||||
pub restore_color: f32,
|
||||
pub offset_angle: f32,
|
||||
pub offset_angle_asc: f32,
|
||||
pub offset_angle_view: f32,
|
||||
pub offset_angle_2: f32,
|
||||
pub offset_angle_2_asc: f32,
|
||||
pub offset_angle_2_view: f32,
|
||||
pub offset_angle_adj: f32,
|
||||
pub offset_multiplier: f32,
|
||||
}
|
||||
|
||||
impl BrushSettings {
|
||||
/// Default soft round brush (smooth Gaussian falloff)
|
||||
pub fn default_round_soft() -> Self {
|
||||
Self {
|
||||
radius_log: 2.0, // radius ≈ 7.4 px
|
||||
radius_log: 2.0,
|
||||
hardness: 0.1,
|
||||
opaque: 0.8,
|
||||
dabs_per_radius: 0.25,
|
||||
opaque_multiply: 0.0,
|
||||
dabs_per_radius: 2.0,
|
||||
dabs_per_actual_radius: 2.0,
|
||||
elliptical_dab_ratio: 1.0,
|
||||
elliptical_dab_angle: 90.0,
|
||||
radius_by_random: 0.0,
|
||||
offset_by_random: 0.0,
|
||||
offset_x: 0.0,
|
||||
offset_y: 0.0,
|
||||
slow_tracking: 0.0,
|
||||
slow_tracking_per_dab: 0.0,
|
||||
color_h: 0.0,
|
||||
color_s: 0.0,
|
||||
color_v: 0.0,
|
||||
change_color_h: 0.0,
|
||||
change_color_v: 0.0,
|
||||
change_color_hsv_s: 0.0,
|
||||
change_color_l: 0.0,
|
||||
change_color_hsl_s: 0.0,
|
||||
lock_alpha: 0.0,
|
||||
eraser: 0.0,
|
||||
smudge: 0.0,
|
||||
smudge_length: 0.5,
|
||||
smudge_radius_log: 0.0,
|
||||
stroke_threshold: 0.0,
|
||||
pressure_radius_gain: 0.5,
|
||||
pressure_opacity_gain: 1.0,
|
||||
opaque_linearize: 0.9,
|
||||
anti_aliasing: 1.0,
|
||||
dabs_per_second: 0.0,
|
||||
offset_by_speed: 0.0,
|
||||
offset_by_speed_slowness: 1.0,
|
||||
speed1_slowness: 0.04,
|
||||
speed2_slowness: 0.8,
|
||||
speed1_gamma: 4.0,
|
||||
speed2_gamma: 4.0,
|
||||
direction_filter: 2.0,
|
||||
stroke_duration_log: 4.0,
|
||||
stroke_holdtime: 0.0,
|
||||
pressure_gain_log: 0.0,
|
||||
smudge_transparency: 0.0,
|
||||
smudge_length_log: 0.0,
|
||||
smudge_bucket: 0.0,
|
||||
paint_mode: 1.0,
|
||||
colorize: 0.0,
|
||||
posterize: 0.0,
|
||||
posterize_num: 0.05,
|
||||
snap_to_pixel: 0.0,
|
||||
custom_input: 0.0,
|
||||
custom_input_slowness: 0.0,
|
||||
gridmap_scale: 0.0,
|
||||
gridmap_scale_x: 1.0,
|
||||
gridmap_scale_y: 1.0,
|
||||
restore_color: 0.0,
|
||||
offset_angle: 0.0,
|
||||
offset_angle_asc: 0.0,
|
||||
offset_angle_view: 0.0,
|
||||
offset_angle_2: 0.0,
|
||||
offset_angle_2_asc: 0.0,
|
||||
offset_angle_2_view: 0.0,
|
||||
offset_angle_adj: 0.0,
|
||||
offset_multiplier: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default hard round brush (sharp edge)
|
||||
pub fn default_round_hard() -> Self {
|
||||
Self {
|
||||
radius_log: 2.0,
|
||||
hardness: 0.9,
|
||||
opaque: 1.0,
|
||||
dabs_per_radius: 0.2,
|
||||
color_h: 0.0,
|
||||
color_s: 0.0,
|
||||
color_v: 0.0,
|
||||
dabs_per_radius: 2.0,
|
||||
pressure_radius_gain: 0.3,
|
||||
pressure_opacity_gain: 0.8,
|
||||
..Self::default_round_soft()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,10 +225,10 @@ impl BrushSettings {
|
|||
o.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
/// Parse a MyPaint .myb JSON brush file (subset).
|
||||
/// Parse a MyPaint .myb JSON brush file.
|
||||
///
|
||||
/// Reads `radius_logarithmic`, `hardness`, `opaque`, `dabs_per_basic_radius`,
|
||||
/// `color_h`, `color_s`, `color_v` from the `settings` key's `base_value` fields.
|
||||
/// Reads all known settings from `settings[key].base_value`.
|
||||
/// Unknown keys are silently ignored for forward compatibility.
|
||||
pub fn from_myb(json: &str) -> Result<Self, String> {
|
||||
let v: serde_json::Value =
|
||||
serde_json::from_str(json).map_err(|e| format!("JSON parse error: {e}"))?;
|
||||
|
|
@ -92,15 +244,13 @@ impl BrushSettings {
|
|||
.unwrap_or(default)
|
||||
};
|
||||
|
||||
// Pressure dynamics: read from the "inputs" mapping of radius/opacity
|
||||
// For simplicity, look for the pressure input point in radius_logarithmic
|
||||
// Pressure dynamics: approximate from the pressure input curve endpoints
|
||||
let pressure_radius_gain = settings
|
||||
.get("radius_logarithmic")
|
||||
.and_then(|s| s.get("inputs"))
|
||||
.and_then(|inp| inp.get("pressure"))
|
||||
.and_then(|pts| pts.as_array())
|
||||
.and_then(|arr| {
|
||||
// arr = [[x0,y0],[x1,y1],...] – approximate as linear gain at x=1.0
|
||||
if arr.len() >= 2 {
|
||||
let y0 = arr[0].get(1)?.as_f64()? as f32;
|
||||
let y1 = arr[arr.len() - 1].get(1)?.as_f64()? as f32;
|
||||
|
|
@ -128,15 +278,81 @@ impl BrushSettings {
|
|||
.unwrap_or(1.0);
|
||||
|
||||
Ok(Self {
|
||||
radius_log: read_base("radius_logarithmic", 2.0),
|
||||
hardness: read_base("hardness", 0.5).clamp(0.0, 1.0),
|
||||
opaque: read_base("opaque", 1.0).clamp(0.0, 1.0),
|
||||
dabs_per_radius: read_base("dabs_per_basic_radius", 0.25).clamp(0.01, 10.0),
|
||||
color_h: read_base("color_h", 0.0),
|
||||
color_s: read_base("color_s", 0.0),
|
||||
color_v: read_base("color_v", 0.0),
|
||||
// Core shape
|
||||
radius_log: read_base("radius_logarithmic", 2.0),
|
||||
hardness: read_base("hardness", 0.8).clamp(0.0, 1.0),
|
||||
opaque: read_base("opaque", 1.0).clamp(0.0, 2.0),
|
||||
opaque_multiply: read_base("opaque_multiply", 0.0),
|
||||
dabs_per_radius: read_base("dabs_per_basic_radius", 0.0).max(0.0),
|
||||
dabs_per_actual_radius: read_base("dabs_per_actual_radius", 2.0).max(0.0),
|
||||
// Elliptical dab
|
||||
elliptical_dab_ratio: read_base("elliptical_dab_ratio", 1.0).max(1.0),
|
||||
elliptical_dab_angle: read_base("elliptical_dab_angle", 90.0),
|
||||
// Jitter / offset
|
||||
radius_by_random: read_base("radius_by_random", 0.0),
|
||||
offset_by_random: read_base("offset_by_random", 0.0),
|
||||
offset_x: read_base("offset_x", 0.0),
|
||||
offset_y: read_base("offset_y", 0.0),
|
||||
// Tracking
|
||||
slow_tracking: read_base("slow_tracking", 0.0),
|
||||
slow_tracking_per_dab: read_base("slow_tracking_per_dab", 0.0),
|
||||
// Color
|
||||
color_h: read_base("color_h", 0.0),
|
||||
color_s: read_base("color_s", 0.0),
|
||||
color_v: read_base("color_v", 0.0),
|
||||
change_color_h: read_base("change_color_h", 0.0),
|
||||
change_color_v: read_base("change_color_v", 0.0),
|
||||
change_color_hsv_s: read_base("change_color_hsv_s", 0.0),
|
||||
change_color_l: read_base("change_color_l", 0.0),
|
||||
change_color_hsl_s: read_base("change_color_hsl_s", 0.0),
|
||||
// Blend
|
||||
lock_alpha: read_base("lock_alpha", 0.0).clamp(0.0, 1.0),
|
||||
eraser: read_base("eraser", 0.0).clamp(0.0, 1.0),
|
||||
// Smudge
|
||||
smudge: read_base("smudge", 0.0).clamp(0.0, 1.0),
|
||||
smudge_length: read_base("smudge_length", 0.5).clamp(0.0, 1.0),
|
||||
smudge_radius_log: read_base("smudge_radius_log", 0.0),
|
||||
// Stroke gating
|
||||
stroke_threshold: read_base("stroke_threshold", 0.0).clamp(0.0, 0.5),
|
||||
// Pressure dynamics
|
||||
pressure_radius_gain,
|
||||
pressure_opacity_gain,
|
||||
// Parse-only
|
||||
opaque_linearize: read_base("opaque_linearize", 0.9),
|
||||
anti_aliasing: read_base("anti_aliasing", 1.0),
|
||||
dabs_per_second: read_base("dabs_per_second", 0.0),
|
||||
offset_by_speed: read_base("offset_by_speed", 0.0),
|
||||
offset_by_speed_slowness: read_base("offset_by_speed_slowness", 1.0),
|
||||
speed1_slowness: read_base("speed1_slowness", 0.04),
|
||||
speed2_slowness: read_base("speed2_slowness", 0.8),
|
||||
speed1_gamma: read_base("speed1_gamma", 4.0),
|
||||
speed2_gamma: read_base("speed2_gamma", 4.0),
|
||||
direction_filter: read_base("direction_filter", 2.0),
|
||||
stroke_duration_log: read_base("stroke_duration_logarithmic", 4.0),
|
||||
stroke_holdtime: read_base("stroke_holdtime", 0.0),
|
||||
pressure_gain_log: read_base("pressure_gain_log", 0.0),
|
||||
smudge_transparency: read_base("smudge_transparency", 0.0),
|
||||
smudge_length_log: read_base("smudge_length_log", 0.0),
|
||||
smudge_bucket: read_base("smudge_bucket", 0.0),
|
||||
paint_mode: read_base("paint_mode", 1.0),
|
||||
colorize: read_base("colorize", 0.0),
|
||||
posterize: read_base("posterize", 0.0),
|
||||
posterize_num: read_base("posterize_num", 0.05),
|
||||
snap_to_pixel: read_base("snap_to_pixel", 0.0),
|
||||
custom_input: read_base("custom_input", 0.0),
|
||||
custom_input_slowness: read_base("custom_input_slowness", 0.0),
|
||||
gridmap_scale: read_base("gridmap_scale", 0.0),
|
||||
gridmap_scale_x: read_base("gridmap_scale_x", 1.0),
|
||||
gridmap_scale_y: read_base("gridmap_scale_y", 1.0),
|
||||
restore_color: read_base("restore_color", 0.0),
|
||||
offset_angle: read_base("offset_angle", 0.0),
|
||||
offset_angle_asc: read_base("offset_angle_asc", 0.0),
|
||||
offset_angle_view: read_base("offset_angle_view", 0.0),
|
||||
offset_angle_2: read_base("offset_angle_2", 0.0),
|
||||
offset_angle_2_asc: read_base("offset_angle_2_asc", 0.0),
|
||||
offset_angle_2_view: read_base("offset_angle_2_view", 0.0),
|
||||
offset_angle_adj: read_base("offset_angle_adj", 0.0),
|
||||
offset_multiplier: read_base("offset_multiplier", 0.0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -146,3 +362,41 @@ impl Default for BrushSettings {
|
|||
Self::default_round_soft()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bundled brush presets
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A named brush preset backed by a bundled .myb file.
|
||||
pub struct BrushPreset {
|
||||
pub name: &'static str,
|
||||
pub settings: BrushSettings,
|
||||
}
|
||||
|
||||
/// Returns the list of bundled brush presets (parsed once from embedded .myb files).
|
||||
///
|
||||
/// Sources: mypaint/mypaint-brushes — CC0 1.0 Universal (Public Domain)
|
||||
pub fn bundled_brushes() -> &'static [BrushPreset] {
|
||||
static CACHE: OnceLock<Vec<BrushPreset>> = OnceLock::new();
|
||||
CACHE.get_or_init(|| {
|
||||
let mut v = Vec::new();
|
||||
macro_rules! brush {
|
||||
($name:literal, $path:literal) => {
|
||||
if let Ok(s) = BrushSettings::from_myb(include_str!($path)) {
|
||||
v.push(BrushPreset { name: $name, settings: s });
|
||||
}
|
||||
};
|
||||
}
|
||||
brush!("Pencil", "../../../src/assets/brushes/pencil.myb");
|
||||
brush!("Pen", "../../../src/assets/brushes/pen.myb");
|
||||
brush!("Charcoal", "../../../src/assets/brushes/charcoal.myb");
|
||||
brush!("Brush", "../../../src/assets/brushes/brush.myb");
|
||||
brush!("Dry Brush", "../../../src/assets/brushes/dry_brush.myb");
|
||||
brush!("Ink", "../../../src/assets/brushes/ink_blot.myb");
|
||||
brush!("Calligraphy", "../../../src/assets/brushes/calligraphy.myb");
|
||||
brush!("Airbrush", "../../../src/assets/brushes/airbrush.myb");
|
||||
brush!("Chalk", "../../../src/assets/brushes/chalk.myb");
|
||||
brush!("Liner", "../../../src/assets/brushes/liner.myb");
|
||||
v
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -766,6 +766,8 @@ struct EditorApp {
|
|||
brush_hardness: f32, // brush hardness 0.0–1.0
|
||||
brush_spacing: f32, // dabs_per_radius (fraction of radius per dab)
|
||||
brush_use_fg: bool, // true = paint with FG (stroke) color, false = BG (fill) color
|
||||
/// Full brush settings for the currently active preset (carries elliptical, jitter, etc.)
|
||||
active_brush_settings: lightningbeam_core::brush_settings::BrushSettings,
|
||||
// Audio engine integration
|
||||
#[allow(dead_code)] // Must be kept alive to maintain audio output
|
||||
audio_stream: Option<cpal::Stream>,
|
||||
|
|
@ -1047,6 +1049,7 @@ impl EditorApp {
|
|||
brush_hardness: 0.5,
|
||||
brush_spacing: 0.1,
|
||||
brush_use_fg: true,
|
||||
active_brush_settings: lightningbeam_core::brush_settings::BrushSettings::default(),
|
||||
audio_stream,
|
||||
audio_controller,
|
||||
audio_event_rx,
|
||||
|
|
@ -5503,6 +5506,7 @@ impl eframe::App for EditorApp {
|
|||
brush_hardness: &mut self.brush_hardness,
|
||||
brush_spacing: &mut self.brush_spacing,
|
||||
brush_use_fg: &mut self.brush_use_fg,
|
||||
active_brush_settings: &mut self.active_brush_settings,
|
||||
audio_controller: self.audio_controller.as_ref(),
|
||||
video_manager: &self.video_manager,
|
||||
playback_time: &mut self.playback_time,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
/// - Document settings (when nothing is focused)
|
||||
|
||||
use eframe::egui::{self, DragValue, Ui};
|
||||
use lightningbeam_core::brush_settings::{bundled_brushes, BrushSettings};
|
||||
use lightningbeam_core::actions::{SetDocumentPropertiesAction, SetShapePropertiesAction};
|
||||
use lightningbeam_core::layer::{AnyLayer, LayerTrait};
|
||||
use lightningbeam_core::selection::FocusSelection;
|
||||
|
|
@ -25,6 +26,8 @@ pub struct InfopanelPane {
|
|||
tool_section_open: bool,
|
||||
/// Whether the shape properties section is expanded
|
||||
shape_section_open: bool,
|
||||
/// Index of the selected brush preset (None = custom / unset)
|
||||
selected_brush_preset: Option<usize>,
|
||||
}
|
||||
|
||||
impl InfopanelPane {
|
||||
|
|
@ -32,6 +35,7 @@ impl InfopanelPane {
|
|||
Self {
|
||||
tool_section_open: true,
|
||||
shape_section_open: true,
|
||||
selected_brush_preset: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,6 +306,12 @@ impl InfopanelPane {
|
|||
|
||||
// Raster paint tools
|
||||
Tool::Draw | Tool::Erase | Tool::Smudge if is_raster_paint_tool => {
|
||||
// Brush preset picker (Draw tool only)
|
||||
if matches!(tool, Tool::Draw) {
|
||||
self.render_brush_preset_grid(ui, shared);
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
// Color source toggle (Draw tool only)
|
||||
if matches!(tool, Tool::Draw) {
|
||||
ui.horizontal(|ui| {
|
||||
|
|
@ -351,6 +361,81 @@ impl InfopanelPane {
|
|||
});
|
||||
}
|
||||
|
||||
/// Render the brush preset thumbnail grid for the Draw raster tool.
|
||||
fn render_brush_preset_grid(&mut self, ui: &mut Ui, shared: &mut SharedPaneState) {
|
||||
let presets = bundled_brushes();
|
||||
if presets.is_empty() { return; }
|
||||
|
||||
let gap = 3.0;
|
||||
let cols = 2usize;
|
||||
let cell_w = ((ui.available_width() - gap * (cols as f32 - 1.0)) / cols as f32).max(50.0);
|
||||
let cell_h = 80.0;
|
||||
|
||||
for (row_idx, chunk) in presets.chunks(cols).enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = gap;
|
||||
for (col_idx, preset) in chunk.iter().enumerate() {
|
||||
let idx = row_idx * cols + col_idx;
|
||||
let is_selected = self.selected_brush_preset == Some(idx);
|
||||
|
||||
let (rect, resp) = ui.allocate_exact_size(
|
||||
egui::vec2(cell_w, cell_h),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
|
||||
let painter = ui.painter();
|
||||
|
||||
let bg = if is_selected {
|
||||
egui::Color32::from_rgb(45, 65, 95)
|
||||
} else if resp.hovered() {
|
||||
egui::Color32::from_rgb(45, 50, 62)
|
||||
} else {
|
||||
egui::Color32::from_rgb(32, 36, 44)
|
||||
};
|
||||
painter.rect_filled(rect, 4.0, bg);
|
||||
if is_selected {
|
||||
painter.rect_stroke(
|
||||
rect, 4.0,
|
||||
egui::Stroke::new(1.5, egui::Color32::from_rgb(80, 140, 220)),
|
||||
egui::StrokeKind::Middle,
|
||||
);
|
||||
}
|
||||
|
||||
// Dab preview (upper portion, leaving 18 px for the name)
|
||||
let preview_rect = egui::Rect::from_min_size(
|
||||
rect.min + egui::vec2(4.0, 4.0),
|
||||
egui::vec2(cell_w - 8.0, cell_h - 22.0),
|
||||
);
|
||||
paint_brush_dab(painter, preview_rect, &preset.settings);
|
||||
|
||||
// Name
|
||||
painter.text(
|
||||
egui::pos2(rect.center().x, rect.max.y - 9.0),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
preset.name,
|
||||
egui::FontId::proportional(9.5),
|
||||
if is_selected {
|
||||
egui::Color32::from_rgb(140, 190, 255)
|
||||
} else {
|
||||
egui::Color32::from_gray(160)
|
||||
},
|
||||
);
|
||||
|
||||
if resp.clicked() {
|
||||
self.selected_brush_preset = Some(idx);
|
||||
let s = &preset.settings;
|
||||
*shared.brush_radius = s.radius_at_pressure(0.5).clamp(1.0, 200.0);
|
||||
*shared.brush_opacity = s.opaque.clamp(0.0, 1.0);
|
||||
*shared.brush_hardness = s.hardness.clamp(0.0, 1.0);
|
||||
*shared.brush_spacing = s.dabs_per_radius;
|
||||
*shared.active_brush_settings = s.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(gap);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform section: deferred to Phase 2 (DCEL elements don't have instance transforms)
|
||||
|
||||
/// Render shape properties section (fill/stroke)
|
||||
|
|
@ -864,6 +949,40 @@ impl InfopanelPane {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw a brush dab preview into `rect` approximating the brush falloff shape.
|
||||
///
|
||||
/// Renders N concentric filled circles from outermost to innermost. Because each
|
||||
/// inner circle overwrites the pixels of all outer circles beneath it, the visible
|
||||
/// alpha at distance `d` from the centre equals the alpha of the innermost circle
|
||||
/// whose radius ≥ `d`. This step-approximates the actual brush falloff formula:
|
||||
/// `opa = ((1 − r) / (1 − hardness))²` for `r > hardness`, 1 inside the hard core.
|
||||
fn paint_brush_dab(painter: &egui::Painter, rect: egui::Rect, s: &BrushSettings) {
|
||||
let center = rect.center();
|
||||
let max_r = (rect.width().min(rect.height()) / 2.0 - 2.0).max(1.0);
|
||||
let h = s.hardness;
|
||||
let a = s.opaque;
|
||||
|
||||
const N: usize = 12;
|
||||
for i in 0..N {
|
||||
// t: normalized radial position of this ring, 1.0 = outermost edge
|
||||
let t = 1.0 - i as f32 / N as f32;
|
||||
let r = max_r * t;
|
||||
|
||||
let opa_weight = if h >= 1.0 || t <= h {
|
||||
1.0f32
|
||||
} else {
|
||||
let x = (1.0 - t) / (1.0 - h).max(1e-4);
|
||||
(x * x).min(1.0)
|
||||
};
|
||||
|
||||
let alpha = (opa_weight * a * 220.0).min(220.0) as u8;
|
||||
painter.circle_filled(
|
||||
center, r,
|
||||
egui::Color32::from_rgba_unmultiplied(200, 200, 220, alpha),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert MIDI note number to note name (e.g. 60 -> "C4")
|
||||
fn midi_note_name(note: u8) -> String {
|
||||
const NAMES: [&str; 12] = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
||||
|
|
|
|||
|
|
@ -194,6 +194,8 @@ pub struct SharedPaneState<'a> {
|
|||
pub brush_spacing: &'a mut f32,
|
||||
/// Whether the brush paints with the foreground (fill) color (true) or background (stroke) color (false)
|
||||
pub brush_use_fg: &'a mut bool,
|
||||
/// Full brush settings for the active preset (carries elliptical, jitter, slow_tracking, etc.)
|
||||
pub active_brush_settings: &'a mut lightningbeam_core::brush_settings::BrushSettings,
|
||||
/// Audio engine controller for playback control (wrapped in Arc<Mutex<>> for thread safety)
|
||||
pub audio_controller: Option<&'a std::sync::Arc<std::sync::Mutex<daw_backend::EngineController>>>,
|
||||
/// Video manager for video decoding and frame caching
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct GpuDab {
|
|||
x: f32, y: f32, radius: f32, hardness: f32, // bytes 0–15
|
||||
opacity: f32, color_r: f32, color_g: f32, color_b: f32, // bytes 16–31
|
||||
color_a: f32, ndx: f32, ndy: f32, smudge_dist: f32, // bytes 32–47
|
||||
blend_mode: u32, _pad0: u32, _pad1: u32, _pad2: u32, // bytes 48–63
|
||||
blend_mode: u32, elliptical_dab_ratio: f32, elliptical_dab_angle: f32, lock_alpha: f32, // bytes 48–63
|
||||
}
|
||||
|
||||
struct Params {
|
||||
|
|
@ -76,7 +76,20 @@ fn bilinear_sample(px: f32, py: f32) -> vec4<f32> {
|
|||
fn apply_dab(current: vec4<f32>, dab: GpuDab, px: i32, py: i32) -> vec4<f32> {
|
||||
let dx = f32(px) + 0.5 - dab.x;
|
||||
let dy = f32(py) + 0.5 - dab.y;
|
||||
let rr = (dx * dx + dy * dy) / (dab.radius * dab.radius);
|
||||
|
||||
// Normalised squared distance — supports circular and elliptical dabs.
|
||||
var rr: f32;
|
||||
if dab.elliptical_dab_ratio > 1.001 {
|
||||
// Rotate into the dab's local frame.
|
||||
// Major axis is along dab.elliptical_dab_angle; minor axis is compressed by ratio.
|
||||
let c = cos(dab.elliptical_dab_angle);
|
||||
let s = sin(dab.elliptical_dab_angle);
|
||||
let dx_r = dx * c + dy * s; // along major axis
|
||||
let dy_r = (-dx * s + dy * c) * dab.elliptical_dab_ratio; // minor axis compressed
|
||||
rr = (dx_r * dx_r + dy_r * dy_r) / (dab.radius * dab.radius);
|
||||
} else {
|
||||
rr = (dx * dx + dy * dy) / (dab.radius * dab.radius);
|
||||
}
|
||||
if rr > 1.0 { return current; }
|
||||
|
||||
// Quadratic falloff: flat inner core, smooth quadratic outer zone.
|
||||
|
|
@ -94,15 +107,17 @@ fn apply_dab(current: vec4<f32>, dab: GpuDab, px: i32, py: i32) -> vec4<f32> {
|
|||
}
|
||||
|
||||
if dab.blend_mode == 0u {
|
||||
// Normal: "over" operator
|
||||
// Normal: "over" operator on premultiplied RGBA.
|
||||
// If lock_alpha > 0.5, preserve the destination alpha unchanged.
|
||||
let dab_a = opa_weight * dab.opacity * dab.color_a;
|
||||
if dab_a <= 0.0 { return current; }
|
||||
let ba = 1.0 - dab_a;
|
||||
let out_a = select(dab_a + ba * current.a, current.a, dab.lock_alpha > 0.5);
|
||||
return vec4<f32>(
|
||||
dab_a * dab.color_r + ba * current.r,
|
||||
dab_a * dab.color_g + ba * current.g,
|
||||
dab_a * dab.color_b + ba * current.b,
|
||||
dab_a + ba * current.a,
|
||||
out_a,
|
||||
);
|
||||
} else if dab.blend_mode == 1u {
|
||||
// Erase: multiplicative alpha reduction
|
||||
|
|
|
|||
|
|
@ -4720,18 +4720,14 @@ impl StagePane {
|
|||
if !is_raster { return; }
|
||||
|
||||
let brush = {
|
||||
use lightningbeam_core::brush_settings::BrushSettings;
|
||||
BrushSettings {
|
||||
radius_log: shared.brush_radius.ln(),
|
||||
hardness: *shared.brush_hardness,
|
||||
opaque: *shared.brush_opacity,
|
||||
dabs_per_radius: *shared.brush_spacing,
|
||||
color_h: 0.0,
|
||||
color_s: 0.0,
|
||||
color_v: 0.0,
|
||||
pressure_radius_gain: 0.3,
|
||||
pressure_opacity_gain: 0.8,
|
||||
}
|
||||
// Start from the active preset (carries elliptical ratio/angle, jitter, etc.)
|
||||
// then override the four parameters the user controls via UI sliders.
|
||||
let mut b = shared.active_brush_settings.clone();
|
||||
b.radius_log = shared.brush_radius.ln();
|
||||
b.hardness = *shared.brush_hardness;
|
||||
b.opaque = *shared.brush_opacity;
|
||||
b.dabs_per_radius = *shared.brush_spacing;
|
||||
b
|
||||
};
|
||||
|
||||
let color = if matches!(blend_mode, lightningbeam_core::raster_layer::RasterBlendMode::Erase) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
Brush presets sourced from the mypaint/mypaint-brushes repository.
|
||||
https://github.com/mypaint/mypaint-brushes
|
||||
|
||||
License: CC0 1.0 Universal (Public Domain Dedication)
|
||||
https://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
Contributors:
|
||||
classic/ brushes — original MyPaint contributors
|
||||
deevad/ brushes — David Revoy (deevad), http://www.davidrevoy.com
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"group": "",
|
||||
"description": "An airbrush",
|
||||
"notes": "A brush preset part of the Brushkit v0.6 \n created in october 2012 by David Revoy ( aka Deevad ) \n source: http://www.davidrevoy.com/article142/ressource-mypaint-brushes \n license: CC-Zero/Public-Domain",
|
||||
"parent_brush_name": "",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.08902229845626071,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.71,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 5.75,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.99,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {
|
||||
"direction": [
|
||||
[0.0, 0.0],
|
||||
[180.0, 180.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.48,
|
||||
"inputs": {}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 0.52,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[0.111111, 0.5],
|
||||
[0.308642, 0.833333],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 4.7,
|
||||
"inputs": {
|
||||
"custom": [
|
||||
[-2.0, 0.45],
|
||||
[2.0, -0.45]
|
||||
]
|
||||
}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"group": "",
|
||||
"parent_brush_name": "classic/brush",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 5.82,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.51,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 70.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.89,
|
||||
"inputs": {}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, -0.989583],
|
||||
[0.38253, -0.59375],
|
||||
[0.656627, 0.041667],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.44,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[0.015, 0.0],
|
||||
[0.069277, 0.9375],
|
||||
[0.25, 1.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 1.01,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, -1.86375],
|
||||
[0.237952, -1.42],
|
||||
[0.5, -0.355],
|
||||
[0.76506, 1.42],
|
||||
[1.0, 2.13]
|
||||
]
|
||||
}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 4.47,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 2.48,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 2.87,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"description": "",
|
||||
"group": "",
|
||||
"notes": "",
|
||||
"parent_brush_name": "classic/calligraphy",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 3.53,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 2.2,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 45.92,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 5.46,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.74,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.05]
|
||||
],
|
||||
"speed1": [
|
||||
[0.0, -0.0],
|
||||
[1.0, -0.04]
|
||||
]
|
||||
}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[0.015, 0.0],
|
||||
[0.015, 1.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pressure_gain_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 2.02,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.5]
|
||||
],
|
||||
"speed1": [
|
||||
[0.0, -0.0],
|
||||
[1.0, -0.12]
|
||||
]
|
||||
}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 0.65,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"snap_to_pixel": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 2.87,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"description": "A chalk brush attempt, using many tiny particles on canvas to simulate grain",
|
||||
"group": "",
|
||||
"notes": "A brush preset part of the Brushkit v0.6 \n created in october 2012 by David Revoy ( aka Deevad ) \n source: http://www.davidrevoy.com/article142/ressource-mypaint-brushes \n license: CC-Zero/Public-Domain",
|
||||
"parent_brush_name": "deevad/chalk",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.69,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 3.93,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 5.07,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.67,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, -0.4],
|
||||
[0.667722, -0.0625],
|
||||
[1.0, 0.6]
|
||||
]
|
||||
}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, -2.0]
|
||||
],
|
||||
"speed1": [
|
||||
[0.0, -0.2142857142857142],
|
||||
[4.0, 1.5]
|
||||
],
|
||||
"speed2": [
|
||||
[0.0, -0.2142857142857142],
|
||||
[4.0, 1.5]
|
||||
]
|
||||
}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 0.2,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.4]
|
||||
]
|
||||
}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pressure_gain_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 0.58,
|
||||
"inputs": {}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"snap_to_pixel": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"group": "",
|
||||
"parent_brush_name": "",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.6354166666666666,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.8807339449541285,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.42745098039215684,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 5.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.2,
|
||||
"inputs": {}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 1.6,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0, 0],
|
||||
[1.0, -1.4]
|
||||
]
|
||||
}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 0.4,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0, 0],
|
||||
[1.0, 0.4]
|
||||
]
|
||||
}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0, 0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 0.7,
|
||||
"inputs": {}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"description": "",
|
||||
"group": "",
|
||||
"notes": "",
|
||||
"parent_brush_name": "classic/dry_brush",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 6.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 6.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.2,
|
||||
"inputs": {}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 1.4]
|
||||
]
|
||||
}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.2]
|
||||
]
|
||||
}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pressure_gain_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.1,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 0.6,
|
||||
"inputs": {
|
||||
"speed2": [
|
||||
[0.0, 0.042857],
|
||||
[4.0, -0.3]
|
||||
]
|
||||
}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"snap_to_pixel": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"description": "",
|
||||
"group": "",
|
||||
"notes": "",
|
||||
"parent_brush_name": "classic/ink_blot",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 3.32,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 15.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.28,
|
||||
"inputs": {}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.17,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.02,
|
||||
"inputs": {
|
||||
"custom": [
|
||||
[-2.0, 0.0],
|
||||
[2.0, 0.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.9,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pressure_gain_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.63,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 2.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"snap_to_pixel": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"group": "",
|
||||
"description": "A small brush to trace regular lines",
|
||||
"notes": "A brush preset part of the Brushkit v0.6 \n created in october 2012 by David Revoy ( aka Deevad ) \n source: http://www.davidrevoy.com/article142/ressource-mypaint-brushes \n license: CC-Zero/Public-Domain",
|
||||
"parent_brush_name": "",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.1289192800566187,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 4.43,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[0.015, 0.0],
|
||||
[0.015, 1.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 0.7999999999999998,
|
||||
"inputs": {}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 2.87,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 1.18,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 10.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"description": "",
|
||||
"group": "",
|
||||
"notes": "",
|
||||
"parent_brush_name": "classic/pen",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 2.2,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.9,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.05]
|
||||
],
|
||||
"speed1": [
|
||||
[0.0, -0.0],
|
||||
[1.0, -0.09]
|
||||
]
|
||||
}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.9,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[0.015, 0.0],
|
||||
[0.015, 1.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pressure_gain_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 0.96,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.5]
|
||||
],
|
||||
"speed1": [
|
||||
[0.0, -0.0],
|
||||
[1.0, -0.15]
|
||||
]
|
||||
}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 0.65,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"snap_to_pixel": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 2.87,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"comment": "MyPaint brush file",
|
||||
"group": "",
|
||||
"parent_brush_name": "classic/pencil",
|
||||
"settings": {
|
||||
"anti_aliasing": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsl_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_hsv_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_l": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"change_color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_h": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_s": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"color_v": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"colorize": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"custom_input_slowness": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_actual_radius": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_basic_radius": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"dabs_per_second": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"direction_filter": {
|
||||
"base_value": 2.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_angle": {
|
||||
"base_value": 90.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"elliptical_dab_ratio": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"eraser": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"hardness": {
|
||||
"base_value": 0.1,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.3]
|
||||
]
|
||||
}
|
||||
},
|
||||
"lock_alpha": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_random": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, -0.3]
|
||||
]
|
||||
}
|
||||
},
|
||||
"offset_by_speed": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"offset_by_speed_slowness": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque": {
|
||||
"base_value": 0.7,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_linearize": {
|
||||
"base_value": 0.9,
|
||||
"inputs": {}
|
||||
},
|
||||
"opaque_multiply": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {
|
||||
"pressure": [
|
||||
[0.0, 0.0],
|
||||
[1.0, 1.0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"radius_by_random": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"radius_logarithmic": {
|
||||
"base_value": 0.2,
|
||||
"inputs": {}
|
||||
},
|
||||
"restore_color": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking": {
|
||||
"base_value": 1.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"slow_tracking_per_dab": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_length": {
|
||||
"base_value": 0.5,
|
||||
"inputs": {}
|
||||
},
|
||||
"smudge_radius_log": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed1_slowness": {
|
||||
"base_value": 0.04,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_gamma": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"speed2_slowness": {
|
||||
"base_value": 0.8,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_duration_logarithmic": {
|
||||
"base_value": 4.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_holdtime": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"stroke_threshold": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
},
|
||||
"tracking_noise": {
|
||||
"base_value": 0.0,
|
||||
"inputs": {}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
Loading…
Reference in New Issue