Fix artboards not exporting with transparency using Vello (#3921)

* Fix hide artboard for raster render mode

* Desktop: Fix transparent viewport blending

* Fix vello render using wrong color space conversion for background

* Review
This commit is contained in:
Timon 2026-03-23 02:20:54 +01:00 committed by GitHub
parent bf486b4cb5
commit 5b1e1cb2fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 26 additions and 38 deletions

View File

@ -71,6 +71,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
if (viewport_srgb.a < 0.001) {
viewport_srgb = constants.background_color;
} else if (viewport_srgb.a < 0.999) {
viewport_srgb = blend(viewport_srgb, constants.background_color);
}
if (overlay_srgb.a < 0.001) {

View File

@ -440,8 +440,6 @@ impl NodeGraphExecutor {
file_type,
name,
size,
#[cfg(feature = "gpu")]
transparent_background,
artboard_name,
artboard_count,
..
@ -491,12 +489,7 @@ impl NodeGraphExecutor {
match file_type {
FileType::Png => {
let result = if transparent_background {
image.write_to(&mut cursor, ImageFormat::Png)
} else {
let image: RgbImage = image.convert();
image.write_to(&mut cursor, ImageFormat::Png)
};
let result = image.write_to(&mut cursor, ImageFormat::Png);
if let Err(err) = result {
return Err(format!("Failed to encode PNG: {err}"));
}

View File

@ -879,42 +879,32 @@ impl Color {
)
}
/// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. Use this if the [`Color`] is in gamma space.
#[inline(always)]
pub fn to_rgba8(&self) -> [u8; 4] {
[(self.red * 255.) as u8, (self.green * 255.) as u8, (self.blue * 255.) as u8, (self.alpha * 255.) as u8]
}
/// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. Use this if the [`Color`] is in linear space.
///
/// # Examples
/// ```
/// use core_types::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// // TODO: Add test
/// ```
#[inline(always)]
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
let gamma = self.to_gamma_srgb();
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
self.to_gamma_srgb().to_rgba8()
}
/// Return the all RGB components as a u8 slice, first component is red, followed by green, followed by blue. Use this if the [`Color`] is in gamma space.
#[inline(always)]
pub fn to_rgb8(&self) -> [u8; 3] {
[(self.red * 255.) as u8, (self.green * 255.) as u8, (self.blue * 255.) as u8]
}
/// Return the all RGB components as a u8 slice, first component is red, followed by green, followed by blue. Use this if the [`Color`] is in linear space.
///
/// # Examples
/// ```
/// use core_types::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// // TODO: Add test
/// ```
#[inline(always)]
pub fn to_rgb8_srgb(&self) -> [u8; 3] {
let gamma = self.to_gamma_srgb();
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8]
self.to_gamma_srgb().to_rgb8()
}
// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
/// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1)
///
/// # Examples
/// ```
/// use core_types::color::Color;
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.).to_hsla();
/// ```
pub fn to_hsla(&self) -> [f32; 4] {
let min_channel = self.red.min(self.green).min(self.blue);
let max_channel = self.red.max(self.green).max(self.blue);

View File

@ -522,18 +522,21 @@ impl Render for Artboard {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko;
// Render background
let color = peniko::Color::new([self.background.r(), self.background.g(), self.background.b(), self.background.a()]);
let [a, b] = [self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()];
let rect = kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), color, None, &rect);
scene.pop_layer();
// Render background
if !render_params.hide_artboards {
let color = peniko::Color::new([self.background.r(), self.background.g(), self.background.b(), self.background.a()]);
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), color, None, &rect);
scene.pop_layer();
}
if self.clip {
scene.push_clip_layer(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), &rect);
}
// Since the content's transform is right multiplied in when rendering the content, we just need to right multiply by the artboard offset here.
let child_transform = transform * DAffine2::from_translation(self.location.as_dvec2());
let mut render_params = render_params.clone();

View File

@ -129,7 +129,7 @@ impl WgpuExecutor {
if let Some(target_texture) = output.as_mut() {
target_texture.ensure_size(&self.context.device, size);
let [r, g, b, a] = background.unwrap_or(Color::TRANSPARENT).to_rgba8_srgb();
let [r, g, b, a] = background.unwrap_or(Color::TRANSPARENT).to_rgba8();
let render_params = RenderParams {
base_color: vello::peniko::Color::from_rgba8(r, g, b, a),
width: size.x,