0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-09 13:49:37 -04:00
deno/ext/canvas/lib.rs

193 lines
5.1 KiB
Rust
Raw Normal View History

2024-01-22 12:08:01 +01:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::ToJsBuffer;
use image::imageops::FilterType;
use image::GenericImageView;
2024-01-22 12:08:01 +01:00
use image::Pixel;
use serde::Deserialize;
use serde::Serialize;
2024-08-21 05:47:41 +09:00
use std::io::Cursor;
2024-01-22 12:08:01 +01:00
use std::path::PathBuf;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ImageResizeQuality {
Pixelated,
Low,
Medium,
High,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ImageProcessArgs {
width: u32,
height: u32,
surface_width: u32,
surface_height: u32,
input_x: i64,
input_y: i64,
output_width: u32,
output_height: u32,
resize_quality: ImageResizeQuality,
flip_y: bool,
premultiply: Option<bool>,
2024-08-21 05:47:41 +09:00
image_bitmap_source: ImageBitmapSource,
}
#[derive(Debug, Deserialize)]
// Follow the cases defined in the spec
enum ImageBitmapSource {
Blob,
ImageData,
2024-01-22 12:08:01 +01:00
}
#[op2]
#[serde]
fn op_image_process(
#[buffer] buf: &[u8],
#[serde] args: ImageProcessArgs,
) -> Result<ToJsBuffer, AnyError> {
2024-08-21 05:47:41 +09:00
let view = match args.image_bitmap_source {
ImageBitmapSource::Blob => image::ImageReader::new(Cursor::new(buf))
.with_guessed_format()?
.decode()?,
ImageBitmapSource::ImageData => {
// > 4.12.5.1.15 Pixel manipulation
// > imagedata.data
// > Returns the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255.
// https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
let image: image::DynamicImage =
image::RgbaImage::from_raw(args.width, args.height, buf.into())
.expect("Invalid ImageData.")
.into();
image
}
};
let color = view.color();
2024-01-22 12:08:01 +01:00
let surface = if !(args.width == args.surface_width
&& args.height == args.surface_height
&& args.input_x == 0
&& args.input_y == 0)
{
2024-08-21 05:47:41 +09:00
let mut surface =
image::DynamicImage::new(args.surface_width, args.surface_height, color);
2024-01-22 12:08:01 +01:00
image::imageops::overlay(&mut surface, &view, args.input_x, args.input_y);
surface
} else {
view
};
let filter_type = match args.resize_quality {
ImageResizeQuality::Pixelated => FilterType::Nearest,
ImageResizeQuality::Low => FilterType::Triangle,
ImageResizeQuality::Medium => FilterType::CatmullRom,
ImageResizeQuality::High => FilterType::Lanczos3,
};
2024-08-21 05:47:41 +09:00
// should use resize_exact
// https://github.com/image-rs/image/issues/1220#issuecomment-632060015
let mut image_out =
surface.resize_exact(args.output_width, args.output_height, filter_type);
2024-01-22 12:08:01 +01:00
if args.flip_y {
image::imageops::flip_vertical_in_place(&mut image_out);
}
// ignore 9.
2024-08-21 05:47:41 +09:00
if color.has_alpha() {
if let Some(premultiply) = args.premultiply {
let is_not_premultiplied = image_out.pixels().any(|(_, _, pixel)| {
(pixel[0].max(pixel[1]).max(pixel[2])) > (255 * pixel[3])
});
if premultiply {
if is_not_premultiplied {
for (_, _, mut pixel) in &mut image_out.pixels() {
let alpha = pixel[3];
pixel.apply_without_alpha(|channel| {
(channel as f32 * (alpha as f32 / 255.0)) as u8
})
}
}
} else if !is_not_premultiplied {
for (_, _, mut pixel) in &mut image_out.pixels() {
let alpha = pixel[3];
2024-01-22 12:08:01 +01:00
pixel.apply_without_alpha(|channel| {
2024-08-21 05:47:41 +09:00
(channel as f32 / (alpha as f32 / 255.0)) as u8
2024-01-22 12:08:01 +01:00
})
}
}
}
}
2024-08-21 05:47:41 +09:00
Ok(image_out.into_bytes().into())
2024-01-22 12:08:01 +01:00
}
#[derive(Debug, Serialize)]
struct DecodedImage {
2024-01-22 12:08:01 +01:00
width: u32,
height: u32,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ImageDecodeOptions {
mime_type: String,
}
2024-01-22 12:08:01 +01:00
#[op2]
#[serde]
fn op_image_decode(
#[buffer] buf: &[u8],
#[serde] options: ImageDecodeOptions,
) -> Result<DecodedImage, AnyError> {
2024-08-21 05:47:41 +09:00
let reader = std::io::BufReader::new(Cursor::new(buf));
let image = match &*options.mime_type {
"image/png" => {
let decoder = image::codecs::png::PngDecoder::new(reader)?;
image::DynamicImage::from_decoder(decoder)?
}
"image/jpeg" => {
let decoder = image::codecs::jpeg::JpegDecoder::new(reader)?;
image::DynamicImage::from_decoder(decoder)?
}
"image/gif" => {
let decoder = image::codecs::gif::GifDecoder::new(reader)?;
image::DynamicImage::from_decoder(decoder)?
}
"image/bmp" => {
let decoder = image::codecs::bmp::BmpDecoder::new(reader)?;
image::DynamicImage::from_decoder(decoder)?
}
"image/x-icon" => {
let decoder = image::codecs::ico::IcoDecoder::new(reader)?;
image::DynamicImage::from_decoder(decoder)?
}
"image/webp" => {
let decoder = image::codecs::webp::WebPDecoder::new(reader)?;
image::DynamicImage::from_decoder(decoder)?
}
_ => unreachable!(),
};
let (width, height) = image.dimensions();
2024-01-22 12:08:01 +01:00
2024-08-21 05:47:41 +09:00
Ok(DecodedImage { width, height })
2024-01-22 12:08:01 +01:00
}
deno_core::extension!(
deno_canvas,
deps = [deno_webidl, deno_web, deno_webgpu],
ops = [op_image_process, op_image_decode],
2024-01-22 12:08:01 +01:00
lazy_loaded_esm = ["01_image.js"],
);
pub fn get_declaration() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_canvas.d.ts")
}