sentryshot_filter 0.1.2

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

use sentryshot_util::{
    ceil_rshift,
    pixfmt::{
        PixelFormat, PIX_FMT_FLAG_ALPHA, PIX_FMT_FLAG_BE, PIX_FMT_FLAG_PLANAR, PIX_FMT_FLAG_RGB,
    },
};
use thiserror::Error;

pub(crate) struct DrawContext {
    //const struct AVPixFmtDescriptor *desc;
    //enum AVPixelFormat format;
    nb_planes: u8,

    // Offset between pixels.
    pub pixelstep: [usize; MAX_PLANES2],

    // Horizontal subsampling.
    pub hsub: [u8; 4],
    // Vertical subsampling.
    pub vsub: [u8; 4],
    //
    //uint8_t hsub_max;
    //uint8_t vsub_max;
    //enum AVColorRange range;
    //unsigned flags;
    //enum AVColorSpace csp;
    //double rgb2yuv[3][3];
}

const MAX_PLANES: u8 = 4;
const MAX_PLANES2: usize = 4;

#[derive(Debug, Error, PartialEq, Eq)]
pub enum NewDrawContextError {
    #[error("big endian pixel formats are unsupported")]
    BigEndianUnsupported,

    #[error("pixel format {0} is unsupported")]
    PixelFormatUnsupported(PixelFormat),

    #[error("bit depth {0} is unsupported")]
    BitDepthUnsupported(u8),

    #[error("too many planes")]
    TooManyPlanes,

    #[error("middle bits")]
    MiddleBits,

    #[error("mixed depths")]
    MixedDepths,

    #[error("strange interleaving")]
    StrangeInterleaving,
}

impl DrawContext {
    pub fn new(pix_fmt: PixelFormat) -> Result<Self, NewDrawContextError> {
        use NewDrawContextError::*;
        if pix_fmt.flags() & PIX_FMT_FLAG_BE != 0 {
            return Err(BigEndianUnsupported);
        }
        if pix_fmt.flags() & !(PIX_FMT_FLAG_PLANAR | PIX_FMT_FLAG_RGB | PIX_FMT_FLAG_ALPHA) != 0 {
            return Err(PixelFormatUnsupported(pix_fmt));
        }
        /*if (csp == AVCOL_SPC_UNSPECIFIED)
            csp = (desc->flags & AV_PIX_FMT_FLAG_RGB) ? AVCOL_SPC_RGB : AVCOL_SPC_SMPTE170M;
        if (!(desc->flags & AV_PIX_FMT_FLAG_RGB) && !(luma = av_csp_luma_coeffs_from_avcsp(csp)))
            return AVERROR(EINVAL);
        if (range == AVCOL_RANGE_UNSPECIFIED)
            range = (format == AV_PIX_FMT_YUVJ420P || format == AV_PIX_FMT_YUVJ422P ||
                     format == AV_PIX_FMT_YUVJ444P || format == AV_PIX_FMT_YUVJ411P ||
                     format == AV_PIX_FMT_YUVJ440P || csp == AVCOL_SPC_RGB)
                    ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
        if (range != AVCOL_RANGE_JPEG && range != AVCOL_RANGE_MPEG)
            return AVERROR(EINVAL);*/

        let mut depthb = 0;
        let mut pixelstep: [usize; MAX_PLANES2] = [0, 0, 0, 0];
        let mut nb_planes = 0;
        for i in 0..pix_fmt.comps().len() {
            let c = &pix_fmt.comps()[i];

            // For now, only 8-16 bits formats.
            if c.depth() < 8 || c.depth() > 16 {
                return Err(BitDepthUnsupported(c.depth()));
            }
            if c.plane() >= MAX_PLANES {
                return Err(TooManyPlanes);
            }

            // data must either be in the high or low bits, never middle.
            if c.shift() != 0 && ((c.shift() + c.depth()) & 0b111) != 0 {
                return Err(MiddleBits);
            }

            // Mixed >8 and <=8 depth.
            let db = (c.depth() + 7) / 8;
            if depthb != 0 && (depthb != db) {
                return Err(MixedDepths);
            }
            depthb = db;
            if db * (c.offset() + 1) > 16 {
                return Err(MixedDepths);
            }
            if (c.offset() % db) != 0 {
                return Err(MixedDepths);
            }

            // Strange interleaving.
            let plane = usize::from(c.plane());
            let step = usize::from(c.step());
            if pixelstep[plane] != 0 && pixelstep[plane] != step {
                return Err(StrangeInterleaving);
            }
            pixelstep[plane] = step;
            if pixelstep[plane] >= 8 {
                return Err(StrangeInterleaving);
            }
            nb_planes = nb_planes.max(c.plane() + 1);
        }
        //draw->format    = format;
        //draw->range     = range;
        //draw->csp       = csp;
        //draw->flags     = flags;
        //if (luma)
        //    ff_fill_rgb2yuv_table(luma, draw->rgb2yuv);

        let log2_chroma_w = pix_fmt.log2_chroma_w();
        //draw->hsub[1] = draw->hsub[2] = draw->hsub_max = desc->log2_chroma_w;
        let log2_chroma_h = pix_fmt.log2_chroma_h();
        //draw->vsub[1] = draw->vsub[2] = draw->vsub_max = desc->log2_chroma_h;

        Ok(DrawContext {
            nb_planes,
            pixelstep,
            hsub: [0, log2_chroma_w, log2_chroma_w, 0],
            vsub: [0, log2_chroma_h, log2_chroma_h, 0],
        })
    }

