Skip to content

Commit

Permalink
fix(image): scale border radius clipping
Browse files Browse the repository at this point in the history
  • Loading branch information
mmstick committed Oct 27, 2023
1 parent 8a6f519 commit 89897da
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 63 deletions.
6 changes: 1 addition & 5 deletions core/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,7 @@ pub trait Renderer: crate::Renderer {
type Handle: Clone + Hash;

/// Returns the dimensions of an image for the given [`Handle`].
fn dimensions(
&self,
handle: &Self::Handle,
border_radius: [f32; 4],
) -> Size<u32>;
fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;

/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
Expand Down
6 changes: 1 addition & 5 deletions graphics/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ pub trait Text {
/// A graphics backend that supports image rendering.
pub trait Image {
/// Returns the dimensions of the provided image.
fn dimensions(
&self,
handle: &image::Handle,
border_radius: [f32; 4],
) -> Size<u32>;
fn dimensions(&self, handle: &image::Handle) -> Size<u32>;
}

/// A graphics backend that supports SVG rendering.
Expand Down
8 changes: 2 additions & 6 deletions graphics/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,8 @@ where
{
type Handle = image::Handle;

fn dimensions(
&self,
handle: &image::Handle,
border_radius: [f32; 4],
) -> Size<u32> {
self.backend().dimensions(handle, border_radius)
fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
self.backend().dimensions(handle)
}

fn draw(
Expand Down
8 changes: 2 additions & 6 deletions renderer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,8 @@ impl<T> text::Renderer for Renderer<T> {
impl<T> crate::core::image::Renderer for Renderer<T> {
type Handle = crate::core::image::Handle;

fn dimensions(
&self,
handle: &crate::core::image::Handle,
border_radius: [f32; 4],
) -> Size<u32> {
delegate!(self, renderer, renderer.dimensions(handle, border_radius))
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
delegate!(self, renderer, renderer.dimensions(handle))
}

fn draw(
Expand Down
8 changes: 2 additions & 6 deletions tiny_skia/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,12 +851,8 @@ impl backend::Text for Backend {

#[cfg(feature = "image")]
impl backend::Image for Backend {
fn dimensions(
&self,
handle: &crate::core::image::Handle,
border_radius: [f32; 4],
) -> Size<u32> {
self.raster_pipeline.dimensions(handle, border_radius)
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
self.raster_pipeline.dimensions(handle)
}
}

Expand Down
71 changes: 41 additions & 30 deletions tiny_skia/src/raster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::core::image as raster;
use crate::core::{Rectangle, Size};
use crate::graphics;

use graphics::image::image_rs::{ImageBuffer, Rgba, RgbaImage};
use rustc_hash::{FxHashMap, FxHashSet};
use std::cell::RefCell;
use std::collections::hash_map;
Expand All @@ -18,14 +17,8 @@ impl Pipeline {
}
}

pub fn dimensions(
&self,
handle: &raster::Handle,
border_radius: [f32; 4],
) -> Size<u32> {
if let Some(image) =
self.cache.borrow_mut().allocate(handle, border_radius)
{
pub fn dimensions(&self, handle: &raster::Handle) -> Size<u32> {
if let Some(image) = self.cache.borrow_mut().allocate(handle) {
Size::new(image.width(), image.height())
} else {
Size::new(0, 0)
Expand All @@ -41,14 +34,31 @@ impl Pipeline {
clip_mask: Option<&tiny_skia::Mask>,
border_radius: [f32; 4],
) {
if let Some(image) =
self.cache.borrow_mut().allocate(handle, border_radius)
{
if let Some(mut image) = self.cache.borrow_mut().allocate(handle) {
let width_scale = bounds.width / image.width() as f32;
let height_scale = bounds.height / image.height() as f32;

let transform = transform.pre_scale(width_scale, height_scale);

let mut scratch;

// Round the borders if a border radius is defined
if border_radius.iter().any(|&corner| corner != 0.0) {
scratch = image.to_owned();
round(&mut scratch.as_mut(), {
let [a, b, c, d] = border_radius;
let scale_by = width_scale.min(height_scale);
let max_radius = image.width().min(image.height()) / 2;
[
((a / scale_by) as u32).max(1).min(max_radius),
((b / scale_by) as u32).max(1).min(max_radius),
((c / scale_by) as u32).max(1).min(max_radius),
((d / scale_by) as u32).max(1).min(max_radius),
]
});
image = scratch.as_ref();
}

pixels.draw_pixmap(
(bounds.x / width_scale) as i32,
(bounds.y / height_scale) as i32,
Expand Down Expand Up @@ -78,20 +88,11 @@ impl Cache {
pub fn allocate(
&mut self,
handle: &raster::Handle,
border_radius: [f32; 4],
) -> Option<tiny_skia::PixmapRef<'_>> {
let id = handle.id();

if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) {
let mut image = graphics::image::load(handle).ok()?.into_rgba8();

// Round the borders if a border radius is defined
if border_radius.iter().any(|&corner| corner != 0.0) {
round(&mut image, {
let [a, b, c, d] = border_radius;
[a as u32, b as u32, c as u32, d as u32]
});
}
let image = graphics::image::load(handle).ok()?.into_rgba8();

let mut buffer =
vec![0u32; image.width() as usize * image.height() as usize];
Expand Down Expand Up @@ -135,8 +136,8 @@ struct Entry {
}

// https://users.rust-lang.org/t/how-to-trim-image-to-circle-image-without-jaggy/70374/2
fn round(img: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, radius: [u32; 4]) {
let (width, height) = img.dimensions();
fn round(img: &mut tiny_skia::PixmapMut, radius: [u32; 4]) {
let (width, height) = (img.width(), img.height());
assert!(radius[0] + radius[1] <= width);
assert!(radius[3] + radius[2] <= width);
assert!(radius[0] + radius[3] <= height);
Expand All @@ -153,7 +154,7 @@ fn round(img: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, radius: [u32; 4]) {
}

fn border_radius(
img: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
img: &mut tiny_skia::PixmapMut,
r: u32,
coordinates: impl Fn(u32, u32) -> (u32, u32),
) {
Expand All @@ -174,9 +175,19 @@ fn border_radius(
let mut alpha: u16 = 0;
let mut skip_draw = true;

let draw = |img: &mut RgbaImage, alpha, x, y| {
fn pixel_id(width: u32, (x, y): (u32, u32)) -> usize {
((width as usize * y as usize) + x as usize) * 4
}

let clear_pixel = |img: &mut tiny_skia::PixmapMut, (x, y): (u32, u32)| {
let pixel = pixel_id(img.width(), (x, y));
img.data_mut()[pixel..pixel + 4].copy_from_slice(&[0; 4]);
};

let draw = |img: &mut tiny_skia::PixmapMut, alpha, x, y| {
debug_assert!((1..=256).contains(&alpha));
let pixel_alpha = &mut img[coordinates(r0 - x, r0 - y)].0[3];
let pixel = pixel_id(img.width(), coordinates(r0 - x, r0 - y));
let pixel_alpha = &mut img.data_mut()[pixel + 3];
*pixel_alpha = ((alpha * *pixel_alpha as u16 + 128) / 256) as u8;
};

Expand All @@ -186,14 +197,14 @@ fn border_radius(
{
let i = x / 16;
for j in y / 16 + 1..r0 {
img[coordinates(r0 - i, r0 - j)].0[3] = 0;
clear_pixel(img, coordinates(r0 - i, r0 - j));
}
}
// remove contents right of current position mirrored
{
let j = x / 16;
for i in y / 16 + 1..r0 {
img[coordinates(r0 - i, r0 - j)].0[3] = 0;
clear_pixel(img, coordinates(r0 - i, r0 - j));
}
}

Expand Down Expand Up @@ -246,7 +257,7 @@ fn border_radius(
let range = y / 16 + 1..r0;
for i in range.clone() {
for j in range.clone() {
img[coordinates(r0 - i, r0 - j)].0[3] = 0;
clear_pixel(img, coordinates(r0 - i, r0 - j));
}
}
}
4 changes: 2 additions & 2 deletions widget/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ where
{
// The raw w/h of the underlying image
let image_size = {
let Size { width, height } = renderer.dimensions(handle, border_radius);
let Size { width, height } = renderer.dimensions(handle);

Size::new(width as f32, height as f32)
};
Expand Down Expand Up @@ -185,7 +185,7 @@ pub fn draw<Renderer, Handle>(
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
let Size { width, height } = renderer.dimensions(handle, border_radius);
let Size { width, height } = renderer.dimensions(handle);
let image_size = Size::new(width as f32, height as f32);

let bounds = layout.bounds();
Expand Down
5 changes: 2 additions & 3 deletions widget/src/image/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let Size { width, height } =
renderer.dimensions(&self.handle, [0.0; 4]);
let Size { width, height } = renderer.dimensions(&self.handle);

let mut size = limits
.width(self.width)
Expand Down Expand Up @@ -412,7 +411,7 @@ pub fn image_size<Renderer>(
where
Renderer: image::Renderer,
{
let Size { width, height } = renderer.dimensions(handle, [0.0; 4]);
let Size { width, height } = renderer.dimensions(handle);

let (width, height) = {
let dimensions = (width as f32, height as f32);
Expand Down

0 comments on commit 89897da

Please sign in to comment.