@@ -75,18 +75,32 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
75
75
let bmp_header_size = BITMAPFILEHEADER_SIZE ;
76
76
77
77
let ( dib_header_size, written_pixel_size, palette_color_count) =
78
- get_pixel_info ( color_type, palette) ?;
79
- let row_pad_size = ( 4 - ( width * written_pixel_size) % 4 ) % 4 ; // each row must be padded to a multiple of 4 bytes
80
- let image_size = width
81
- . checked_mul ( height)
82
- . and_then ( |v| v. checked_mul ( written_pixel_size) )
83
- . and_then ( |v| v. checked_add ( height * row_pad_size) )
78
+ written_pixel_info ( color_type, palette) ?;
79
+
80
+ let ( padded_row, image_size) = width
81
+ . checked_mul ( written_pixel_size)
82
+ // each row must be padded to a multiple of 4 bytes
83
+ . and_then ( |v| v. checked_next_multiple_of ( 4 ) )
84
+ . and_then ( |v| {
85
+ let image_bytes = v. checked_mul ( height) ?;
86
+ Some ( ( v, image_bytes) )
87
+ } )
84
88
. ok_or_else ( || {
85
89
ImageError :: Parameter ( ParameterError :: from_kind (
86
90
ParameterErrorKind :: DimensionMismatch ,
87
91
) )
88
92
} ) ?;
89
- let palette_size = palette_color_count * 4 ; // all palette colors are BGRA
93
+
94
+ let row_padding = padded_row - width * written_pixel_size;
95
+
96
+ // all palette colors are BGRA
97
+ let palette_size = palette_color_count. checked_mul ( 4 ) . ok_or_else ( || {
98
+ ImageError :: Encoding ( EncodingError :: new (
99
+ ImageFormatHint :: Exact ( ImageFormat :: Bmp ) ,
100
+ "calculated palette size larger than 2^32" ,
101
+ ) )
102
+ } ) ?;
103
+
90
104
let file_size = bmp_header_size
91
105
. checked_add ( dib_header_size)
92
106
. and_then ( |v| v. checked_add ( palette_size) )
@@ -98,14 +112,23 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
98
112
) )
99
113
} ) ?;
100
114
115
+ let image_data_offset = bmp_header_size
116
+ . checked_add ( dib_header_size)
117
+ . and_then ( |v| v. checked_add ( palette_size) )
118
+ . ok_or_else ( || {
119
+ ImageError :: Encoding ( EncodingError :: new (
120
+ ImageFormatHint :: Exact ( ImageFormat :: Bmp ) ,
121
+ "calculated BMP size larger than 2^32" ,
122
+ ) )
123
+ } ) ?;
124
+
101
125
// write BMP header
102
126
self . writer . write_u8 ( b'B' ) ?;
103
127
self . writer . write_u8 ( b'M' ) ?;
104
128
self . writer . write_u32 :: < LittleEndian > ( file_size) ?; // file size
105
129
self . writer . write_u16 :: < LittleEndian > ( 0 ) ?; // reserved 1
106
130
self . writer . write_u16 :: < LittleEndian > ( 0 ) ?; // reserved 2
107
- self . writer
108
- . write_u32 :: < LittleEndian > ( bmp_header_size + dib_header_size + palette_size) ?; // image data offset
131
+ self . writer . write_u32 :: < LittleEndian > ( image_data_offset) ?; // image data offset
109
132
110
133
// write DIB header
111
134
self . writer . write_u32 :: < LittleEndian > ( dib_header_size) ?;
@@ -141,13 +164,13 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
141
164
142
165
// write image data
143
166
match color_type {
144
- ExtendedColorType :: Rgb8 => self . encode_rgb ( image, width, height, row_pad_size , 3 ) ?,
145
- ExtendedColorType :: Rgba8 => self . encode_rgba ( image, width, height, row_pad_size , 4 ) ?,
167
+ ExtendedColorType :: Rgb8 => self . encode_rgb ( image, width, height, row_padding , 3 ) ?,
168
+ ExtendedColorType :: Rgba8 => self . encode_rgba ( image, width, height, row_padding , 4 ) ?,
146
169
ExtendedColorType :: L8 => {
147
- self . encode_gray ( image, width, height, row_pad_size , 1 , palette) ?;
170
+ self . encode_gray ( image, width, height, row_padding , 1 , palette) ?;
148
171
}
149
172
ExtendedColorType :: La8 => {
150
- self . encode_gray ( image, width, height, row_pad_size , 2 , palette) ?;
173
+ self . encode_gray ( image, width, height, row_padding , 2 , palette) ?;
151
174
}
152
175
_ => {
153
176
return Err ( ImageError :: Unsupported (
@@ -167,7 +190,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
167
190
image : & [ u8 ] ,
168
191
width : u32 ,
169
192
height : u32 ,
170
- row_pad_size : u32 ,
193
+ row_padding : u32 ,
171
194
bytes_per_pixel : u32 ,
172
195
) -> io:: Result < ( ) > {
173
196
let width = width as usize ;
@@ -184,7 +207,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
184
207
// written as BGR
185
208
self . writer . write_all ( & [ b, g, r] ) ?;
186
209
}
187
- self . write_row_pad ( row_pad_size ) ?;
210
+ self . write_row_pad ( row_padding ) ?;
188
211
}
189
212
190
213
Ok ( ( ) )
@@ -195,7 +218,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
195
218
image : & [ u8 ] ,
196
219
width : u32 ,
197
220
height : u32 ,
198
- row_pad_size : u32 ,
221
+ row_padding : u32 ,
199
222
bytes_per_pixel : u32 ,
200
223
) -> io:: Result < ( ) > {
201
224
let width = width as usize ;
@@ -213,7 +236,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
213
236
// written as BGRA
214
237
self . writer . write_all ( & [ b, g, r, a] ) ?;
215
238
}
216
- self . write_row_pad ( row_pad_size ) ?;
239
+ self . write_row_pad ( row_padding ) ?;
217
240
}
218
241
219
242
Ok ( ( ) )
@@ -224,7 +247,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
224
247
image : & [ u8 ] ,
225
248
width : u32 ,
226
249
height : u32 ,
227
- row_pad_size : u32 ,
250
+ row_padding : u32 ,
228
251
bytes_per_pixel : u32 ,
229
252
palette : Option < & [ [ u8 ; 3 ] ] > ,
230
253
) -> io:: Result < ( ) > {
@@ -261,7 +284,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
261
284
}
262
285
}
263
286
264
- self . write_row_pad ( row_pad_size ) ?;
287
+ self . write_row_pad ( row_padding ) ?;
265
288
}
266
289
267
290
Ok ( ( ) )
@@ -297,37 +320,42 @@ impl<W: Write> ImageEncoder for BmpEncoder<'_, W> {
297
320
}
298
321
}
299
322
300
- fn get_unsupported_error_message ( c : ExtendedColorType ) -> String {
301
- format ! ( "Unsupported color type {c:?}. Supported types: RGB(8), RGBA(8), Gray(8), GrayA(8)." )
302
- }
303
-
304
323
/// Returns a tuple representing: (dib header size, written pixel size, palette color count).
305
- fn get_pixel_info (
324
+ fn written_pixel_info (
306
325
c : ExtendedColorType ,
307
326
palette : Option < & [ [ u8 ; 3 ] ] > ,
308
- ) -> io :: Result < ( u32 , u32 , u32 ) > {
309
- let sizes = match c {
310
- ExtendedColorType :: Rgb8 => ( BITMAPINFOHEADER_SIZE , 3 , 0 ) ,
311
- ExtendedColorType :: Rgba8 => ( BITMAPV4HEADER_SIZE , 4 , 0 ) ,
327
+ ) -> Result < ( u32 , u32 , u32 ) , ImageError > {
328
+ let ( header , color_bytes , palette_count ) = match c {
329
+ ExtendedColorType :: Rgb8 => ( BITMAPINFOHEADER_SIZE , 3 , Some ( 0 ) ) ,
330
+ ExtendedColorType :: Rgba8 => ( BITMAPV4HEADER_SIZE , 4 , Some ( 0 ) ) ,
312
331
ExtendedColorType :: L8 => (
313
332
BITMAPINFOHEADER_SIZE ,
314
333
1 ,
315
- palette. map ( |p| p. len ( ) ) . unwrap_or ( 256 ) as u32 ,
334
+ u32 :: try_from ( palette. map ( |p| p. len ( ) ) . unwrap_or ( 256 ) ) . ok ( ) ,
316
335
) ,
317
336
ExtendedColorType :: La8 => (
318
337
BITMAPINFOHEADER_SIZE ,
319
338
1 ,
320
- palette. map ( |p| p. len ( ) ) . unwrap_or ( 256 ) as u32 ,
339
+ u32 :: try_from ( palette. map ( |p| p. len ( ) ) . unwrap_or ( 256 ) ) . ok ( ) ,
321
340
) ,
322
341
_ => {
323
- return Err ( io:: Error :: new (
324
- io:: ErrorKind :: InvalidInput ,
325
- & get_unsupported_error_message ( c) [ ..] ,
326
- ) )
342
+ return Err ( ImageError :: Unsupported (
343
+ UnsupportedError :: from_format_and_kind (
344
+ ImageFormat :: Bmp . into ( ) ,
345
+ UnsupportedErrorKind :: Color ( c) ,
346
+ ) ,
347
+ ) ) ;
327
348
}
328
349
} ;
329
350
330
- Ok ( sizes)
351
+ let palette_count = palette_count. ok_or_else ( || {
352
+ ImageError :: Encoding ( EncodingError :: new (
353
+ ImageFormatHint :: Exact ( ImageFormat :: Bmp ) ,
354
+ "calculated palette size larger than 2^32" ,
355
+ ) )
356
+ } ) ?;
357
+
358
+ Ok ( ( header, color_bytes, palette_count) )
331
359
}
332
360
333
361
#[ cfg( test) ]
@@ -421,4 +449,13 @@ mod tests {
421
449
assert_eq ! ( 2 , decoded[ 7 ] ) ;
422
450
assert_eq ! ( 2 , decoded[ 8 ] ) ;
423
451
}
452
+
453
+ #[ test]
454
+ fn regression_issue_2604 ( ) {
455
+ let mut image = vec ! [ ] ;
456
+ let mut encoder = BmpEncoder :: new ( & mut image) ;
457
+ encoder
458
+ . encode ( & [ ] , 1 << 31 , 0 , ExtendedColorType :: Rgb8 )
459
+ . unwrap_err ( ) ;
460
+ }
424
461
}
0 commit comments