iced-rs/iced · error
Create color
Error message
Create color
What it means
tiny_skia::Color::from_rgba returns None when any component is NaN (out-of-range floats are tolerated; NaN is not). While converting iced gradient stops into tiny-skia gradient stops, the tiny_skia engine unwraps that result with .expect("Create color") (tiny_skia/src/engine.rs:154), so a NaN in any channel (r, g, b, a) of any gradient stop panics during drawing.
Solutions
- Sanitize stop colors before building gradients: replace non-finite components (check f32::is_finite) with defaults and clamp to 0..=1
- Trace the math that produced the stop color - usually a division by zero in interpolation or normalization
- Use from_rgba8 or integer inputs for fixed palettes so NaN cannot occur
- Add a debug_assert! when computing animated colors so bad values surface with context
Example fix
// before
let g = t / weight; // weight == 0.0 -> NaN -> panic in renderer
stops.push(GradientStop::new(t, Color::from_rgba(t, g, 1.0 - t, 1.0)));
// after
fn safe_color(r: f32, g: f32, b: f32, a: f32) -> Color {
let fix = |v: f32, d: f32| if v.is_finite() { v.clamp(0.0, 1.0) } else { d };
Color::from_rgba(fix(r, 0.0), fix(g, 0.0), fix(b, 0.0), fix(a, 1.0))
}
stops.push(GradientStop::new(t, safe_color(t, t / weight.max(f32::EPSILON), 1.0 - t, 1.0))); Defensive patterns
Strategy: validation
Validate before calling
fn safe_color(r: f32, g: f32, b: f32, a: f32) -> Color {
let fix = |v: f32, d: f32| if v.is_finite() { v.clamp(0.0, 1.0) } else { d };
Color::from_rgba(fix(r, 0.0), fix(g, 0.0), fix(b, 0.0), fix(a, 1.0))
}
// validate stops before building a gradient
for stop in &stops {
debug_assert!([stop.color.r, stop.color.g, stop.color.b, stop.color.a]
.iter().all(|c| c.is_finite()), "NaN in gradient stop");
} Type guard
fn is_valid_color(c: &Color) -> bool {
[c.r, c.g, c.b, c.a].iter().all(|ch| ch.is_finite())
} Prevention
- Guard every division feeding color math (normalize denominators with max(EPSILON))
- Clamp interpolation parameters t to 0..=1 before lerping colors
- Validate theme/data-driven colors at load time with is_finite checks
- Prefer from_rgba8 for fixed palettes so NaN cannot enter
When it happens
Trigger: Drawing a gradient (linear/radial fills) whose stop colors contain NaN - typically 0.0/0.0 or inf - inf from interpolation code (lerp with an out-of-domain t, division by zero when normalizing), or animated colors computed from uninitialized/invalid state.
Common situations: Color interpolation/animation helpers that divide without guarding zero denominators; gradients generated from data (heatmaps) with degenerate values; themes parsed from bad numeric input; NaN sneaking in through serialization.
Related errors
- Caches must not be empty
- Create clip mask
- Create pixel map
- Create softbuffer context
- Create softbuffer surface for window
AI-assisted analysis of iced-rs/iced@3de451447b (2026-08-17).
Data as JSON: /api/errors/5955a9ffec577cac.
Report an issue: GitHub.
Appendix: source
Thrown at tiny_skia/src/engine.rs:154
shader: match background {
Background::Color(color) => tiny_skia::Shader::SolidColor(into_color(*color)),
Background::Gradient(Gradient::Linear(linear)) => {
let (start, end) = quad.bounds.chord(linear.angle);
let stops: Vec<tiny_skia::GradientStop> = linear
.stops
.into_iter()
.flatten()
.map(|stop| {
tiny_skia::GradientStop::new(
stop.offset,
tiny_skia::Color::from_rgba(
stop.color.b,
stop.color.g,
stop.color.r,
stop.color.a,
)
.expect("Create color"),
)
})
.collect();
tiny_skia::LinearGradient::new(
tiny_skia::Point {
x: start.x,
y: start.y,
},
tiny_skia::Point { x: end.x, y: end.y },
if stops.is_empty() {
vec![tiny_skia::GradientStop::new(0.0, tiny_skia::Color::BLACK)]
} else {
stops
},
tiny_skia::SpreadMode::Pad,
tiny_skia::Transform::identity(),
)View on GitHub (pinned to 3de451447b)