Skip to content

Commit

Permalink
Fix avif decode
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Mar 3, 2025
1 parent 9a9f4ea commit 600115e
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 21 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ base64-simd = "0.8"
cssparser = "0.29"
infer = "0.19"
libavif = { version = "0.14", default-features = false, features = ["codec-aom"] }
libavif-sys = { version = "0.17", default-features = false, features = ["codec-aom"] }
napi = { version = "3.0.0-alpha.27", default-features = false, features = ["napi4", "web_stream", "serde-json"] }
napi-derive = { version = "3.0.0-alpha.25", default-features = false }
nom = "8"
Expand Down
10 changes: 6 additions & 4 deletions __test__/regression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,10 @@ test('transform-with-non-inverted-matrix', (t) => {
})

// https://github.com/Brooooooklyn/canvas/issues/996
test('load avif image segmentation fault', async (t) => {
await t.notThrowsAsync(async () => {
await loadImage(join(__dirname, 'fixtures', 'issue-996.avif'))
})
test('draw-avif-image', async (t) => {
const canvas = createCanvas(1920, 1080)
const ctx = canvas.getContext('2d')
const image = await loadImage(join(__dirname, 'fixtures', 'issue-996.avif'))
ctx.drawImage(image, 0, 0)
await snapshotImage(t, { ctx, canvas })
})
Binary file added __test__/snapshots/draw-avif-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion scripts/build-skia.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ const GN_ARGS = [
`skia_use_harfbuzz=true`,
`skia_use_icu=true`,
`skia_use_libheif=true`,
`skia_use_libavif=true`,
// the libavif would conflict with the Rust libavif, use the Rust library to handle avif images
`skia_use_libavif=false`,
`skia_use_libjxl_decode=${!TARGET_TRIPLE.startsWith('riscv64')}`,
`skia_use_libjpeg_turbo_decode=true`,
`skia_use_libjpeg_turbo_encode=true`,
Expand Down
2 changes: 1 addition & 1 deletion skia
Submodule skia updated 1 files
+18 −0 src/core/SkBlurEngine.cpp
162 changes: 161 additions & 1 deletion src/avif.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::result;
use std::{fmt::Display, result};

use libavif::{AvifData, RgbPixels, YuvFormat};
use libavif_sys as sys;
use napi::bindgen_prelude::*;
use napi_derive::napi;

Expand Down Expand Up @@ -134,3 +135,162 @@ pub(crate) fn encode(
encoder.set_max_threads(config.threads);
encoder.encode(&image).map_err(SkError::EncodeAvifError)
}

/// Enum representing AVIF error codes
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AvifErrorCode {
Ok = 0,
UnknownError = 1,
InvalidFtyp = 2,
NoContent = 3,
NoYuvFormatSelected = 4,
ReformatFailed = 5,
UnsupportedDepth = 6,
EncodeColorFailed = 7,
EncodeAlphaFailed = 8,
BmffParseFailed = 9,
MissingImageItem = 10,
DecodeColorFailed = 11,
DecodeAlphaFailed = 12,
ColorAlphaSizeMismatch = 13,
IspeSizeMismatch = 14,
NoCodecAvailable = 15,
NoImagesRemaining = 16,
InvalidExifPayload = 17,
InvalidImageGrid = 18,
InvalidCodecSpecificOption = 19,
TruncatedData = 20,
IoNotSet = 21,
IoError = 22,
WaitingOnIo = 23,
InvalidArgument = 24,
NotImplemented = 25,
OutOfMemory = 26,
CannotChangeSetting = 27,
IncompatibleImage = 28,
InternalError = 29,
EncodeGainMapFailed = 30,
DecodeGainMapFailed = 31,
InvalidToneMappedImage = 32,
}

impl Display for AvifErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}

#[derive(Debug)]
pub enum AvifError {
Known(AvifErrorCode),
Unknown(u32),
}

impl std::fmt::Display for AvifError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AvifError::Known(code) => write!(f, "AvifError: {}", code),
AvifError::Unknown(code) => write!(f, "AvifError: {}", code),
}
}
}

impl std::error::Error for AvifError {}

