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