1use alloc::format;
10use core::fmt::{Debug, Display, Formatter};
11
12use zune_core::bit_depth::BitType;
13use zune_core::bytestream::{ZByteIoError, ZByteWriterTrait, ZWriter};
14use zune_core::colorspace::ColorSpace;
15use zune_core::options::EncoderOptions;
16
17pub enum PPMEncodeErrors {
19 Static(&'static str),
20 TooShortInput(usize, usize),
21 UnsupportedColorspace(ColorSpace),
22 IoError(ZByteIoError)
23}
24
25impl Debug for PPMEncodeErrors {
26 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
27 match self {
28 PPMEncodeErrors::Static(ref errors) => {
29 writeln!(f, "{errors}")
30 }
31 PPMEncodeErrors::TooShortInput(expected, found) => {
32 writeln!(f, "Expected input of length {expected} but found {found}")
33 }
34 PPMEncodeErrors::UnsupportedColorspace(colorspace) => {
35 writeln!(f, "Unsupported colorspace {colorspace:?} for ppm")
36 }
37 PPMEncodeErrors::IoError(err) => {
38 writeln!(f, "I/O error: {:?}", err)
39 }
40 }
41 }
42}
43
44impl From<ZByteIoError> for PPMEncodeErrors {
45 fn from(value: ZByteIoError) -> Self {
46 Self::IoError(value)
47 }
48}
49
50enum PPMVersions {
51 P5,
52 P6,
53 P7
54}
55
56impl Display for PPMVersions {
57 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
58 match self {
59 Self::P6 => write!(f, "P6"),
60 Self::P5 => write!(f, "P5"),
61 Self::P7 => write!(f, "P7")
62 }
63 }
64}
65
66pub struct PPMEncoder<'a> {
94 data: &'a [u8],
95 options: EncoderOptions
96}
97
98impl<'a> PPMEncoder<'a> {
99 pub fn new(data: &'a [u8], options: EncoderOptions) -> PPMEncoder<'a> {
110 PPMEncoder { data, options }
111 }
112
113 fn encode_headers<T: ZByteWriterTrait>(
114 &self, stream: &mut ZWriter<T>
115 ) -> Result<(), PPMEncodeErrors> {
116 let version = version_for_colorspace(self.options.colorspace()).ok_or(
117 PPMEncodeErrors::UnsupportedColorspace(self.options.colorspace())
118 )?;
119
120 let width = self.options.width();
121 let height = self.options.height();
122 let components = self.options.colorspace().num_components();
123 let max_val = self.options.depth().max_value();
124 let colorspace = self.options.colorspace();
125
126 let header = match version {
127 PPMVersions::P5 | PPMVersions::P6 => {
128 format!("{version}\n{width}\n{height}\n{max_val}\n")
129 }
130 PPMVersions::P7 => {
131 let tuple_type = convert_tuple_type_to_pam(colorspace);
132
133 format!(
134 "P7\nWIDTH {width}\nHEIGHT {height}\nDEPTH {components}\nMAXVAL {max_val}\nTUPLTYPE {tuple_type}\n ENDHDR\n",
135 )
136 }
137 };
138
139 stream.write_all(header.as_bytes()).unwrap();
140
141 Ok(())
142 }
143 pub fn encode<T: ZByteWriterTrait>(&self, out: T) -> Result<usize, PPMEncodeErrors> {
154 let found = self.data.len();
155 let expected = calc_expected_size(self.options);
156
157 if expected != found {
158 return Err(PPMEncodeErrors::TooShortInput(expected, found));
159 }
160 let mut stream = ZWriter::new(out);
161 stream.reserve(expected + 37)?; self.encode_headers(&mut stream)?;
164
165 match self.options.depth().bit_type() {
166 BitType::U8 => stream.write_all(self.data)?,
167 BitType::U16 => {
168 for slice in self.data.chunks_exact(2) {
170 let byte = u16::from_ne_bytes(slice.try_into().unwrap());
171 stream.write_u16_be_err(byte)?;
172 }
173 }
174 _ => unreachable!()
175 }
176 let position = stream.bytes_written();
177 Ok(position)
178 }
179}
180
181fn version_for_colorspace(colorspace: ColorSpace) -> Option<PPMVersions> {
182 match colorspace {
183 ColorSpace::Luma => Some(PPMVersions::P5),
184 ColorSpace::RGB => Some(PPMVersions::P6),
185 ColorSpace::RGBA | ColorSpace::LumaA => Some(PPMVersions::P7),
186 _ => None
187 }
188}
189
190fn convert_tuple_type_to_pam(colorspace: ColorSpace) -> &'static str {
191 match colorspace {
192 ColorSpace::Luma => "GRAYSCALE",
193 ColorSpace::RGB => "RGB",
194 ColorSpace::LumaA => "GRAYSCALE_ALPHA",
195 ColorSpace::RGBA => "RGB_ALPHA",
196 _ => unreachable!()
197 }
198}
199
200const PPM_HEADER_SIZE: usize = 100;
201
202#[inline]
206pub fn max_out_size(options: &EncoderOptions) -> usize {
207 options
208 .width()
209 .checked_mul(options.depth().size_of())
210 .unwrap()
211 .checked_mul(options.height())
212 .unwrap()
213 .checked_mul(options.colorspace().num_components())
214 .unwrap()
215 .checked_add(PPM_HEADER_SIZE)
216 .unwrap()
217}
218
219fn calc_expected_size(options: EncoderOptions) -> usize {
220 max_out_size(&options).checked_sub(PPM_HEADER_SIZE).unwrap()
221}