impl From<u32> for AvifError {
fn from(code: u32) -> Self {
match code {
0 => AvifError::Known(AvifErrorCode::Ok),
1 => AvifError::Known(AvifErrorCode::UnknownError),
2 => AvifError::Known(AvifErrorCode::InvalidFtyp),
3 => AvifError::Known(AvifErrorCode::NoContent),
4 => AvifError::Known(AvifErrorCode::NoYuvFormatSelected),
5 => AvifError::Known(AvifErrorCode::ReformatFailed),
6 => AvifError::Known(AvifErrorCode::UnsupportedDepth),
7 => AvifError::Known(AvifErrorCode::EncodeColorFailed),
8 => AvifError::Known(AvifErrorCode::EncodeAlphaFailed),
9 => AvifError::Known(AvifErrorCode::BmffParseFailed),
10 => AvifError::Known(AvifErrorCode::MissingImageItem),
11 => AvifError::Known(AvifErrorCode::DecodeColorFailed),
12 => AvifError::Known(AvifErrorCode::DecodeAlphaFailed),
13 => AvifError::Known(AvifErrorCode::ColorAlphaSizeMismatch),
14 => AvifError::Known(AvifErrorCode::IspeSizeMismatch),
15 => AvifError::Known(AvifErrorCode::NoCodecAvailable),
16 => AvifError::Known(AvifErrorCode::NoImagesRemaining),
17 => AvifError::Known(AvifErrorCode::InvalidExifPayload),
18 => AvifError::Known(AvifErrorCode::InvalidImageGrid),
19 => AvifError::Known(AvifErrorCode::InvalidCodecSpecificOption),
20 => AvifError::Known(AvifErrorCode::TruncatedData),
21 => AvifError::Known(AvifErrorCode::IoNotSet),
22 => AvifError::Known(AvifErrorCode::IoError),
23 => AvifError::Known(AvifErrorCode::WaitingOnIo),
24 => AvifError::Known(AvifErrorCode::InvalidArgument),
25 => AvifError::Known(AvifErrorCode::NotImplemented),
26 => AvifError::Known(AvifErrorCode::OutOfMemory),
27 => AvifError::Known(AvifErrorCode::CannotChangeSetting),
28 => AvifError::Known(AvifErrorCode::IncompatibleImage),
29 => AvifError::Known(AvifErrorCode::InternalError),
30 => AvifError::Known(AvifErrorCode::EncodeGainMapFailed),
31 => AvifError::Known(AvifErrorCode::DecodeGainMapFailed),
32 => AvifError::Known(AvifErrorCode::InvalidToneMappedImage),
_ => AvifError::Unknown(code),
}
}
}

impl AvifError {
pub fn from_code(code: u32) -> result::Result<(), AvifError> {
match code {
0 => Ok(()),
_ => Err(AvifError::Unknown(code)),
}
}
}

pub struct AvifImage {
image: *mut sys::avifImage,
rgb_image: sys::avifRGBImage,
pub width: u32,
pub height: u32,
pub row_bytes: u32,
pub data: *mut u8,
}

impl AvifImage {
pub fn decode_from(avif_bytes: &[u8]) -> result::Result<Self, AvifError> {
let decoder = unsafe { sys::avifDecoderCreate() };
let image = unsafe { sys::avifImageCreateEmpty() };
AvifError::from_code(unsafe {
sys::avifDecoderReadMemory(decoder, image, avif_bytes.as_ptr(), avif_bytes.len())
})?;

unsafe {
sys::avifDecoderDestroy(decoder);
}
let mut rgb_image = sys::avifRGBImage::default();
unsafe {
sys::avifRGBImageSetDefaults(&mut rgb_image, image);
rgb_image.format = sys::AVIF_RGB_FORMAT_RGBA;
rgb_image.depth = 8;
AvifError::from_code(sys::avifRGBImageAllocatePixels(&mut rgb_image))?;
AvifError::from_code(sys::avifImageYUVToRGB(image, &mut rgb_image))?;
};
Ok(Self {
image,
width: unsafe { (*image).width },
height: unsafe { (*image).height },
data: rgb_image.pixels,
row_bytes: rgb_image.rowBytes,
rgb_image,
})
}
}

