use std::io::BufReader;
use std::io::Cursor;
use deno_core::JsBuffer;
use deno_core::ToJsBuffer;
use deno_core::op2;
use image::DynamicImage;
use image::ImageDecoder;
use image::RgbaImage;
use image::codecs::bmp::BmpDecoder;
use image::codecs::ico::IcoDecoder;
use image::codecs::jpeg::JpegDecoder;
use image::codecs::png::PngDecoder;
use image::imageops::FilterType;
use image::imageops::overlay;
use image::metadata::Orientation;
use crate::CanvasError;
use crate::image_ops::create_image_from_raw_bytes;
use crate::image_ops::premultiply_alpha as process_premultiply_alpha;
use crate::image_ops::to_srgb_from_icc_profile;
use crate::image_ops::unpremultiply_alpha;
#[derive(Debug, PartialEq)]
enum ImageBitmapSource {
Blob,
ImageData,
ImageBitmap,
}
#[derive(Debug, PartialEq)]
enum ImageOrientation {
FlipY,
FromImage,
}
#[derive(Debug, PartialEq)]
enum PremultiplyAlpha {
Default,
Premultiply,
None,
}
#[derive(Debug, PartialEq)]
enum ColorSpaceConversion {
Default,
None,
}
#[derive(Debug, PartialEq)]
enum ResizeQuality {
Pixelated,
Low,
Medium,
High,
}
#[derive(Debug, PartialEq)]
enum MimeType {
NoMatch,
Png,
Jpeg,
Gif,
Bmp,
Ico,
Webp,
}
type DecodeBitmapDataReturn =
(DynamicImage, u32, u32, Option<Orientation>, Option<Vec<u8>>);
fn decode_bitmap_data(
buf: &[u8],
width: u32,
height: u32,
image_bitmap_source: &ImageBitmapSource,
mime_type: MimeType,
) -> Result<DecodeBitmapDataReturn, CanvasError> {
let (image, width, height, orientation, icc_profile) =
match image_bitmap_source {
ImageBitmapSource::Blob => {
let (image, orientation, icc_profile) = match mime_type {
MimeType::Png => {
let mut decoder = PngDecoder::new(BufReader::new(Cursor::new(buf)))
.map_err(CanvasError::image_error_to_invalid_image)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.icc_profile()?;
(
DynamicImage::from_decoder(decoder)
.map_err(CanvasError::image_error_to_invalid_image)?,
orientation,
icc_profile,
)
}
MimeType::Jpeg => {
let mut decoder =
JpegDecoder::new(BufReader::new(Cursor::new(buf)))
.map_err(CanvasError::image_error_to_invalid_image)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.icc_profile()?;
(
DynamicImage::from_decoder(decoder)
.map_err(CanvasError::image_error_to_invalid_image)?,
orientation,
icc_profile,
)
}
MimeType::Gif => {
unimplemented!();
}
MimeType::Bmp => {
let mut decoder = BmpDecoder::new(BufReader::new(Cursor::new(buf)))
.map_err(CanvasError::image_error_to_invalid_image)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.icc_profile()?;
(
DynamicImage::from_decoder(decoder)
.map_err(CanvasError::image_error_to_invalid_image)?,
orientation,
icc_profile,
)
}
MimeType::Ico => {
let mut decoder = IcoDecoder::new(BufReader::new(Cursor::new(buf)))
.map_err(CanvasError::image_error_to_invalid_image)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.icc_profile()?;
(
DynamicImage::from_decoder(decoder)
.map_err(CanvasError::image_error_to_invalid_image)?,
orientation,
icc_profile,
)
}
MimeType::Webp => {
unimplemented!();
}
MimeType::NoMatch => unreachable!(),
};
let width = image.width();
let height = image.height();
(image, width, height, Some(orientation), icc_profile)
}
ImageBitmapSource::ImageData => {
let image = match RgbaImage::from_raw(width, height, buf.into()) {
Some(image) => image.into(),
None => {
return Err(CanvasError::NotBigEnoughChunk(width, height));
}
};
(image, width, height, None, None)
}
ImageBitmapSource::ImageBitmap => {
let image = create_image_from_raw_bytes(width, height, buf)?;
(image, width, height, None, None)
}
};
Ok((image, width, height, orientation, icc_profile))
}
fn apply_color_space_conversion(
image: DynamicImage,
icc_profile: Option<Vec<u8>>,
color_space_conversion: &ColorSpaceConversion,
) -> Result<DynamicImage, CanvasError> {
match color_space_conversion {
ColorSpaceConversion::None => Ok(image),
ColorSpaceConversion::Default => {
to_srgb_from_icc_profile(image, icc_profile)
}
}
}
fn apply_premultiply_alpha(
image: DynamicImage,
image_bitmap_source: &ImageBitmapSource,
premultiply_alpha: &PremultiplyAlpha,
) -> Result<DynamicImage, CanvasError> {
match premultiply_alpha {
PremultiplyAlpha::Default => Ok(image),
PremultiplyAlpha::Premultiply => process_premultiply_alpha(image),
PremultiplyAlpha::None => {
if *image_bitmap_source == ImageBitmapSource::ImageData {
return Ok(image);
}
unpremultiply_alpha(image)
}
}
}
#[derive(Debug, PartialEq)]
struct ParsedArgs {
resize_width: Option<u32>,
resize_height: Option<u32>,
sx: Option<i32>,
sy: Option<i32>,
sw: Option<i32>,
sh: Option<i32>,
image_orientation: ImageOrientation,
premultiply_alpha: PremultiplyAlpha,
color_space_conversion: ColorSpaceConversion,
resize_quality: ResizeQuality,
image_bitmap_source: ImageBitmapSource,
mime_type: MimeType,
}
#[allow(clippy::too_many_arguments)]
fn parse_args(
sx: i32,
sy: i32,
sw: i32,
sh: i32,
image_orientation: u8,
premultiply_alpha: u8,
color_space_conversion: u8,
resize_width: u32,
resize_height: u32,
resize_quality: u8,
image_bitmap_source: u8,
mime_type: u8,
) -> ParsedArgs {
let resize_width = if resize_width == 0 {
None
} else {
Some(resize_width)
};
let resize_height = if resize_height == 0 {
None
} else {
Some(resize_height)
};
let sx = if sx == 0 { None } else { Some(sx) };
let sy = if sy == 0 { None } else { Some(sy) };
let sw = if sw == 0 { None } else { Some(sw) };
let sh = if sh == 0 { None } else { Some(sh) };
let image_orientation = match image_orientation {
0 => ImageOrientation::FromImage,
1 => ImageOrientation::FlipY,
_ => unreachable!(),
};
let premultiply_alpha = match premultiply_alpha {
0 => PremultiplyAlpha::Default,
1 => PremultiplyAlpha::Premultiply,
2 => PremultiplyAlpha::None,
_ => unreachable!(),
};
let color_space_conversion = match color_space_conversion {
0 => ColorSpaceConversion::Default,
1 => ColorSpaceConversion::None,
_ => unreachable!(),
};
let resize_quality = match resize_quality {
0 => ResizeQuality::Low,
1 => ResizeQuality::Pixelated,
2 => ResizeQuality::Medium,
3 => ResizeQuality::High,
_ => unreachable!(),
};
let image_bitmap_source = match image_bitmap_source {
0 => ImageBitmapSource::Blob,
1 => ImageBitmapSource::ImageData,
2 => ImageBitmapSource::ImageBitmap,
_ => unreachable!(),
};
let mime_type = match mime_type {
0 => MimeType::NoMatch,
1 => MimeType::Png,
2 => MimeType::Jpeg,
3 => MimeType::Gif,
4 => MimeType::Bmp,
5 => MimeType::Ico,
6 => MimeType::Webp,
_ => unreachable!(),
};
ParsedArgs {
resize_width,
resize_height,
sx,
sy,
sw,
sh,
image_orientation,
premultiply_alpha,
color_space_conversion,
resize_quality,
image_bitmap_source,
mime_type,
}
}
#[op2]
#[serde]
#[allow(clippy::too_many_arguments)]
pub(super) fn op_create_image_bitmap(
#[buffer] buf: JsBuffer,
width: u32,
height: u32,
sx: i32,
sy: i32,
sw: i32,
sh: i32,
image_orientation: u8,
premultiply_alpha: u8,
color_space_conversion: u8,
resize_width: u32,
resize_height: u32,
resize_quality: u8,
image_bitmap_source: u8,
mime_type: u8,
) -> Result<(ToJsBuffer, u32, u32), CanvasError> {
let ParsedArgs {
resize_width,
resize_height,
sx,
sy,
sw,
sh,
image_orientation,
premultiply_alpha,
color_space_conversion,
resize_quality,
image_bitmap_source,
mime_type,
} = parse_args(
sx,
sy,
sw,
sh,
image_orientation,
premultiply_alpha,
color_space_conversion,
resize_width,
resize_height,
resize_quality,
image_bitmap_source,
mime_type,
);
let (image, width, height, orientation, icc_profile) =
decode_bitmap_data(&buf, width, height, &image_bitmap_source, mime_type)?;
#[rustfmt::skip]
let source_rectangle: [[i32; 2]; 4] =
if let (Some(sx), Some(sy), Some(sw), Some(sh)) = (sx, sy, sw, sh) {
[
[sx, sy],
[sx + sw, sy],
[sx + sw, sy + sh],
[sx, sy + sh]
]
} else {
[
[0, 0],
[width as i32, 0],
[width as i32, height as i32],
[0, height as i32],
]
};
let input_x = -(source_rectangle[0][0] as i64);
let input_y = -(source_rectangle[0][1] as i64);
let surface_width = (source_rectangle[1][0] - source_rectangle[0][0]) as u32;
let surface_height = (source_rectangle[3][1] - source_rectangle[0][1]) as u32;
let output_width = if let Some(resize_width) = resize_width {
resize_width
} else if let Some(resize_height) = resize_height {
(surface_width * resize_height).div_ceil(surface_height)
} else {
surface_width
};
let output_height = if let Some(resize_height) = resize_height {
resize_height
} else if let Some(resize_width) = resize_width {
(surface_height * resize_width).div_ceil(surface_width)
} else {
surface_height
};
let image = if !(width == surface_width
&& height == surface_height
&& input_x == 0
&& input_y == 0)
{
let mut surface =
DynamicImage::new(surface_width, surface_height, image.color());
overlay(&mut surface, &image, input_x, input_y);
surface
} else {
image
};
let filter_type = match resize_quality {
ResizeQuality::Pixelated => FilterType::Nearest,
ResizeQuality::Low => FilterType::Triangle,
ResizeQuality::Medium => FilterType::CatmullRom,
ResizeQuality::High => FilterType::Lanczos3,
};
let mut image = image.resize_exact(output_width, output_height, filter_type);
let image = match image_bitmap_source {
ImageBitmapSource::Blob => {
let orientation = orientation.unwrap();
DynamicImage::apply_orientation(&mut image, orientation);
match image_orientation {
ImageOrientation::FlipY => image.flipv(),
ImageOrientation::FromImage => image,
}
}
ImageBitmapSource::ImageData | ImageBitmapSource::ImageBitmap => {
match image_orientation {
ImageOrientation::FlipY => image.flipv(),
ImageOrientation::FromImage => image,
}
}
};
let image =
apply_color_space_conversion(image, icc_profile, &color_space_conversion)?;
let image =
apply_premultiply_alpha(image, &image_bitmap_source, &premultiply_alpha)?;
Ok((image.into_bytes().into(), output_width, output_height))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_args() {
let parsed_args = parse_args(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
assert_eq!(
parsed_args,
ParsedArgs {
resize_width: None,
resize_height: None,
sx: None,
sy: None,
sw: None,
sh: None,
image_orientation: ImageOrientation::FromImage,
premultiply_alpha: PremultiplyAlpha::Default,
color_space_conversion: ColorSpaceConversion::Default,
resize_quality: ResizeQuality::Low,
image_bitmap_source: ImageBitmapSource::Blob,
mime_type: MimeType::NoMatch,
}
);
}
}