use crate::Vec2;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ColorRange {
#[default]
Limited,
Full,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum MatrixCoeffs {
Identity,
Bt601,
#[default]
Bt709,
Bt2020Ncl,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Primaries {
#[default]
Bt709,
Bt601_625,
Bt601_525,
Bt2020,
DciP3,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Transfer {
Bt709,
#[default]
Srgb,
Pq,
Hlg,
Linear,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ChromaSiting {
#[default]
Left,
Center,
TopLeft,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PixelFormat {
#[default]
Nv12,
I420,
I444,
P010,
Rgba,
}
impl PixelFormat {
pub fn num_planes(&self) -> u32 {
match self {
PixelFormat::Nv12 | PixelFormat::P010 => 2,
PixelFormat::I420 | PixelFormat::I444 => 3,
PixelFormat::Rgba => 1,
}
}
pub fn chroma_size(&self, luma_w: u32, luma_h: u32) -> (u32, u32) {
match self {
PixelFormat::Nv12 | PixelFormat::I420 | PixelFormat::P010 => {
(luma_w.div_ceil(2), luma_h.div_ceil(2))
}
PixelFormat::I444 => (luma_w, luma_h),
PixelFormat::Rgba => (0, 0),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ColorInfo {
pub range: ColorRange,
pub matrix: MatrixCoeffs,
pub primaries: Primaries,
pub transfer: Transfer,
pub chroma_siting: ChromaSiting,
}
impl Default for ColorInfo {
fn default() -> Self {
Self {
range: ColorRange::Limited,
matrix: MatrixCoeffs::Bt709,
primaries: Primaries::Bt709,
transfer: Transfer::Bt709,
chroma_siting: ChromaSiting::Left,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct YuvTransform {
pub m: [[f32; 3]; 3],
pub b: [f32; 3],
}
impl ColorInfo {
pub fn to_yuv_transform(&self) -> YuvTransform {
let (y_scale, y_off, uv_scale, uv_off) = match self.range {
ColorRange::Limited => (255.0 / 219.0, 16.0 / 255.0, 255.0 / 224.0, 128.0 / 255.0),
ColorRange::Full => (1.0, 0.0, 1.0, 0.5),
};
if self.matrix == MatrixCoeffs::Identity {
return YuvTransform {
m: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
b: [0.0; 3],
};
}
let (kr, kb) = match self.matrix {
MatrixCoeffs::Bt601 => (0.299_f32, 0.114_f32),
MatrixCoeffs::Bt709 => (0.2126_f32, 0.0722_f32),
MatrixCoeffs::Bt2020Ncl => (0.2627_f32, 0.0593_f32),
_ => unreachable!(), };
let kg = 1.0 - kr - kb;
let cb_factor = 2.0 * kb * (1.0 - kb) / (1.0 - kg);
let cr_factor = 2.0 * kr * (1.0 - kr) / (1.0 - kg);
let m00 = y_scale;
let m01 = 0.0;
let m02 = 2.0 * (1.0 - kr) * uv_scale;
let m10 = y_scale;
let m11 = -cb_factor * uv_scale;
let m12 = -cr_factor * uv_scale;
let m20 = y_scale;
let m21 = 2.0 * (1.0 - kb) * uv_scale;
let m22 = 0.0;
let b0 = -y_scale * y_off - 2.0 * (1.0 - kr) * uv_scale * uv_off;
let b1 = -y_scale * y_off + cb_factor * uv_scale * uv_off + cr_factor * uv_scale * uv_off;
let b2 = -y_scale * y_off - 2.0 * (1.0 - kb) * uv_scale * uv_off;
YuvTransform {
m: [[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]],
b: [b0, b1, b2],
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Color(pub u8, pub u8, pub u8, pub u8);
impl Color {
pub const TRANSPARENT: Color = Color(0, 0, 0, 0);
pub const BLACK: Color = Color(0, 0, 0, 255);
pub const WHITE: Color = Color(255, 255, 255, 255);
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Color(r, g, b, 255)
}
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Color(r, g, b, a)
}
pub fn from_hex(hex: &str) -> Self {
let s = hex.trim_start_matches('#');
let (r, g, b, a) = match s.len() {
6 => (
u8::from_str_radix(&s[0..2], 16).unwrap_or(0),
u8::from_str_radix(&s[2..4], 16).unwrap_or(0),
u8::from_str_radix(&s[4..6], 16).unwrap_or(0),
255,
),
8 => (
u8::from_str_radix(&s[0..2], 16).unwrap_or(0),
u8::from_str_radix(&s[2..4], 16).unwrap_or(0),
u8::from_str_radix(&s[4..6], 16).unwrap_or(0),
u8::from_str_radix(&s[6..8], 16).unwrap_or(255),
),
_ => (0, 0, 0, 255),
};
Color(r, g, b, a)
}
pub fn with_alpha(self, a: u8) -> Self {
Color(self.0, self.1, self.2, a)
}
pub fn with_alpha_f32(self, a: f32) -> Self {
Color(
self.0,
self.1,
self.2,
(a * 255.0).round().clamp(0.0, 255.0) as u8,
)
}
pub fn composite_over(&self, background: Color) -> Color {
let a = self.3 as f32 / 255.0;
let inv_a = 1.0 - a;
Color(
(self.0 as f32 * a + background.0 as f32 * inv_a)
.round()
.clamp(0.0, 255.0) as u8,
(self.1 as f32 * a + background.1 as f32 * inv_a)
.round()
.clamp(0.0, 255.0) as u8,
(self.2 as f32 * a + background.2 as f32 * inv_a)
.round()
.clamp(0.0, 255.0) as u8,
background.3,
)
}
pub fn to_linear(self) -> [f32; 4] {
fn srgb_to_linear(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
let r = srgb_to_linear(self.0 as f32 / 255.0);
let g = srgb_to_linear(self.1 as f32 / 255.0);
let b = srgb_to_linear(self.2 as f32 / 255.0);
let a = self.3 as f32 / 255.0;
[r, g, b, a]
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum Brush {
Solid(Color),
Linear {
start: Vec2,
end: Vec2,
start_color: Color,
end_color: Color,
},
}
impl From<Color> for Brush {
fn from(c: Color) -> Self {
Brush::Solid(c)
}
}
pub struct LinearGradient {
pub start: Vec2,
pub end: Vec2,
pub start_color: Color,
pub end_color: Color,
}
impl LinearGradient {
pub fn vertical(top: Color, bottom: Color) -> Brush {
Brush::Linear {
start: Vec2 { x: 0.0, y: 0.0 },
end: Vec2 { x: 0.0, y: 1.0 }, start_color: top,
end_color: bottom,
}
}
pub fn horizontal(left: Color, right: Color) -> Brush {
Brush::Linear {
start: Vec2 { x: 0.0, y: 0.0 },
end: Vec2 { x: 1.0, y: 0.0 },
start_color: left,
end_color: right,
}
}
}