use std::any::Any;
use std::sync::Arc;
use ezu_core::TileId as CoreTileId;
use ezu_graph::{EvalCtx, EvalError, FactoryCtx, FactoryError, PortKind, PortValue, RasterBuf};
use ezu_style as spec;
use hokusai::Brush;
use serde_json::Value;
use crate::Canvas;
pub struct FilteredFeatures {
pub extent: u32,
pub polygons: Vec<ezu_features::Polygon>,
pub lines: Vec<Vec<(i32, i32)>>,
pub points: Vec<(i32, i32)>,
}
pub type BrushPayload = Brush;
pub(super) fn resolve_field(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
) -> Result<Value, FactoryError> {
let v = fields
.get(name)
.ok_or_else(|| FactoryError::MissingField(name.to_string()))?;
if let Some(s) = v.as_str() {
match spec::FieldRef::classify(s) {
spec::FieldRef::Param(p) => {
let decl = ctx
.params
.get(p)
.ok_or_else(|| FactoryError::UnknownParam(p.to_string()))?;
return Ok(decl.default.clone());
}
spec::FieldRef::Node(_) => {
return Err(FactoryError::BadField {
field: name.into(),
msg: "expected literal or $param, got @node-ref".into(),
});
}
spec::FieldRef::Literal(_) => {}
}
}
Ok(v.clone())
}
pub(super) fn read_color(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
) -> Result<[f32; 4], FactoryError> {
let v = resolve_field(fields, name, ctx)?;
let s = v.as_str().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected #rrggbb[aa] string".into(),
})?;
parse_hex_color(s).ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("bad color: {s}"),
})
}
pub(super) fn read_color_u8(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
) -> Result<[u8; 4], FactoryError> {
let v = resolve_field(fields, name, ctx)?;
let s = v.as_str().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected #rrggbb[aa] string".into(),
})?;
parse_hex_color_u8(s).ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("bad color: {s}"),
})
}
pub(super) fn read_number(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
) -> Result<f64, FactoryError> {
let v = resolve_field(fields, name, ctx)?;
v.as_f64().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected number".into(),
})
}
pub(super) fn read_number_or(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
default: f64,
) -> Result<f64, FactoryError> {
if !fields.contains_key(name) {
return Ok(default);
}
read_number(fields, name, ctx)
}
pub(super) fn read_string_or(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
default: &str,
) -> Result<String, FactoryError> {
if !fields.contains_key(name) {
return Ok(default.to_string());
}
let v = resolve_field(fields, name, ctx)?;
let s = v.as_str().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected string".into(),
})?;
Ok(s.to_string())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum BoundaryMode {
Clamp,
Transparent,
Mirror,
}
pub(super) fn read_boundary(
fields: &serde_json::Map<String, Value>,
name: &str,
default: BoundaryMode,
) -> Result<BoundaryMode, FactoryError> {
let Some(v) = fields.get(name) else {
return Ok(default);
};
let s = v.as_str().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected string".into(),
})?;
match s {
"clamp" => Ok(BoundaryMode::Clamp),
"transparent" => Ok(BoundaryMode::Transparent),
"mirror" => Ok(BoundaryMode::Mirror),
_ => Err(FactoryError::BadField {
field: name.into(),
msg: format!("unknown boundary `{s}`, expected clamp/transparent/mirror"),
}),
}
}
#[inline]
fn wrap_index(i: i64, dim: u32, mode: BoundaryMode) -> Option<u32> {
let d = dim as i64;
if d == 0 {
return None;
}
if i >= 0 && i < d {
return Some(i as u32);
}
match mode {
BoundaryMode::Clamp => Some(i.clamp(0, d - 1) as u32),
BoundaryMode::Transparent => None,
BoundaryMode::Mirror => {
if d == 1 {
return Some(0);
}
let period = 2 * (d - 1);
let mut k = i % period;
if k < 0 {
k += period;
}
if k >= d {
k = period - k;
}
Some(k as u32)
}
}
}
#[inline]
fn read_pixel_or(src: &RasterBuf, ix: i64, iy: i64, mode: BoundaryMode) -> [f32; 4] {
let Some(x) = wrap_index(ix, src.width, mode) else {
return [0.0; 4];
};
let Some(y) = wrap_index(iy, src.height, mode) else {
return [0.0; 4];
};
let p = src.pixel(x, y);
[p[0] as f32, p[1] as f32, p[2] as f32, p[3] as f32]
}
pub(super) fn sample_bilinear(src: &RasterBuf, x: f64, y: f64, mode: BoundaryMode) -> [u8; 4] {
let fx = x.floor();
let fy = y.floor();
let tx = (x - fx) as f32;
let ty = (y - fy) as f32;
let ix = fx as i64;
let iy = fy as i64;
let p00 = read_pixel_or(src, ix, iy, mode);
let p10 = read_pixel_or(src, ix + 1, iy, mode);
let p01 = read_pixel_or(src, ix, iy + 1, mode);
let p11 = read_pixel_or(src, ix + 1, iy + 1, mode);
let mut out = [0u8; 4];
for c in 0..4 {
let a = p00[c] + (p10[c] - p00[c]) * tx;
let b = p01[c] + (p11[c] - p01[c]) * tx;
let v = a + (b - a) * ty;
out[c] = v.round().clamp(0.0, 255.0) as u8;
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Anchor {
Tile,
World,
}
pub(super) fn read_anchor(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
) -> Result<Anchor, FactoryError> {
let s = read_string_or(fields, name, ctx, "tile")?;
match s.as_str() {
"tile" => Ok(Anchor::Tile),
"world" => Ok(Anchor::World),
_ => Err(FactoryError::BadField {
field: name.into(),
msg: format!("expected `tile` or `world`, got `{s}`"),
}),
}
}
pub(super) fn read_xy(
fields: &serde_json::Map<String, Value>,
name: &str,
ctx: &FactoryCtx<'_>,
default: [f32; 2],
) -> Result<[f32; 2], FactoryError> {
if !fields.contains_key(name) {
return Ok(default);
}
let v = resolve_field(fields, name, ctx)?;
let arr = v.as_array().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected [x, y] array".into(),
})?;
if arr.len() != 2 {
return Err(FactoryError::BadField {
field: name.into(),
msg: format!("expected exactly 2 numbers, got {}", arr.len()),
});
}
let x = arr[0].as_f64().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "x must be number".into(),
})? as f32;
let y = arr[1].as_f64().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "y must be number".into(),
})? as f32;
Ok([x, y])
}
pub(super) fn read_stops(
fields: &serde_json::Map<String, Value>,
name: &str,
_ctx: &FactoryCtx<'_>,
) -> Result<Vec<(f32, [f32; 4])>, FactoryError> {
let v = fields
.get(name)
.ok_or_else(|| FactoryError::MissingField(name.to_string()))?;
let arr = v.as_array().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected array of [t, color] pairs".into(),
})?;
if arr.len() < 2 {
return Err(FactoryError::BadField {
field: name.into(),
msg: "gradient needs at least 2 stops".into(),
});
}
let mut out = Vec::with_capacity(arr.len());
let mut prev_t: Option<f32> = None;
for (i, pt) in arr.iter().enumerate() {
let pair = pt.as_array().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("entry {i}: expected [t, color] pair"),
})?;
if pair.len() != 2 {
return Err(FactoryError::BadField {
field: name.into(),
msg: format!("entry {i}: expected exactly 2 entries"),
});
}
let t = pair[0].as_f64().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("entry {i}: t must be number"),
})? as f32;
let s = pair[1].as_str().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("entry {i}: color must be hex string"),
})?;
let color = parse_hex_color(s).ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("entry {i}: bad color `{s}`"),
})?;
if let Some(p) = prev_t {
if t < p {
return Err(FactoryError::BadField {
field: name.into(),
msg: format!("entry {i}: t must be non-decreasing"),
});
}
}
prev_t = Some(t);
out.push((t, color));
}
Ok(out)
}
pub(super) fn sample_stops(stops: &[(f32, [f32; 4])], t: f32) -> [f32; 4] {
if stops.is_empty() {
return [0.0; 4];
}
if t <= stops[0].0 {
return stops[0].1;
}
let last = stops.last().expect("stops is non-empty (guarded above)");
if t >= last.0 {
return last.1;
}
for w in stops.windows(2) {
if t >= w[0].0 && t <= w[1].0 {
let d = w[1].0 - w[0].0;
if d < 1e-6 {
return w[1].1;
}
let f = (t - w[0].0) / d;
return [
w[0].1[0] + (w[1].1[0] - w[0].1[0]) * f,
w[0].1[1] + (w[1].1[1] - w[0].1[1]) * f,
w[0].1[2] + (w[1].1[2] - w[0].1[2]) * f,
w[0].1[3] + (w[1].1[3] - w[0].1[3]) * f,
];
}
}
last.1
}
pub(super) fn read_optional_string(
fields: &serde_json::Map<String, Value>,
name: &str,
) -> Result<Option<String>, FactoryError> {
let Some(v) = fields.get(name) else {
return Ok(None);
};
let s = v.as_str().ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: "expected string".into(),
})?;
Ok(Some(s.to_string()))
}
fn parse_hex_color(s: &str) -> Option<[f32; 4]> {
let [r, g, b, a] = parse_hex_color_u8(s)?;
Some([
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
])
}
fn parse_hex_color_u8(s: &str) -> Option<[u8; 4]> {
let s = s.strip_prefix('#')?;
let (r, g, b, a) = match s.len() {
6 => (
u8::from_str_radix(&s[0..2], 16).ok()?,
u8::from_str_radix(&s[2..4], 16).ok()?,
u8::from_str_radix(&s[4..6], 16).ok()?,
255,
),
8 => (
u8::from_str_radix(&s[0..2], 16).ok()?,
u8::from_str_radix(&s[2..4], 16).ok()?,
u8::from_str_radix(&s[4..6], 16).ok()?,
u8::from_str_radix(&s[6..8], 16).ok()?,
),
_ => return None,
};
Some([r, g, b, a])
}
pub(super) fn srgb_to_linear_rgba(c: [f32; 4]) -> [f32; 4] {
fn ch(v: f32) -> f32 {
if v <= 0.04045 {
v / 12.92
} else {
((v + 0.055) / 1.055).powf(2.4)
}
}
[ch(c[0]), ch(c[1]), ch(c[2]), c[3]]
}
pub(super) fn color_to_premul_u8(c: [f32; 4]) -> [u8; 4] {
let a = c[3].clamp(0.0, 1.0);
[
(c[0] * a * 255.0).round() as u8,
(c[1] * a * 255.0).round() as u8,
(c[2] * a * 255.0).round() as u8,
(a * 255.0).round() as u8,
]
}
pub(super) fn rgba8_to_color(c: [u8; 4]) -> tiny_skia::Color {
tiny_skia::Color::from_rgba8(c[0], c[1], c[2], c[3])
}
pub(super) fn tint_alpha_color(c: [u8; 4], alpha_mul: f32) -> tiny_skia::Color {
let a = ((c[3] as f32) * alpha_mul.clamp(0.0, 1.0)).round() as u8;
tiny_skia::Color::from_rgba8(c[0], c[1], c[2], a)
}
pub(super) fn make_canvas(ctx: &EvalCtx<'_>) -> Result<Canvas, EvalError> {
Canvas::new_padded(ctx.canvas.tile_size, ctx.canvas.tile_size, ctx.canvas.pad).ok_or_else(
|| {
EvalError::Other(format!(
"canvas allocation failed for tile-size={} pad={}",
ctx.canvas.tile_size, ctx.canvas.pad
))
},
)
}
pub(super) fn canvas_into_raster(canvas: Canvas) -> RasterBuf {
let pixmap = canvas.into_pixmap();
let (w, h) = (pixmap.width(), pixmap.height());
RasterBuf {
width: w,
height: h,
pixels: pixmap.take(),
}
}
pub(super) fn empty_raster(ctx: &EvalCtx<'_>) -> PortValue {
let size = ctx.canvas.padded_size();
PortValue::Raster(Arc::new(RasterBuf::new(size, size)))
}
pub(super) fn core_tile(ctx: &EvalCtx<'_>) -> CoreTileId {
CoreTileId::new(ctx.tile.z, ctx.tile.x, ctx.tile.y)
}
pub(super) fn features_value(
extent: u32,
polygons: Vec<ezu_features::Polygon>,
lines: Vec<Vec<(i32, i32)>>,
points: Vec<(i32, i32)>,
) -> PortValue {
let payload = FilteredFeatures {
extent,
polygons,
lines,
points,
};
PortValue::Features(Arc::new(payload) as Arc<dyn Any + Send + Sync>)
}
pub(super) const ACCEPTS_RASTER_OR_SPRITE: &[PortKind] = &[PortKind::Raster, PortKind::Sprite];
pub(super) fn unwrap_raster_or_sprite(
v: &PortValue,
port_name: &str,
) -> Result<(Arc<RasterBuf>, RasterKind), EvalError> {
match v {
PortValue::Raster(r) => Ok((r.clone(), RasterKind::Raster)),
PortValue::Sprite(s) => Ok((s.clone(), RasterKind::Sprite)),
_ => Err(EvalError::MissingInput(port_name.into())),
}
}
pub(super) fn wrap_raster_like(buf: Arc<RasterBuf>, kind: RasterKind) -> PortValue {
match kind {
RasterKind::Raster => PortValue::Raster(buf),
RasterKind::Sprite => PortValue::Sprite(buf),
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum RasterKind {
Raster,
Sprite,
}
pub(super) fn raster_or_sprite_output(input_kinds: &[Option<PortKind>]) -> PortKind {
match input_kinds.first().and_then(|k| *k) {
Some(PortKind::Sprite) => PortKind::Sprite,
_ => PortKind::Raster,
}
}
pub(super) fn downcast_features(v: &PortValue) -> Result<Arc<FilteredFeatures>, EvalError> {
let PortValue::Features(o) = v else {
return Err(EvalError::Other("expected Features".into()));
};
o.clone()
.downcast::<FilteredFeatures>()
.map_err(|_| EvalError::Other("features payload type mismatch".into()))
}
pub(super) fn downcast_brush(v: &PortValue) -> Result<Arc<BrushPayload>, EvalError> {
let PortValue::Brush(o) = v else {
return Err(EvalError::Other("expected Brush".into()));
};
o.clone()
.downcast::<BrushPayload>()
.map_err(|_| EvalError::Other("brush payload type mismatch".into()))
}