GraphiteEditor/Graphite · error

failed to encode image as png

Error message

failed to encode image as png

What it means

Image<Color>::to_png() in graphene-raster-types flattens the RGBA pixels and hands them to the image crate's PngEncoder, then calls .expect("failed to encode image as png"), so any encoder error aborts the process instead of returning a Result. Because the encoder writes into an in-memory Vec, I/O errors are effectively impossible; the realistic failures are parameter errors. to_flat_u8() already asserts data.len() == width*height and produces exactly width*height*4 bytes, so the live triggers are degenerate images (width or height of 0) and size overflow on 32-bit/wasm32 targets.

Source

Thrown at node-graph/libraries/raster-types/src/image.rs:168

				// `Image<Color>` pixels are stored linear-light with premultiplied alpha
				let srgba = SRGBA8::new(v[0], v[1], v[2], v[3]);
				Color::from(srgba).apply_opacity(v[3] as f32 / 255.)
			})
			.collect();
		Image {
			width,
			height,
			data,
			base64_string: None,
		}
	}

	pub fn to_png(&self) -> Vec<u8> {
		use ::image::ImageEncoder;
		let (data, width, height) = self.to_flat_u8();
		let mut png = Vec::new();
		let encoder = ::image::codecs::png::PngEncoder::new(&mut png);
		encoder.write_image(&data, width, height, ::image::ExtendedColorType::Rgba8).expect("failed to encode image as png");
		png
	}
}

use super::*;
impl<P: Alpha + RGB + AssociatedAlpha> Image<P>
where
	P::ColorChannel: Linear,
	<P as Alpha>::AlphaChannel: Linear,
{
	/// Flattens each channel cast to a u8
	pub fn to_flat_u8(&self) -> (Vec<u8>, u32, u32) {
		let Image { width, height, data, .. } = self;
		assert_eq!(data.len(), *width as usize * *height as usize);

		// Cache the last sRGB value we computed, speeds up fills.
		let mut last_r = 0.;
		let mut last_r_srgb = 0u8;

View on GitHub (pinned to c507b35645)

Solutions

  1. Guard degenerate sizes before encoding: if width or height is 0, return early or substitute a 1x1 transparent PNG instead of calling the encoder
  2. Keep the invariant data.len() == width*height intact wherever Image fields are mutated, so to_flat_u8() and the encoder agree on dimensions
  3. On 32-bit/wasm32 targets, do the size math in u64 and cap or tile the export so width*height*4 fits usize
  4. Replace .expect with Result propagation (return image::ImageError from to_png) so callers can handle encoding failures

Example fix

// before
let (data, width, height) = self.to_flat_u8();
let encoder = ::image::codecs::png::PngEncoder::new(&mut png);
encoder.write_image(&data, width, height, ::image::ExtendedColorType::Rgba8).expect("failed to encode image as png");

// after
let (data, width, height) = self.to_flat_u8();
assert!(width > 0 && height > 0, "cannot png-encode a zero-sized image");
assert_eq!(data.len(), width as usize * height as usize * 4, "pixel buffer does not match dimensions");
let encoder = ::image::codecs::png::PngEncoder::new(&mut png);
encoder.write_image(&data, width, height, ::image::ExtendedColorType::Rgba8).expect("failed to encode image as png");
Defensive patterns

Strategy: validation

Validate before calling

// Validate before calling to_png()
let w = image.width as u64;
let h = image.height as u64;
let encodable = w > 0 && h > 0 && w * h * 4 <= usize::MAX as u64;
if !encodable {
    // substitute a placeholder or skip the export instead of panicking inside to_png()
}

Type guard

fn is_png_encodable<P>(image: &Image<P>) -> bool {
    image.width > 0 && image.height > 0 && image.width as u64 * image.height as u64 * 4 <= usize::MAX as u64
}

Try / catch

let png = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| image.to_png()));
match png {
    Ok(bytes) => { /* proceed */ }
    Err(payload) => { /* log payload, export a 1x1 transparent placeholder instead */ }
}

Prevention

When it happens

Trigger: Calling to_png() on an Image whose width or height is 0 (empty artboard, zero-resolution footprint); rasters so large that width*height*4 overflows usize on 32-bit/wasm32 builds during flattening or encoding; any future refactor that breaks the data.len() == width*height invariant upheld by to_flat_u8() at image.rs:182, which makes write_image fail with a DimensionMismatch parameter error.

Common situations: Exporting a PNG from an empty document or an artboard sized to zero; multi-gigapixel exports under wasm32; builds where the Image.data vector was rebuilt or truncated by custom node code; upgrades of the image crate that validate dimensions more strictly.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/3c9c185af6d19e2a. Report an issue: GitHub.