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