feat(core/files): support for jixel decoding

closes #342
This commit is contained in:
Paul Makles
2024-10-02 12:20:14 +01:00
parent 25fc692dc1
commit efa7ba78ed
3 changed files with 58 additions and 23 deletions

View File

@@ -4,7 +4,7 @@ use aes_gcm::{
aead::{AeadCore, AeadMutInPlace, OsRng}, aead::{AeadCore, AeadMutInPlace, OsRng},
Aes256Gcm, Key, KeyInit, Nonce, Aes256Gcm, Key, KeyInit, Nonce,
}; };
use image::DynamicImage; use image::{DynamicImage, ImageBuffer};
use revolt_config::{config, report_internal_error, FilesS3}; use revolt_config::{config, report_internal_error, FilesS3};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
@@ -152,38 +152,67 @@ pub fn video_size(f: &NamedTempFile) -> Option<(i64, i64)> {
} }
/// Decode image from reader /// Decode image from reader
pub fn decode_image<R: Read + BufRead + Seek>( pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Result<DynamicImage> {
reader: &mut R, match mime {
is_jxl: bool, // Read image using jxl-oxide crate
) -> Result<DynamicImage> { "image/jxl" => {
if is_jxl { let jxl_image = report_internal_error!(jxl_oxide::JxlImage::builder().read(reader))?;
Err(create_error!(LabelMe)) if let Ok(frame) = jxl_image.render_frame(0) {
} else { match frame.color_channels().len() {
3 => Ok(DynamicImage::ImageRgb8(
DynamicImage::ImageRgb32F(
ImageBuffer::from_vec(
jxl_image.width(),
jxl_image.height(),
frame.image().buf().to_vec(),
)
.ok_or_else(|| create_error!(LabelMe))?,
)
.to_rgb8(),
)),
4 => Ok(DynamicImage::ImageRgba8(
DynamicImage::ImageRgba32F(
ImageBuffer::from_vec(
jxl_image.width(),
jxl_image.height(),
frame.image().buf().to_vec(),
)
.ok_or_else(|| create_error!(LabelMe))?,
)
.to_rgba8(),
)),
_ => Err(create_error!(LabelMe)),
}
} else {
Err(create_error!(LabelMe))
}
}
// Read image using resvg
"image/svg+xml" => Err(create_error!(LabelMe)),
// Check if we can read using image-rs crate // Check if we can read using image-rs crate
report_internal_error!(report_internal_error!( _ => report_internal_error!(report_internal_error!(
image::ImageReader::new(reader).with_guessed_format() image::ImageReader::new(reader).with_guessed_format()
)? )?
.decode()) .decode()),
} }
} }
/// Check whether given reader has a valid image /// Check whether given reader has a valid image
pub fn is_valid_image<R: Read + BufRead + Seek>(reader: &mut R, is_jxl: bool) -> bool { pub fn is_valid_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> bool {
if is_jxl { match mime {
// Check if we can read using jxl-oxide crate // Check if we can read using jxl-oxide crate
jxl_oxide::JxlImage::builder() "image/jxl" => jxl_oxide::JxlImage::builder()
.read(reader) .read(reader)
.inspect_err(|err| tracing::error!("Failed to read JXL! {err:?}")) .inspect_err(|err| tracing::error!("Failed to read JXL! {err:?}"))
.is_ok() .is_ok(),
} else { // Check if we can read using image-rs crate
!matches!( _ => !matches!(
// Check if we can read using image-rs crate
image::ImageReader::new(reader) image::ImageReader::new(reader)
.with_guessed_format() .with_guessed_format()
.inspect_err(|err| tracing::error!("Failed to read image! {err:?}")) .inspect_err(|err| tracing::error!("Failed to read image! {err:?}"))
.map(|f| f.decode()), .map(|f| f.decode()),
Err(_) | Ok(Err(_)) Err(_) | Ok(Err(_))
) ),
} }
} }

View File

@@ -11,7 +11,6 @@ use axum::{
Json, Router, Json, Router,
}; };
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart}; use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
use image::ImageReader;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use revolt_config::{config, report_internal_error}; use revolt_config::{config, report_internal_error};
use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User}; use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User};
@@ -355,7 +354,11 @@ async fn fetch_preview(
let data = retrieve_file_by_hash(&hash).await?; let data = retrieve_file_by_hash(&hash).await?;
// Read image and create thumbnail // Read image and create thumbnail
let data = create_thumbnail(decode_image(&mut Cursor::new(data), false)?, tag).await; let data = create_thumbnail(
decode_image(&mut Cursor::new(data), &file.content_type)?,
tag,
)
.await;
Ok(( Ok((
[ [

View File

@@ -71,7 +71,7 @@ impl Request {
let reader = &mut Cursor::new(&bytes); let reader = &mut Cursor::new(&bytes);
if matches!(mime.subtype(), mime::GIF) { if matches!(mime.subtype(), mime::GIF) {
if is_valid_image(reader, false) { if is_valid_image(reader, "image/gif") {
Ok(("image/gif".to_owned(), bytes.to_vec())) Ok(("image/gif".to_owned(), bytes.to_vec()))
} else { } else {
Err(create_error!(LabelMe)) Err(create_error!(LabelMe))
@@ -79,8 +79,11 @@ impl Request {
} else { } else {
Ok(( Ok((
"image/webp".to_owned(), "image/webp".to_owned(),
create_thumbnail(decode_image(reader, false)?, "attachments") create_thumbnail(
.await, decode_image(reader, mime.as_ref())?,
"attachments",
)
.await,
)) ))
} }
} else { } else {