1use otf_pixels_core::{
37 Codec, DecodeCapability, Decoder, Encoder, Format, ImageDescriptor, Limits, PixelFormat,
38 PixelsError, Result, Sink, Source,
39};
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[non_exhaustive]
47pub struct RawFormat {
48 pub descriptor: ImageDescriptor,
50 pub stride: usize,
56}
57
58impl RawFormat {
59 #[must_use]
61 pub const fn packed(descriptor: ImageDescriptor) -> Self {
62 Self {
63 descriptor,
64 stride: descriptor.row_bytes(),
65 }
66 }
67
68 pub fn with_stride(descriptor: ImageDescriptor, stride: usize) -> Result<Self> {
75 let row_bytes = descriptor.row_bytes();
76 if stride < row_bytes {
77 return Err(PixelsError::invalid_argument(
78 "stride",
79 format!("stride {stride} is shorter than a {row_bytes}-byte row"),
80 ));
81 }
82 Ok(Self { descriptor, stride })
83 }
84
85 pub fn from_dimensions(
96 width: u32,
97 height: u32,
98 pixel: PixelFormat,
99 limits: &Limits,
100 ) -> Result<Self> {
101 Ok(Self::packed(ImageDescriptor::with_limits(
102 width, height, pixel, limits,
103 )?))
104 }
105
106 #[must_use]
108 pub const fn padding(&self) -> usize {
109 self.stride - self.descriptor.row_bytes()
110 }
111}
112
113#[derive(Debug, Clone, Copy, Default)]
120pub struct RawCodec;
121
122impl Codec for RawCodec {
123 fn format(&self) -> Format {
124 Format::Raw
125 }
126
127 fn magic_len(&self) -> usize {
128 0
129 }
130
131 fn probe(&self, _prefix: &[u8]) -> bool {
132 false
133 }
134}
135
136#[derive(Debug)]
138pub struct RawDecoder<S: Source> {
139 layout: RawFormat,
140 source: S,
141 rows_read: u32,
142 padding: Vec<u8>,
144}
145
146impl<S: Source> RawDecoder<S> {
147 pub fn new(layout: RawFormat, source: S) -> Result<Self> {
157 if layout.descriptor.byte_len().is_none() {
158 return Err(PixelsError::invalid_argument(
159 "descriptor",
160 "image byte length overflows this platform's address space",
161 ));
162 }
163 Ok(Self {
164 layout,
165 source,
166 rows_read: 0,
167 padding: vec![0; layout.padding()],
168 })
169 }
170
171 #[must_use]
173 pub const fn layout(&self) -> RawFormat {
174 self.layout
175 }
176
177 #[must_use]
179 pub const fn rows_read(&self) -> u32 {
180 self.rows_read
181 }
182}
183
184impl<S: Source + std::fmt::Debug> Decoder for RawDecoder<S> {
185 fn descriptor(&self) -> ImageDescriptor {
186 self.layout.descriptor
187 }
188
189 fn capability(&self) -> DecodeCapability {
190 DecodeCapability::Sequential
194 }
195
196 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
197 let expected = self.layout.descriptor.row_bytes();
198 if out.len() != expected {
199 return Err(PixelsError::invalid_argument(
200 "out",
201 format!("row buffer is {} bytes, expected {expected}", out.len()),
202 ));
203 }
204 if self.rows_read >= self.layout.descriptor.height {
205 return Err(PixelsError::invalid_argument(
206 "out",
207 format!(
208 "all {} rows have already been read",
209 self.layout.descriptor.height
210 ),
211 ));
212 }
213 self.source.read_exact(out)?;
215 if !self.padding.is_empty() {
216 self.source.read_exact(&mut self.padding)?;
217 }
218 self.rows_read += 1;
219 Ok(())
220 }
221}
222
223#[derive(Debug, Default)]
225pub struct RawEncoder {
226 layout: Option<RawFormat>,
227 rows_written: u32,
228 padding: Vec<u8>,
230}
231
232impl RawEncoder {
233 #[must_use]
235 pub const fn new() -> Self {
236 Self {
237 layout: None,
238 rows_written: 0,
239 padding: Vec::new(),
240 }
241 }
242
243 #[must_use]
245 pub fn with_layout(layout: RawFormat) -> Self {
246 Self {
247 layout: Some(layout),
248 rows_written: 0,
249 padding: vec![0; layout.padding()],
250 }
251 }
252
253 #[must_use]
255 pub const fn rows_written(&self) -> u32 {
256 self.rows_written
257 }
258}
259
260impl Encoder for RawEncoder {
261 fn write_header(&mut self, desc: &ImageDescriptor, _sink: &mut dyn Sink) -> Result<()> {
262 if self.rows_written > 0 {
263 return Err(PixelsError::invalid_argument(
264 "descriptor",
265 "write_header called after rows were already written",
266 ));
267 }
268 match self.layout {
269 Some(layout) if layout.descriptor != *desc => {
272 return Err(PixelsError::invalid_argument(
273 "descriptor",
274 format!(
275 "encoder was built for {} but the pipeline produced {desc}",
276 layout.descriptor
277 ),
278 ));
279 }
280 Some(_) => {}
281 None => {
282 let layout = RawFormat::packed(*desc);
283 self.padding = vec![0; layout.padding()];
284 self.layout = Some(layout);
285 }
286 }
287 Ok(())
289 }
290
291 fn write_row(&mut self, row: &[u8], sink: &mut dyn Sink) -> Result<()> {
292 let Some(layout) = self.layout else {
293 return Err(PixelsError::invalid_argument(
294 "row",
295 "write_row called before write_header",
296 ));
297 };
298 let expected = layout.descriptor.row_bytes();
299 if row.len() != expected {
300 return Err(PixelsError::invalid_argument(
301 "row",
302 format!("row is {} bytes, expected {expected}", row.len()),
303 ));
304 }
305 if self.rows_written >= layout.descriptor.height {
306 return Err(PixelsError::invalid_argument(
307 "row",
308 format!(
309 "all {} declared rows have already been written",
310 layout.descriptor.height
311 ),
312 ));
313 }
314 sink.write_all(row)?;
315 if !self.padding.is_empty() {
316 sink.write_all(&self.padding)?;
317 }
318 self.rows_written += 1;
319 Ok(())
320 }
321
322 fn finish(&mut self, sink: &mut dyn Sink) -> Result<()> {
323 let Some(layout) = self.layout else {
324 return Err(PixelsError::malformed(
325 "raw",
326 "finish called before write_header",
327 ));
328 };
329 let declared = layout.descriptor.height;
330 if self.rows_written != declared {
331 return Err(PixelsError::malformed(
333 "raw",
334 format!("wrote {} of {declared} declared rows", self.rows_written),
335 ));
336 }
337 sink.flush()
338 }
339}
340
341#[cfg(test)]
342#[allow(
343 clippy::unwrap_used,
344 clippy::indexing_slicing,
345 clippy::panic,
346 reason = "tests operate on known-good values and assert shapes directly"
347)]
348mod tests {
349 use super::*;
350 use otf_pixels_core::{ErrorCode, Limit};
351
352 fn descriptor(width: u32, height: u32) -> ImageDescriptor {
353 ImageDescriptor::new(width, height, PixelFormat::Gray8).unwrap()
354 }
355
356 fn decode_all(layout: RawFormat, bytes: &[u8]) -> Result<Vec<u8>> {
357 let mut decoder = RawDecoder::new(layout, bytes)?;
358 let mut out = Vec::new();
359 let mut row = vec![0_u8; layout.descriptor.row_bytes()];
360 for _ in 0..layout.descriptor.height {
361 decoder.read_row(&mut row)?;
362 out.extend_from_slice(&row);
363 }
364 Ok(out)
365 }
366
367 #[test]
368 fn packed_streams_round_trip() {
369 let desc = descriptor(2, 2);
370 let pixels = [1, 2, 3, 4];
371 let decoded = decode_all(RawFormat::packed(desc), &pixels).unwrap();
372 assert_eq!(decoded, pixels);
373
374 let mut sink = Vec::new();
375 let mut encoder = RawEncoder::new();
376 encoder.write_header(&desc, &mut sink).unwrap();
377 encoder.write_row(&[1, 2], &mut sink).unwrap();
378 encoder.write_row(&[3, 4], &mut sink).unwrap();
379 encoder.finish(&mut sink).unwrap();
380 assert_eq!(sink, pixels);
381 }
382
383 #[test]
384 fn stride_padding_is_skipped_on_decode_and_zeroed_on_encode() {
385 let desc = descriptor(2, 2);
386 let layout = RawFormat::with_stride(desc, 4).unwrap();
387 assert_eq!(layout.padding(), 2);
388 let stream = [1, 2, 9, 9, 3, 4, 9, 9];
390 assert_eq!(decode_all(layout, &stream).unwrap(), [1, 2, 3, 4]);
391
392 let mut sink = Vec::new();
393 let mut encoder = RawEncoder::with_layout(layout);
394 encoder.write_header(&desc, &mut sink).unwrap();
395 encoder.write_row(&[1, 2], &mut sink).unwrap();
396 encoder.write_row(&[3, 4], &mut sink).unwrap();
397 encoder.finish(&mut sink).unwrap();
398 assert_eq!(sink, [1, 2, 0, 0, 3, 4, 0, 0]);
399 }
400
401 #[test]
402 fn a_truncated_stream_is_malformed_not_a_panic() {
403 let layout = RawFormat::packed(descriptor(4, 4));
404 for len in 0..16 {
406 let stream = vec![7_u8; len];
407 let err = decode_all(layout, &stream).unwrap_err();
408 assert_eq!(err.code(), ErrorCode::Malformed, "truncated at {len} bytes");
409 }
410 assert!(decode_all(layout, &[7_u8; 16]).is_ok());
411 }
412
413 #[test]
414 fn a_stream_truncated_inside_padding_is_malformed() {
415 let layout = RawFormat::with_stride(descriptor(2, 2), 4).unwrap();
416 let err = decode_all(layout, &[1, 2, 9]).unwrap_err();
418 assert_eq!(err.code(), ErrorCode::Malformed);
419 }
420
421 #[test]
422 fn reading_past_the_end_is_an_error_not_a_panic() {
423 let layout = RawFormat::packed(descriptor(2, 1));
424 let stream: &[u8] = &[1, 2];
425 let mut decoder = RawDecoder::new(layout, stream).unwrap();
426 let mut row = vec![0_u8; 2];
427 decoder.read_row(&mut row).unwrap();
428 let err = decoder.read_row(&mut row).unwrap_err();
429 assert_eq!(err.code(), ErrorCode::InvalidArgument);
430 assert_eq!(decoder.rows_read(), 1);
431 }
432
433 #[test]
434 fn a_wrong_sized_row_buffer_is_rejected() {
435 let layout = RawFormat::packed(descriptor(4, 1));
436 let stream: &[u8] = &[1, 2, 3, 4];
437 let mut decoder = RawDecoder::new(layout, stream).unwrap();
438 assert_eq!(
439 decoder.read_row(&mut [0; 3]).unwrap_err().code(),
440 ErrorCode::InvalidArgument
441 );
442 assert_eq!(
443 decoder.read_row(&mut [0; 5]).unwrap_err().code(),
444 ErrorCode::InvalidArgument
445 );
446 }
447
448 #[test]
449 fn hostile_dimensions_are_rejected_before_allocation() {
450 let err =
453 RawFormat::from_dimensions(u32::MAX, u32::MAX, PixelFormat::Rgba8, &Limits::default())
454 .unwrap_err();
455 assert_eq!(err.code(), ErrorCode::LimitExceeded);
456 match err {
457 PixelsError::LimitExceeded { limit, .. } => assert_eq!(limit, Limit::MaxPixels),
458 other => panic!("unexpected error: {other}"),
459 }
460 }
461
462 #[test]
463 fn a_short_stride_is_rejected() {
464 let err = RawFormat::with_stride(descriptor(4, 4), 3).unwrap_err();
465 assert_eq!(err.code(), ErrorCode::InvalidArgument);
466 assert_eq!(
467 RawFormat::with_stride(descriptor(4, 4), 4)
468 .unwrap()
469 .padding(),
470 0
471 );
472 }
473
474 #[test]
475 fn finishing_early_never_yields_partial_output() {
476 let desc = descriptor(2, 3);
477 let mut sink = Vec::new();
478 let mut encoder = RawEncoder::new();
479 encoder.write_header(&desc, &mut sink).unwrap();
480 encoder.write_row(&[1, 2], &mut sink).unwrap();
481 let err = encoder.finish(&mut sink).unwrap_err();
482 assert_eq!(err.code(), ErrorCode::Malformed);
483 assert!(err.to_string().contains("1 of 3"), "{err}");
484 }
485
486 #[test]
487 fn writing_more_rows_than_declared_is_rejected() {
488 let desc = descriptor(2, 1);
489 let mut sink = Vec::new();
490 let mut encoder = RawEncoder::new();
491 encoder.write_header(&desc, &mut sink).unwrap();
492 encoder.write_row(&[1, 2], &mut sink).unwrap();
493 let err = encoder.write_row(&[3, 4], &mut sink).unwrap_err();
494 assert_eq!(err.code(), ErrorCode::InvalidArgument);
495 }
496
497 #[test]
498 fn encoding_out_of_order_is_rejected() {
499 let mut sink = Vec::new();
500 let mut encoder = RawEncoder::new();
501 assert_eq!(
503 encoder.write_row(&[1, 2], &mut sink).unwrap_err().code(),
504 ErrorCode::InvalidArgument
505 );
506 assert_eq!(
508 encoder.finish(&mut sink).unwrap_err().code(),
509 ErrorCode::Malformed
510 );
511 }
512
513 #[test]
514 fn a_fixed_layout_encoder_rejects_a_mismatched_pipeline() {
515 let layout = RawFormat::with_stride(descriptor(2, 2), 4).unwrap();
516 let mut encoder = RawEncoder::with_layout(layout);
517 let mut sink = Vec::new();
518 let err = encoder
519 .write_header(&descriptor(3, 2), &mut sink)
520 .unwrap_err();
521 assert_eq!(err.code(), ErrorCode::InvalidArgument);
522 }
523
524 #[test]
525 fn every_v1_pixel_format_round_trips() {
526 for &pixel in PixelFormat::ALL {
527 let desc = ImageDescriptor::new(3, 2, pixel).unwrap();
528 let bytes: Vec<u8> = (0..desc.byte_len().unwrap())
529 .map(|i| (i % 251) as u8)
530 .collect();
531 let decoded = decode_all(RawFormat::packed(desc), &bytes).unwrap();
532 assert_eq!(decoded, bytes, "{pixel} did not round-trip");
533 }
534 }
535
536 #[test]
537 fn raw_never_claims_a_stream_by_sniffing() {
538 let codec = RawCodec;
539 assert_eq!(codec.format(), Format::Raw);
540 assert_eq!(codec.magic_len(), 0);
541 assert!(!codec.probe(&[]));
542 assert!(!codec.probe(&[0x89, b'P', b'N', b'G']));
543 }
544
545 #[test]
546 fn decoding_reads_exactly_one_row_at_a_time() {
547 let desc = descriptor(2, 4);
550 let stream: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8];
551 let mut cursor = std::io::Cursor::new(stream);
552 let mut decoder = RawDecoder::new(RawFormat::packed(desc), &mut cursor).unwrap();
553 let mut row = vec![0_u8; 2];
554 decoder.read_row(&mut row).unwrap();
555 assert_eq!(row, [1, 2]);
556 drop(decoder);
557 assert_eq!(cursor.position(), 2, "only one row was consumed");
558 }
559}