Skip to content

Commit

Permalink
Make image sampling pad in all directions and remove half-pixel offset (
Browse files Browse the repository at this point in the history
#722)

Constrains image sampling to the actual atlas region for a given image
and adds a half pixel adjustment to sample from the pixel center.

Addresses #719 and maybe #656

Example 2x2 image with red, blue, cyan and magenta pixels:

<img width="387" alt="vello_image_sampling"
src="https://github.com/user-attachments/assets/6bc02607-e9fa-4c79-83c9-241e76a42fb0">

---------

Co-authored-by: Daniel McNab <[email protected]>
  • Loading branch information
dfrg and DJMcNab authored Nov 8, 2024
1 parent 64fc566 commit 0bef450
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 12 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ You can find its changes [documented below](#030---2024-10-04).

This release has an [MSRV][] of 1.75.

### Fixed

- Offset in image rendering, and sampling outside correct atlas area ([#722][] by [@dfrg])

## [0.3.0][] - 2024-10-04

This release has an [MSRV][] of 1.75.
Expand Down Expand Up @@ -186,6 +190,7 @@ This release has an [MSRV][] of 1.75.
[#701]: https://github.com/linebender/vello/pull/701
[#706]: https://github.com/linebender/vello/pull/706
[#711]: https://github.com/linebender/vello/pull/711
[#722]: https://github.com/linebender/vello/pull/722

[Unreleased]: https://github.com/linebender/vello/compare/v0.3.0...HEAD
<!-- Note that this still comparing against 0.2.0, because 0.2.1 is a cherry-picked patch -->
Expand Down
89 changes: 88 additions & 1 deletion examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,15 @@ export_scenes!(
mmark(crate::mmark::MMark::new(80_000), "mmark", false),
many_draw_objects(many_draw_objects),
blurred_rounded_rect(blurred_rounded_rect),
image_sampling(image_sampling),
image_extend_modes(image_extend_modes)
);

/// Implementations for the test scenes.
/// In a module because the exported [`ExampleScene`] creation functions use the same names.
mod impls {
use std::f64::consts::PI;
use std::f64::consts::{FRAC_1_SQRT_2, PI};
use std::sync::Arc;

use crate::SceneParams;
use kurbo::RoundedRect;
Expand Down Expand Up @@ -1761,4 +1764,88 @@ mod impls {
std_dev,
);
}

pub(super) fn image_sampling(scene: &mut Scene, params: &mut SceneParams) {
params.resolution = Some(Vec2::new(1100., 1100.));
params.base_color = Some(Color::WHITE);
let mut blob: Vec<u8> = Vec::new();
[Color::RED, Color::BLUE, Color::CYAN, Color::MAGENTA]
.iter()
.for_each(|c| {
let b = c.to_premul_u32().to_ne_bytes();
blob.push(b[3]);
blob.push(b[2]);
blob.push(b[1]);
blob.push(b[0]);
});
let data = vello::peniko::Blob::new(Arc::new(blob));
let image = vello::peniko::Image::new(data, vello::peniko::Format::Rgba8, 2, 2);

scene.draw_image(
&image,
Affine::scale(200.).then_translate((100., 100.).into()),
);
scene.draw_image(
&image,
Affine::translate((-1., -1.))
// 45° rotation
.then_rotate(PI / 4.)
.then_translate((1., 1.).into())
// So the major axis is sqrt(2.) larger
.then_scale(200. * FRAC_1_SQRT_2)
.then_translate((100., 600.0).into()),
);
scene.draw_image(
&image,
Affine::scale_non_uniform(100., 200.).then_translate((600.0, 100.0).into()),
);
scene.draw_image(
&image,
Affine::skew(0.1, 0.25)
.then_scale(200.0)
.then_translate((600.0, 600.0).into()),
);
}

pub(super) fn image_extend_modes(scene: &mut Scene, params: &mut SceneParams) {
params.resolution = Some(Vec2::new(1500., 1500.));
params.base_color = Some(Color::WHITE);
let mut blob: Vec<u8> = Vec::new();
[Color::RED, Color::BLUE, Color::CYAN, Color::MAGENTA]
.iter()
.for_each(|c| {
let b = c.to_premul_u32().to_ne_bytes();
blob.push(b[3]);
blob.push(b[2]);
blob.push(b[1]);
blob.push(b[0]);
});
let data = vello::peniko::Blob::new(Arc::new(blob));
let image = vello::peniko::Image::new(data, vello::peniko::Format::Rgba8, 2, 2);
let image = image.with_extend(Extend::Pad);
// Pad extend mode
scene.fill(
Fill::NonZero,
Affine::scale(100.).then_translate((100., 100.).into()),
&image,
Some(Affine::translate((2., 2.)).then_scale(100.)),
&Rect::new(0., 0., 6., 6.),
);
let image = image.with_extend(Extend::Reflect);
scene.fill(
Fill::NonZero,
Affine::scale(100.).then_translate((100., 800.).into()),
&image,
Some(Affine::translate((2., 2.))),
&Rect::new(0., 0., 6., 6.),
);
let image = image.with_extend(Extend::Repeat);
scene.fill(
Fill::NonZero,
Affine::scale(100.).then_translate((800., 100.).into()),
&image,
Some(Affine::translate((2., 2.))),
&Rect::new(0., 0., 6., 6.),
);
}
}
5 changes: 4 additions & 1 deletion vello_shaders/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ fn main() {
let mut shaders = match compile::ShaderInfo::from_default() {
Ok(s) => s,
Err(err) => {
eprintln!("{err}");
let formatted = err.to_string();
for line in formatted.lines() {
println!("cargo:warning={line}");
}
return;
}
};
Expand Down
18 changes: 12 additions & 6 deletions vello_shaders/shader/fine.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -1160,13 +1160,19 @@ fn main(
}
case CMD_IMAGE: {
let image = read_image(cmd_ix);
let atlas_extents = image.atlas_offset + image.extents;
let atlas_max = image.atlas_offset + image.extents - vec2(1.0);
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
let my_xy = vec2(xy.x + f32(i), xy.y);
let atlas_uv = image.matrx.xy * my_xy.x + image.matrx.zw * my_xy.y + image.xlat + image.atlas_offset;
// This currently clips to the image bounds. TODO: extend modes
if all(atlas_uv < atlas_extents) && area[i] != 0.0 {
let uv_quad = vec4(max(floor(atlas_uv), image.atlas_offset), min(ceil(atlas_uv), atlas_extents));
// We only need to load from the textures if the value will be used.
if area[i] != 0.0 {
let my_xy = vec2(xy.x + f32(i), xy.y);
let atlas_uv = image.matrx.xy * my_xy.x + image.matrx.zw * my_xy.y + image.xlat + image.atlas_offset - vec2(0.5);
// This currently only implements the Pad extend mode
// TODO: Support repeat and reflect
// TODO: If the image couldn't be added to the atlas (i.e. was too big), this isn't robust
let atlas_uv_clamped = clamp(atlas_uv, image.atlas_offset, atlas_max);
// We know that the floor and ceil are within the atlas area because atlas_max and
// atlas_offset are integers
let uv_quad = vec4(floor(atlas_uv_clamped), ceil(atlas_uv_clamped));
let uv_frac = fract(atlas_uv);
let a = premul_alpha(textureLoad(image_atlas, vec2<i32>(uv_quad.xy), 0));
let b = premul_alpha(textureLoad(image_atlas, vec2<i32>(uv_quad.xw), 0));
Expand Down
Binary file modified vello_tests/smoke_snapshots/two_emoji.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions vello_tests/snapshots/big_bitmap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vello_tests/snapshots/image_extend_modes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vello_tests/snapshots/image_sampling.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions vello_tests/snapshots/little_bitmap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions vello_tests/tests/snapshot_test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,19 @@ fn snapshot_longpathdash_butt() {
let params = TestParams::new("longpathdash_butt", 440, 80);
snapshot_test_scene(test_scene, params);
}

#[test]
#[cfg_attr(skip_gpu_tests, ignore)]
fn snapshot_image_sampling() {
let test_scene = test_scenes::image_sampling();
let params = TestParams::new("image_sampling", 400, 400);
snapshot_test_scene(test_scene, params);
}

#[test]
#[cfg_attr(skip_gpu_tests, ignore)]
fn snapshot_image_extend_modes() {
let test_scene = test_scenes::image_extend_modes();
let params = TestParams::new("image_extend_modes", 375, 375);
snapshot_test_scene(test_scene, params);
}

0 comments on commit 0bef450

Please sign in to comment.