blob: 27b5f9fdb10d5980e42afd9300b0633a40628815 [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Image rendering example scene.
use std::f64::consts::PI;
use vello_common::color::PremulRgba8;
use vello_common::kurbo::{BezPath, Point, Shape, Vec2};
use vello_common::peniko::ImageFormat;
use vello_common::peniko::ImageSampler;
use vello_common::pixmap::Pixmap;
use vello_common::{
kurbo::{Affine, Rect},
paint::{Image, ImageSource},
peniko::{Extend, ImageQuality},
};
use crate::{ExampleScene, RenderingContext};
/// Image scene state
#[derive(Debug, Default)]
pub struct ImageScene {
img_sources: Vec<ImageSource>,
}
impl ImageScene {
/// Create a new image scene
pub fn new(img_sources: Vec<ImageSource>) -> Self {
Self { img_sources }
}
}
impl ExampleScene for ImageScene {
fn render(&mut self, ctx: &mut impl RenderingContext, root_transform: Affine) {
let splash_flower_id = self.img_sources[0].clone();
let cowboy_id = self.img_sources[1].clone();
ctx.set_transform(
root_transform
* Affine::translate((200.0, 50.0))
* Affine::rotate(PI / 4.0)
* Affine::scale(0.5),
);
ctx.set_paint_transform(Affine::translate((0.0, 0.0)));
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
ctx.set_transform(
root_transform
* Affine::translate((500.0, 50.0))
* Affine::skew(0.5, 0.5)
* Affine::scale(0.5),
);
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
ctx.set_transform(root_transform * Affine::translate((0.0, 500.0)));
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_path(&heart_shape());
ctx.set_transform(root_transform * Affine::translate((400.0, 480.0)));
ctx.push_clip_layer(&circular_star(Point::new(300.0, 220.0), 5, 100.0, 150.0).to_path(0.1));
ctx.set_paint_transform(Affine::IDENTITY);
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Repeat,
y_extend: Extend::Repeat,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
ctx.pop_layer();
ctx.set_transform(root_transform * Affine::translate((1000.0, 50.0)));
ctx.set_paint_transform(Affine::scale(0.25));
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Repeat,
y_extend: Extend::Repeat,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
ctx.set_transform(root_transform * Affine::translate((1000.0, 600.0)));
ctx.set_paint_transform(Affine::scale(0.25));
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Reflect,
y_extend: Extend::Repeat,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
ctx.set_transform(root_transform * Affine::translate((1000.0, 1200.0)));
ctx.set_paint_transform(Affine::scale(0.25));
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Repeat,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
ctx.set_transform(root_transform * Affine::translate((100.0, 1000.0)));
ctx.set_paint_transform(Affine::IDENTITY);
ctx.set_paint(Image {
image: cowboy_id,
sampler: ImageSampler {
x_extend: Extend::Repeat,
y_extend: Extend::Repeat,
quality: ImageQuality::High,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 800.0, 160.0));
ctx.set_transform(
root_transform
* Affine::translate((200.0, 1200.0))
* Affine::rotate(PI / 4.0)
* Affine::scale(0.5),
);
ctx.set_paint_transform(Affine::scale(0.25));
ctx.set_paint(Image {
image: splash_flower_id.clone(),
sampler: ImageSampler {
x_extend: Extend::Repeat,
y_extend: Extend::Repeat,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(0.0, 0.0, 640.0, 480.0));
}
}
impl ImageScene {
/// Read the flower image
pub fn read_flower_image() -> Pixmap {
let image_data = include_bytes!("../../../examples/assets/splash-flower.jpg");
Self::read_image(image_data)
}
/// Read the flower image
#[expect(clippy::cast_possible_truncation, reason = "deliberate quantization")]
pub fn read_image(data: &[u8]) -> Pixmap {
let image_data = decode_image(data);
let premul_data: Vec<PremulRgba8> = image_data
.data
.chunks_exact(4)
.map(|rgba| {
let alpha = u16::from(rgba[3]);
let premultiply = |component| (alpha * (u16::from(component)) / 255) as u8;
PremulRgba8 {
r: premultiply(rgba[0]),
g: premultiply(rgba[1]),
b: premultiply(rgba[2]),
a: alpha as u8,
}
})
.collect();
Pixmap::from_parts(
premul_data,
image_data.width as u16,
image_data.height as u16,
)
}
/// Read the cowboy image
pub fn read_cowboy_image() -> Pixmap {
let data = include_bytes!("../../vello_sparse_tests/tests/assets/cowboy.png");
Pixmap::from_png(&data[..]).unwrap()
}
}
/// Decode an image from a byte slice
pub fn decode_image(data: &[u8]) -> ImageData {
let image = image::load_from_memory(data).expect("Failed to decode image");
let width = image.width();
let height = image.height();
ImageData {
data: image.into_rgba8().into_vec(),
format: ImageFormat::Rgba8,
width,
height,
}
}
/// Image data
#[derive(Clone, PartialEq, Debug)]
pub struct ImageData {
/// Blob containing the image data.
pub data: Vec<u8>,
/// Pixel format of the image.
pub format: ImageFormat,
/// Width of the image.
pub width: u32,
/// Height of the image.
pub height: u32,
}
fn heart_shape() -> BezPath {
let mut path = BezPath::new();
path.move_to((320.0, 100.0));
// Left top curve of the heart
path.curve_to((160.0, 20.0), (80.0, 120.0), (320.0, 380.0));
// Right top curve of the heart
path.curve_to((560.0, 120.0), (480.0, 20.0), (320.0, 100.0));
path.close_path();
path
}
fn circular_star(center: Point, n: usize, inner: f64, outer: f64) -> BezPath {
let mut path = BezPath::new();
let start_angle = -std::f64::consts::FRAC_PI_2;
path.move_to(center + outer * Vec2::from_angle(start_angle));
for i in 1..n * 2 {
let th = start_angle + i as f64 * std::f64::consts::PI / n as f64;
let r = if i % 2 == 0 { outer } else { inner };
path.line_to(center + r * Vec2::from_angle(th));
}
path.close_path();
path
}