blob: d43bf573aec4eea7354adb86c3e9ef9b064d93b8 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::libyuv;
use super::rgb_impl;
use crate::image::Plane;
use crate::image::YuvRange;
use crate::internal_utils::pixels::*;
use crate::internal_utils::*;
use crate::*;
#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq)]
pub enum Format {
Rgb,
#[default]
Rgba,
Argb,
Bgr,
Bgra,
Abgr,
Rgb565,
Rgba1010102, // https://developer.android.com/reference/android/graphics/Bitmap.Config#RGBA_1010102
}
impl Format {
pub(crate) fn offsets(&self) -> [usize; 4] {
match self {
Format::Rgb => [0, 1, 2, 0],
Format::Rgba => [0, 1, 2, 3],
Format::Argb => [1, 2, 3, 0],
Format::Bgr => [2, 1, 0, 0],
Format::Bgra => [2, 1, 0, 3],
Format::Abgr => [3, 2, 1, 0],
Format::Rgb565 | Format::Rgba1010102 => [0; 4],
}
}
pub fn r_offset(&self) -> usize {
self.offsets()[0]
}
pub fn g_offset(&self) -> usize {
self.offsets()[1]
}
pub fn b_offset(&self) -> usize {
self.offsets()[2]
}
pub fn alpha_offset(&self) -> usize {
self.offsets()[3]
}
pub(crate) fn has_alpha(&self) -> bool {
!matches!(self, Format::Rgb | Format::Bgr | Format::Rgb565)
}
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub enum ChromaUpsampling {
#[default]
Automatic,
Fastest,
BestQuality,
Nearest,
Bilinear,
}
impl ChromaUpsampling {
#[cfg(feature = "libyuv")]
pub(crate) fn nearest_neighbor_filter_allowed(&self) -> bool {
!matches!(self, Self::Bilinear | Self::BestQuality)
}
#[cfg(feature = "libyuv")]
pub(crate) fn bilinear_or_better_filter_allowed(&self) -> bool {
!matches!(self, Self::Nearest | Self::Fastest)
}
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub enum ChromaDownsampling {
#[default]
Automatic,
Fastest,
BestQuality,
Average,
SharpYuv,
}
#[derive(Default)]
pub struct Image {
pub width: u32,
pub height: u32,
pub depth: u8,
pub format: Format,
pub chroma_upsampling: ChromaUpsampling,
pub chroma_downsampling: ChromaDownsampling,
pub premultiply_alpha: bool,
pub is_float: bool,
pub max_threads: i32,
pub pixels: Option<Pixels>,
pub row_bytes: u32,
}
#[derive(Debug, Default, PartialEq)]
pub enum AlphaMultiplyMode {
#[default]
NoOp,
Multiply,
UnMultiply,
}
impl Image {
pub(crate) fn max_channel(&self) -> u16 {
((1i32 << self.depth) - 1) as u16
}
pub(crate) fn max_channel_f(&self) -> f32 {
self.max_channel() as f32
}
pub fn create_from_yuv(image: &image::Image) -> Self {
Self {
width: image.width,
height: image.height,
depth: image.depth,
format: Format::Rgba,
chroma_upsampling: ChromaUpsampling::Automatic,
chroma_downsampling: ChromaDownsampling::Automatic,
premultiply_alpha: false,
is_float: false,
max_threads: 1,
pixels: None,
row_bytes: 0,
}
}
pub(crate) fn pixels(&mut self) -> *mut u8 {
if self.pixels.is_none() {
return std::ptr::null_mut();
}
match self.pixels.unwrap_mut() {
Pixels::Pointer(ptr) => ptr.ptr_mut(),
Pixels::Pointer16(ptr) => ptr.ptr_mut() as *mut u8,
Pixels::Buffer(buffer) => buffer.as_mut_ptr(),
Pixels::Buffer16(buffer) => buffer.as_mut_ptr() as *mut u8,
}
}
pub fn row(&self, row: u32) -> AvifResult<&[u8]> {
self.pixels
.as_ref()
.ok_or(AvifError::NoContent)?
.slice(checked_mul!(row, self.row_bytes)?, self.row_bytes)
}
pub fn row_mut(&mut self, row: u32) -> AvifResult<&mut [u8]> {
self.pixels
.as_mut()
.ok_or(AvifError::NoContent)?
.slice_mut(checked_mul!(row, self.row_bytes)?, self.row_bytes)
}
pub fn row16(&self, row: u32) -> AvifResult<&[u16]> {
self.pixels
.as_ref()
.ok_or(AvifError::NoContent)?
.slice16(checked_mul!(row, self.row_bytes / 2)?, self.row_bytes / 2)
}
pub fn row16_mut(&mut self, row: u32) -> AvifResult<&mut [u16]> {
self.pixels
.as_mut()
.ok_or(AvifError::NoContent)?
.slice16_mut(checked_mul!(row, self.row_bytes / 2)?, self.row_bytes / 2)
}
pub fn allocate(&mut self) -> AvifResult<()> {
let row_bytes = checked_mul!(self.width, self.pixel_size())?;
if self.channel_size() == 1 {
let buffer_size: usize = usize_from_u32(checked_mul!(row_bytes, self.height)?)?;
let buffer: Vec<u8> = vec![0; buffer_size];
self.pixels = Some(Pixels::Buffer(buffer));
} else {
let buffer_size: usize = usize_from_u32(checked_mul!(row_bytes / 2, self.height)?)?;
let buffer: Vec<u16> = vec![0; buffer_size];
self.pixels = Some(Pixels::Buffer16(buffer));
}
self.row_bytes = row_bytes;
Ok(())
}
pub(crate) fn depth_valid(&self) -> bool {
match (self.format, self.is_float, self.depth) {
(Format::Rgb565, false, 8) => true,
(Format::Rgb565, _, _) => false,
(_, true, 16) => true, // IEEE 754 half-precision binary16
(_, false, 8 | 10 | 12 | 16) => true,
_ => false,
}
}
pub fn has_alpha(&self) -> bool {
match self.format {
Format::Rgba | Format::Bgra | Format::Argb | Format::Abgr | Format::Rgba1010102 => true,
Format::Rgb | Format::Bgr | Format::Rgb565 => false,
}
}
pub(crate) fn channel_size(&self) -> u32 {
match self.depth {
8 => 1,
10 | 12 | 16 => 2,
_ => panic!(),
}
}
pub(crate) fn channel_count(&self) -> u32 {
match self.format {
Format::Rgba | Format::Bgra | Format::Argb | Format::Abgr => 4,
Format::Rgb | Format::Bgr => 3,
Format::Rgb565 => 2,
Format::Rgba1010102 => 0, // This is never used.
}
}
pub(crate) fn pixel_size(&self) -> u32 {
match self.format {
Format::Rgba | Format::Bgra | Format::Argb | Format::Abgr => self.channel_size() * 4,
Format::Rgb | Format::Bgr => self.channel_size() * 3,
Format::Rgb565 => 2,
Format::Rgba1010102 => 4,
}
}
fn convert_to_half_float(&mut self) -> AvifResult<()> {
let scale = 1.0 / self.max_channel_f();
match libyuv::convert_to_half_float(self, scale) {
Ok(_) => return Ok(()),
Err(err) => {
if err != AvifError::NotImplemented {
return Err(err);
}
}
}
// This constant comes from libyuv. For details, see here:
// https://chromium.googlesource.com/libyuv/libyuv/+/2f87e9a7/source/row_common.cc#3537
let reinterpret_f32_as_u32 = |f: f32| u32::from_le_bytes(f.to_le_bytes());
let multiplier = 1.925_93e-34 * scale;
for y in 0..self.height {
let row = self.row16_mut(y)?;
for pixel in row {
*pixel = (reinterpret_f32_as_u32((*pixel as f32) * multiplier) >> 13) as u16;
}
}
Ok(())
}
pub fn convert_from_yuv(&mut self, image: &image::Image) -> AvifResult<()> {
if !image.has_plane(Plane::Y) || !image.depth_valid() || !self.depth_valid() {
return Err(AvifError::ReformatFailed);
}
if matches!(
image.matrix_coefficients,
MatrixCoefficients::Reserved
| MatrixCoefficients::Bt2020Cl
| MatrixCoefficients::Smpte2085
| MatrixCoefficients::ChromaDerivedCl
| MatrixCoefficients::Ictcp
) {
return Err(AvifError::NotImplemented);
}
if image.matrix_coefficients == MatrixCoefficients::Ycgco
&& image.yuv_range == YuvRange::Limited
{
return Err(AvifError::NotImplemented);
}
if matches!(
image.matrix_coefficients,
MatrixCoefficients::YcgcoRe | MatrixCoefficients::YcgcoRo
) {
if image.yuv_range == YuvRange::Limited {
return Err(AvifError::NotImplemented);
}
let bit_offset =
if image.matrix_coefficients == MatrixCoefficients::YcgcoRe { 2 } else { 1 };
if image.depth - bit_offset != self.depth {
return Err(AvifError::NotImplemented);
}
}
// Android MediaCodec maps all underlying YUV formats to PixelFormat::Yuv420. So do not
// perform this validation for Android MediaCodec. The libyuv wrapper will simply use Bt601
// coefficients for this color conversion.
#[cfg(not(feature = "android_mediacodec"))]
if image.matrix_coefficients == MatrixCoefficients::Identity
&& !matches!(image.yuv_format, PixelFormat::Yuv444 | PixelFormat::Yuv400)
{
return Err(AvifError::NotImplemented);
}
let mut alpha_multiply_mode = AlphaMultiplyMode::NoOp;
if image.has_alpha() && self.has_alpha() {
if !image.alpha_premultiplied && self.premultiply_alpha {
alpha_multiply_mode = AlphaMultiplyMode::Multiply;
} else if image.alpha_premultiplied && !self.premultiply_alpha {
alpha_multiply_mode = AlphaMultiplyMode::UnMultiply;
}
}
let mut converted_with_libyuv: bool = false;
let mut alpha_reformatted_with_libyuv = false;
if alpha_multiply_mode == AlphaMultiplyMode::NoOp || self.has_alpha() {
match libyuv::yuv_to_rgb(image, self) {
Ok(alpha_reformatted) => {
alpha_reformatted_with_libyuv = alpha_reformatted;
converted_with_libyuv = true;
}
Err(err) => {
if err != AvifError::NotImplemented {
return Err(err);
}
}
}
}
if matches!(
image.yuv_format,
PixelFormat::AndroidNv12 | PixelFormat::AndroidNv21
) | matches!(self.format, Format::Rgba1010102)
{
// These conversions are only supported via libyuv.
if converted_with_libyuv {
if image.has_alpha() && matches!(self.format, Format::Rgba1010102) {
// If the source image has an alpha channel, scale them to 2 bits and fill it
// into the rgb image. Otherwise, libyuv writes them as opaque by default.
self.import_alpha_from(image)?;
}
return Ok(());
} else {
return Err(AvifError::NotImplemented);
}
}
if self.has_alpha() && !alpha_reformatted_with_libyuv {
if image.has_alpha() {
self.import_alpha_from(image)?;
} else {
self.set_opaque()?;
}
}
if !converted_with_libyuv {
let mut converted_by_fast_path = false;
if (matches!(
self.chroma_upsampling,
ChromaUpsampling::Nearest | ChromaUpsampling::Fastest
) || matches!(image.yuv_format, PixelFormat::Yuv444 | PixelFormat::Yuv400))
&& (alpha_multiply_mode == AlphaMultiplyMode::NoOp || self.format.has_alpha())
{
match rgb_impl::yuv_to_rgb_fast(image, self) {
Ok(_) => converted_by_fast_path = true,
Err(err) => {
if err != AvifError::NotImplemented {
return Err(err);
}
}
}
}
if !converted_by_fast_path {
rgb_impl::yuv_to_rgb_any(image, self, alpha_multiply_mode)?;
alpha_multiply_mode = AlphaMultiplyMode::NoOp;
}
}
match alpha_multiply_mode {
AlphaMultiplyMode::Multiply => self.premultiply_alpha()?,
AlphaMultiplyMode::UnMultiply => self.unpremultiply_alpha()?,
AlphaMultiplyMode::NoOp => {}
}
if self.is_float {
self.convert_to_half_float()?;
}
Ok(())
}
pub fn shuffle_channels_to(self, format: Format) -> AvifResult<Image> {
if self.format == format {
return Ok(self);
}
if self.format == Format::Rgb565 || format == Format::Rgb565 {
return Err(AvifError::NotImplemented);
}
let mut dst = Image {
format,
pixels: None,
row_bytes: 0,
..self
};
dst.allocate()?;
let src_channel_count = self.channel_count();
let dst_channel_count = dst.channel_count();
let src_offsets = self.format.offsets();
let dst_offsets = dst.format.offsets();
let src_has_alpha = self.has_alpha();
let dst_has_alpha = dst.has_alpha();
let dst_max_channel = dst.max_channel();
for y in 0..self.height {
if self.depth == 8 {
let src_row = self.row(y)?;
let dst_row = &mut dst.row_mut(y)?;
for x in 0..self.width {
let src_pixel_i = (src_channel_count * x) as usize;
let dst_pixel_i = (dst_channel_count * x) as usize;
for c in 0..3 {
dst_row[dst_pixel_i + dst_offsets[c]] =
src_row[src_pixel_i + src_offsets[c]];
}
if dst_has_alpha {
dst_row[dst_pixel_i + dst_offsets[3]] = if src_has_alpha {
src_row[src_pixel_i + src_offsets[3]]
} else {
dst_max_channel as u8
};
}
}
} else {
let src_row = self.row16(y)?;
let dst_row = &mut dst.row16_mut(y)?;
for x in 0..self.width {
let src_pixel_i = (src_channel_count * x) as usize;
let dst_pixel_i = (dst_channel_count * x) as usize;
for c in 0..3 {
dst_row[dst_pixel_i + dst_offsets[c]] =
src_row[src_pixel_i + src_offsets[c]];
}
if dst_has_alpha {
dst_row[dst_pixel_i + dst_offsets[3]] = if src_has_alpha {
src_row[src_pixel_i + src_offsets[3]]
} else {
dst_max_channel
};
}
}
}
}
Ok(dst)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::image::YuvRange;
use crate::image::ALL_PLANES;
use crate::image::MAX_PLANE_COUNT;
use crate::Category;
use test_case::test_case;
use test_case::test_matrix;
const WIDTH: usize = 3;
const HEIGHT: usize = 3;
struct YuvParams {
width: u32,
height: u32,
depth: u8,
format: PixelFormat,
yuv_range: YuvRange,
color_primaries: ColorPrimaries,
matrix_coefficients: MatrixCoefficients,
planes: [[&'static [u16]; HEIGHT]; MAX_PLANE_COUNT],
}
const YUV_PARAMS: [YuvParams; 1] = [YuvParams {
width: WIDTH as u32,
height: HEIGHT as u32,
depth: 12,
format: PixelFormat::Yuv420,
yuv_range: YuvRange::Limited,
color_primaries: ColorPrimaries::Srgb,
matrix_coefficients: MatrixCoefficients::Bt709,
planes: [
[
&[1001, 1001, 1001],
&[1001, 1001, 1001],
&[1001, 1001, 1001],
],
[&[1637, 1637], &[1637, 1637], &[1637, 1637]],
[&[3840, 3840], &[3840, 3840], &[3840, 3840]],
[&[0, 0, 2039], &[0, 2039, 4095], &[2039, 4095, 4095]],
],
}];
struct RgbParams {
params: (
/*yuv_param_index:*/ usize,
/*format:*/ Format,
/*depth:*/ u8,
/*premultiply_alpha:*/ bool,
/*is_float:*/ bool,
),
expected_rgba: [&'static [u16]; HEIGHT],
}
const RGB_PARAMS: [RgbParams; 5] = [
RgbParams {
params: (0, Format::Rgba, 16, true, false),
expected_rgba: [
&[0, 0, 0, 0, 0, 0, 0, 0, 32631, 1, 0, 32631],
&[0, 0, 0, 0, 32631, 1, 0, 32631, 65535, 2, 0, 65535],
&[32631, 1, 0, 32631, 65535, 2, 0, 65535, 65535, 2, 0, 65535],
],
},
RgbParams {
params: (0, Format::Rgba, 16, true, true),
expected_rgba: [
&[0, 0, 0, 0, 0, 0, 0, 0, 14327, 256, 0, 14327],
&[0, 0, 0, 0, 14327, 256, 0, 14327, 15360, 512, 0, 15360],
&[
14327, 256, 0, 14327, 15360, 512, 0, 15360, 15360, 512, 0, 15360,
],
],
},
RgbParams {
params: (0, Format::Rgba, 16, false, true),
expected_rgba: [
&[15360, 512, 0, 0, 15360, 512, 0, 0, 15360, 512, 0, 14327],
&[15360, 512, 0, 0, 15360, 512, 0, 14327, 15360, 512, 0, 15360],
&[
15360, 512, 0, 14327, 15360, 512, 0, 15360, 15360, 512, 0, 15360,
],
],
},
RgbParams {
params: (0, Format::Rgba, 16, false, false),
expected_rgba: [
&[65535, 2, 0, 0, 65535, 2, 0, 0, 65535, 2, 0, 32631],
&[65535, 2, 0, 0, 65535, 2, 0, 32631, 65535, 2, 0, 65535],
&[65535, 2, 0, 32631, 65535, 2, 0, 65535, 65535, 2, 0, 65535],
],
},
RgbParams {
params: (0, Format::Bgra, 16, true, false),
expected_rgba: [
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32631, 32631],
&[0, 0, 0, 0, 0, 1, 32631, 32631, 0, 2, 65535, 65535],
&[0, 1, 32631, 32631, 0, 2, 65535, 65535, 0, 2, 65535, 65535],
],
},
];
#[test_matrix(0usize..5)]
fn rgb_conversion(rgb_param_index: usize) -> AvifResult<()> {
let rgb_params = &RGB_PARAMS[rgb_param_index];
let yuv_params = &YUV_PARAMS[rgb_params.params.0];
let mut image = image::Image {
width: yuv_params.width,
height: yuv_params.height,
depth: yuv_params.depth,
yuv_format: yuv_params.format,
color_primaries: yuv_params.color_primaries,
matrix_coefficients: yuv_params.matrix_coefficients,
yuv_range: yuv_params.yuv_range,
..image::Image::default()
};
image.allocate_planes(Category::Color)?;
image.allocate_planes(Category::Alpha)?;
let yuva_planes = &yuv_params.planes;
for plane in ALL_PLANES {
let plane_index = plane.as_usize();
if yuva_planes[plane_index].is_empty() {
continue;
}
for y in 0..image.height(plane) {
let row16 = image.row16_mut(plane, y as u32)?;
assert_eq!(row16.len(), yuva_planes[plane_index][y].len());
let dst = &mut row16[..];
dst.copy_from_slice(yuva_planes[plane_index][y]);
}
}
let mut rgb = Image::create_from_yuv(&image);
assert_eq!(rgb.width, image.width);
assert_eq!(rgb.height, image.height);
assert_eq!(rgb.depth, image.depth);
rgb.format = rgb_params.params.1;
rgb.depth = rgb_params.params.2;
rgb.premultiply_alpha = rgb_params.params.3;
rgb.is_float = rgb_params.params.4;
rgb.allocate()?;
rgb.convert_from_yuv(&image)?;
for y in 0..rgb.height as usize {
let row16 = rgb.row16(y as u32)?;
assert_eq!(&row16[..], rgb_params.expected_rgba[y]);
}
Ok(())
}
#[test_case(Format::Rgba, &[0, 1, 2, 3])]
#[test_case(Format::Abgr, &[3, 2, 1, 0])]
#[test_case(Format::Rgb, &[0, 1, 2])]
fn shuffle_channels_to(format: Format, expected: &[u8]) {
let image = Image {
width: 1,
height: 1,
depth: 8,
format: Format::Rgba,
pixels: Some(Pixels::Buffer(vec![0u8, 1, 2, 3])),
row_bytes: 4,
..Default::default()
};
assert_eq!(
image.shuffle_channels_to(format).unwrap().row(0).unwrap(),
expected
);
}
}