impl Drop for AvifImage {
fn drop(&mut self) {
unsafe {
sys::avifImageDestroy(self.image);
sys::avifRGBImageFreePixels(&mut self.rgb_image);
}
}
}
47 changes: 35 additions & 12 deletions src/image.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::borrow::Cow;
use std::str::FromStr;
use std::{ptr, str};
use std::{borrow::Cow, ptr, str, str::FromStr};

use base64_simd::STANDARD;
use napi::{bindgen_prelude::*, check_status, NapiRaw, NapiValue, Ref};

use crate::avif::AvifImage;
use crate::error::SkError;
use crate::global_fonts::get_font;
use crate::sk::Bitmap;
use crate::sk::ColorSpace;
use crate::sk::{AlphaType, Bitmap, ColorSpace, ColorType};

#[napi]
pub struct ImageData {
Expand Down Expand Up @@ -325,20 +323,21 @@ struct BitmapDecoder {
this_ref: Ref<Object>,
}

#[derive(Debug)]
pub(crate) struct DecodedBitmap {
bitmap: DecodeStatus,
width: f64,
height: f64,
}

#[derive(Debug)]
unsafe impl Send for DecodedBitmap {}

struct BitmapInfo {
data: Bitmap,
is_svg: bool,
#[allow(dead_code)]
decoded_image: Option<AvifImage>,
}

#[derive(Debug)]
enum DecodeStatus {
Ok(BitmapInfo),
Empty,
Expand Down Expand Up @@ -388,6 +387,7 @@ impl Task for BitmapDecoder {
DecodeStatus::Ok(BitmapInfo {
data: Bitmap::from_buffer(image_binary.as_ptr().cast_mut(), image_binary.len()),
is_svg: false,
decoded_image: None,
})
} else {
DecodeStatus::InvalidImage
Expand All @@ -403,10 +403,31 @@ impl Task for BitmapDecoder {
} else {
false
} {
DecodeStatus::Ok(BitmapInfo {
data: Bitmap::from_buffer(data_ref.as_ptr().cast_mut(), length),
is_svg: false,
})
if libavif::is_avif(data_ref.as_ref()) {
let avif_image = AvifImage::decode_from(data_ref.as_ref())
.map_err(|e| Error::new(Status::InvalidArg, format!("Decode avif image failed {e}")))?;

let bitmap = Bitmap::from_image_data(
avif_image.data,
avif_image.width as usize,
avif_image.height as usize,
avif_image.row_bytes as usize,
(avif_image.row_bytes * avif_image.height) as usize,
ColorType::RGBA8888,
AlphaType::Premultiplied,
);
DecodeStatus::Ok(BitmapInfo {
data: bitmap,
is_svg: false,
decoded_image: Some(avif_image),
})
} else {
DecodeStatus::Ok(BitmapInfo {
data: Bitmap::from_buffer(data_ref.as_ptr().cast_mut(), length),
is_svg: false,
decoded_image: None,
})
}
} else if is_svg_image(&data_ref, length) {
let font = get_font().map_err(SkError::from)?;
if (self.width - -1.0).abs() > f64::EPSILON && (self.height - -1.0).abs() > f64::EPSILON {
Expand All @@ -421,6 +442,7 @@ impl Task for BitmapDecoder {
DecodeStatus::Ok(BitmapInfo {
data: bitmap,
is_svg: true,
decoded_image: None,
})
} else {
DecodeStatus::InvalidSvg
Expand All @@ -432,6 +454,7 @@ impl Task for BitmapDecoder {
DecodeStatus::Ok(BitmapInfo {
data: bitmap,
is_svg: true,
decoded_image: None,
})
} else {
DecodeStatus::InvalidSvg
Expand Down
4 changes: 2 additions & 2 deletions src/sk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3558,8 +3558,8 @@ impl Bitmap {
};
Bitmap(ffi::skiac_bitmap_info {
bitmap,
width: row_bytes as i32,
height: (size / row_bytes / 4) as i32,
width: width as i32,
height: height as i32,
is_canvas: false,
})
}
Expand Down

0 comments on commit 600115e

Please sign in to comment.