6
6
//
7
7
// It returns a Vec<Lab>, which can be passed to the k-means process.
8
8
9
- use std:: ffi :: OsStr ;
9
+ use std:: fmt :: Display ;
10
10
use std:: fs:: File ;
11
11
use std:: io:: BufReader ;
12
12
use std:: path:: PathBuf ;
13
13
14
14
use image:: codecs:: gif:: GifDecoder ;
15
+ use image:: codecs:: webp:: WebPDecoder ;
15
16
use image:: imageops:: FilterType ;
16
- use image:: { AnimationDecoder , DynamicImage , Frame } ;
17
+ use image:: { AnimationDecoder , DynamicImage , Frame , ImageFormat } ;
17
18
use palette:: cast:: from_component_slice;
18
19
use palette:: { IntoColor , Lab , Srgba } ;
19
20
20
- pub fn get_image_colors ( path : & PathBuf ) -> Vec < Lab > {
21
- let image_bytes = match path. extension ( ) . and_then ( OsStr :: to_str) {
22
- Some ( ext) if ext. to_lowercase ( ) == "gif" => get_bytes_for_gif ( & path) ,
23
- _ => get_bytes_for_non_gif ( & path) ,
21
+ pub fn get_image_colors ( path : & PathBuf ) -> Result < Vec < Lab > , GetImageColorsErr > {
22
+ let format = get_format ( path) ?;
23
+
24
+ let f = File :: open ( path) ?;
25
+ let reader = BufReader :: new ( f) ;
26
+
27
+ let image_bytes = match format {
28
+ ImageFormat :: Gif => {
29
+ let decoder = GifDecoder :: new ( reader) ?;
30
+ get_bytes_for_animated_image ( decoder)
31
+ }
32
+
33
+ ImageFormat :: WebP => {
34
+ let decoder = WebPDecoder :: new ( reader) ?;
35
+ get_bytes_for_animated_image ( decoder)
36
+ }
37
+
38
+ format => {
39
+ let decoder = image:: load ( reader, format) ?;
40
+ get_bytes_for_static_image ( decoder)
41
+ }
24
42
} ;
25
43
26
44
let lab: Vec < Lab > = from_component_slice :: < Srgba < u8 > > ( & image_bytes)
27
45
. iter ( )
28
46
. map ( |x| x. into_format :: < _ , f32 > ( ) . into_color ( ) )
29
47
. collect ( ) ;
30
48
31
- lab
49
+ Ok ( lab)
32
50
}
33
51
34
- fn get_bytes_for_non_gif ( path : & PathBuf ) -> Vec < u8 > {
35
- let img = match image:: open ( & path) {
36
- Ok ( im) => im,
37
- Err ( e) => {
38
- eprintln ! ( "{}" , e) ;
39
- std:: process:: exit ( 1 ) ;
52
+ pub enum GetImageColorsErr {
53
+ IoError ( std:: io:: Error ) ,
54
+ ImageError ( image:: ImageError ) ,
55
+ GetFormatError ( String ) ,
56
+ }
57
+
58
+ impl Display for GetImageColorsErr {
59
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
60
+ match self {
61
+ GetImageColorsErr :: IoError ( io_error) => write ! ( f, "{}" , io_error) ,
62
+ GetImageColorsErr :: ImageError ( image_error) => write ! ( f, "{}" , image_error) ,
63
+ GetImageColorsErr :: GetFormatError ( format_error) => write ! ( f, "{}" , format_error) ,
40
64
}
65
+ }
66
+ }
67
+
68
+ impl From < std:: io:: Error > for GetImageColorsErr {
69
+ fn from ( e : std:: io:: Error ) -> GetImageColorsErr {
70
+ return GetImageColorsErr :: IoError ( e) ;
71
+ }
72
+ }
73
+
74
+ impl From < image:: ImageError > for GetImageColorsErr {
75
+ fn from ( e : image:: ImageError ) -> GetImageColorsErr {
76
+ return GetImageColorsErr :: ImageError ( e) ;
77
+ }
78
+ }
79
+
80
+ fn get_format ( path : & PathBuf ) -> Result < ImageFormat , GetImageColorsErr > {
81
+ let format = match path. extension ( ) {
82
+ Some ( ext) => Ok ( image:: ImageFormat :: from_extension ( ext) ) ,
83
+ None => Err ( GetImageColorsErr :: GetFormatError (
84
+ "Path has no file extension, so could not determine image format" . to_string ( ) ,
85
+ ) ) ,
41
86
} ;
42
87
88
+ match format {
89
+ Ok ( Some ( format) ) => Ok ( format) ,
90
+ Ok ( None ) => Err ( GetImageColorsErr :: GetFormatError (
91
+ "Unable to determine image format from file extension" . to_string ( ) ,
92
+ ) ) ,
93
+ Err ( e) => Err ( e) ,
94
+ }
95
+ }
96
+
97
+ fn get_bytes_for_static_image ( img : DynamicImage ) -> Vec < u8 > {
43
98
// Resize the image after we open it. For this tool I'd rather get a good answer
44
99
// quickly than a great answer slower.
45
100
//
@@ -60,20 +115,10 @@ fn get_bytes_for_non_gif(path: &PathBuf) -> Vec<u8> {
60
115
resized_img. into_rgba8 ( ) . into_raw ( )
61
116
}
62
117
63
- fn get_bytes_for_gif ( path : & PathBuf ) -> Vec < u8 > {
64
- let f = match File :: open ( path) {
65
- Ok ( im) => im,
66
- Err ( e) => {
67
- eprintln ! ( "{}" , e) ;
68
- std:: process:: exit ( 1 ) ;
69
- }
70
- } ;
71
-
72
- let f = BufReader :: new ( f) ;
73
-
74
- let decoder = GifDecoder :: new ( f) . ok ( ) . unwrap ( ) ;
118
+ fn get_bytes_for_animated_image < ' a > ( decoder : impl AnimationDecoder < ' a > ) -> Vec < u8 > {
119
+ let frames: Vec < Frame > = decoder. into_frames ( ) . collect_frames ( ) . unwrap ( ) ;
75
120
76
- // If the GIF is animated, we want to make sure we look at multiple
121
+ // If the image is animated, we want to make sure we look at multiple
77
122
// frames when choosing the dominant colour.
78
123
//
79
124
// We don't want to pass all the frames to the k-means analysis, because
@@ -82,8 +127,7 @@ fn get_bytes_for_gif(path: &PathBuf) -> Vec<u8> {
82
127
//
83
128
// For that reason, we select a sample of up to 50 frames and use those
84
129
// as the basis for analysis.
85
- let frames: Vec < Frame > = decoder. into_frames ( ) . collect_frames ( ) . unwrap ( ) ;
86
-
130
+ //
87
131
// How this works: it tells us we should be looking at the nth frame.
88
132
// Examples:
89
133
//
@@ -144,11 +188,11 @@ mod test {
144
188
// processed correctly.
145
189
#[ test]
146
190
fn it_gets_colors_for_mri_fruit ( ) {
147
- get_image_colors ( & PathBuf :: from ( "./src/tests/garlic.gif" ) ) ;
191
+ assert ! ( get_image_colors( & PathBuf :: from( "./src/tests/garlic.gif" ) ) . is_ok ( ) ) ;
148
192
}
149
193
150
194
#[ test]
151
195
fn get_colors_for_webp ( ) {
152
- get_image_colors ( & PathBuf :: from ( "./src/tests/purple.webp" ) ) ;
196
+ assert ! ( get_image_colors( & PathBuf :: from( "./src/tests/purple.webp" ) ) . is_ok ( ) ) ;
153
197
}
154
198
}
0 commit comments