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"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libgfx_rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"yuv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libjs_rust"
|
||||
version = "0.1.0"
|
||||
@@ -892,6 +900,15 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yuv"
|
||||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d3a7e2cda3061858987ee2fb028f61695f5ee13f9490d75be6c3900df9a4ea"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"Libraries/LibGfx/Rust",
|
||||
"Libraries/LibJS/Rust",
|
||||
"Libraries/LibRegex/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
|
||||
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)
|
||||
target_link_libraries(LibGfx PRIVATE Fontconfig::Fontconfig)
|
||||
endif()
|
||||
|
||||
@@ -208,8 +208,10 @@ ErrorOr<NonnullRefPtr<ImmutableBitmap>> ImmutableBitmap::create_from_yuv(Nonnull
|
||||
auto context = SkiaBackendContext::the();
|
||||
auto* gr_context = context ? context->sk_context() : nullptr;
|
||||
|
||||
if (!gr_context)
|
||||
return Error::from_string_literal("GPU context is unavailable");
|
||||
if (!gr_context) {
|
||||
auto bitmap = TRY(yuv_data->to_bitmap());
|
||||
return create(move(bitmap), move(color_space));
|
||||
}
|
||||
|
||||
if (yuv_data->bit_depth() > 8)
|
||||
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
|
||||
*/
|
||||
|
||||
#include <AK/Time.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibGfx/SkiaBackendContext.h>
|
||||
#include <LibGfx/YUVData.h>
|
||||
#include <RustFFI.h>
|
||||
|
||||
#include <core/SkColorSpace.h>
|
||||
#include <core/SkImage.h>
|
||||
@@ -102,6 +105,126 @@ Bytes YUVData::v_data()
|
||||
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()
|
||||
{
|
||||
auto const shift = 16 - m_impl->bit_depth;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <AK/Error.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Size.h>
|
||||
#include <LibMedia/Color/CodingIndependentCodePoints.h>
|
||||
#include <LibMedia/Subsampling.h>
|
||||
@@ -42,6 +44,8 @@ public:
|
||||
Bytes u_data();
|
||||
Bytes v_data();
|
||||
|
||||
ErrorOr<NonnullRefPtr<Bitmap>> to_bitmap() const;
|
||||
|
||||
SkYUVAPixmaps make_pixmaps() const;
|
||||
|
||||
void expand_samples_to_full_16_bit_range();
|
||||
|
||||
@@ -656,6 +656,13 @@
|
||||
"sha256": "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e",
|
||||
"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",
|
||||
"archive-type": "tar-gzip",
|
||||
@@ -794,6 +801,7 @@
|
||||
"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\": \"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\": \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\"}' > cargo/vendor/zerofrom-derive-0.1.6/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf\"}' > cargo/vendor/zerotrie-0.2.4/.cargo-checksum.json",
|
||||
|
||||
Reference in New Issue
Block a user