97
97
98
98
#![ deny(
99
99
nonstandard_style,
100
- unused,
100
+ // unused,
101
101
unsafe_code,
102
102
future_incompatible,
103
103
rust_2018_idioms,
106
106
clippy:: pedantic
107
107
) ]
108
108
109
+ use std:: fmt:: Debug ;
110
+
109
111
use image:: { DynamicImage , GenericImage , GenericImageView , Pixel } ;
110
- use num_traits:: { Num , NumCast } ;
112
+ use num_traits:: { clamp , Num , NumCast } ;
111
113
use thiserror:: Error ;
112
114
113
115
/// An enumeration of the errors that may be emitted from the `image_pyramid`
@@ -181,24 +183,24 @@ fn accumulate<P, K>(acc: &mut [K], pixel: &P, weight: K)
181
183
where
182
184
P : Pixel ,
183
185
<P as Pixel >:: Subpixel : Into < K > ,
184
- K : Num + Copy ,
186
+ K : Num + Copy + Debug ,
185
187
{
186
188
acc
187
189
. iter_mut ( )
188
- . enumerate ( )
189
- . take ( P :: CHANNEL_COUNT as usize )
190
- . for_each ( | ( i , a ) | {
191
- * a = * a + pixel . channels ( ) [ i ] . into ( ) * weight ;
190
+ . zip ( pixel . channels ( ) . iter ( ) )
191
+ . for_each ( | ( a , c ) | {
192
+ let new_val = << P as Pixel > :: Subpixel as Into < K > > :: into ( * c ) * weight ;
193
+ * a = * a + new_val ;
192
194
} ) ;
193
195
}
194
196
195
- pub struct Kernel < K > {
197
+ struct Kernel < K > {
196
198
data : Vec < K > ,
197
199
width : u32 ,
198
200
height : u32 ,
199
201
}
200
202
201
- impl < K : Num + Copy > Kernel < K > {
203
+ impl < K : Num + Copy + Debug > Kernel < K > {
202
204
/// Construct a kernel from a slice and its dimensions. The input slice is
203
205
/// in row-major form. For example, a 3x3 matrix with data
204
206
/// `[0,1,0,1,2,1,0,1,0`] describes the following matrix:
@@ -261,6 +263,10 @@ impl<K: Num + Copy> Kernel<K> {
261
263
/// | 0 1 0 |
262
264
/// └ ┘
263
265
/// ```
266
+ /// ...where `6` is computed dynamically by summing the elements of the
267
+ /// kernel. In other words, all the weights in a normalized kernel sum to
268
+ /// 1.0. This is useful, as many filters have this property
269
+ ///
264
270
/// # Errors
265
271
///
266
272
/// - If `width == 0 || height == 0`, [`ImagePyramidError::Internal`] is
@@ -272,28 +278,30 @@ impl<K: Num + Copy> Kernel<K> {
272
278
///
273
279
/// In debug builds, this factory panics under the conditions that [`Err`] is
274
280
/// returned for release builds.
275
- pub fn new_normalized ( data : & [ K ] , width : u32 , height : u32 ) -> Result < Self , ImagePyramidError > {
281
+ pub fn new_normalized ( data : & [ K ] , width : u32 , height : u32 ) -> Result < Kernel < f32 > , ImagePyramidError >
282
+ where K : Into < f32 >
283
+ {
276
284
let mut sum = K :: zero ( ) ;
277
285
for i in data {
278
286
sum = sum + * i;
279
287
}
280
- let data_norm: Vec < K > = data. iter ( ) . map ( |x| * x / sum) . collect ( ) ;
281
- Self :: new ( & data_norm, width, height)
288
+ let data_norm: Vec < f32 > = data. iter ( ) . map ( |x| < K as Into < f32 > > :: into ( * x ) / < K as Into < f32 > > :: into ( sum) ) . collect ( ) ;
289
+ Kernel :: < f32 > :: new ( & data_norm, width, height)
282
290
}
283
291
284
292
/// Returns 2d correlation of an image. Intermediate calculations are
285
293
/// performed at type K, and the results converted to pixel Q via f. Pads by
286
294
/// continuity.
287
295
#[ allow( unsafe_code) ]
288
- pub fn filter < I , F > ( & self , image : & I , mut f : F ) -> I
296
+ #[ allow( unused) ]
297
+ pub fn filter_in_place < I , F > ( & self , image : & mut I , mut f : F )
289
298
where
290
299
I : GenericImage + Clone ,
291
300
<<I as GenericImageView >:: Pixel as Pixel >:: Subpixel : Into < K > ,
292
301
F : FnMut ( & mut <<I as GenericImageView >:: Pixel as Pixel >:: Subpixel , K ) ,
293
302
{
294
303
use core:: cmp:: { max, min} ;
295
304
let ( width, height) = image. dimensions ( ) ;
296
- let mut out: I = image. clone ( ) ;
297
305
let num_channels = <<I as GenericImageView >:: Pixel as Pixel >:: CHANNEL_COUNT as usize ;
298
306
let zero = K :: zero ( ) ;
299
307
let mut acc = vec ! [ zero; num_channels] ;
@@ -313,11 +321,11 @@ impl<K: Num + Copy> Kernel<K> {
313
321
for k_y in 0 ..k_height {
314
322
#[ allow( clippy:: cast_possible_truncation) ]
315
323
#[ allow( clippy:: cast_sign_loss) ]
316
- let y_p = min ( height - 1 , max ( 0 , y + k_y - k_height / 2 ) ) as u32 ;
324
+ let y_p = clamp ( y + k_y - k_height / 2 , 0 , height - 1 ) as u32 ;
317
325
for k_x in 0 ..k_width {
318
326
#[ allow( clippy:: cast_possible_truncation) ]
319
327
#[ allow( clippy:: cast_sign_loss) ]
320
- let x_p = min ( width - 1 , max ( 0 , x + k_x - k_width / 2 ) ) as u32 ;
328
+ let x_p = clamp ( x + k_x - k_width / 2 , 0 , width - 1 ) as u32 ;
321
329
#[ allow( clippy:: cast_possible_truncation) ]
322
330
#[ allow( clippy:: cast_sign_loss) ]
323
331
let k_idx = ( k_y * k_width + k_x) as usize ;
@@ -329,17 +337,15 @@ impl<K: Num + Copy> Kernel<K> {
329
337
) ;
330
338
}
331
339
}
332
- let mut out_pel = out . get_pixel ( x_u32, y_u32) ;
340
+ let mut out_pel = image . get_pixel ( x_u32, y_u32) ;
333
341
let out_channels = out_pel. channels_mut ( ) ;
334
342
for ( a, c) in acc. iter_mut ( ) . zip ( out_channels. iter_mut ( ) ) {
335
343
f ( c, * a) ;
336
344
* a = zero;
337
345
}
338
- out . put_pixel ( x_u32, y_u32, out_pel) ;
346
+ image . put_pixel ( x_u32, y_u32, out_pel) ;
339
347
}
340
348
}
341
-
342
- out
343
349
}
344
350
}
345
351
@@ -494,20 +500,20 @@ impl<'a> CanComputePyramid for ImageToProcess<'a> {
494
500
) -> Result < Vec < DynamicImage > , ImagePyramidError > {
495
501
let mut levels = vec ! [ image. clone( ) ] ;
496
502
let kernel = match params. smoothing_type {
497
- SmoothingType :: Gaussian => Kernel :: new_normalized ( & [ 1 , 2 , 3 , 2 , 4 , 2 , 1 , 2 , 1 ] , 3 , 3 ) ?,
498
- SmoothingType :: Box => Kernel :: new_normalized ( & [ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ] , 3 , 3 ) ?,
499
- SmoothingType :: Triangle => Kernel :: new_normalized ( & [ 1 , 2 , 1 , 2 , 4 , 2 , 1 , 2 , 1 ] , 3 , 3 ) ?,
503
+ SmoothingType :: Gaussian => Kernel :: new_normalized ( & [ 1u8 , 2 , 3 , 2 , 4 , 2 , 1 , 2 , 1 ] , 3 , 3 ) ?,
504
+ SmoothingType :: Box => Kernel :: new_normalized ( & [ 1u8 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ] , 3 , 3 ) ?,
505
+ SmoothingType :: Triangle => Kernel :: new_normalized ( & [ 1u8 , 2 , 1 , 2 , 4 , 2 , 1 , 2 , 1 ] , 3 , 3 ) ?,
500
506
} ;
501
507
let mut current_level = image. clone ( ) ;
502
508
#[ allow( clippy:: cast_possible_truncation) ]
503
509
#[ allow( clippy:: cast_precision_loss) ]
504
510
#[ allow( clippy:: cast_sign_loss) ]
505
511
while current_level. width ( ) > 1 && current_level. height ( ) > 1 {
506
- current_level = kernel. filter ( & current_level, |c, a| * c = num_traits :: clamp ( a , 0 , 255 ) ) ;
512
+ kernel. filter_in_place ( & mut current_level, |c, a| * c = a as u8 ) ;
507
513
current_level = current_level. resize_exact (
508
514
( current_level. width ( ) as f32 * params. scale_factor . get ( ) ) as u32 ,
509
515
( current_level. height ( ) as f32 * params. scale_factor . get ( ) ) as u32 ,
510
- image:: imageops:: FilterType :: Nearest ,
516
+ image:: imageops:: FilterType :: Gaussian ,
511
517
) ;
512
518
levels. push ( current_level. clone ( ) ) ;
513
519
}
@@ -591,6 +597,28 @@ mod tests {
591
597
592
598
use super :: * ;
593
599
600
+ #[ test]
601
+ fn kernel_filter_in_place ( ) {
602
+ let mut image = DynamicImage :: new_rgb8 ( 3 , 3 ) ;
603
+ let mut other = DynamicImage :: new_rgb8 ( 3 , 3 ) ;
604
+ let mut i = 0 ;
605
+ for y in 0 ..3 {
606
+ for x in 0 ..3 {
607
+ let mut pel = image. get_pixel ( x, y) ;
608
+ pel. apply_without_alpha ( |_| i) ;
609
+ image. put_pixel ( x, y, pel) ;
610
+
611
+ let mut pel = other. get_pixel ( x, y) ;
612
+ pel. apply_without_alpha ( |_| i + 1 ) ;
613
+ other. put_pixel ( x, y, pel) ;
614
+ i += 1 ;
615
+ }
616
+ }
617
+ let kernel = Kernel :: new_normalized ( & [ 1u8 , 2 , 1 , 2 , 4 , 2 , 1 , 2 , 1 ] , 3 , 3 ) . unwrap ( ) ;
618
+ kernel. filter_in_place ( & mut image, |c, a| * c = a as u8 ) ;
619
+ assert_eq ! ( image. get_pixel( 1 , 1 ) , image:: Rgba :: <u8 >( [ 4 , 4 , 4 , 255 ] ) ) ;
620
+ }
621
+
594
622
#[ test]
595
623
fn compute_image_pyramid_imagepyramidtype_steerable_unimplemented ( ) {
596
624
let image = DynamicImage :: new_rgb8 ( 640 , 480 ) ;
0 commit comments