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:
parent
198c90a5c6
commit
b9fe5e8e37
6 changed files with 136 additions and 54 deletions
55
Cargo.lock
generated
55
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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
BIN
tests/testdata/image/1x1-red8.jpeg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue