librsvg source for verification 2026-05-22

This commit is contained in:
2026-05-22 16:45:08 +08:00
commit 75af7ac721
2138 changed files with 161177 additions and 0 deletions

58
rsvg/benches/box_blur.rs Normal file
View File

@@ -0,0 +1,58 @@
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use rsvg::bench_only::{
AlphaOnly, Horizontal, IRect, NotAlphaOnly, SharedImageSurface, SurfaceType, Vertical,
};
const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
x0: 64,
y0: 64,
x1: 64 + 64,
y1: 64 + 64,
};
fn bench_box_blur(c: &mut Criterion) {
let mut group = c.benchmark_group("box_blur 9");
for input in [(false, false), (false, true), (true, false), (true, true)].iter() {
group.bench_with_input(
BenchmarkId::from_parameter(format!("{:?}", input)),
&input,
|b, &(vertical, alpha_only)| {
let surface_type = if *alpha_only {
SurfaceType::AlphaOnly
} else {
SurfaceType::SRgb
};
let input_surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, surface_type).unwrap();
let mut output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, SURFACE_SIDE, SURFACE_SIDE)
.unwrap();
const KERNEL_SIZE: usize = 9;
let f = match (vertical, alpha_only) {
(true, true) => SharedImageSurface::box_blur_loop::<Vertical, AlphaOnly>,
(true, false) => SharedImageSurface::box_blur_loop::<Vertical, NotAlphaOnly>,
(false, true) => SharedImageSurface::box_blur_loop::<Horizontal, AlphaOnly>,
(false, false) => SharedImageSurface::box_blur_loop::<Horizontal, NotAlphaOnly>,
};
b.iter(|| {
f(
&input_surface,
&mut output_surface,
BOUNDS,
KERNEL_SIZE,
KERNEL_SIZE / 2,
)
})
},
);
}
}
criterion_group!(benches, bench_box_blur);
criterion_main!(benches);

44
rsvg/benches/composite.rs Normal file
View File

@@ -0,0 +1,44 @@
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use rsvg::bench_only::{
ExclusiveImageSurface, IRect, SharedImageSurface, SurfaceType, composite_arithmetic,
};
const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
x0: 64,
y0: 64,
x1: 64 + 64,
y1: 64 + 64,
};
fn bench_composite(c: &mut Criterion) {
c.bench_function("composite arithmetic", |b| {
let input_surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let input_2_surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let mut output_surface =
ExclusiveImageSurface::new(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let bounds = black_box(BOUNDS);
b.iter(|| {
composite_arithmetic(
&input_surface,
&input_2_surface,
&mut output_surface,
bounds,
0.5,
0.5,
0.5,
0.5,
);
})
});
}
criterion_group!(benches, bench_composite);
criterion_main!(benches);

258
rsvg/benches/lighting.rs Normal file
View File

@@ -0,0 +1,258 @@
use criterion::{Criterion, criterion_group, criterion_main};
use nalgebra::{Matrix3, Vector2};
use rsvg::bench_only::Normal;
use rsvg::bench_only::{EdgeMode, IRect, PixelRectangle, Pixels, SharedImageSurface, SurfaceType};
/// Computes and returns the normal vector for the light filters.
fn normal(surface: &SharedImageSurface, bounds: IRect, x: u32, y: u32) -> Normal {
assert!(x as i32 >= bounds.x0);
assert!(y as i32 >= bounds.y0);
assert!((x as i32) < bounds.x1);
assert!((y as i32) < bounds.y1);
// Get the correct sobel kernel and factor for the pixel position.
// Performance note: it's possible to replace the matrices with normal arrays.
#[rustfmt::skip]
let (factor_x, kx, factor_y, ky) = match (x as i32, y as i32) {
(x, y) if (x, y) == (bounds.x0, bounds.y0) => (
2. / 3.,
Matrix3::new(
0, 0, 0,
0, -2, 2,
0, -1, 1,
),
2. / 3.,
Matrix3::new(
0, 0, 0,
0, -2, -1,
0, 2, 1,
),
),
(x, y) if (x + 1, y) == (bounds.x1, bounds.y0) => (
2. / 3.,
Matrix3::new(
0, 0, 0,
-2, 2, 0,
-1, 1, 0,
),
2. / 3.,
Matrix3::new(
0, 0, 0,
-1, -2, 0,
1, 2, 0,
),
),
(x, y) if (x, y + 1) == (bounds.x0, bounds.y1) => (
2. / 3.,
Matrix3::new(
0, -1, 1,
0, -2, 2,
0, 0, 0,
),
2. / 3.,
Matrix3::new(
0, -2, -1,
0, 2, 1,
0, 0, 0,
),
),
(x, y) if (x + 1, y + 1) == (bounds.x1, bounds.y1) => (
2. / 3.,
Matrix3::new(
-1, 1, 0,
-2, 2, 0,
0, 0, 0,
),
2. / 3.,
Matrix3::new(
-1, -2, 0,
1, 2, 0,
0, 0, 0,
),
),
(_, y) if y == bounds.y0 => (
1. / 3.,
Matrix3::new(
0, 0, 0,
-2, 0, 2,
-1, 0, 1,
),
1. / 2.,
Matrix3::new(
0, 0, 0,
-1, -2, -1,
1, 2, 1,
),
),
(x, _) if x == bounds.x0 => (
1. / 2.,
Matrix3::new(
0, -1, 1,
0, -2, 2,
0, -1, 1,
),
1. / 3.,
Matrix3::new(
0, -2, -1,
0, 0, 0,
0, 2, 1,
),
),
(x, _) if x + 1 == bounds.x1 => (
1. / 2.,
Matrix3::new(
-1, 1, 0,
-2, 2, 0,
-1, 1, 0,
),
1. / 3.,
Matrix3::new(
-1, -2, 0,
0, 0, 0,
1, 2, 0,
),
),
(_, y) if y + 1 == bounds.y1 => (
1. / 3.,
Matrix3::new(
-1, 0, 1,
-2, 0, 2,
0, 0, 0,
),
1. / 2.,
Matrix3::new(
-1, -2, -1,
1, 2, 1,
0, 0, 0,
),
),
_ => (
1. / 4.,
Matrix3::new(
-1, 0, 1,
-2, 0, 2,
-1, 0, 1,
),
1. / 4.,
Matrix3::new(
-1, -2, -1,
0, 0, 0,
1, 2, 1,
),
),
};
let kernel_bounds = IRect::new(x as i32 - 1, y as i32 - 1, x as i32 + 2, y as i32 + 2);
let mut nx = 0;
let mut ny = 0;
for (x, y, pixel) in PixelRectangle::within(surface, bounds, kernel_bounds, EdgeMode::None) {
let kernel_x = (x - kernel_bounds.x0) as usize;
let kernel_y = (y - kernel_bounds.y0) as usize;
nx += i16::from(pixel.a) * kx[(kernel_y, kernel_x)];
ny += i16::from(pixel.a) * ky[(kernel_y, kernel_x)];
}
// Negative nx and ny to account for the different coordinate system.
Normal {
factor: Vector2::new(factor_x, factor_y),
normal: Vector2::new(-nx, -ny),
}
}
const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
x0: 64,
y0: 64,
x1: 64 + 64,
y1: 64 + 64,
};
fn bench_normal(c: &mut Criterion) {
c.bench_function("normal", |b| {
let surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
b.iter(|| {
let mut z = 0;
for (x, y, _pixel) in Pixels::within(&surface, BOUNDS) {
let n = normal(&surface, BOUNDS, x, y);
z += n.normal.x;
}
z
})
});
c.bench_function("normal unrolled", |b| {
let surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
b.iter(|| {
let mut z = 0;
// Top left.
{
let n = Normal::top_left(&surface, BOUNDS);
z += n.normal.x;
}
// Top right.
{
let n = Normal::top_right(&surface, BOUNDS);
z += n.normal.x;
}
// Bottom left.
{
let n = Normal::bottom_left(&surface, BOUNDS);
z += n.normal.x;
}
// Bottom right.
{
let n = Normal::bottom_right(&surface, BOUNDS);
z += n.normal.x;
}
// Top row.
for x in BOUNDS.x0 as u32 + 1..BOUNDS.x1 as u32 - 1 {
let n = Normal::top_row(&surface, BOUNDS, x);
z += n.normal.x;
}
// Bottom row.
for x in BOUNDS.x0 as u32 + 1..BOUNDS.x1 as u32 - 1 {
let n = Normal::bottom_row(&surface, BOUNDS, x);
z += n.normal.x;
}
// Left column.
for y in BOUNDS.y0 as u32 + 1..BOUNDS.y1 as u32 - 1 {
let n = Normal::left_column(&surface, BOUNDS, y);
z += n.normal.x;
}
// Right column.
for y in BOUNDS.y0 as u32 + 1..BOUNDS.y1 as u32 - 1 {
let n = Normal::right_column(&surface, BOUNDS, y);
z += n.normal.x;
}
// Interior pixels.
for y in BOUNDS.y0 as u32 + 1..BOUNDS.y1 as u32 - 1 {
for x in BOUNDS.x0 as u32 + 1..BOUNDS.x1 as u32 - 1 {
let n = Normal::interior(&surface, BOUNDS, x, y);
z += n.normal.x;
}
}
z
})
});
}
criterion_group!(benches, bench_normal);
criterion_main!(benches);

View File

@@ -0,0 +1,74 @@
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use rsvg::bench_only::Lexer;
use rsvg::bench_only::PathBuilder;
static INPUT: &str = "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140";
static BYTES: &[u8; 49] = b"M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140";
static SLICE_EDGES: [(usize, usize); 14] = [
(1, 3),
(4, 6),
(9, 11),
(12, 14),
(15, 17),
(18, 20),
(20, 23),
(24, 26),
(27, 29),
(30, 33),
(34, 37),
(38, 41),
(42, 45),
(46, 49),
];
fn lex_path(input: &str) {
let lexer = Lexer::new(black_box(input));
for (_pos, _token) in lexer {
// no-op
}
}
fn path_parser(c: &mut Criterion) {
c.bench_function("parse path into builder", |b| {
let input = black_box(INPUT);
b.iter(|| {
let mut builder = PathBuilder::default();
let _ = builder.parse(input);
});
});
c.bench_function("lex str", |b| {
let input = black_box(INPUT);
b.iter(|| {
lex_path(input);
});
});
// look at how much time *just* the parse::<i32> part of the lexer should be taking...
c.bench_function("std i32 parse (bytes)", |b| {
let input = black_box(BYTES);
let slice_boundaries = black_box(SLICE_EDGES);
b.iter(|| {
for (a, b) in slice_boundaries.iter() {
let a: usize = *a;
let b: usize = *b;
unsafe {
let _ = std::str::from_utf8_unchecked(&input[a..b])
.parse::<i32>()
.unwrap();
}
}
});
});
}
criterion_group!(benches, path_parser);
criterion_main!(benches);

View File

