1use super::header::Header;
2use crate::{
3 codecs::tga::header::ImageType, error::EncodingError, ColorType, ImageEncoder, ImageError,
4 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) => {
23 f.write_fmt(format_args!("Invalid TGA height: {}", s))
24 }
25 }
26 }
27}
28
29impl From<EncoderError> for ImageError {
30 fn from(e: EncoderError) -> ImageError {
31 ImageError::Encoding(EncodingError::new(format:ImageFormat::Tga.into(), err:e))
32 }
33}
34
35impl error::Error for EncoderError {}
36
37/// TGA encoder.
38pub struct TgaEncoder<W: Write> {
39 writer: W,
40
41 /// Run-length encoding
42 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 /// 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
233impl<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)]
247mod 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