1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00

support 8bit JPG

This commit is contained in:
Hajime-san 2024-08-21 05:47:41 +09:00
parent 198c90a5c6
commit b9fe5e8e37
6 changed files with 136 additions and 54 deletions

55
Cargo.lock generated
View file

@ -598,6 +598,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.6.0"
@ -3782,17 +3788,29 @@ dependencies = [
[[package]]
name = "image"
version = "0.24.9"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
dependencies = [
"bytemuck",
"byteorder",
"byteorder-lite",
"color_quant",
"gif",
"jpeg-decoder",
"image-webp",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
dependencies = [
"byteorder-lite",
"quick-error 2.0.1",
]
[[package]]
@ -3973,12 +3991,6 @@ dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
[[package]]
name = "js-sys"
version = "0.3.69"
@ -5504,6 +5516,12 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-junit"
version = "0.3.6"
@ -5826,7 +5844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
dependencies = [
"hostname",
"quick-error",
"quick-error 1.2.3",
]
[[package]]
@ -8766,3 +8784,18 @@ dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-jpeg"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
dependencies = [
"zune-core",
]

View file

@ -18,6 +18,8 @@ const {
PromiseResolve,
PromiseReject,
RangeError,
ObjectAssign,
ArrayPrototypeJoin,
} = primordials;
import {
_data,
@ -164,6 +166,11 @@ function createImageBitmap(
options = undefined,
) {
const prefix = "Failed to execute 'createImageBitmap'";
// Add the value when implementing to add support for ImageBitmapSource
const imageBitmapSources = [
"Blob",
"ImageData",
];
// Overload: createImageBitmap(image [, options ])
if (arguments.length < 3) {
@ -212,6 +219,16 @@ function createImageBitmap(
const imageBitmap = webidl.createBranded(ImageBitmap);
// 6. Switch on image:
let imageBitmapSource;
if (ObjectPrototypeIsPrototypeOf(BlobPrototype, image)) {
imageBitmapSource = imageBitmapSources[0];
}
if (ObjectPrototypeIsPrototypeOf(ImageDataPrototype, image)) {
imageBitmapSource = imageBitmapSources[1];
}
const _options = ObjectAssign(options, { imageBitmapSource });
if (ObjectPrototypeIsPrototypeOf(ImageDataPrototype, image)) {
const processedImage = processImage(
image[_data],
@ -221,7 +238,7 @@ function createImageBitmap(
sy,
sw,
sh,
options,
_options,
);
imageBitmap[_bitmapData] = processedImage.data;
imageBitmap[_width] = processedImage.outputWidth;
@ -230,23 +247,23 @@ function createImageBitmap(
}
if (ObjectPrototypeIsPrototypeOf(BlobPrototype, image)) {
return (async () => {
const data = await image.arrayBuffer();
const data = new Uint8Array(await image.arrayBuffer());
const mimeType = sniffImage(image.type);
const { data: imageData, width, height } = op_image_decode(
new Uint8Array(data),
const { width, height } = op_image_decode(
data,
{
mimeType,
},
);
const processedImage = processImage(
imageData,
data,
width,
height,
sxOrOptions,
sy,
sw,
sh,
options,
_options,
);
imageBitmap[_bitmapData] = processedImage.data;
imageBitmap[_width] = processedImage.outputWidth;
@ -254,7 +271,13 @@ function createImageBitmap(
return imageBitmap;
})();
} else {
return PromiseReject(new TypeError("Invalid or unsupported image value"));
return PromiseReject(
new TypeError(
`${prefix}: The provided value is not of type '(${
ArrayPrototypeJoin(imageBitmapSources, " or ")
})'.`,
),
);
}
}
@ -332,6 +355,7 @@ function processImage(input, width, height, sx, sy, sw, sh, options) {
premultiply: options.premultiplyAlpha === "default"
? null
: (options.premultiplyAlpha === "premultiply"),
imageBitmapSource: options.imageBitmapSource,
},
);

View file

@ -16,5 +16,5 @@ path = "lib.rs"
[dependencies]
deno_core.workspace = true
deno_webgpu.workspace = true
image = { version = "0.24.7", default-features = false, features = ["png","jpeg","bmp","ico","webp","gif"] }
image = { version = "0.25.2", default-features = false, features = ["png","jpeg","bmp","ico","webp","gif"] }
serde = { workspace = true, features = ["derive"] }

View file

@ -6,9 +6,9 @@ use deno_core::ToJsBuffer;
use image::imageops::FilterType;
use image::GenericImageView;
use image::Pixel;
use image::RgbaImage;
use serde::Deserialize;
use serde::Serialize;
use std::io::Cursor;
use std::path::PathBuf;
#[derive(Debug, Deserialize)]
@ -34,6 +34,14 @@ struct ImageProcessArgs {
resize_quality: ImageResizeQuality,
flip_y: bool,
premultiply: Option<bool>,
image_bitmap_source: ImageBitmapSource,
}
#[derive(Debug, Deserialize)]
// Follow the cases defined in the spec
enum ImageBitmapSource {
Blob,
ImageData,
}
#[op2]
@ -42,16 +50,31 @@ fn op_image_process(
#[buffer] buf: &[u8],
#[serde] args: ImageProcessArgs,
) -> Result<ToJsBuffer, AnyError> {
let view =
RgbaImage::from_vec(args.width, args.height, buf.to_vec()).unwrap();
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();
let surface = if !(args.width == args.surface_width
&& args.height == args.surface_height
&& args.input_x == 0
&& args.input_y == 0)
{
let mut surface = RgbaImage::new(args.surface_width, args.surface_height);
let mut surface =
image::DynamicImage::new(args.surface_width, args.surface_height, color);
image::imageops::overlay(&mut surface, &view, args.input_x, args.input_y);
surface
@ -66,12 +89,10 @@ fn op_image_process(
ImageResizeQuality::High => FilterType::Lanczos3,
};
let mut image_out = image::imageops::resize(
&surface,
args.output_width,
args.output_height,
filter_type,
);
// 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);
if args.flip_y {
image::imageops::flip_vertical_in_place(&mut image_out);
@ -79,36 +100,37 @@ fn op_image_process(
// ignore 9.
if let Some(premultiply) = args.premultiply {
let is_not_premultiplied = image_out.pixels().any(|pixel| {
(pixel.0[0].max(pixel.0[1]).max(pixel.0[2])) > (255 * pixel.0[3])
});
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 pixel in image_out.pixels_mut() {
let alpha = pixel.0[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];
pixel.apply_without_alpha(|channel| {
(channel as f32 * (alpha as f32 / 255.0)) as u8
(channel as f32 / (alpha as f32 / 255.0)) as u8
})
}
}
} else if !is_not_premultiplied {
for pixel in image_out.pixels_mut() {
let alpha = pixel.0[3];
pixel.apply_without_alpha(|channel| {
(channel as f32 / (alpha as f32 / 255.0)) as u8
})
}
}
}
Ok(image_out.to_vec().into())
Ok(image_out.into_bytes().into())
}
#[derive(Debug, Serialize)]
struct DecodedImage {
data: ToJsBuffer,
width: u32,
height: u32,
}
@ -125,7 +147,7 @@ fn op_image_decode(
#[buffer] buf: &[u8],
#[serde] options: ImageDecodeOptions,
) -> Result<DecodedImage, AnyError> {
let reader = std::io::BufReader::new(std::io::Cursor::new(buf));
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)?;
@ -155,11 +177,7 @@ fn op_image_decode(
};
let (width, height) = image.dimensions();
Ok(DecodedImage {
data: image.into_bytes().into(),
width,
height,
})
Ok(DecodedImage { width, height })
}
deno_core::extension!(

BIN
tests/testdata/image/1x1-red8.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

View file

@ -100,6 +100,13 @@ Deno.test(async function imageBitmapFromBlob() {
// deno-fmt-ignore
assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([255, 0, 0, 255]));
}
{
const imageData = new Blob([await Deno.readFile(`${prefix}/1x1-red8.jpeg`)], { type: "image/jpeg" });
const imageBitmap = await createImageBitmap(imageData);
// @ts-ignore: Deno[Deno.internal].core allowed
// deno-fmt-ignore
assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([254, 0, 0]));
}
{
const imageData = new Blob([await Deno.readFile(`${prefix}/1x1-red8.bmp`)], { type: "image/bmp" });
const imageBitmap = await createImageBitmap(imageData);