Skip to content

Commit 4e935b5

Browse files
committed
add proptests for geometric-transforms
1 parent e0a07f6 commit 4e935b5

1 file changed

Lines changed: 298 additions & 0 deletions

File tree

src/geometric_transformations.rs

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,10 @@ where
765765
Fi: Fn(f32, f32) -> P + Send + Sync,
766766
{
767767
let width = out.width();
768+
if width == 0 {
769+
return;
770+
}
771+
768772
let raw_out = out.as_mut();
769773
let pitch = P::CHANNEL_COUNT as usize * width as usize;
770774

@@ -1375,6 +1379,300 @@ mod tests {
13751379
}
13761380
}
13771381

1382+
#[cfg(not(miri))]
1383+
#[cfg(test)]
1384+
mod proptests {
1385+
use super::*;
1386+
use crate::proptest_utils::arbitrary_image;
1387+
use image::GenericImageView;
1388+
use image::{GrayImage, Luma};
1389+
use proptest::prelude::*;
1390+
1391+
const EPS: f32 = 1e-3;
1392+
1393+
fn assert_point_close(actual: (f32, f32), expected: (f32, f32)) -> Result<(), TestCaseError> {
1394+
prop_assert!((actual.0 - expected.0).abs() <= EPS);
1395+
prop_assert!((actual.1 - expected.1).abs() <= EPS);
1396+
Ok(())
1397+
}
1398+
1399+
proptest! {
1400+
#[test]
1401+
fn proptest_projection_from_matrix_translation(
1402+
tx in -20.0f32..20.0,
1403+
ty in -20.0f32..20.0,
1404+
x in -20.0f32..20.0,
1405+
y in -20.0f32..20.0,
1406+
) {
1407+
#[rustfmt::skip]
1408+
let projection = Projection::from_matrix([
1409+
1.0, 0.0, tx,
1410+
0.0, 1.0, ty,
1411+
0.0, 0.0, 1.0,
1412+
]).unwrap();
1413+
1414+
assert_point_close(projection * (x, y), (x + tx, y + ty))?;
1415+
}
1416+
1417+
#[test]
1418+
fn proptest_projection_and_then_matches_sequential_application(
1419+
tx in -20.0f32..20.0,
1420+
ty in -20.0f32..20.0,
1421+
sx in 0.5f32..4.0,
1422+
sy in 0.5f32..4.0,
1423+
x in -20.0f32..20.0,
1424+
y in -20.0f32..20.0,
1425+
) {
1426+
let first = Projection::translate(tx, ty);
1427+
let second = Projection::scale(sx, sy);
1428+
let projection = first.and_then(second);
1429+
1430+
assert_point_close(projection * (x, y), second * (first * (x, y)))?;
1431+
}
1432+
1433+
#[test]
1434+
fn proptest_projection_translate_offsets_points(
1435+
tx in -20.0f32..20.0,
1436+
ty in -20.0f32..20.0,
1437+
x in -20.0f32..20.0,
1438+
y in -20.0f32..20.0,
1439+
) {
1440+
assert_point_close(Projection::translate(tx, ty) * (x, y), (x + tx, y + ty))?;
1441+
}
1442+
1443+
#[test]
1444+
fn proptest_projection_rotate_zero_is_identity(x in -20.0f32..20.0, y in -20.0f32..20.0) {
1445+
assert_point_close(Projection::rotate(0.0) * (x, y), (x, y))?;
1446+
}
1447+
1448+
#[test]
1449+
fn proptest_projection_scale_scales_points(
1450+
sx in 0.5f32..4.0,
1451+
sy in 0.5f32..4.0,
1452+
x in -20.0f32..20.0,
1453+
y in -20.0f32..20.0,
1454+
) {
1455+
assert_point_close(Projection::scale(sx, sy) * (x, y), (sx * x, sy * y))?;
1456+
}
1457+
1458+
#[test]
1459+
fn proptest_projection_invert_round_trips_translation(
1460+
tx in -20.0f32..20.0,
1461+
ty in -20.0f32..20.0,
1462+
x in -20.0f32..20.0,
1463+
y in -20.0f32..20.0,
1464+
) {
1465+
let projection = Projection::translate(tx, ty);
1466+
1467+
assert_point_close(projection.invert() * (projection * (x, y)), (x, y))?;
1468+
}
1469+
1470+
#[test]
1471+
fn proptest_projection_from_control_points_translation(
1472+
tx in -20.0f32..20.0,
1473+
ty in -20.0f32..20.0,
1474+
x in 0.0f32..10.0,
1475+
y in 0.0f32..10.0,
1476+
) {
1477+
let from = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
1478+
let to = [(tx, ty), (10.0 + tx, ty), (10.0 + tx, 10.0 + ty), (tx, 10.0 + ty)];
1479+
let projection = Projection::from_control_points(from, to).unwrap();
1480+
1481+
assert_point_close(projection * (x, y), (x + tx, y + ty))?;
1482+
}
1483+
1484+
#[test]
1485+
fn proptest_rotate_about_center_zero_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1486+
let rotated = rotate_about_center(
1487+
&img,
1488+
0.0,
1489+
Interpolation::Nearest,
1490+
Border::Constant(Luma([0])),
1491+
);
1492+
1493+
prop_assert_eq!(rotated, img);
1494+
}
1495+
1496+
#[test]
1497+
fn proptest_rotate_zero_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1498+
let rotated = rotate(
1499+
&img,
1500+
(0.0, 0.0),
1501+
0.0,
1502+
Interpolation::Nearest,
1503+
Border::Constant(Luma([0])),
1504+
);
1505+
1506+
prop_assert_eq!(rotated, img);
1507+
}
1508+
1509+
#[test]
1510+
fn proptest_rotate_about_center_no_crop_zero_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1511+
let rotated = rotate_about_center_no_crop(
1512+
&img,
1513+
0.0,
1514+
Interpolation::Nearest,
1515+
Border::Constant(Luma([0])),
1516+
);
1517+
1518+
prop_assert_eq!(rotated, img);
1519+
}
1520+
1521+
#[test]
1522+
fn proptest_rotate90_four_times_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1523+
let rotated = rotate90(&rotate90(&rotate90(&rotate90(&img))));
1524+
1525+
prop_assert_eq!(rotated, img);
1526+
}
1527+
1528+
#[test]
1529+
fn proptest_rotate270_four_times_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1530+
let rotated = rotate270(&rotate270(&rotate270(&rotate270(&img))));
1531+
1532+
prop_assert_eq!(rotated, img);
1533+
}
1534+
1535+
#[test]
1536+
fn proptest_rotate180_twice_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1537+
let rotated = rotate180(&rotate180(&img));
1538+
1539+
prop_assert_eq!(rotated, img);
1540+
}
1541+
1542+
#[test]
1543+
fn proptest_rotate180_mut_matches_rotate180(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1544+
let expected = rotate180(&img);
1545+
let mut actual = img.clone();
1546+
rotate180_mut(&mut actual);
1547+
1548+
prop_assert_eq!(actual, expected);
1549+
}
1550+
1551+
#[test]
1552+
fn proptest_translate_zero_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1553+
let translated = translate(&img, (0, 0), Border::Constant(Luma([0])));
1554+
1555+
prop_assert_eq!(translated, img);
1556+
}
1557+
1558+
#[test]
1559+
fn proptest_translate_constant_matches_coordinate_mapping(
1560+
img in arbitrary_image::<Luma<u8>>(0..8, 0..8),
1561+
tx in -10i32..10,
1562+
ty in -10i32..10,
1563+
) {
1564+
let default = Luma([251]);
1565+
let translated = translate(&img, (tx, ty), Border::Constant(default));
1566+
let (width, height) = img.dimensions();
1567+
1568+
prop_assert_eq!(translated.dimensions(), img.dimensions());
1569+
1570+
for y in 0..height {
1571+
for x in 0..width {
1572+
let src_x = x as i64 - tx as i64;
1573+
let src_y = y as i64 - ty as i64;
1574+
let expected = if src_x >= 0
1575+
&& src_y >= 0
1576+
&& img.in_bounds(src_x as u32, src_y as u32)
1577+
{
1578+
*img.get_pixel(src_x as u32, src_y as u32)
1579+
} else {
1580+
default
1581+
};
1582+
1583+
prop_assert_eq!(*translated.get_pixel(x, y), expected);
1584+
}
1585+
}
1586+
}
1587+
1588+
#[test]
1589+
fn proptest_translate_replicate_matches_coordinate_mapping(
1590+
img in arbitrary_image::<Luma<u8>>(1..8, 1..8),
1591+
tx in -10i32..10,
1592+
ty in -10i32..10,
1593+
) {
1594+
let translated = translate(&img, (tx, ty), Border::Replicate);
1595+
let (width, height) = img.dimensions();
1596+
1597+
prop_assert_eq!(translated.dimensions(), img.dimensions());
1598+
1599+
for y in 0..height {
1600+
for x in 0..width {
1601+
let src_x = (x as i64 - tx as i64).clamp(0, width as i64 - 1) as u32;
1602+
let src_y = (y as i64 - ty as i64).clamp(0, height as i64 - 1) as u32;
1603+
1604+
prop_assert_eq!(*translated.get_pixel(x, y), *img.get_pixel(src_x, src_y));
1605+
}
1606+
}
1607+
}
1608+
1609+
#[test]
1610+
fn proptest_warp_identity_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1611+
let warped = warp(
1612+
&img,
1613+
Projection::translate(0.0, 0.0),
1614+
Interpolation::Nearest,
1615+
Border::Constant(Luma([0])),
1616+
);
1617+
1618+
prop_assert_eq!(warped, img);
1619+
}
1620+
1621+
#[test]
1622+
fn proptest_warp_into_matches_warp(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1623+
let projection = Projection::translate(0.0, 0.0);
1624+
let expected = warp(
1625+
&img,
1626+
projection,
1627+
Interpolation::Nearest,
1628+
Border::Constant(Luma([0])),
1629+
);
1630+
let mut actual = GrayImage::new(img.width(), img.height());
1631+
warp_into(
1632+
&img,
1633+
projection,
1634+
Interpolation::Nearest,
1635+
Border::Constant(Luma([0])),
1636+
&mut actual,
1637+
);
1638+
1639+
prop_assert_eq!(actual, expected);
1640+
}
1641+
1642+
#[test]
1643+
fn proptest_warp_with_identity_is_identity(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1644+
let warped = warp_with(
1645+
&img,
1646+
|x, y| (x, y),
1647+
Interpolation::Nearest,
1648+
Border::Constant(Luma([0])),
1649+
);
1650+
1651+
prop_assert_eq!(warped, img);
1652+
}
1653+
1654+
#[test]
1655+
fn proptest_warp_into_with_matches_warp_with(img in arbitrary_image::<Luma<u8>>(0..8, 0..8)) {
1656+
let expected = warp_with(
1657+
&img,
1658+
|x, y| (x, y),
1659+
Interpolation::Nearest,
1660+
Border::Constant(Luma([0])),
1661+
);
1662+
let mut actual = GrayImage::new(img.width(), img.height());
1663+
warp_into_with(
1664+
&img,
1665+
|x, y| (x, y),
1666+
Interpolation::Nearest,
1667+
Border::Constant(Luma([0])),
1668+
&mut actual,
1669+
);
1670+
1671+
prop_assert_eq!(actual, expected);
1672+
}
1673+
}
1674+
}
1675+
13781676
#[cfg(not(miri))]
13791677
#[cfg(test)]
13801678
mod benches {

0 commit comments

Comments
 (0)