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