sentryshot_filter 0.1.2

Port of FFmpeg's filters
Documentation
// SPDX-License-Identifier: MPL-2.0+

use crate::draw::{DrawContext, NewDrawContextError};
use sentryshot_util::{Frame, ResetBufferError};
use std::num::NonZeroU16;
use thiserror::Error;

#[derive(Debug, Error, PartialEq, Eq)]
pub enum PadError {
    #[error("reset buffer: {0}")]
    ResetBuffer(#[from] ResetBufferError),

    #[error("size out of bounds: {0}+{1} > {2}")]
    OutOfBounds(u16, u16, u16),

    #[error("create draw context: {0}")]
    NewDrawContext(#[from] NewDrawContextError),
}

/// Pads `src_frame` and stores it in `dst_frame`
///
/// `dst_frame` will be resized if necessary.
pub fn pad(
    src_frame: &Frame,
    dst_frame: &mut Frame,
    dst_width: NonZeroU16,
    dst_height: NonZeroU16,
    x: u16,
    y: u16,
) -> Result<(), PadError> {
    use PadError::*;
    let src_width = src_frame.width().get();
    let src_height = src_frame.height().get();

    if x + src_width > dst_width.get() {
        return Err(OutOfBounds(x, src_width, dst_width.get()));
    }
    if y + src_height > dst_height.get() {
        return Err(OutOfBounds(y, src_height, dst_height.get()));
    }

    let pix_fmt = src_frame.pix_fmt();

    dst_frame.set_width(dst_width);
    dst_frame.set_height(dst_height);
    dst_frame.set_pix_fmt(pix_fmt);

    // The line size doesn't actually change.
    *dst_frame.linesize_mut() = *src_frame.linesize();

    let draw = DrawContext::new(src_frame.pix_fmt())?;

    //needs_copy = frame_needs_copy(s, in);

    //if (needs_copy) {
    //av_log(inlink->dst, AV_LOG_DEBUG, "Direct padding impossible allocating new frame\n");
    dst_frame.reset_buffer(dst_width, dst_height, src_frame.pix_fmt(), 1)?;
    /*} else {
        int i;

        out = in;
        for (i = 0; i < 4 && out->data[i] && out->linesize[i]; i++) {
            int hsub = s->draw.hsub[i];
            int vsub = s->draw.vsub[i];
            out->data[i] -= (s->x >> hsub) * s->draw.pixelstep[i] +
                            (s->y >> vsub) * out->linesize[i];
        }
    }*/

    /* top bar */
    /*if (s->y) {
        ff_fill_rectangle(&s->draw, &s->color,
                          out->data, out->linesize,
                          0, 0, s->w, s->y);
    }

    /* bottom bar */
    if (s->h > s->y + s->in_h) {
        ff_fill_rectangle(&s->draw, &s->color,
                          out->data, out->linesize,
                          0, s->y + s->in_h, s->w, s->h - s->y - s->in_h);
    }

    /* left border */
    ff_fill_rectangle(&s->draw, &s->color, out->data, out->linesize,
                      0, s->y, s->x, in->height);*/

    let (dst_linesize, dst_data) = dst_frame.line_and_data_mut();
    draw.copy_rectangle(
        dst_data,
        dst_linesize,
        src_frame.data(),
        src_frame.linesize(),
        x,
        y,
        0,
        0,
        src_width,
        src_height,
    );
    /*if (needs_copy) {
        ff_copy_rectangle2(&s->draw,
                          out->data, out->linesize, in->data, in->linesize,
                          s->x, s->y, 0, 0, in->width, in->height);
    }

    /* right border */
    ff_fill_rectangle(&s->draw, &s->color, out->data, out->linesize,
                      s->x + s->in_w, s->y, s->w - s->x - s->in_w,
                      in->height);*/

    Ok(())
}

#[allow(clippy::too_many_arguments, clippy::unwrap_used)]
#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;
    use pretty_hex::pretty_hex;
    use sentryshot_util::pixfmt::PixelFormat;
    use std::num::NonZeroU16;
    use test_case::test_case;

    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        2, 2, 0, 0;
        "rgb24 minimal"
    )]
    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        &[0,0,0, 1,1,1, 0,0,0,
          2,2,2, 3,3,3, 0,0,0,],
        3, 2, 0, 0;
        "rgb24 right"
    )]
    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3,
          0,0,0, 0,0,0],
        2, 3, 0, 0;
        "rgb24 bottom"
    )]
    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[1,1,1, 2,2,2,
          3,3,3, 4,4,4],
        &[0,0,0, 1,1,1, 2,2,2,
          0,0,0, 3,3,3, 4,4,4],
        3, 2, 1, 0;
        "rgb24 left"
    )]
    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        &[0,0,0, 0,0,0,
          0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        2, 3, 0, 1;
        "rgb24 top"
    )]
    fn test_pad(
        src_width: u16,
        src_height: u16,
        pix_fmt: PixelFormat,
        input: &[u8],
        want: &[u8],
        width: u16,
        height: u16,
        x: u16,
        y: u16,
    ) {
        let src_frame = Frame::from_raw(
            input,
            pix_fmt,
            NonZeroU16::new(src_width).unwrap(),
            NonZeroU16::new(src_height).unwrap(),
            1,
        )
        .unwrap();

        let mut dst_frame = Frame::new();
        pad(
            &src_frame,
            &mut dst_frame,
            NonZeroU16::new(width).unwrap(),
            NonZeroU16::new(height).unwrap(),
            x,
            y,
        )
        .unwrap();

        let mut raw = Vec::new();
        dst_frame.copy_to_buffer(&mut raw, 1).unwrap();

        assert_eq!(width, dst_frame.width().get());
        assert_eq!(height, dst_frame.height().get());
        assert_eq!(pix_fmt, dst_frame.pix_fmt());
        assert_eq!(pretty_hex(&want), pretty_hex(&raw));
    }

    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        2, 2, 1, 0,
        PadError::OutOfBounds(1, 2, 2);
        "width out of bounds"
    )]
    #[test_case(
        2, 2, PixelFormat::RGB24,
        &[0,0,0, 1,1,1,
          2,2,2, 3,3,3],
        2, 2, 0, 3,
        PadError::OutOfBounds(3, 2, 2);
        "height out of bounds"
    )]
    fn test_pad_errors(
        src_width: u16,
        src_height: u16,
        pix_fmt: PixelFormat,
        input: &[u8],
        width: u16,
        height: u16,
        x: u16,
        y: u16,
        want: PadError,
    ) {
        let src_frame = Frame::from_raw(
            input,
            pix_fmt,
            NonZeroU16::new(src_width).unwrap(),
            NonZeroU16::new(src_height).unwrap(),
            1,
        )
        .unwrap();

        let mut dst_frame = Frame::new();
        let result = pad(
            &src_frame,
            &mut dst_frame,
            NonZeroU16::new(width).unwrap(),
            NonZeroU16::new(height).unwrap(),
            x,
            y,
        );
        assert_eq!(result, Err(want));
    }
}