mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 13:00:36 -05:00
support imageOrientation
correctly
This commit is contained in:
parent
acc4119d74
commit
83c40c4f1a
4 changed files with 148 additions and 134 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -3825,9 +3825,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.2"
|
version = "0.25.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
|
@ -3842,9 +3842,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image-webp"
|
name = "image-webp"
|
||||||
version = "0.1.3"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
|
checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
"quick-error 2.0.1",
|
"quick-error 2.0.1",
|
||||||
|
|
|
@ -18,7 +18,7 @@ bytemuck = "1.17.1"
|
||||||
deno_core.workspace = true
|
deno_core.workspace = true
|
||||||
deno_terminal.workspace = true
|
deno_terminal.workspace = true
|
||||||
deno_webgpu.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,
|
# 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.
|
# however it supports only 8-bit color depth currently.
|
||||||
# https://searchfox.org/mozilla-central/rev/f09e3f9603a08b5b51bf504846091579bc2ff531/gfx/qcms/src/transform.rs#130-137
|
# https://searchfox.org/mozilla-central/rev/f09e3f9603a08b5b51bf504846091579bc2ff531/gfx/qcms/src/transform.rs#130-137
|
||||||
|
|
|
@ -16,7 +16,9 @@ use image::codecs::png::PngDecoder;
|
||||||
use image::codecs::webp::WebPDecoder;
|
use image::codecs::webp::WebPDecoder;
|
||||||
use image::imageops::overlay;
|
use image::imageops::overlay;
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
|
use image::metadata::Orientation;
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
|
use image::ImageDecoder;
|
||||||
use image::ImageError;
|
use image::ImageError;
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
|
|
||||||
|
@ -72,7 +74,8 @@ enum MimeType {
|
||||||
Webp,
|
Webp,
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecodeBitmapDataReturn = (DynamicImage, u32, u32, Option<Vec<u8>>);
|
type DecodeBitmapDataReturn =
|
||||||
|
(DynamicImage, u32, u32, Option<Orientation>, Option<Vec<u8>>);
|
||||||
|
|
||||||
fn decode_bitmap_data(
|
fn decode_bitmap_data(
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
|
@ -81,104 +84,117 @@ fn decode_bitmap_data(
|
||||||
image_bitmap_source: &ImageBitmapSource,
|
image_bitmap_source: &ImageBitmapSource,
|
||||||
mime_type: MimeType,
|
mime_type: MimeType,
|
||||||
) -> Result<DecodeBitmapDataReturn, AnyError> {
|
) -> Result<DecodeBitmapDataReturn, AnyError> {
|
||||||
let (image, width, height, icc_profile) = match image_bitmap_source {
|
let (image, width, height, orientation, icc_profile) =
|
||||||
ImageBitmapSource::Blob => {
|
match image_bitmap_source {
|
||||||
fn image_decoding_error(error: ImageError) -> AnyError {
|
ImageBitmapSource::Blob => {
|
||||||
DOMExceptionInvalidStateError::new(&image_error_message(
|
fn image_decoding_error(error: ImageError) -> AnyError {
|
||||||
"decoding",
|
DOMExceptionInvalidStateError::new(&image_error_message(
|
||||||
&error.to_string(),
|
"decoding",
|
||||||
))
|
&error.to_string(),
|
||||||
.into()
|
))
|
||||||
|
.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 {
|
ImageBitmapSource::ImageData => {
|
||||||
// Should we support the "image/apng" MIME type here?
|
// > 4.12.5.1.15 Pixel manipulation
|
||||||
MimeType::Png => {
|
// > imagedata.data
|
||||||
let mut decoder: PngDecoder<ImageDecoderFromReaderType> =
|
// > Returns the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255.
|
||||||
ImageDecoderFromReader::to_decoder(
|
// https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
|
||||||
BufReader::new(Cursor::new(buf)),
|
let image = match RgbaImage::from_raw(width, height, buf.into()) {
|
||||||
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()) {
|
|
||||||
Some(image) => image.into(),
|
Some(image) => image.into(),
|
||||||
None => {
|
None => {
|
||||||
return Err(type_error(image_error_message(
|
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.
|
/// 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:
|
// 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)?;
|
decode_bitmap_data(&buf, width, height, &image_bitmap_source, mime_type)?;
|
||||||
|
|
||||||
// crop bitmap data
|
// crop bitmap data
|
||||||
|
@ -480,27 +496,29 @@ pub(super) fn op_create_image_bitmap(
|
||||||
};
|
};
|
||||||
// should use resize_exact
|
// should use resize_exact
|
||||||
// https://github.com/image-rs/image/issues/1220#issuecomment-632060015
|
// 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.
|
// 8.
|
||||||
// issues for imageOrientation
|
let image = match image_bitmap_source {
|
||||||
// https://github.com/whatwg/html/issues/8085
|
ImageBitmapSource::Blob => {
|
||||||
// https://github.com/whatwg/html/issues/7210
|
// Note: According to browser behavior and wpt results, if Exif contains image orientation,
|
||||||
// https://github.com/whatwg/html/issues/8118
|
// it applies the rotation from it before following the value of imageOrientation.
|
||||||
let image = if image_orientation == ImageOrientation::FlipY {
|
// This is not stated in the spec but in MDN currently.
|
||||||
image.flipv()
|
// https://github.com/mdn/content/pull/34366
|
||||||
} else {
|
|
||||||
// TODO(Hajime-san): If the EXIF contains image orientation information, decode the image in the appropriate orientation.
|
// SAFETY: The orientation is always Some if the image is from a Blob.
|
||||||
// The image create is expected to release this feature soon.
|
let orientation = orientation.unwrap();
|
||||||
// https://github.com/image-rs/image/blob/bd0f7451a367de7ae3d898dcf1e96e9d0a1c4fa1/CHANGES.md#version-0253
|
DynamicImage::apply_orientation(&mut image, orientation);
|
||||||
// https://github.com/image-rs/image/issues/1958
|
|
||||||
// https://github.com/image-rs/image/pull/2299
|
match image_orientation {
|
||||||
// https://github.com/image-rs/image/pull/2328
|
ImageOrientation::FlipY => image.flipv(),
|
||||||
// if image_bitmap_source == ImageBitmapSource::Blob {
|
ImageOrientation::FromImage => image,
|
||||||
// image.apply_orientation()
|
}
|
||||||
// } else {
|
}
|
||||||
image
|
ImageBitmapSource::ImageData => match image_orientation {
|
||||||
// }
|
ImageOrientation::FlipY => image.flipv(),
|
||||||
|
ImageOrientation::FromImage => image,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 9.
|
// 9.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// 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";
|
const prefix = "tests/testdata/image";
|
||||||
|
|
||||||
|
@ -112,9 +112,7 @@ Deno.test("imageOrientation", async (t) => {
|
||||||
START,
|
START,
|
||||||
END,
|
END,
|
||||||
);
|
);
|
||||||
// FIXME: When the implementation is fixed, fix this to assertEquals
|
assertEquals(targetPixel, new Uint8Array([253, 0, 0]));
|
||||||
// However, in the case of jpg images, numerical errors may occur.
|
|
||||||
assertNotEquals(targetPixel, new Uint8Array([255, 1, 0]));
|
|
||||||
});
|
});
|
||||||
// reference:
|
// reference:
|
||||||
// https://github.com/web-platform-tests/wpt/blob/a1f4bbf4c6e1a9a861a145a34cd097ea260b5a49/html/canvas/element/manual/imagebitmap/createImageBitmap-exif-orientation.html#L55
|
// 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,
|
START,
|
||||||
END,
|
END,
|
||||||
);
|
);
|
||||||
// FIXME: When the implementation is fixed, fix this to assertEquals
|
assertEquals(targetPixel, new Uint8Array([253, 127, 127]));
|
||||||
// However, in the case of jpg images, numerical errors may occur.
|
|
||||||
assertNotEquals(targetPixel, new Uint8Array([254, 128, 129]));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue