@@ -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) ]
13801678mod benches {
0 commit comments