1 | use super::header::Header; |
2 | use crate::{ |
3 | codecs::tga::header::ImageType, error::EncodingError, ColorType, ImageEncoder, ImageError, |
4 | ImageFormat, ImageResult, |
5 | }; |
6 | use std::{error, fmt, io::Write}; |
7 | |
8 | /// Errors that can occur during encoding and saving of a TGA image. |
9 | #[derive (Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] |
10 | enum EncoderError { |
11 | /// Invalid TGA width. |
12 | WidthInvalid(u32), |
13 | |
14 | /// Invalid TGA height. |
15 | HeightInvalid(u32), |
16 | } |
17 | |
18 | impl fmt::Display for EncoderError { |
19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
20 | match self { |
21 | EncoderError::WidthInvalid(s: &u32) => f.write_fmt(format_args!("Invalid TGA width: {}" , s)), |
22 | EncoderError::HeightInvalid(s: &u32) => { |
23 | f.write_fmt(format_args!("Invalid TGA height: {}" , s)) |
24 | } |
25 | } |
26 | } |
27 | } |
28 | |
29 | impl From<EncoderError> for ImageError { |
30 | fn from(e: EncoderError) -> ImageError { |
31 | ImageError::Encoding(EncodingError::new(format:ImageFormat::Tga.into(), err:e)) |
32 | } |
33 | } |
34 | |
35 | impl error::Error for EncoderError {} |
36 | |
37 | /// TGA encoder. |
38 | pub struct TgaEncoder<W: Write> { |
39 | writer: W, |
40 | |
41 | /// Run-length encoding |
42 | use_rle: bool, |
43 | } |
44 | |
45 | const MAX_RUN_LENGTH: u8 = 128; |
46 | |
47 | #[derive (Debug, Eq, PartialEq)] |
48 | enum PacketType { |
49 | Raw, |
50 | Rle, |
51 | } |
52 | |
53 | impl<W: Write> TgaEncoder<W> { |
54 | /// Create a new encoder that writes its output to ```w```. |
55 | pub fn new(w: W) -> TgaEncoder<W> { |
56 | TgaEncoder { |
57 | writer: w, |
58 | use_rle: true, |
59 | } |
60 | } |
61 | |
62 | /// Disables run-length encoding |
63 | pub fn disable_rle(mut self) -> TgaEncoder<W> { |
64 | self.use_rle = false; |
65 | self |
66 | } |
67 | |
68 | /// Writes a raw packet to the writer |
69 | fn write_raw_packet(&mut self, pixels: &[u8], counter: u8) -> ImageResult<()> { |
70 | // Set high bit = 0 and store counter - 1 (because 0 would be useless) |
71 | // The counter fills 7 bits max, so the high bit is set to 0 implicitly |
72 | let header = counter - 1; |
73 | self.writer.write_all(&[header])?; |
74 | self.writer.write_all(pixels)?; |
75 | Ok(()) |
76 | } |
77 | |
78 | /// Writes a run-length encoded packet to the writer |
79 | fn write_rle_encoded_packet(&mut self, pixel: &[u8], counter: u8) -> ImageResult<()> { |
80 | // Set high bit = 1 and store counter - 1 (because 0 would be useless) |
81 | let header = 0x80 | (counter - 1); |
82 | self.writer.write_all(&[header])?; |
83 | self.writer.write_all(pixel)?; |
84 | Ok(()) |
85 | } |
86 | |
87 | /// Writes the run-length encoded buffer to the writer |
88 | fn run_length_encode(&mut self, image: &[u8], color_type: ColorType) -> ImageResult<()> { |
89 | use PacketType::*; |
90 | |
91 | let bytes_per_pixel = color_type.bytes_per_pixel(); |
92 | let capacity_in_bytes = usize::from(MAX_RUN_LENGTH) * usize::from(bytes_per_pixel); |
93 | |
94 | // Buffer to temporarily store pixels |
95 | // so we can choose whether to use RLE or not when we need to |
96 | let mut buf = Vec::with_capacity(capacity_in_bytes); |
97 | |
98 | let mut counter = 0; |
99 | let mut prev_pixel = None; |
100 | let mut packet_type = Rle; |
101 | |
102 | for pixel in image.chunks(usize::from(bytes_per_pixel)) { |
103 | // Make sure we are not at the first pixel |
104 | if let Some(prev) = prev_pixel { |
105 | if pixel == prev { |
106 | if packet_type == Raw && counter > 0 { |
107 | self.write_raw_packet(&buf, counter)?; |
108 | counter = 0; |
109 | buf.clear(); |
110 | } |
111 | |
112 | packet_type = Rle; |
113 | } else if packet_type == Rle && counter > 0 { |
114 | self.write_rle_encoded_packet(prev, counter)?; |
115 | counter = 0; |
116 | packet_type = Raw; |
117 | buf.clear(); |
118 | } |
119 | } |
120 | |
121 | counter += 1; |
122 | buf.extend_from_slice(pixel); |
123 | |
124 | debug_assert!(buf.len() <= capacity_in_bytes); |
125 | |
126 | if counter == MAX_RUN_LENGTH { |
127 | match packet_type { |
128 | Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter), |
129 | Raw => self.write_raw_packet(&buf, counter), |
130 | }?; |
131 | |
132 | counter = 0; |
133 | packet_type = Rle; |
134 | buf.clear(); |
135 | } |
136 | |
137 | prev_pixel = Some(pixel); |
138 | } |
139 | |
140 | if counter > 0 { |
141 | match packet_type { |
142 | Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter), |
143 | Raw => self.write_raw_packet(&buf, counter), |
144 | }?; |
145 | } |
146 | |
147 | Ok(()) |
148 | } |
149 | |
150 | /// Encodes the image ```buf``` that has dimensions ```width``` |
151 | /// and ```height``` and ```ColorType``` ```color_type```. |
152 | /// |
153 | /// The dimensions of the image must be between 0 and 65535 (inclusive) or |
154 | /// an error will be returned. |
155 | /// |
156 | /// # Panics |
157 | /// |
158 | /// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`. |
159 | #[track_caller ] |
160 | pub fn encode( |
161 | mut self, |
162 | buf: &[u8], |
163 | width: u32, |
164 | height: u32, |
165 | color_type: ColorType, |
166 | ) -> ImageResult<()> { |
167 | let expected_buffer_len = |
168 | (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); |
169 | assert_eq!( |
170 | expected_buffer_len, |
171 | buf.len() as u64, |
172 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x {height} image" , |
173 | buf.len(), |
174 | ); |
175 | |
176 | // Validate dimensions. |
177 | let width = u16::try_from(width) |
178 | .map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?; |
179 | |
180 | let height = u16::try_from(height) |
181 | .map_err(|_| ImageError::from(EncoderError::HeightInvalid(height)))?; |
182 | |
183 | // Write out TGA header. |
184 | let header = Header::from_pixel_info(color_type, width, height, self.use_rle)?; |
185 | header.write_to(&mut self.writer)?; |
186 | |
187 | let image_type = ImageType::new(header.image_type); |
188 | |
189 | match image_type { |
190 | //TODO: support RunColorMap, and change match to image_type.is_encoded() |
191 | ImageType::RunTrueColor | ImageType::RunGrayScale => { |
192 | // Write run-length encoded image data |
193 | |
194 | match color_type { |
195 | ColorType::Rgb8 | ColorType::Rgba8 => { |
196 | let mut image = Vec::from(buf); |
197 | |
198 | for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { |
199 | pixel.swap(0, 2); |
200 | } |
201 | |
202 | self.run_length_encode(&image, color_type)?; |
203 | } |
204 | _ => { |
205 | self.run_length_encode(buf, color_type)?; |
206 | } |
207 | } |
208 | } |
209 | _ => { |
210 | // Write uncompressed image data |
211 | |
212 | match color_type { |
213 | ColorType::Rgb8 | ColorType::Rgba8 => { |
214 | let mut image = Vec::from(buf); |
215 | |
216 | for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { |
217 | pixel.swap(0, 2); |
218 | } |
219 | |
220 | self.writer.write_all(&image)?; |
221 | } |
222 | _ => { |
223 | self.writer.write_all(buf)?; |
224 | } |
225 | } |
226 | } |
227 | } |
228 | |
229 | Ok(()) |
230 | } |
231 | } |
232 | |
233 | impl<W: Write> ImageEncoder for TgaEncoder<W> { |
234 | #[track_caller ] |
235 | fn write_image( |
236 | self, |
237 | buf: &[u8], |
238 | width: u32, |
239 | height: u32, |
240 | color_type: ColorType, |
241 | ) -> ImageResult<()> { |
242 | self.encode(buf, width, height, color_type) |
243 | } |
244 | } |
245 | |
246 | #[cfg (test)] |
247 | mod tests { |
248 | use super::{EncoderError, TgaEncoder}; |
249 | use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; |
250 | use std::{error::Error, io::Cursor}; |
251 | |
252 | #[test ] |
253 | fn test_image_width_too_large() { |
254 | // TGA cannot encode images larger than 65,535×65,535 |
255 | // create a 65,536×1 8-bit black image buffer |
256 | let size = usize::from(u16::MAX) + 1; |
257 | let dimension = size as u32; |
258 | let img = vec![0u8; size]; |
259 | |
260 | // Try to encode an image that is too large |
261 | let mut encoded = Vec::new(); |
262 | let encoder = TgaEncoder::new(&mut encoded); |
263 | let result = encoder.encode(&img, dimension, 1, ColorType::L8); |
264 | |
265 | match result { |
266 | Err(ImageError::Encoding(err)) => { |
267 | let err = err |
268 | .source() |
269 | .unwrap() |
270 | .downcast_ref::<EncoderError>() |
271 | .unwrap(); |
272 | assert_eq!(*err, EncoderError::WidthInvalid(dimension)); |
273 | } |
274 | other => panic!( |
275 | "Encoding an image that is too wide should return a InvalidWidth \ |
276 | it returned {:?} instead" , |
277 | other |
278 | ), |
279 | } |
280 | } |
281 | |
282 | #[test ] |
283 | fn test_image_height_too_large() { |
284 | // TGA cannot encode images larger than 65,535×65,535 |
285 | // create a 65,536×1 8-bit black image buffer |
286 | let size = usize::from(u16::MAX) + 1; |
287 | let dimension = size as u32; |
288 | let img = vec![0u8; size]; |
289 | |
290 | // Try to encode an image that is too large |
291 | let mut encoded = Vec::new(); |
292 | let encoder = TgaEncoder::new(&mut encoded); |
293 | let result = encoder.encode(&img, 1, dimension, ColorType::L8); |
294 | |
295 | match result { |
296 | Err(ImageError::Encoding(err)) => { |
297 | let err = err |
298 | .source() |
299 | .unwrap() |
300 | .downcast_ref::<EncoderError>() |
301 | .unwrap(); |
302 | assert_eq!(*err, EncoderError::HeightInvalid(dimension)); |
303 | } |
304 | other => panic!( |
305 | "Encoding an image that is too tall should return a InvalidHeight \ |
306 | it returned {:?} instead" , |
307 | other |
308 | ), |
309 | } |
310 | } |
311 | |
312 | #[test ] |
313 | fn test_compression_diff() { |
314 | let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]; |
315 | |
316 | let uncompressed_bytes = { |
317 | let mut encoded_data = Vec::new(); |
318 | let encoder = TgaEncoder::new(&mut encoded_data).disable_rle(); |
319 | encoder |
320 | .encode(&image, 5, 1, ColorType::Rgb8) |
321 | .expect("could not encode image" ); |
322 | |
323 | encoded_data |
324 | }; |
325 | |
326 | let compressed_bytes = { |
327 | let mut encoded_data = Vec::new(); |
328 | let encoder = TgaEncoder::new(&mut encoded_data); |
329 | encoder |
330 | .encode(&image, 5, 1, ColorType::Rgb8) |
331 | .expect("could not encode image" ); |
332 | |
333 | encoded_data |
334 | }; |
335 | |
336 | assert!(uncompressed_bytes.len() > compressed_bytes.len()); |
337 | } |
338 | |
339 | mod compressed { |
340 | use super::*; |
341 | |
342 | fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> { |
343 | let mut encoded_data = Vec::new(); |
344 | { |
345 | let encoder = TgaEncoder::new(&mut encoded_data); |
346 | encoder |
347 | .encode(image, width, height, c) |
348 | .expect("could not encode image" ); |
349 | } |
350 | let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode" ); |
351 | |
352 | let mut buf = vec![0; decoder.total_bytes() as usize]; |
353 | decoder.read_image(&mut buf).expect("failed to decode" ); |
354 | buf |
355 | } |
356 | |
357 | #[test ] |
358 | fn mixed_packets() { |
359 | let image = [ |
360 | 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
361 | ]; |
362 | let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8); |
363 | assert_eq!(decoded.len(), image.len()); |
364 | assert_eq!(decoded.as_slice(), image); |
365 | } |
366 | |
367 | #[test ] |
368 | fn round_trip_gray() { |
369 | let image = [0, 1, 2]; |
370 | let decoded = round_trip_image(&image, 3, 1, ColorType::L8); |
371 | assert_eq!(decoded.len(), image.len()); |
372 | assert_eq!(decoded.as_slice(), image); |
373 | } |
374 | |
375 | #[test ] |
376 | fn round_trip_graya() { |
377 | let image = [0, 1, 2, 3, 4, 5]; |
378 | let decoded = round_trip_image(&image, 1, 3, ColorType::La8); |
379 | assert_eq!(decoded.len(), image.len()); |
380 | assert_eq!(decoded.as_slice(), image); |
381 | } |
382 | |
383 | #[test ] |
384 | fn round_trip_single_pixel_rgb() { |
385 | let image = [0, 1, 2]; |
386 | let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); |
387 | assert_eq!(decoded.len(), image.len()); |
388 | assert_eq!(decoded.as_slice(), image); |
389 | } |
390 | |
391 | #[test ] |
392 | fn round_trip_three_pixel_rgb() { |
393 | let image = [0, 1, 2, 0, 1, 2, 0, 1, 2]; |
394 | let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8); |
395 | assert_eq!(decoded.len(), image.len()); |
396 | assert_eq!(decoded.as_slice(), image); |
397 | } |
398 | |
399 | #[test ] |
400 | fn round_trip_3px_rgb() { |
401 | let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel |
402 | let decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); |
403 | assert_eq!(decoded.len(), image.len()); |
404 | assert_eq!(decoded.as_slice(), image); |
405 | } |
406 | |
407 | #[test ] |
408 | fn round_trip_different() { |
409 | let image = [0, 1, 2, 0, 1, 3, 0, 1, 4]; |
410 | let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8); |
411 | assert_eq!(decoded.len(), image.len()); |
412 | assert_eq!(decoded.as_slice(), image); |
413 | } |
414 | |
415 | #[test ] |
416 | fn round_trip_different_2() { |
417 | let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4]; |
418 | let decoded = round_trip_image(&image, 4, 1, ColorType::Rgb8); |
419 | assert_eq!(decoded.len(), image.len()); |
420 | assert_eq!(decoded.as_slice(), image); |
421 | } |
422 | |
423 | #[test ] |
424 | fn round_trip_different_3() { |
425 | let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4, 0, 1, 2]; |
426 | let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8); |
427 | assert_eq!(decoded.len(), image.len()); |
428 | assert_eq!(decoded.as_slice(), image); |
429 | } |
430 | |
431 | #[test ] |
432 | fn round_trip_bw() { |
433 | // This example demonstrates the run-length counter being saturated |
434 | // It should never overflow and can be 128 max |
435 | let image = crate::open("tests/images/tga/encoding/black_white.tga" ).unwrap(); |
436 | let (width, height) = (image.width(), image.height()); |
437 | let image = image.as_rgb8().unwrap().to_vec(); |
438 | |
439 | let decoded = round_trip_image(&image, width, height, ColorType::Rgb8); |
440 | assert_eq!(decoded.len(), image.len()); |
441 | assert_eq!(decoded.as_slice(), image); |
442 | } |
443 | } |
444 | |
445 | mod uncompressed { |
446 | use super::*; |
447 | |
448 | fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> { |
449 | let mut encoded_data = Vec::new(); |
450 | { |
451 | let encoder = TgaEncoder::new(&mut encoded_data).disable_rle(); |
452 | encoder |
453 | .encode(image, width, height, c) |
454 | .expect("could not encode image" ); |
455 | } |
456 | |
457 | let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode" ); |
458 | |
459 | let mut buf = vec![0; decoder.total_bytes() as usize]; |
460 | decoder.read_image(&mut buf).expect("failed to decode" ); |
461 | buf |
462 | } |
463 | |
464 | #[test ] |
465 | fn round_trip_single_pixel_rgb() { |
466 | let image = [0, 1, 2]; |
467 | let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); |
468 | assert_eq!(decoded.len(), image.len()); |
469 | assert_eq!(decoded.as_slice(), image); |
470 | } |
471 | |
472 | #[test ] |
473 | fn round_trip_single_pixel_rgba() { |
474 | let image = [0, 1, 2, 3]; |
475 | let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); |
476 | assert_eq!(decoded.len(), image.len()); |
477 | assert_eq!(decoded.as_slice(), image); |
478 | } |
479 | |
480 | #[test ] |
481 | fn round_trip_gray() { |
482 | let image = [0, 1, 2]; |
483 | let decoded = round_trip_image(&image, 3, 1, ColorType::L8); |
484 | assert_eq!(decoded.len(), image.len()); |
485 | assert_eq!(decoded.as_slice(), image); |
486 | } |
487 | |
488 | #[test ] |
489 | fn round_trip_graya() { |
490 | let image = [0, 1, 2, 3, 4, 5]; |
491 | let decoded = round_trip_image(&image, 1, 3, ColorType::La8); |
492 | assert_eq!(decoded.len(), image.len()); |
493 | assert_eq!(decoded.as_slice(), image); |
494 | } |
495 | |
496 | #[test ] |
497 | fn round_trip_3px_rgb() { |
498 | let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel |
499 | let decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); |
500 | assert_eq!(decoded.len(), image.len()); |
501 | assert_eq!(decoded.as_slice(), image); |
502 | } |
503 | } |
504 | } |
505 | |