From 5f94476b2a45cb44a8ce35ae154598adad78a3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Sun, 19 Mar 2023 21:08:21 +0100 Subject: [PATCH] render: Add BitmapFormat::Yuv420p and BitmapFormat::Yuva420p --- Cargo.lock | 1 + core/src/display_object/bitmap.rs | 3 ++ render/Cargo.toml | 1 + render/src/bitmap.rs | 75 ++++++++++++++++++++++++++++--- render/webgl/src/lib.rs | 8 ++-- 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b7870af4..a1bff7e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3514,6 +3514,7 @@ dependencies = [ "flate2", "gc-arena", "gif", + "h263-rs-yuv", "jpeg-decoder", "lyon", "png", diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index 9f4641568..c6d018dbf 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -146,6 +146,9 @@ impl<'gc> Bitmap<'gc> { match bitmap.format() { BitmapFormat::Rgba => true, BitmapFormat::Rgb => false, + _ => unreachable!( + "Bitmap objects can only be constructed from RGB or RGBA source bitmaps" + ), }, pixels, ); diff --git a/render/Cargo.toml b/render/Cargo.toml index 6bd216c75..7675b2890 100644 --- a/render/Cargo.toml +++ b/render/Cargo.toml @@ -23,6 +23,7 @@ gc-arena = { workspace = true } enum-map = "2.5.0" serde = { version = "1.0.159", features = ["derive"] } clap = { version = "4.2.1", features = ["derive"], optional = true } +h263-rs-yuv = { git = "https://github.com/ruffle-rs/h263-rs", rev = "d5d78eb251c1ce1f1da57c63db14f0fdc77a4b36"} [dependencies.jpeg-decoder] version = "0.3.0" diff --git a/render/src/bitmap.rs b/render/src/bitmap.rs index fa57501f3..a046b5977 100644 --- a/render/src/bitmap.rs +++ b/render/src/bitmap.rs @@ -1,4 +1,5 @@ use gc_arena::Collect; +use h263_rs_yuv::bt601::yuv420_to_rgba; use std::fmt::Debug; use std::sync::Arc; @@ -89,14 +90,47 @@ impl Bitmap { pub fn to_rgba(mut self) -> Self { // Converts this bitmap to RGBA, if it is not already. - if self.format == BitmapFormat::Rgb { - self.data = self - .data - .chunks_exact(3) - .flat_map(|rgb| [rgb[0], rgb[1], rgb[2], 255]) - .collect(); - self.format = BitmapFormat::Rgba; + match self.format { + BitmapFormat::Rgb => { + self.data = self + .data + .chunks_exact(3) + .flat_map(|rgb| [rgb[0], rgb[1], rgb[2], 255]) + .collect(); + } + BitmapFormat::Rgba => {} // no-op + BitmapFormat::Yuv420p => { + let luma_len = (self.width * self.height) as usize; + let chroma_len = (self.chroma_width() * self.chroma_height()) as usize; + + let y = &self.data[0..luma_len]; + let u = &self.data[luma_len..luma_len + chroma_len]; + let v = &self.data[luma_len + chroma_len..luma_len + 2 * chroma_len]; + + self.data = yuv420_to_rgba(y, u, v, self.width as usize); + } + BitmapFormat::Yuva420p => { + let luma_len = (self.width * self.height) as usize; + let chroma_len = (self.chroma_width() * self.chroma_height()) as usize; + + let y = &self.data[0..luma_len]; + let u = &self.data[luma_len..luma_len + chroma_len]; + let v = &self.data[luma_len + chroma_len..luma_len + chroma_len + chroma_len]; + let a = &self.data[luma_len + 2 * chroma_len..2 * luma_len + 2 * chroma_len]; + + let rgba = yuv420_to_rgba(y, u, v, self.width as usize); + + // RGB components need to be clamped to alpha to avoid invalid premultiplied colors + self.data = rgba + .chunks_exact(4) + .zip(a) + .flat_map(|(rgba, a)| [rgba[0].min(*a), rgba[1].min(*a), rgba[2].min(*a), *a]) + .collect() + } } + + self.format = BitmapFormat::Rgba; + self } @@ -110,6 +144,20 @@ impl Bitmap { self.height } + pub fn chroma_width(&self) -> u32 { + match self.format { + BitmapFormat::Yuv420p | BitmapFormat::Yuva420p => (self.width + 1) / 2, + _ => unreachable!("Can't get chroma width for non-YUV bitmap"), + } + } + + pub fn chroma_height(&self) -> u32 { + match self.format { + BitmapFormat::Yuv420p | BitmapFormat::Yuva420p => (self.height + 1) / 2, + _ => unreachable!("Can't get chroma height for non-YUV bitmap"), + } + } + #[inline] pub fn format(&self) -> BitmapFormat { self.format @@ -129,6 +177,9 @@ impl Bitmap { let chunks = match self.format { BitmapFormat::Rgb => self.data.chunks_exact(3), BitmapFormat::Rgba => self.data.chunks_exact(4), + _ => unimplemented!( + "Can't iterate over non-RGB(A) bitmaps as colors, convert with `to_rgba` first" + ), }; chunks.map(|chunk| { let red = chunk[0]; @@ -148,6 +199,12 @@ pub enum BitmapFormat { /// 32-bit RGBA with premultiplied alpha. Rgba, + + /// planar YUV 420 + Yuv420p, + + /// planar YUV 420, premultiplied with alpha (RGB channels are to be clamped after conversion) + Yuva420p, } impl BitmapFormat { @@ -156,6 +213,10 @@ impl BitmapFormat { match self { BitmapFormat::Rgb => width * height * 3, BitmapFormat::Rgba => width * height * 4, + BitmapFormat::Yuv420p => width * height + ((width + 1) / 2) * ((height + 1) / 2) * 2, + BitmapFormat::Yuva420p => { + width * height * 2 + ((width + 1) / 2) * ((height + 1) / 2) * 2 + } } } } diff --git a/render/webgl/src/lib.rs b/render/webgl/src/lib.rs index b9357ffcd..4d90e8e09 100644 --- a/render/webgl/src/lib.rs +++ b/render/webgl/src/lib.rs @@ -988,9 +988,11 @@ impl RenderBackend for WebGlRenderBackend { } fn register_bitmap(&mut self, bitmap: Bitmap) -> Result { - let format = match bitmap.format() { - BitmapFormat::Rgb => Gl::RGB, - BitmapFormat::Rgba => Gl::RGBA, + let (format, bitmap) = match bitmap.format() { + BitmapFormat::Rgb => (Gl::RGB, bitmap), + BitmapFormat::Rgba | BitmapFormat::Yuv420p | BitmapFormat::Yuva420p => { + (Gl::RGBA, bitmap.to_rgba()) + } }; let texture = self