mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibGfx: Implement YUV->RGBA color conversion for CPU painting
Using the Rust yuv crate, eagerly convert from YUV to RGBA on the CPU when a GPU context is unavailable. Time spent converting an 8-bit YUV frame with this crate is better than libyuv on ARM by about 20%, and on x86 with AVX2, it achieves similar numbers to libyuv.
This commit is contained in:
committed by
Gregory Bertilson
parent
f434b56ffa
commit
3cfe1f7542
Notes:
github-actions[bot]
2026-04-18 06:25:55 +00:00
Author: https://github.com/Zaggy1024 Commit: https://github.com/LadybirdBrowser/ladybird/commit/3cfe1f7542e
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -361,6 +361,14 @@ version = "0.2.183"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libgfx_rust"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cbindgen",
|
||||||
|
"yuv",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libjs_rust"
|
name = "libjs_rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -892,6 +900,15 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yuv"
|
||||||
|
version = "0.8.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47d3a7e2cda3061858987ee2fb028f61695f5ee13f9490d75be6c3900df9a4ea"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"Libraries/LibGfx/Rust",
|
||||||
"Libraries/LibJS/Rust",
|
"Libraries/LibJS/Rust",
|
||||||
"Libraries/LibRegex/Rust",
|
"Libraries/LibRegex/Rust",
|
||||||
"Libraries/LibUnicode/Rust",
|
"Libraries/LibUnicode/Rust",
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ find_package(harfbuzz REQUIRED)
|
|||||||
target_link_libraries(LibGfx PRIVATE PkgConfig::WOFF2 JPEG::JPEG PNG::PNG avif WebP::webp WebP::webpdecoder
|
target_link_libraries(LibGfx PRIVATE PkgConfig::WOFF2 JPEG::JPEG PNG::PNG avif WebP::webp WebP::webpdecoder
|
||||||
WebP::webpdemux WebP::libwebpmux skia harfbuzz)
|
WebP::webpdemux WebP::libwebpmux skia harfbuzz)
|
||||||
|
|
||||||
|
import_rust_crate(MANIFEST_PATH Rust/Cargo.toml CRATE_NAME libgfx_rust FFI_HEADER RustFFI.h)
|
||||||
|
target_link_libraries(LibGfx PRIVATE libgfx_rust)
|
||||||
|
|
||||||
if (HAS_FONTCONFIG)
|
if (HAS_FONTCONFIG)
|
||||||
target_link_libraries(LibGfx PRIVATE Fontconfig::Fontconfig)
|
target_link_libraries(LibGfx PRIVATE Fontconfig::Fontconfig)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -208,8 +208,10 @@ ErrorOr<NonnullRefPtr<ImmutableBitmap>> ImmutableBitmap::create_from_yuv(Nonnull
|
|||||||
auto context = SkiaBackendContext::the();
|
auto context = SkiaBackendContext::the();
|
||||||
auto* gr_context = context ? context->sk_context() : nullptr;
|
auto* gr_context = context ? context->sk_context() : nullptr;
|
||||||
|
|
||||||
if (!gr_context)
|
if (!gr_context) {
|
||||||
return Error::from_string_literal("GPU context is unavailable");
|
auto bitmap = TRY(yuv_data->to_bitmap());
|
||||||
|
return create(move(bitmap), move(color_space));
|
||||||
|
}
|
||||||
|
|
||||||
if (yuv_data->bit_depth() > 8)
|
if (yuv_data->bit_depth() > 8)
|
||||||
yuv_data->expand_samples_to_full_16_bit_range();
|
yuv_data->expand_samples_to_full_16_bit_range();
|
||||||
|
|||||||
13
Libraries/LibGfx/Rust/Cargo.toml
Normal file
13
Libraries/LibGfx/Rust/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "libgfx_rust"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yuv = "0.8"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cbindgen = "0.29"
|
||||||
40
Libraries/LibGfx/Rust/build.rs
Normal file
40
Libraries/LibGfx/Rust/build.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-present, the Ladybird developers.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
||||||
|
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
println!("cargo:rerun-if-changed=cbindgen.toml");
|
||||||
|
println!("cargo:rerun-if-env-changed=FFI_OUTPUT_DIR");
|
||||||
|
println!("cargo:rerun-if-changed=src");
|
||||||
|
|
||||||
|
let ffi_out_dir = env::var("FFI_OUTPUT_DIR")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|_| out_dir.clone());
|
||||||
|
|
||||||
|
cbindgen::generate(manifest_dir).map_or_else(
|
||||||
|
|error| match error {
|
||||||
|
cbindgen::Error::ParseSyntaxError { .. } => {}
|
||||||
|
e => panic!("{e:?}"),
|
||||||
|
},
|
||||||
|
|bindings| {
|
||||||
|
let header_path = out_dir.join("RustFFI.h");
|
||||||
|
bindings.write_to_file(&header_path);
|
||||||
|
|
||||||
|
if ffi_out_dir != out_dir {
|
||||||
|
bindings.write_to_file(ffi_out_dir.join("RustFFI.h"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
23
Libraries/LibGfx/Rust/cbindgen.toml
Normal file
23
Libraries/LibGfx/Rust/cbindgen.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
language = "C++"
|
||||||
|
header = """/*
|
||||||
|
* Copyright (c) 2026-present, the Ladybird developers.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/"""
|
||||||
|
pragma_once = true
|
||||||
|
include_version = true
|
||||||
|
line_length = 120
|
||||||
|
tab_width = 4
|
||||||
|
no_includes = true
|
||||||
|
sys_includes = ["stdint.h", "stddef.h"]
|
||||||
|
usize_is_size_t = true
|
||||||
|
namespaces = ["Gfx", "FFI"]
|
||||||
|
|
||||||
|
[parse]
|
||||||
|
parse_deps = false
|
||||||
|
|
||||||
|
[parse.expand]
|
||||||
|
all_features = false
|
||||||
|
|
||||||
|
[export.mangle]
|
||||||
|
rename_types = "PascalCase"
|
||||||
241
Libraries/LibGfx/Rust/src/lib.rs
Normal file
241
Libraries/LibGfx/Rust/src/lib.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-present, the Ladybird developers.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
use yuv::{YuvPlanarImage, YuvRange, YuvStandardMatrix};
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum YUVRange {
|
||||||
|
Limited = 0,
|
||||||
|
Full = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum YUVMatrix {
|
||||||
|
Bt709 = 0,
|
||||||
|
Fcc = 1,
|
||||||
|
Bt470BG = 2,
|
||||||
|
Bt601 = 3,
|
||||||
|
Smpte240 = 4,
|
||||||
|
Bt2020 = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<YUVRange> for YuvRange {
|
||||||
|
fn from(range: YUVRange) -> Self {
|
||||||
|
match range {
|
||||||
|
YUVRange::Limited => YuvRange::Limited,
|
||||||
|
YUVRange::Full => YuvRange::Full,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<YUVMatrix> for YuvStandardMatrix {
|
||||||
|
fn from(matrix: YUVMatrix) -> Self {
|
||||||
|
match matrix {
|
||||||
|
YUVMatrix::Bt709 => YuvStandardMatrix::Bt709,
|
||||||
|
YUVMatrix::Fcc => YuvStandardMatrix::Fcc,
|
||||||
|
YUVMatrix::Bt470BG => YuvStandardMatrix::Bt470_6,
|
||||||
|
YUVMatrix::Bt601 => YuvStandardMatrix::Bt601,
|
||||||
|
YUVMatrix::Smpte240 => YuvStandardMatrix::Smpte240,
|
||||||
|
YUVMatrix::Bt2020 => YuvStandardMatrix::Bt2020,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// All plane pointers must be valid for the specified dimensions and strides.
|
||||||
|
/// `dst` must point to a buffer of at least `dst_stride * height` bytes.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn yuv_u8_to_rgba(
|
||||||
|
y_plane: *const u8,
|
||||||
|
y_stride: u32,
|
||||||
|
u_plane: *const u8,
|
||||||
|
u_stride: u32,
|
||||||
|
v_plane: *const u8,
|
||||||
|
v_stride: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
subsampling_x: bool,
|
||||||
|
subsampling_y: bool,
|
||||||
|
dst: *mut u8,
|
||||||
|
dst_stride: u32,
|
||||||
|
range: YUVRange,
|
||||||
|
matrix: YUVMatrix,
|
||||||
|
) -> bool {
|
||||||
|
let y_len = y_stride as usize * height as usize;
|
||||||
|
let uv_height = if subsampling_y {
|
||||||
|
height.div_ceil(2)
|
||||||
|
} else {
|
||||||
|
height
|
||||||
|
} as usize;
|
||||||
|
let uv_len = u_stride as usize * uv_height;
|
||||||
|
|
||||||
|
let planar_image = YuvPlanarImage {
|
||||||
|
y_plane: unsafe { core::slice::from_raw_parts(y_plane, y_len) },
|
||||||
|
y_stride,
|
||||||
|
u_plane: unsafe { core::slice::from_raw_parts(u_plane, uv_len) },
|
||||||
|
u_stride,
|
||||||
|
v_plane: unsafe { core::slice::from_raw_parts(v_plane, uv_len) },
|
||||||
|
v_stride,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dst_len = dst_stride as usize * height as usize;
|
||||||
|
let dst_slice = unsafe { core::slice::from_raw_parts_mut(dst, dst_len) };
|
||||||
|
|
||||||
|
let result = match (subsampling_x, subsampling_y) {
|
||||||
|
(true, true) => yuv::yuv420_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
(true, false) => yuv::yuv422_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
(false, true) => return false,
|
||||||
|
(false, false) => yuv::yuv444_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
result.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// All plane pointers must be valid for the specified dimensions and strides.
|
||||||
|
/// `dst` must point to a buffer of at least `dst_stride * height` bytes.
|
||||||
|
/// Values in the u16 planes must be in 0-1023 range for 10-bit or 0-4095 for 12-bit.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn yuv_u16_to_rgba(
|
||||||
|
y_plane: *const u16,
|
||||||
|
y_stride: u32,
|
||||||
|
u_plane: *const u16,
|
||||||
|
u_stride: u32,
|
||||||
|
v_plane: *const u16,
|
||||||
|
v_stride: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
bit_depth: u8,
|
||||||
|
subsampling_x: bool,
|
||||||
|
subsampling_y: bool,
|
||||||
|
dst: *mut u8,
|
||||||
|
dst_stride: u32,
|
||||||
|
range: YUVRange,
|
||||||
|
matrix: YUVMatrix,
|
||||||
|
) -> bool {
|
||||||
|
let y_len = y_stride as usize * height as usize;
|
||||||
|
let uv_height = if subsampling_y {
|
||||||
|
height.div_ceil(2)
|
||||||
|
} else {
|
||||||
|
height
|
||||||
|
} as usize;
|
||||||
|
let uv_len = u_stride as usize * uv_height;
|
||||||
|
|
||||||
|
let planar_image = YuvPlanarImage {
|
||||||
|
y_plane: unsafe { core::slice::from_raw_parts(y_plane, y_len) },
|
||||||
|
y_stride,
|
||||||
|
u_plane: unsafe { core::slice::from_raw_parts(u_plane, uv_len) },
|
||||||
|
u_stride,
|
||||||
|
v_plane: unsafe { core::slice::from_raw_parts(v_plane, uv_len) },
|
||||||
|
v_stride,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dst_len = dst_stride as usize * height as usize;
|
||||||
|
let dst_slice = unsafe { core::slice::from_raw_parts_mut(dst, dst_len) };
|
||||||
|
|
||||||
|
let result = if bit_depth <= 10 {
|
||||||
|
match (subsampling_x, subsampling_y) {
|
||||||
|
(true, true) => yuv::i010_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
(true, false) => yuv::i210_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
(false, true) => return false,
|
||||||
|
(false, false) => yuv::i410_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 12-bit 4:4:4 has no 8-bit RGBA output; shift to 10-bit and use I410.
|
||||||
|
if !subsampling_x && !subsampling_y {
|
||||||
|
let y_10: Vec<u16> = unsafe { core::slice::from_raw_parts(y_plane, y_len) }
|
||||||
|
.iter()
|
||||||
|
.map(|&v| v >> 2)
|
||||||
|
.collect();
|
||||||
|
let u_10: Vec<u16> = unsafe { core::slice::from_raw_parts(u_plane, uv_len) }
|
||||||
|
.iter()
|
||||||
|
.map(|&v| v >> 2)
|
||||||
|
.collect();
|
||||||
|
let v_10: Vec<u16> = unsafe { core::slice::from_raw_parts(v_plane, uv_len) }
|
||||||
|
.iter()
|
||||||
|
.map(|&v| v >> 2)
|
||||||
|
.collect();
|
||||||
|
let planar_10 = YuvPlanarImage {
|
||||||
|
y_plane: &y_10,
|
||||||
|
y_stride,
|
||||||
|
u_plane: &u_10,
|
||||||
|
u_stride,
|
||||||
|
v_plane: &v_10,
|
||||||
|
v_stride,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
return yuv::i410_to_rgba(
|
||||||
|
&planar_10,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
)
|
||||||
|
.is_ok();
|
||||||
|
}
|
||||||
|
match (subsampling_x, subsampling_y) {
|
||||||
|
(true, true) => yuv::i012_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
(true, false) => yuv::i212_to_rgba(
|
||||||
|
&planar_image,
|
||||||
|
dst_slice,
|
||||||
|
dst_stride,
|
||||||
|
range.into(),
|
||||||
|
matrix.into(),
|
||||||
|
),
|
||||||
|
(false, true) => return false,
|
||||||
|
(false, false) => unreachable!(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result.is_ok()
|
||||||
|
}
|
||||||
@@ -4,9 +4,12 @@
|
|||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Time.h>
|
||||||
|
#include <LibGfx/Bitmap.h>
|
||||||
#include <LibGfx/ImmutableBitmap.h>
|
#include <LibGfx/ImmutableBitmap.h>
|
||||||
#include <LibGfx/SkiaBackendContext.h>
|
#include <LibGfx/SkiaBackendContext.h>
|
||||||
#include <LibGfx/YUVData.h>
|
#include <LibGfx/YUVData.h>
|
||||||
|
#include <RustFFI.h>
|
||||||
|
|
||||||
#include <core/SkColorSpace.h>
|
#include <core/SkColorSpace.h>
|
||||||
#include <core/SkImage.h>
|
#include <core/SkImage.h>
|
||||||
@@ -102,6 +105,126 @@ Bytes YUVData::v_data()
|
|||||||
return m_impl->v_buffer.span();
|
return m_impl->v_buffer.span();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FFI::YUVMatrix yuv_matrix_for_cicp(Media::CodingIndependentCodePoints const& cicp)
|
||||||
|
{
|
||||||
|
switch (cicp.matrix_coefficients()) {
|
||||||
|
case Media::MatrixCoefficients::Identity:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
case Media::MatrixCoefficients::FCC:
|
||||||
|
return FFI::YUVMatrix::Fcc;
|
||||||
|
case Media::MatrixCoefficients::BT470BG:
|
||||||
|
return FFI::YUVMatrix::Bt470BG;
|
||||||
|
case Media::MatrixCoefficients::BT601:
|
||||||
|
return FFI::YUVMatrix::Bt601;
|
||||||
|
case Media::MatrixCoefficients::SMPTE240:
|
||||||
|
return FFI::YUVMatrix::Smpte240;
|
||||||
|
case Media::MatrixCoefficients::BT2020NonConstantLuminance:
|
||||||
|
case Media::MatrixCoefficients::BT2020ConstantLuminance:
|
||||||
|
return FFI::YUVMatrix::Bt2020;
|
||||||
|
case Media::MatrixCoefficients::BT709:
|
||||||
|
case Media::MatrixCoefficients::Unspecified:
|
||||||
|
default:
|
||||||
|
return FFI::YUVMatrix::Bt709;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<NonnullRefPtr<Bitmap>> YUVData::to_bitmap() const
|
||||||
|
{
|
||||||
|
auto const& impl = *m_impl;
|
||||||
|
VERIFY(impl.bit_depth <= 12);
|
||||||
|
|
||||||
|
auto bitmap = TRY(Bitmap::create(BitmapFormat::RGBA8888, AlphaType::Premultiplied, impl.size));
|
||||||
|
auto* dst = reinterpret_cast<u8*>(bitmap->scanline(0));
|
||||||
|
auto dst_stride = static_cast<u32>(bitmap->pitch());
|
||||||
|
|
||||||
|
auto width = static_cast<u32>(impl.size.width());
|
||||||
|
auto height = static_cast<u32>(impl.size.height());
|
||||||
|
|
||||||
|
if (impl.cicp.matrix_coefficients() == Media::MatrixCoefficients::Identity) {
|
||||||
|
if (impl.subsampling.x() || impl.subsampling.y())
|
||||||
|
return Error::from_string_literal("Subsampled RGB is unsupported");
|
||||||
|
|
||||||
|
if (impl.bit_depth <= 8) {
|
||||||
|
auto const* y_data = impl.y_buffer.data();
|
||||||
|
auto const* u_data = impl.u_buffer.data();
|
||||||
|
auto const* v_data = impl.v_buffer.data();
|
||||||
|
auto y_stride = static_cast<int>(width);
|
||||||
|
|
||||||
|
for (u32 row = 0; row < height; row++) {
|
||||||
|
auto* dst_row = dst + (static_cast<size_t>(row) * dst_stride);
|
||||||
|
auto const* y_row = y_data + (static_cast<size_t>(row) * y_stride);
|
||||||
|
auto const* u_row = u_data + (static_cast<size_t>(row) * y_stride);
|
||||||
|
auto const* v_row = v_data + (static_cast<size_t>(row) * y_stride);
|
||||||
|
for (u32 col = 0; col < width; col++) {
|
||||||
|
dst_row[(col * 4) + 0] = v_row[col];
|
||||||
|
dst_row[(col * 4) + 1] = y_row[col];
|
||||||
|
dst_row[(col * 4) + 2] = u_row[col];
|
||||||
|
dst_row[(col * 4) + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Our buffers hold native N-bit values in the low bits of each u16; shift right to reduce
|
||||||
|
// to 8-bit for the output.
|
||||||
|
auto shift = impl.bit_depth - 8;
|
||||||
|
auto const* y_data = reinterpret_cast<u16 const*>(impl.y_buffer.data());
|
||||||
|
auto const* u_data = reinterpret_cast<u16 const*>(impl.u_buffer.data());
|
||||||
|
auto const* v_data = reinterpret_cast<u16 const*>(impl.v_buffer.data());
|
||||||
|
auto y_stride = static_cast<int>(width);
|
||||||
|
|
||||||
|
for (u32 row = 0; row < height; row++) {
|
||||||
|
auto* dst_row = dst + (static_cast<size_t>(row) * dst_stride);
|
||||||
|
auto const* y_row = y_data + (static_cast<size_t>(row) * y_stride);
|
||||||
|
auto const* u_row = u_data + (static_cast<size_t>(row) * y_stride);
|
||||||
|
auto const* v_row = v_data + (static_cast<size_t>(row) * y_stride);
|
||||||
|
for (u32 col = 0; col < width; col++) {
|
||||||
|
dst_row[(col * 4) + 0] = static_cast<u8>(v_row[col] >> shift);
|
||||||
|
dst_row[(col * 4) + 1] = static_cast<u8>(y_row[col] >> shift);
|
||||||
|
dst_row[(col * 4) + 2] = static_cast<u8>(u_row[col] >> shift);
|
||||||
|
dst_row[(col * 4) + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uv_size = impl.subsampling.subsampled_size(impl.size).to_type<u32>();
|
||||||
|
|
||||||
|
bool full_range = impl.cicp.video_full_range_flag() == Media::VideoFullRangeFlag::Full;
|
||||||
|
auto range = full_range ? FFI::YUVRange::Full : FFI::YUVRange::Limited;
|
||||||
|
auto matrix = yuv_matrix_for_cicp(impl.cicp);
|
||||||
|
|
||||||
|
auto y_stride = width;
|
||||||
|
auto uv_stride = uv_size.width();
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
if (impl.bit_depth <= 8) {
|
||||||
|
success = FFI::yuv_u8_to_rgba(
|
||||||
|
impl.y_buffer.data(), y_stride,
|
||||||
|
impl.u_buffer.data(), uv_stride,
|
||||||
|
impl.v_buffer.data(), uv_stride,
|
||||||
|
width, height,
|
||||||
|
impl.subsampling.x(), impl.subsampling.y(),
|
||||||
|
dst, dst_stride,
|
||||||
|
range, matrix);
|
||||||
|
} else {
|
||||||
|
success = FFI::yuv_u16_to_rgba(
|
||||||
|
reinterpret_cast<u16 const*>(impl.y_buffer.data()), y_stride,
|
||||||
|
reinterpret_cast<u16 const*>(impl.u_buffer.data()), uv_stride,
|
||||||
|
reinterpret_cast<u16 const*>(impl.v_buffer.data()), uv_stride,
|
||||||
|
width, height,
|
||||||
|
impl.bit_depth,
|
||||||
|
impl.subsampling.x(), impl.subsampling.y(),
|
||||||
|
dst, dst_stride,
|
||||||
|
range, matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return Error::from_string_literal("YUV-to-RGB conversion failed");
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
void YUVData::expand_samples_to_full_16_bit_range()
|
void YUVData::expand_samples_to_full_16_bit_range()
|
||||||
{
|
{
|
||||||
auto const shift = 16 - m_impl->bit_depth;
|
auto const shift = 16 - m_impl->bit_depth;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <AK/Error.h>
|
#include <AK/Error.h>
|
||||||
#include <AK/FixedArray.h>
|
#include <AK/FixedArray.h>
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
|
#include <AK/NonnullRefPtr.h>
|
||||||
|
#include <LibGfx/Forward.h>
|
||||||
#include <LibGfx/Size.h>
|
#include <LibGfx/Size.h>
|
||||||
#include <LibMedia/Color/CodingIndependentCodePoints.h>
|
#include <LibMedia/Color/CodingIndependentCodePoints.h>
|
||||||
#include <LibMedia/Subsampling.h>
|
#include <LibMedia/Subsampling.h>
|
||||||
@@ -42,6 +44,8 @@ public:
|
|||||||
Bytes u_data();
|
Bytes u_data();
|
||||||
Bytes v_data();
|
Bytes v_data();
|
||||||
|
|
||||||
|
ErrorOr<NonnullRefPtr<Bitmap>> to_bitmap() const;
|
||||||
|
|
||||||
SkYUVAPixmaps make_pixmaps() const;
|
SkYUVAPixmaps make_pixmaps() const;
|
||||||
|
|
||||||
void expand_samples_to_full_16_bit_range();
|
void expand_samples_to_full_16_bit_range();
|
||||||
|
|||||||
@@ -656,6 +656,13 @@
|
|||||||
"sha256": "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e",
|
"sha256": "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e",
|
||||||
"dest": "cargo/vendor/yoke-derive-0.8.2"
|
"dest": "cargo/vendor/yoke-derive-0.8.2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"archive-type": "tar-gzip",
|
||||||
|
"url": "https://static.crates.io/crates/yuv/yuv-0.8.13.crate",
|
||||||
|
"sha256": "47d3a7e2cda3061858987ee2fb028f61695f5ee13f9490d75be6c3900df9a4ea",
|
||||||
|
"dest": "cargo/vendor/yuv-0.8.13"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
@@ -794,6 +801,7 @@
|
|||||||
"echo '{\"files\": {}, \"package\": \"9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9\"}' > cargo/vendor/writeable-0.6.2/.cargo-checksum.json",
|
"echo '{\"files\": {}, \"package\": \"9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9\"}' > cargo/vendor/writeable-0.6.2/.cargo-checksum.json",
|
||||||
"echo '{\"files\": {}, \"package\": \"abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca\"}' > cargo/vendor/yoke-0.8.2/.cargo-checksum.json",
|
"echo '{\"files\": {}, \"package\": \"abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca\"}' > cargo/vendor/yoke-0.8.2/.cargo-checksum.json",
|
||||||
"echo '{\"files\": {}, \"package\": \"de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e\"}' > cargo/vendor/yoke-derive-0.8.2/.cargo-checksum.json",
|
"echo '{\"files\": {}, \"package\": \"de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e\"}' > cargo/vendor/yoke-derive-0.8.2/.cargo-checksum.json",
|
||||||
|
"echo '{\"files\": {}, \"package\": \"47d3a7e2cda3061858987ee2fb028f61695f5ee13f9490d75be6c3900df9a4ea\"}' > cargo/vendor/yuv-0.8.13/.cargo-checksum.json",
|
||||||
"echo '{\"files\": {}, \"package\": \"50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5\"}' > cargo/vendor/zerofrom-0.1.6/.cargo-checksum.json",
|
"echo '{\"files\": {}, \"package\": \"50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5\"}' > cargo/vendor/zerofrom-0.1.6/.cargo-checksum.json",
|
||||||
"echo '{\"files\": {}, \"package\": \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\"}' > cargo/vendor/zerofrom-derive-0.1.6/.cargo-checksum.json",
|
"echo '{\"files\": {}, \"package\": \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\"}' > cargo/vendor/zerofrom-derive-0.1.6/.cargo-checksum.json",
|
||||||
"echo '{\"files\": {}, \"package\": \"0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf\"}' > cargo/vendor/zerotrie-0.2.4/.cargo-checksum.json",
|
"echo '{\"files\": {}, \"package\": \"0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf\"}' > cargo/vendor/zerotrie-0.2.4/.cargo-checksum.json",
|
||||||
|
|||||||
Reference in New Issue
Block a user