Skip to main content

rakata_formats/tga/
writer.rs

1//! TGA (Targa) writer.
2
3use std::io::{Cursor, Write};
4
5use crate::binary::{write_u16, write_u8};
6
7use super::{validate_rgba_len, Tga, TgaBinaryError};
8
9/// Writes TGA data to a writer.
10///
11/// Output is always canonical: uncompressed true-color 32-bit BGRA with
12/// top-left origin and 8-bit alpha.
13///
14/// # Errors
15///
16/// [`TgaBinaryError::InvalidHeader`] when the image id is longer than the 255
17/// bytes its length field holds, and [`TgaBinaryError::Io`] when the writer
18/// fails, which can leave a partial file.
19///
20/// The input's own compression and colour-map settings are not errors. Output
21/// is always the canonical form, so a run-length encoded image read in comes
22/// back out uncompressed.
23#[cfg_attr(
24    feature = "tracing",
25    tracing::instrument(level = "debug", skip(writer, tga))
26)]
27pub fn write_tga<W: Write>(writer: &mut W, tga: &Tga) -> Result<(), TgaBinaryError> {
28    validate_rgba_len(tga.header.width, tga.header.height, &tga.rgba_pixels)?;
29    write_tga_canonical(writer, tga)
30}
31
32/// Serializes TGA data into a byte vector.
33///
34/// # Errors
35///
36/// [`TgaBinaryError::InvalidHeader`] on the terms [`write_tga`] gives. The
37/// `Vec` target has no I/O to fail at.
38#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(tga)))]
39pub fn write_tga_to_vec(tga: &Tga) -> Result<Vec<u8>, TgaBinaryError> {
40    let mut cursor = Cursor::new(Vec::new());
41    write_tga(&mut cursor, tga)?;
42    Ok(cursor.into_inner())
43}
44
45fn write_tga_canonical<W: Write>(writer: &mut W, tga: &Tga) -> Result<(), TgaBinaryError> {
46    let id_len = u8::try_from(tga.image_id.len())
47        .map_err(|_| TgaBinaryError::InvalidHeader("image_id is longer than 255 bytes".into()))?;
48
49    write_u8(writer, id_len)?;
50    write_u8(writer, 0)?; // no color map
51    write_u8(writer, 2)?; // uncompressed true-color
52
53    write_u16(writer, 0)?; // color_map_start
54    write_u16(writer, 0)?; // color_map_len
55    write_u8(writer, 0)?; // color_map_depth
56
57    write_u16(writer, 0)?; // x_origin
58    write_u16(writer, 0)?; // y_origin
59    write_u16(writer, tga.header.width)?;
60    write_u16(writer, tga.header.height)?;
61    write_u8(writer, 32)?; // pixel depth
62    write_u8(writer, 0x20 | 0x08)?; // top-left origin + 8-bit alpha
63    writer.write_all(&tga.image_id)?;
64    write_truecolor_uncompressed(writer, &tga.rgba_pixels, true)?;
65    Ok(())
66}
67
68fn write_truecolor_uncompressed<W: Write>(
69    writer: &mut W,
70    rgba_pixels: &[u8],
71    include_alpha: bool,
72) -> Result<(), TgaBinaryError> {
73    for rgba in rgba_pixels.chunks_exact(4) {
74        if include_alpha {
75            writer.write_all(&[rgba[2], rgba[1], rgba[0], rgba[3]])?;
76        } else {
77            writer.write_all(&[rgba[2], rgba[1], rgba[0]])?;
78        }
79    }
80    Ok(())
81}