@@ -0,0 +1,96 @@
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use rsvg::bench_only::{ExclusiveImageSurface, IRect, Pixels, SharedImageSurface, SurfaceType};
const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
x0: 64,
y0: 32,
x1: 448,
y1: 480,
};
fn bench_pixel_iterators(c: &mut Criterion) {
c.bench_function("pixel_iterators straightforward", |b| {
let mut surface =
ExclusiveImageSurface::new(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let stride = surface.stride() as i32;
let data = surface.data();
let bounds = black_box(BOUNDS);
b.iter(|| {
let mut r = 0usize;
let mut g = 0usize;
let mut b = 0usize;
let mut a = 0usize;
for y in bounds.y_range() {
for x in bounds.x_range() {
let base = (y * stride + x * 4) as usize;
r += data[base] as usize;
g += data[base + 1] as usize;
b += data[base + 2] as usize;
a += data[base + 3] as usize;
}
}
(r, g, b, a)
})
});
c.bench_function("pixel_iterators get_pixel", |b| {
let surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let bounds = black_box(BOUNDS);
b.iter(|| {
let mut r = 0usize;
let mut g = 0usize;
let mut b = 0usize;
let mut a = 0usize;
for y in bounds.y_range() {
for x in bounds.x_range() {
let pixel = surface.get_pixel(x as u32, y as u32);
r += pixel.r as usize;
g += pixel.g as usize;
b += pixel.b as usize;
a += pixel.a as usize;
}
}
(r, g, b, a)
})
});
c.bench_function("pixel_iterators pixels", |b| {
let surface =
SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let bounds = black_box(BOUNDS);
b.iter(|| {
let mut r = 0usize;
let mut g = 0usize;
let mut b = 0usize;
let mut a = 0usize;
for (_x, _y, pixel) in Pixels::within(&surface, bounds) {
r += pixel.r as usize;
g += pixel.g as usize;
b += pixel.b as usize;
a += pixel.a as usize;
}
(r, g, b, a)
})
});
}
criterion_group!(benches, bench_pixel_iterators);
criterion_main!(benches);

57
rsvg/benches/pixel_ops.rs Normal file
View File

@@ -0,0 +1,57 @@
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use rsvg::bench_only::{Pixel, PixelOps};
const OTHER: Pixel = Pixel {
r: 0x10,
g: 0x20,
b: 0x30,
a: 0x40,
};
const N: usize = 1024;
fn make_pixels(n: usize) -> Vec<Pixel> {
(0..n)
.map(|i| Pixel {
r: (i / 2) as u8,
g: (i / 3) as u8,
b: (i / 4) as u8,
a: i as u8,
})
.collect()
}
fn bench_op<F>(pixels: &[Pixel], op: F)
where
F: Fn(&Pixel) -> Pixel,
{
let result: Vec<Pixel> = pixels.iter().map(op).collect();
black_box(result);
}
fn bench_pixel_ops(c: &mut Criterion) {
c.bench_function("pixel_diff", |b| {
let pixels = black_box(make_pixels(N));
let other = black_box(OTHER);
b.iter(|| bench_op(&pixels, |pixel| pixel.diff(&other)))
});
c.bench_function("pixel_to_luminance_mask", |b| {
let pixels = black_box(make_pixels(N));
b.iter(|| bench_op(&pixels, |pixel| pixel.to_luminance_mask()))
});
c.bench_function("pixel_premultiply", |b| {
let pixels = black_box(make_pixels(N));
b.iter(|| bench_op(&pixels, |pixel| pixel.premultiply()))
});
c.bench_function("pixel_unpremultiply", |b| {
let pixels = black_box(make_pixels(N));
b.iter(|| bench_op(&pixels, |pixel| pixel.unpremultiply()))
});
}
criterion_group!(benches, bench_pixel_ops);
criterion_main!(benches);

50
rsvg/benches/srgb.rs Normal file
View File

@@ -0,0 +1,50 @@
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use rsvg::bench_only::{
ExclusiveImageSurface, IRect, ImageSurfaceDataExt, Pixel, SurfaceType, linearize,
map_unpremultiplied_components_loop,
};
const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
x0: 64,
y0: 32,
x1: 448,
y1: 480,
};
fn bench_srgb_linearization(c: &mut Criterion) {
c.bench_function("srgb map_unpremultiplied_components", |b| {
let mut surface =
ExclusiveImageSurface::new(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::LinearRgb).unwrap();
// Fill the surface with non-zero alpha (otherwise linearization is a no-op).
surface.modify(&mut |data, stride| {
for y in BOUNDS.y_range() {
for x in BOUNDS.x_range() {
let pixel = Pixel {
r: 0,
g: 0,
b: 0,
a: 127,
};
data.set_pixel(stride, pixel, x as u32, y as u32);
}
}
});
let surface = surface.share().unwrap();
let mut output_surface =
ExclusiveImageSurface::new(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
let bounds = black_box(BOUNDS);
b.iter(|| {
map_unpremultiplied_components_loop(&surface, &mut output_surface, bounds, linearize);
})
});
}
criterion_group!(benches, bench_srgb_linearization);
criterion_main!(benches);