Skip to main content

zencodec_io/
encode_write.rs

1//! Write a streaming encoder's output to a `std::io::Write` as it is
2//! produced.
3
4use core::fmt;
5use std::io::{self, Write};
6
7use zencodec::UnsupportedOperation;
8use zencodec::encode::{AnimationFrameEncoder, Encoder, OutputTiming};
9use zenpixels::PixelSlice;
10
11/// Write every byte `enc` has pending to `w`, then consume it. Returns how
12/// many bytes were written.
13///
14/// The drain step of a streaming encode, without the footgun of forgetting
15/// [`consume_output`](Encoder::consume_output) and sending the same bytes
16/// twice. On an I/O error nothing is consumed, and the bytes `w` accepted
17/// before the error are unknown: abort the transport.
18pub fn write_pending<E: Encoder>(enc: &mut E, w: &mut impl Write) -> io::Result<usize> {
19    let n = enc.pending_output().len();
20    if n > 0 {
21        w.write_all(enc.pending_output())?;
22        enc.consume_output(n);
23    }
24    Ok(n)
25}
26
27/// [`write_pending`] for an [`AnimationFrameEncoder`]: write the bytes of the
28/// frames pushed so far.
29pub fn write_pending_animation<A: AnimationFrameEncoder>(
30    enc: &mut A,
31    w: &mut impl Write,
32) -> io::Result<usize> {
33    let n = enc.pending_output().len();
34    if n > 0 {
35        w.write_all(enc.pending_output())?;
36        enc.consume_output(n);
37    }
38    Ok(n)
39}
40
41/// Failure of [`encode_to_writer`].
42#[derive(Debug)]
43#[non_exhaustive]
44pub enum EncodeWriteError<E> {
45    /// The encoder failed.
46    Encoder(E),
47    /// Writing the output failed.
48    Io(io::Error),
49}
50
51impl<E: fmt::Display> fmt::Display for EncodeWriteError<E> {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            Self::Encoder(e) => write!(f, "encoder: {e}"),
55            Self::Io(e) => write!(f, "write: {e}"),
56        }
57    }
58}
59
60impl<E: core::error::Error + 'static> core::error::Error for EncodeWriteError<E> {
61    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
62        match self {
63            Self::Encoder(e) => Some(e),
64            Self::Io(e) => Some(e),
65        }
66    }
67}
68
69/// What [`encode_to_writer`] did.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71#[non_exhaustive]
72pub struct EncodeWriteReport {
73    /// What the encoder's [`begin`](Encoder::begin) reported;
74    /// [`OutputTiming::AT_FINISH`] when it declined row-level encode and the
75    /// image went through [`encode`](Encoder::encode) instead.
76    pub timing: OutputTiming,
77    /// Bytes written to the writer.
78    pub bytes: u64,
79}
80
81/// Encode `pixels` into `w`, sending bytes as the encoder produces them.
82///
83/// Declares the image with [`begin`](Encoder::begin), pushes it in strips,
84/// and writes pending output after `begin` (the header) and after every
85/// strip, so an encoder that streams sends its first bytes before it has
86/// seen most rows and holds little output. An encoder that reports
87/// [`OutputTiming::AT_FINISH`] gets the same rows and writes everything at
88/// the end. One whose configuration cannot encode from rows (`begin`
89/// declines with [`UnsupportedOperation::RowLevelEncode`]) encodes the image
90/// in one call instead.
91///
92/// Strips are a multiple of the encoder's
93/// [`preferred_strip_height`](Encoder::preferred_strip_height), at least 16
94/// rows, so a one-row preference doesn't turn into one write per row.
95///
96/// On an error after bytes were written, the output is a prefix of a file
97/// that will never be finished: abort the transport rather than end it
98/// normally.
99#[deprecated(
100    note = "Use row_encode::encode for policy-controlled rows, or Encoder::encode followed by writing its output for one-shot encoding"
101)]
102pub fn encode_to_writer<E: Encoder>(
103    mut enc: E,
104    pixels: PixelSlice<'_>,
105    w: &mut impl Write,
106) -> Result<EncodeWriteReport, EncodeWriteError<E::Error>> {
107    let (width, height, desc) = (pixels.width(), pixels.rows(), pixels.descriptor());
108    let timing = match enc.begin(width, height, desc) {
109        Ok(t) => t,
110        Err(e) if is_row_level_refusal(&e) => {
111            // A failed `begin` leaves the encoder as it was.
112            let out = enc.encode(pixels).map_err(EncodeWriteError::Encoder)?;
113            w.write_all(out.data()).map_err(EncodeWriteError::Io)?;
114            return Ok(EncodeWriteReport {
115                timing: OutputTiming::AT_FINISH,
116                bytes: out.len() as u64,
117            });
118        }
119        Err(e) => return Err(EncodeWriteError::Encoder(e)),
120    };
121    let mut bytes = write_pending(&mut enc, w).map_err(EncodeWriteError::Io)? as u64;
122    let unit = enc.preferred_strip_height().max(1);
123    let strip = unit * 16u32.div_ceil(unit);
124    let mut y = 0;
125    while y < height {
126        let h = strip.min(height - y);
127        let rows = pixels.sub_rows(y, h);
128        enc.push_rows(rows).map_err(EncodeWriteError::Encoder)?;
129        bytes += write_pending(&mut enc, w).map_err(EncodeWriteError::Io)? as u64;
130        y += h;
131    }
132    let rest = enc.finish().map_err(EncodeWriteError::Encoder)?;
133    w.write_all(rest.data()).map_err(EncodeWriteError::Io)?;
134    bytes += rest.len() as u64;
135    Ok(EncodeWriteReport { timing, bytes })
136}
137
138fn is_row_level_refusal<E: core::error::Error + 'static>(e: &E) -> bool {
139    use zencodec::CodecErrorExt as _;
140    match e.error_category() {
141        // An envelope is authoritative even when native detail has no cause chain.
142        Some(category) => {
143            category
144                == zencodec::ErrorCategory::Request(zencodec::RequestError::Unsupported(
145                    UnsupportedOperation::RowLevelEncode,
146                ))
147        }
148        None => e.unsupported_operation() == Some(&UnsupportedOperation::RowLevelEncode),
149    }
150}