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

  1. Sanitize stop colors before building gradients: replace non-finite components (check f32::is_finite) with defaults and clamp to 0..=1
  2. Trace the math that produced the stop color - usually a division by zero in interpolation or normalization
  3. Use from_rgba8 or integer inputs for fixed palettes so NaN cannot occur
  4. 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

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


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)