    //ff_fill_rectangle(FFDrawContext *draw, FFDrawColor *color,
    //                      uint8_t *dst[], int dst_linesize[],
    //                     int dst_x, int dst_y, int w, int h)
    //fn ff_fill_rectangle(&self) {
    //int plane, x, y, wp, hp;
    //uint8_t *p0, *p;
    //FFDrawColor color_tmp = *color;

    /*for (plane = 0; plane < draw->nb_planes; plane++) {
        p0 = pointer_at(draw, dst, dst_linesize, plane, dst_x, dst_y);
        wp = AV_CEIL_RSHIFT(w, draw->hsub[plane]);
        hp = AV_CEIL_RSHIFT(h, draw->vsub[plane]);
        if (!hp)
            return;
        p = p0;

        if (HAVE_BIGENDIAN && draw->desc->comp[0].depth > 8) {
            for (x = 0; 2*x < draw->pixelstep[plane]; x++)
                color_tmp.comp[plane].u16[x] = av_bswap16(color_tmp.comp[plane].u16[x]);
        }

        /* copy first line from color */
        for (x = 0; x < wp; x++) {
            memcpy(p, color_tmp.comp[plane].u8, draw->pixelstep[plane]);
            p += draw->pixelstep[plane];
        }
        wp *= draw->pixelstep[plane];
        /* copy next lines from first line */
        p = p0 + dst_linesize[plane];
        for (y = 1; y < hp; y++) {
            memcpy(p, p0, wp);
            p += dst_linesize[plane];
        }
    }*/
    //}

    #[allow(clippy::similar_names)]
    fn slice_offset(&self, linesize: &[usize], plane: usize, x: u16, y: u16) -> usize {
        let y = usize::from(y);
        let x = usize::from(x);
        let vsub = usize::from(self.vsub[plane]);
        let hsub = usize::from(self.hsub[plane]);
        (y >> vsub) * linesize[plane] + (x >> hsub) * self.pixelstep[plane]
    }

    fn slice_at<'a>(
        &self,
        data: &'a [Vec<u8>],
        linesize: &[usize],
        plane: u8,
        x: u16,
        y: u16,
    ) -> &'a [u8] {
        let plane = usize::from(plane);
        let offset = self.slice_offset(linesize, plane, x, y);
        &data[plane][offset..]
    }

    fn slice_at_mut<'a>(
        &self,
        data: &'a mut [Vec<u8>],
        linesize: &[usize],
        plane: u8,
        x: u16,
        y: u16,
    ) -> &'a mut [u8] {
        let plane = usize::from(plane);
        let offset = self.slice_offset(linesize, plane, x, y);
        &mut data[plane][offset..]
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn copy_rectangle(
        &self,
        dst: &mut [Vec<u8>],
        dst_linesize: &[usize],
        src: &[Vec<u8>],
        src_linesize: &[usize],
        dst_x: u16,
        dst_y: u16,
        src_x: u16,
        src_y: u16,
        w: u16,
        h: u16,
    ) {
        for plane in 0..self.nb_planes {
            let mut p = self.slice_at(src, src_linesize, plane, src_x, src_y);
            let mut q = self.slice_at_mut(dst, dst_linesize, plane, dst_x, dst_y);
            let plane = usize::from(plane);
            let wp = usize::from(ceil_rshift(w, self.hsub[plane])) * self.pixelstep[plane];
            let hp = ceil_rshift(h, self.vsub[plane]);
            // Don't advance pointers on last iteration.
            for _ in 0..hp - 1 {
                q[..wp].copy_from_slice(&p[..wp]);
                p = &p[src_linesize[plane]..];
                q = &mut q[dst_linesize[plane]..];
            }
            q[..wp].copy_from_slice(&p[..wp]);
        }
    }
}