diff --git a/vello/src/render.rs b/vello/src/render.rs index cab8bad08e..d544bf84d7 100644 --- a/vello/src/render.rs +++ b/vello/src/render.rs @@ -152,9 +152,6 @@ impl Render { ImageProxy::new(images.width, images.height, ImageFormat::Rgba8) }; for image in images.images { - if image.0.format != peniko::ImageFormat::Rgba8 { - unimplemented!("Unsupported image format: {:?}", image.0.format); - } if image.0.alpha_type != peniko::ImageAlphaType::Alpha { unimplemented!("Unsupported image alpha type: {:?}", image.0.alpha_type); } diff --git a/vello_encoding/src/encoding.rs b/vello_encoding/src/encoding.rs index 62ad1498c6..907bcdb15b 100644 --- a/vello_encoding/src/encoding.rs +++ b/vello_encoding/src/encoding.rs @@ -432,9 +432,6 @@ impl Encoding { /// Encodes an image brush. pub fn encode_image<'b>(&mut self, brush: impl Into>, alpha: f32) { let brush = brush.into(); - if brush.image.format != peniko::ImageFormat::Rgba8 { - unimplemented!("Unsupported image format: {:?}", brush.image.format); - } if brush.image.alpha_type != peniko::ImageAlphaType::Alpha { unimplemented!("Unsupported image alpha type: {:?}", brush.image.alpha_type); } @@ -457,10 +454,11 @@ impl Encoding { .extend_from_slice(bytemuck::cast_slice(bytemuck::bytes_of(&DrawImage { xy: 0, width_height: (brush.image.width << 16) | (brush.image.height & 0xFFFF), - sample_alpha: ((quality as u32) << 12) + sample_alpha: ((brush.image.format as u32) << 14 + | (quality as u32) << 12 | ((x_extend as u32) << 10) | ((y_extend as u32) << 8) - | alpha as u32, + | alpha as u32), }))); } diff --git a/vello_shaders/shader/fine.wgsl b/vello_shaders/shader/fine.wgsl index 9276871ff8..8a52cf3e7b 100644 --- a/vello_shaders/shader/fine.wgsl +++ b/vello_shaders/shader/fine.wgsl @@ -813,7 +813,8 @@ fn read_image(cmd_ix: u32) -> CmdImage { let width_height = info[info_offset + 7u]; let sample_alpha = info[info_offset + 8u]; let alpha = f32(sample_alpha & 0xFFu) / 255.0; - let quality = sample_alpha >> 12u; + let format = sample_alpha >> 14u; + let quality = (sample_alpha >> 12u) & 0x3u; let x_extend = (sample_alpha >> 10u) & 0x3u; let y_extend = (sample_alpha >> 8u) & 0x3u; // The following are not intended to be bitcasts @@ -821,7 +822,7 @@ fn read_image(cmd_ix: u32) -> CmdImage { let y = f32(xy & 0xffffu); let width = f32(width_height >> 16u); let height = f32(width_height & 0xffffu); - return CmdImage(matrx, xlat, vec2(x, y), vec2(width, height), x_extend, y_extend, quality, alpha); + return CmdImage(matrx, xlat, vec2(x, y), vec2(width, height), format, x_extend, y_extend, quality, alpha); } fn read_end_clip(cmd_ix: u32) -> CmdEndClip { @@ -830,6 +831,21 @@ fn read_end_clip(cmd_ix: u32) -> CmdEndClip { return CmdEndClip(blend, alpha); } +const PIXEL_FORMAT_RGBA: u32 = 0u; +const PIXEL_FORMAT_BGRA: u32 = 1u; +// Normalises subpixel order loaded from an image, based on the image's format. +fn pixel_format(pixel: vec4f, format: u32) -> vec4f { + switch format { + case PIXEL_FORMAT_BGRA: { + // The conversion from RGBA to BGRA is its own inverse. + return pixel.bgra; + } + case PIXEL_FORMAT_RGBA, default: { + return pixel; + } + } +} + const EXTEND_PAD: u32 = 0u; const EXTEND_REPEAT: u32 = 1u; const EXTEND_REFLECT: u32 = 2u; @@ -1198,13 +1214,13 @@ fn main( let atlas_uv_clamped = clamp(atlas_uv, image.atlas_offset, atlas_max); // Nearest neighbor sampling let fg_rgba = premul_alpha(textureLoad(image_atlas, vec2(atlas_uv_clamped), 0)); - let fg_i = fg_rgba * area[i] * image.alpha; + let fg_i = pixel_format(fg_rgba * area[i] * image.alpha, image.format); rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; } } } case IMAGE_QUALITY_MEDIUM, default: { - // We don't have an implementation for `IMAGE_QUALITY_HIGH` yet, just use the same as medium + // We don't have an implementation for `IMAGE_QUALITY_HIGH` yet, just use the same as medium for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { // We only need to load from the textures if the value will be used. if area[i] != 0.0 { @@ -1225,7 +1241,7 @@ fn main( let d = premul_alpha(textureLoad(image_atlas, vec2(uv_quad.zw), 0)); // Bilinear sampling let fg_rgba = mix(mix(a, b, uv_frac.y), mix(c, d, uv_frac.y), uv_frac.x); - let fg_i = fg_rgba * area[i] * image.alpha; + let fg_i = pixel_format(fg_rgba * area[i] * image.alpha, image.format); rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; } } diff --git a/vello_shaders/shader/shared/ptcl.wgsl b/vello_shaders/shader/shared/ptcl.wgsl index d0b41cbed2..d4f4b2f54f 100644 --- a/vello_shaders/shader/shared/ptcl.wgsl +++ b/vello_shaders/shader/shared/ptcl.wgsl @@ -49,7 +49,7 @@ struct CmdColor { struct CmdBlurRect { // Solid fill color. rgba_color: u32, - + // 2x2 transformation matrix (inverse). matrx: vec4, // 2D translation (inverse) @@ -97,6 +97,7 @@ struct CmdImage { xlat: vec2, atlas_offset: vec2, extents: vec2, + format: u32, x_extend_mode: u32, y_extend_mode: u32, quality: u32, diff --git a/vello_tests/tests/property.rs b/vello_tests/tests/property.rs index dbafb881e6..f0e8b5837d 100644 --- a/vello_tests/tests/property.rs +++ b/vello_tests/tests/property.rs @@ -14,6 +14,7 @@ use vello::Scene; use vello::kurbo::{Affine, Rect}; use vello::peniko::{Brush, Color, ImageFormat, color::palette}; +use vello::peniko::{ImageAlphaType, ImageData, ImageSampler}; use vello_tests::TestParams; fn simple_square(use_cpu: bool) { @@ -101,3 +102,47 @@ fn empty_scene_gpu() { fn empty_scene_cpu() { empty_scene(true); } + +#[test] +#[cfg_attr(skip_gpu_tests, ignore)] +fn bgra_image() { + let mut scene = Scene::new(); + let colors = [ + palette::css::RED, + palette::css::BLUE, + palette::css::LIME, + palette::css::WHITE, + ]; + let blob: Vec = colors + .iter() + .flat_map(|c| { + let [r, g, b, a] = c.to_rgba8().to_u8_array(); + [b, g, r, a] + }) + .collect(); + let image = vello::peniko::ImageBrush { + image: ImageData { + data: blob.into(), + format: ImageFormat::Bgra8, + width: 2, + height: 2, + alpha_type: ImageAlphaType::Alpha, + }, + sampler: ImageSampler { + quality: vello::peniko::ImageQuality::Low, + ..Default::default() + }, + }; + scene.draw_image(&image, Affine::IDENTITY); + let scene_image = + vello_tests::render_then_debug_sync(&scene, &TestParams::new("bgra", 2, 2)).unwrap(); + assert_eq!(scene_image.format, ImageFormat::Rgba8); + for (i, pixel) in scene_image.data.data().chunks_exact(4).enumerate() { + let &[r, g, b, a] = pixel else { unreachable!() }; + let image_color = Color::from_rgba8(r, g, b, a); + let color = colors[i]; + if image_color.premultiply().difference(color.premultiply()) > 1e-4 { + panic!("Got {image_color:?}, expected color {color:?}"); + } + } +}