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

support imageOrientation correctly

This commit is contained in:
Hajime-san 2024-10-20 20:17:05 +09:00
parent acc4119d74
commit 83c40c4f1a
4 changed files with 148 additions and 134 deletions

8
Cargo.lock generated
View file

@ -3825,9 +3825,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.2"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
dependencies = [
"bytemuck",
"byteorder-lite",
@ -3842,9 +3842,9 @@ dependencies = [
[[package]]
name = "image-webp"
version = "0.1.3"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
dependencies = [
"byteorder-lite",
"quick-error 2.0.1",

View file

@ -18,7 +18,7 @@ bytemuck = "1.17.1"
deno_core.workspace = true
deno_terminal.workspace = true
deno_webgpu.workspace = true
image = { version = "0.25.2", default-features = false, features = ["png", "jpeg", "bmp", "ico", "webp", "gif"] }
image = { version = "0.25.4", default-features = false, features = ["png", "jpeg", "bmp", "ico", "webp", "gif"] }
# NOTE: The qcms is a color space conversion crate which parses ICC profiles that used in Gecko,
# however it supports only 8-bit color depth currently.
# https://searchfox.org/mozilla-central/rev/f09e3f9603a08b5b51bf504846091579bc2ff531/gfx/qcms/src/transform.rs#130-137

View file

@ -16,7 +16,9 @@ use image::codecs::png::PngDecoder;
use image::codecs::webp::WebPDecoder;
use image::imageops::overlay;
use image::imageops::FilterType;
use image::metadata::Orientation;
use image::DynamicImage;
use image::ImageDecoder;
use image::ImageError;
use image::RgbaImage;
@ -72,7 +74,8 @@ enum MimeType {
Webp,
}
type DecodeBitmapDataReturn = (DynamicImage, u32, u32, Option<Vec<u8>>);
type DecodeBitmapDataReturn =
(DynamicImage, u32, u32, Option<Orientation>, Option<Vec<u8>>);
fn decode_bitmap_data(
buf: &[u8],
@ -81,104 +84,117 @@ fn decode_bitmap_data(
image_bitmap_source: &ImageBitmapSource,
mime_type: MimeType,
) -> Result<DecodeBitmapDataReturn, AnyError> {
let (image, width, height, icc_profile) = match image_bitmap_source {
ImageBitmapSource::Blob => {
fn image_decoding_error(error: ImageError) -> AnyError {
DOMExceptionInvalidStateError::new(&image_error_message(
"decoding",
&error.to_string(),
))
.into()
let (image, width, height, orientation, icc_profile) =
match image_bitmap_source {
ImageBitmapSource::Blob => {
fn image_decoding_error(error: ImageError) -> AnyError {
DOMExceptionInvalidStateError::new(&image_error_message(
"decoding",
&error.to_string(),
))
.into()
}
let (image, orientation, icc_profile) = match mime_type {
// Should we support the "image/apng" MIME type here?
MimeType::Png => {
let mut decoder: PngDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
orientation,
icc_profile,
)
}
MimeType::Jpeg => {
let mut decoder: JpegDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
orientation,
icc_profile,
)
}
MimeType::Gif => {
let mut decoder: GifDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
orientation,
icc_profile,
)
}
MimeType::Bmp => {
let mut decoder: BmpDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
orientation,
icc_profile,
)
}
MimeType::Ico => {
let mut decoder: IcoDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
orientation,
icc_profile,
)
}
MimeType::Webp => {
let mut decoder: WebPDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let orientation = decoder.orientation()?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
orientation,
icc_profile,
)
}
// This pattern is unreachable due to current block is already checked by the ImageBitmapSource above.
MimeType::NoMatch => unreachable!(),
};
let width = image.width();
let height = image.height();
(image, width, height, Some(orientation), icc_profile)
}
let (image, icc_profile) = match mime_type {
// Should we support the "image/apng" MIME type here?
MimeType::Png => {
let mut decoder: PngDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
icc_profile,
)
}
MimeType::Jpeg => {
let mut decoder: JpegDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
icc_profile,
)
}
MimeType::Gif => {
let mut decoder: GifDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
icc_profile,
)
}
MimeType::Bmp => {
let mut decoder: BmpDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
icc_profile,
)
}
MimeType::Ico => {
let mut decoder: IcoDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
icc_profile,
)
}
MimeType::Webp => {
let mut decoder: WebPDecoder<ImageDecoderFromReaderType> =
ImageDecoderFromReader::to_decoder(
BufReader::new(Cursor::new(buf)),
image_decoding_error,
)?;
let icc_profile = decoder.get_icc_profile();
(
decoder.to_intermediate_image(image_decoding_error)?,
icc_profile,
)
}
// This pattern is unreachable due to current block is already checked by the ImageBitmapSource above.
MimeType::NoMatch => unreachable!(),
};
let width = image.width();
let height = image.height();
(image, width, height, icc_profile)
}
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 = match RgbaImage::from_raw(width, height, buf.into()) {
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 = match RgbaImage::from_raw(width, height, buf.into()) {
Some(image) => image.into(),
None => {
return Err(type_error(image_error_message(
@ -188,11 +204,11 @@ fn decode_bitmap_data(
}
};
(image, width, height, None)
}
};
(image, width, height, None, None)
}
};
Ok((image, width, height, icc_profile))
Ok((image, width, height, orientation, icc_profile))
}
/// According to the spec, it's not clear how to handle the color space conversion.
@ -400,7 +416,7 @@ pub(super) fn op_create_image_bitmap(
);
// 6. Switch on image:
let (image, width, height, icc_profile) =
let (image, width, height, orientation, icc_profile) =
decode_bitmap_data(&buf, width, height, &image_bitmap_source, mime_type)?;
// crop bitmap data
@ -480,27 +496,29 @@ pub(super) fn op_create_image_bitmap(
};
// should use resize_exact
// https://github.com/image-rs/image/issues/1220#issuecomment-632060015
let image = image.resize_exact(output_width, output_height, filter_type);
let mut image = image.resize_exact(output_width, output_height, filter_type);
// 8.
// issues for imageOrientation
// https://github.com/whatwg/html/issues/8085
// https://github.com/whatwg/html/issues/7210
// https://github.com/whatwg/html/issues/8118
let image = if image_orientation == ImageOrientation::FlipY {
image.flipv()
} else {
// TODO(Hajime-san): If the EXIF contains image orientation information, decode the image in the appropriate orientation.
// The image create is expected to release this feature soon.
// https://github.com/image-rs/image/blob/bd0f7451a367de7ae3d898dcf1e96e9d0a1c4fa1/CHANGES.md#version-0253
// https://github.com/image-rs/image/issues/1958
// https://github.com/image-rs/image/pull/2299
// https://github.com/image-rs/image/pull/2328
// if image_bitmap_source == ImageBitmapSource::Blob {
// image.apply_orientation()
// } else {
image
// }
let image = match image_bitmap_source {
ImageBitmapSource::Blob => {
// Note: According to browser behavior and wpt results, if Exif contains image orientation,
// it applies the rotation from it before following the value of imageOrientation.
// This is not stated in the spec but in MDN currently.
// https://github.com/mdn/content/pull/34366
// SAFETY: The orientation is always Some if the image is from a Blob.
let orientation = orientation.unwrap();
DynamicImage::apply_orientation(&mut image, orientation);
match image_orientation {
ImageOrientation::FlipY => image.flipv(),
ImageOrientation::FromImage => image,
}
}
ImageBitmapSource::ImageData => match image_orientation {
ImageOrientation::FlipY => image.flipv(),
ImageOrientation::FromImage => image,
},
};
// 9.

View file

@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertEquals, assertNotEquals, assertRejects } from "./test_util.ts";
import { assertEquals, assertRejects } from "./test_util.ts";
const prefix = "tests/testdata/image";
@ -112,9 +112,7 @@ Deno.test("imageOrientation", async (t) => {
START,
END,
);
// FIXME: When the implementation is fixed, fix this to assertEquals
// However, in the case of jpg images, numerical errors may occur.
assertNotEquals(targetPixel, new Uint8Array([255, 1, 0]));
assertEquals(targetPixel, new Uint8Array([253, 0, 0]));
});
// reference:
// https://github.com/web-platform-tests/wpt/blob/a1f4bbf4c6e1a9a861a145a34cd097ea260b5a49/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation.html#L55
@ -127,9 +125,7 @@ Deno.test("imageOrientation", async (t) => {
START,
END,
);
// FIXME: When the implementation is fixed, fix this to assertEquals
// However, in the case of jpg images, numerical errors may occur.
assertNotEquals(targetPixel, new Uint8Array([254, 128, 129]));
assertEquals(targetPixel, new Uint8Array([253, 127, 127]));
});
});