skia-safe 0.58.0

Safe Skia Bindings for Rust
use crate::{prelude::*, scalar, BlendMode, Color, Color4f, ColorSpace, NativeFlattenable};
use skia_bindings::{self as sb, SkColorFilter, SkFlattenable, SkRefCntBase};
use std::fmt;

pub type ColorFilter = RCHandle<SkColorFilter>;
unsafe_send_sync!(ColorFilter);

impl NativeBase<SkRefCntBase> for SkColorFilter {}

impl NativeRefCountedBase for SkColorFilter {
    type Base = SkRefCntBase;
}

impl NativeBase<SkFlattenable> for SkColorFilter {}

impl NativeFlattenable for SkColorFilter {
    fn native_flattenable(&self) -> &SkFlattenable {
        self.base()
    }

    fn native_deserialize(data: &[u8]) -> *mut Self {
        unsafe { sb::C_SkColorFilter_Deserialize(data.as_ptr() as _, data.len()) }
    }
}

impl fmt::Debug for ColorFilter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ColorFilter")
            .field("as_a_color_mode", &self.to_a_color_mode())
            .field("as_a_color_matrix", &self.to_a_color_matrix())
            .field("is_alpha_unchanged", &self.is_alpha_unchanged())
            .finish()
    }
}

impl ColorFilter {
    pub fn to_a_color_mode(&self) -> Option<(Color, BlendMode)> {
        let mut color: Color = 0.into();
        let mut mode: BlendMode = Default::default();
        unsafe { self.native().asAColorMode(color.native_mut(), &mut mode) }
            .if_true_some((color, mode))
    }

    pub fn to_a_color_matrix(&self) -> Option<[scalar; 20]> {
        let mut matrix: [scalar; 20] = Default::default();
        unsafe { self.native().asAColorMatrix(&mut matrix[0]) }.if_true_some(matrix)
    }

    pub fn is_alpha_unchanged(&self) -> bool {
        unsafe { self.native().isAlphaUnchanged() }
    }

    pub fn filter_color(&self, color: impl Into<Color>) -> Color {
        // Color resolves to u32, so the C++ ABI can be used.
        Color::from_native_c(unsafe { self.native().filterColor(color.into().into_native()) })
    }

    pub fn filter_color4f(
        &self,
        color: impl AsRef<Color4f>,
        src_color_space: &ColorSpace,
        dst_color_space: Option<&ColorSpace>,
    ) -> Color4f {
        Color4f::from_native_c(unsafe {
            sb::C_SkColorFilter_filterColor4f(
                self.native(),
                color.as_ref().native(),
                src_color_space.native_mut_force(),
                dst_color_space.native_ptr_or_null_mut_force(),
            )
        })
    }

    pub fn composed(&self, inner: impl Into<ColorFilter>) -> Option<Self> {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilter_makeComposed(self.native(), inner.into().into_ptr())
        })
    }
}

pub mod color_filters {
    use crate::{prelude::*, Color4f, ColorSpace};
    use crate::{scalar, BlendMode, Color, ColorFilter, ColorMatrix};
    use skia_bindings as sb;

    pub fn compose(
        outer: impl Into<ColorFilter>,
        inner: impl Into<ColorFilter>,
    ) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilters_Compose(outer.into().into_ptr(), inner.into().into_ptr())
        })
    }

    pub fn matrix(color_matrix: &ColorMatrix) -> ColorFilter {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Matrix(color_matrix.native()) })
            .unwrap()
    }

    pub fn matrix_row_major(array: &[scalar; 20]) -> ColorFilter {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_MatrixRowMajor(array.as_ptr()) })
            .unwrap()
    }

    // A version of Matrix which operates in HSLA space instead of RGBA.
    // I.e. HSLA-to-RGBA(Matrix(RGBA-to-HSLA(input))).
    pub fn hsla_matrix_of_color_matrix(color_matrix: &ColorMatrix) -> ColorFilter {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilters_HSLAMatrixOfColorMatrix(color_matrix.native())
        })
        .unwrap()
    }

    /// See [`hsla_matrix_of_color_matrix()`]
    pub fn hsla_matrix(row_major: &[f32; 20]) -> ColorFilter {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_HSLAMatrix(row_major.as_ptr()) })
            .unwrap()
    }

    pub fn blend(c: impl Into<Color>, mode: BlendMode) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Blend(c.into().into_native(), mode) })
    }

    /// Blends between the constant color (src) and input color (dst) based on the [`BlendMode`].
    /// If the color space is `None`, the constant color is assumed to be defined in sRGB.
    pub fn blend_with_color_space(
        c: impl Into<Color4f>,
        color_space: impl Into<Option<ColorSpace>>,
        mode: BlendMode,
    ) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilters_Blend2(
                c.into().native(),
                color_space.into().into_ptr_or_null(),
                mode,
            )
        })
    }

    pub fn linear_to_srgb_gamma() -> ColorFilter {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_LinearToSRGBGamma() }).unwrap()
    }

    pub fn srgb_to_linear_gamma() -> ColorFilter {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_SRGBToLinearGamma() }).unwrap()
    }

    pub fn lerp(
        t: f32,
        dst: impl Into<ColorFilter>,
        src: impl Into<ColorFilter>,
    ) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilters_Lerp(t, dst.into().into_ptr(), src.into().into_ptr())
        })
    }

    /// Create a table color filter, copying the table into the filter, and
    /// applying it to all 4 components.
    /// `a' = table[a];`
    /// `r' = table[r];`
    /// `g' = table[g];`
    /// `b' = table[b];`
    /// Components are operated on in unpremultiplied space. If the incoming
    /// colors are premultiplied, they are temporarily unpremultiplied, then
    /// the table is applied, and then the result is re-multiplied.
    pub fn table(table: &[u8; 256]) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Table(table.as_ptr()) })
    }

    /// Create a table color filter, with a different table for each
    /// component [A, R, G, B]. If a given table is `None`, then it is
    /// treated as identity, with the component left unchanged. If a table
    /// is not `None`, then its contents are copied into the filter.
    pub fn table_argb<'a>(
        table_a: impl Into<Option<&'a [u8; 256]>>,
        table_r: impl Into<Option<&'a [u8; 256]>>,
        table_g: impl Into<Option<&'a [u8; 256]>>,
        table_b: impl Into<Option<&'a [u8; 256]>>,
    ) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilters_TableARGB(
                table_a.into().map(|t| t.as_ref()).as_ptr_or_null(),
                table_r.into().map(|t| t.as_ref()).as_ptr_or_null(),
                table_g.into().map(|t| t.as_ref()).as_ptr_or_null(),
                table_b.into().map(|t| t.as_ref()).as_ptr_or_null(),
            )
        })
    }

    /// Create a color filter that multiplies the RGB channels by one color, and
    /// then adds a second color, pinning the result for each component to
    /// [0..255]. The alpha components of the mul and add arguments
    /// are ignored.
    pub fn lighting(mul: impl Into<Color>, add: impl Into<Color>) -> Option<ColorFilter> {
        ColorFilter::from_ptr(unsafe {
            sb::C_SkColorFilters_Lighting(mul.into().into_native(), add.into().into_native())
        })
    }
}

#[cfg(test)]

mod tests {
    use crate::prelude::*;
    use crate::{color_filters, BlendMode, Color, Color4f, ColorSpace};

    #[test]
    fn color_mode_roundtrip() {
        let color = Color::CYAN;
        let mode = BlendMode::ColorBurn;
        let cf = color_filters::blend(color, mode).unwrap();
        let (c, m) = cf.to_a_color_mode().unwrap();
        assert_eq!(color, c);
        assert_eq!(mode, m);
    }

    #[test]
    fn ref_count() {
        let color = Color::CYAN;
        let mode = BlendMode::ColorBurn;
        let cf = color_filters::blend(color, mode).unwrap();
        let rc = cf.native()._ref_cnt();
        assert_eq!(1, rc);
    }

    #[test]
    fn filter_color() {
        let color = Color::CYAN;
        let mode = BlendMode::ColorBurn;
        let cf = color_filters::blend(color, mode).unwrap();
        let _fc = cf.filter_color(Color::DARK_GRAY);
        let _fc = cf.filter_color4f(
            Color4f::new(0.0, 0.0, 0.0, 0.0),
            &ColorSpace::new_srgb(),
            None,
        );
    }
}