Commit 86ae1272 authored by Noam Rosenthal's avatar Noam Rosenthal Committed by Commit Bot

Read image resolution from EXIF, apply to intrinsic size

See https://github.com/whatwg/html/pull/5574

Currently applies to JPEG only.

Use the EXIF resolution/size information we read from the
JPEG image to determine a density-corrected preferred size,
which overrides the intrinsic size of the image.

We only apply this intrinsic size when an image has both
resolution and size information in its exif.

When asking an Image for display size, the preferred size
is returned, and the source rect for reading from the image
is scaled when applicable.

Added many WPT reference tests.

Bug: 1069755
Change-Id: I01a38c72bb46974a0c112175934cd3c2c48607a9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2292219
Commit-Queue: Stephen Chenney <schenney@chromium.org>
Reviewed-by: default avatarStephen Chenney <schenney@chromium.org>
Cr-Commit-Position: refs/heads/master@{#817600}
parent 44ba73e0
...@@ -1087,7 +1087,7 @@ Vector<uint8_t> ImageBitmap::CopyBitmapData() { ...@@ -1087,7 +1087,7 @@ Vector<uint8_t> ImageBitmap::CopyBitmapData() {
unsigned ImageBitmap::width() const { unsigned ImageBitmap::width() const {
if (!image_) if (!image_)
return 0; return 0;
IntSize size = image_->SizeRespectingOrientation(); IntSize size = image_->PreferredDisplaySize();
DCHECK_GT(size.Width(), 0); DCHECK_GT(size.Width(), 0);
return size.Width(); return size.Width();
} }
...@@ -1095,7 +1095,7 @@ unsigned ImageBitmap::width() const { ...@@ -1095,7 +1095,7 @@ unsigned ImageBitmap::width() const {
unsigned ImageBitmap::height() const { unsigned ImageBitmap::height() const {
if (!image_) if (!image_)
return 0; return 0;
IntSize size = image_->SizeRespectingOrientation(); IntSize size = image_->PreferredDisplaySize();
DCHECK_GT(size.Height(), 0); DCHECK_GT(size.Height(), 0);
return size.Height(); return size.Height();
} }
...@@ -1109,7 +1109,7 @@ IntSize ImageBitmap::Size() const { ...@@ -1109,7 +1109,7 @@ IntSize ImageBitmap::Size() const {
return IntSize(); return IntSize();
DCHECK_GT(image_->width(), 0); DCHECK_GT(image_->width(), 0);
DCHECK_GT(image_->height(), 0); DCHECK_GT(image_->height(), 0);
return image_->SizeRespectingOrientation(); return image_->PreferredDisplaySize();
} }
ScriptPromise ImageBitmap::CreateImageBitmap(ScriptState* script_state, ScriptPromise ImageBitmap::CreateImageBitmap(ScriptState* script_state,
......
...@@ -136,10 +136,15 @@ void BitmapImage::UpdateSize() const { ...@@ -136,10 +136,15 @@ void BitmapImage::UpdateSize() const {
return; return;
size_ = decoder_->FrameSizeAtIndex(0); size_ = decoder_->FrameSizeAtIndex(0);
if (decoder_->OrientationAtIndex(0).UsesWidthAsHeight()) density_corrected_size_ = decoder_->DensityCorrectedSizeAtIndex(0);
if (decoder_->OrientationAtIndex(0).UsesWidthAsHeight()) {
size_respecting_orientation_ = size_.TransposedSize(); size_respecting_orientation_ = size_.TransposedSize();
else density_corrected_size_respecting_orientation_ =
density_corrected_size_.TransposedSize();
} else {
size_respecting_orientation_ = size_; size_respecting_orientation_ = size_;
density_corrected_size_respecting_orientation_ = density_corrected_size_;
}
have_size_ = true; have_size_ = true;
} }
...@@ -148,8 +153,14 @@ IntSize BitmapImage::Size() const { ...@@ -148,8 +153,14 @@ IntSize BitmapImage::Size() const {
return size_; return size_;
} }
IntSize BitmapImage::SizeRespectingOrientation() const { IntSize BitmapImage::DensityCorrectedSize() const {
return density_corrected_size_.IsEmpty() ? Size() : density_corrected_size_;
}
IntSize BitmapImage::PreferredDisplaySize() const {
UpdateSize(); UpdateSize();
if (!density_corrected_size_respecting_orientation_.IsEmpty())
return density_corrected_size_respecting_orientation_;
return size_respecting_orientation_; return size_respecting_orientation_;
} }
...@@ -265,6 +276,13 @@ void BitmapImage::Draw( ...@@ -265,6 +276,13 @@ void BitmapImage::Draw(
} }
FloatRect adjusted_src_rect = src_rect; FloatRect adjusted_src_rect = src_rect;
if (!density_corrected_size_.IsEmpty()) {
FloatSize src_size(size_);
adjusted_src_rect.Scale(
src_size.Width() / density_corrected_size_.Width(),
src_size.Height() / density_corrected_size_.Height());
}
adjusted_src_rect.Intersect(SkRect::MakeWH(image.width(), image.height())); adjusted_src_rect.Intersect(SkRect::MakeWH(image.width(), image.height()));
if (adjusted_src_rect.IsEmpty() || dst_rect.IsEmpty()) if (adjusted_src_rect.IsEmpty() || dst_rect.IsEmpty())
...@@ -404,6 +422,12 @@ ImageOrientation BitmapImage::CurrentFrameOrientation() const { ...@@ -404,6 +422,12 @@ ImageOrientation BitmapImage::CurrentFrameOrientation() const {
: kDefaultImageOrientation; : kDefaultImageOrientation;
} }
IntSize BitmapImage::CurrentFrameDensityCorrectedSize() const {
return decoder_ ? decoder_->DensityCorrectedSizeAtIndex(
PaintImage::kDefaultFrameIndex)
: IntSize();
}
int BitmapImage::RepetitionCount() { int BitmapImage::RepetitionCount() {
if ((repetition_count_status_ == kUnknown) || if ((repetition_count_status_ == kUnknown) ||
((repetition_count_status_ == kUncertain) && all_data_received_)) { ((repetition_count_status_ == kUncertain) && all_data_received_)) {
......
...@@ -65,7 +65,7 @@ class PLATFORM_EXPORT BitmapImage final : public Image { ...@@ -65,7 +65,7 @@ class PLATFORM_EXPORT BitmapImage final : public Image {
bool CurrentFrameHasSingleSecurityOrigin() const override; bool CurrentFrameHasSingleSecurityOrigin() const override;
IntSize Size() const override; IntSize Size() const override;
IntSize SizeRespectingOrientation() const override; IntSize PreferredDisplaySize() const override;
bool HasDefaultOrientation() const override; bool HasDefaultOrientation() const override;
bool GetHotSpot(IntPoint&) const override; bool GetHotSpot(IntPoint&) const override;
String FilenameExtension() const override; String FilenameExtension() const override;
...@@ -95,6 +95,7 @@ class PLATFORM_EXPORT BitmapImage final : public Image { ...@@ -95,6 +95,7 @@ class PLATFORM_EXPORT BitmapImage final : public Image {
size_t FrameCount() override; size_t FrameCount() override;
PaintImage PaintImageForCurrentFrame() override; PaintImage PaintImageForCurrentFrame() override;
ImageOrientation CurrentFrameOrientation() const override; ImageOrientation CurrentFrameOrientation() const override;
IntSize CurrentFrameDensityCorrectedSize() const override;
PaintImage PaintImageForTesting(); PaintImage PaintImageForTesting();
void AdvanceAnimationForTesting() override { void AdvanceAnimationForTesting() override {
...@@ -104,6 +105,8 @@ class PLATFORM_EXPORT BitmapImage final : public Image { ...@@ -104,6 +105,8 @@ class PLATFORM_EXPORT BitmapImage final : public Image {
decoder_ = std::move(decoder); decoder_ = std::move(decoder);
} }
IntSize DensityCorrectedSize() const override;
protected: protected:
bool IsSizeAvailable() override; bool IsSizeAvailable() override;
...@@ -155,6 +158,8 @@ class PLATFORM_EXPORT BitmapImage final : public Image { ...@@ -155,6 +158,8 @@ class PLATFORM_EXPORT BitmapImage final : public Image {
mutable IntSize size_; // The size to use for the overall image (will just mutable IntSize size_; // The size to use for the overall image (will just
// be the size of the first image). // be the size of the first image).
mutable IntSize size_respecting_orientation_; mutable IntSize size_respecting_orientation_;
mutable IntSize density_corrected_size_;
mutable IntSize density_corrected_size_respecting_orientation_;
// This caches the PaintImage created with the last updated encoded data to // This caches the PaintImage created with the last updated encoded data to
// ensure re-use of generated decodes. This is cleared each time the encoded // ensure re-use of generated decodes. This is cleared each time the encoded
......
...@@ -347,9 +347,10 @@ bool Image::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) { ...@@ -347,9 +347,10 @@ bool Image::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) {
IntSize Image::Size( IntSize Image::Size(
RespectImageOrientationEnum respect_image_orientation) const { RespectImageOrientationEnum respect_image_orientation) const {
if (respect_image_orientation == kRespectImageOrientation) if (respect_image_orientation == kRespectImageOrientation) {
return SizeRespectingOrientation(); return PreferredDisplaySize();
return Size(); }
return DensityCorrectedSize();
} }
SkBitmap Image::AsSkBitmapForCurrentFrame( SkBitmap Image::AsSkBitmapForCurrentFrame(
...@@ -359,9 +360,25 @@ SkBitmap Image::AsSkBitmapForCurrentFrame( ...@@ -359,9 +360,25 @@ SkBitmap Image::AsSkBitmapForCurrentFrame(
return {}; return {};
auto* bitmap_image = DynamicTo<BitmapImage>(this); auto* bitmap_image = DynamicTo<BitmapImage>(this);
if (respect_image_orientation == kRespectImageOrientation && bitmap_image) { IntSize density_corrected_size;
ImageOrientation orientation = bitmap_image->CurrentFrameOrientation(); if (bitmap_image)
paint_image = ResizeAndOrientImage(paint_image, orientation); density_corrected_size = bitmap_image->DensityCorrectedSize();
if (bitmap_image && (respect_image_orientation == kRespectImageOrientation ||
!density_corrected_size.IsEmpty())) {
ImageOrientation orientation =
respect_image_orientation == kRespectImageOrientation
? bitmap_image->CurrentFrameOrientation()
: kDefaultImageOrientation;
FloatSize image_scale(1, 1);
if (density_corrected_size != bitmap_image->Size()) {
image_scale =
FloatSize(density_corrected_size.Width() / bitmap_image->width(),
density_corrected_size.Height() / bitmap_image->height());
}
paint_image = ResizeAndOrientImage(paint_image, orientation, image_scale);
if (!paint_image) if (!paint_image)
return {}; return {};
} }
......
...@@ -107,8 +107,9 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> { ...@@ -107,8 +107,9 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> {
virtual bool HasIntrinsicSize() const { return true; } virtual bool HasIntrinsicSize() const { return true; }
virtual IntSize Size() const = 0; virtual IntSize Size() const = 0;
virtual IntSize DensityCorrectedSize() const { return Size(); }
IntSize Size(RespectImageOrientationEnum) const; IntSize Size(RespectImageOrientationEnum) const;
virtual IntSize SizeRespectingOrientation() const { return Size(); } virtual IntSize PreferredDisplaySize() const { return Size(); }
virtual FloatSize SizeAsFloat( virtual FloatSize SizeAsFloat(
RespectImageOrientationEnum respect_orientation) const { RespectImageOrientationEnum respect_orientation) const {
return FloatSize(Size(respect_orientation)); return FloatSize(Size(respect_orientation));
...@@ -214,6 +215,8 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> { ...@@ -214,6 +215,8 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> {
return kDefaultImageOrientation; return kDefaultImageOrientation;
} }
virtual IntSize CurrentFrameDensityCorrectedSize() const { return IntSize(); }
// Correct the src rect (rotate and maybe translate it) to account for a // Correct the src rect (rotate and maybe translate it) to account for a
// non-default image orientation. The image must have non-default orientation // non-default image orientation. The image must have non-default orientation
// to call this method. The image_size is the oriented size of the image (i.e. // to call this method. The image_size is the oriented size of the image (i.e.
......
...@@ -35,7 +35,7 @@ scoped_refptr<StaticBitmapImage> StaticBitmapImage::Create( ...@@ -35,7 +35,7 @@ scoped_refptr<StaticBitmapImage> StaticBitmapImage::Create(
orientation); orientation);
} }
IntSize StaticBitmapImage::SizeRespectingOrientation() const { IntSize StaticBitmapImage::PreferredDisplaySize() const {
if (orientation_.UsesWidthAsHeight()) if (orientation_.UsesWidthAsHeight())
return Size().TransposedSize(); return Size().TransposedSize();
else else
......
...@@ -40,7 +40,7 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image { ...@@ -40,7 +40,7 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image {
// Methods overridden by all sub-classes // Methods overridden by all sub-classes
~StaticBitmapImage() override = default; ~StaticBitmapImage() override = default;
IntSize SizeRespectingOrientation() const override; IntSize PreferredDisplaySize() const override;
virtual scoped_refptr<StaticBitmapImage> ConvertToColorSpace( virtual scoped_refptr<StaticBitmapImage> ConvertToColorSpace(
sk_sp<SkColorSpace>, sk_sp<SkColorSpace>,
......
...@@ -369,13 +369,29 @@ static void ReadExifDirectory(JOCTET* dir_start, ...@@ -369,13 +369,29 @@ static void ReadExifDirectory(JOCTET* dir_start,
break; break;
case ExifTags::kPixelXDimensionTag: case ExifTags::kPixelXDimensionTag:
if (type == kUnsignedShortType && count == 1) if (count != 1)
metadata.size.SetWidth(ReadUint16(value_ptr, is_big_endian)); break;
switch (type) {
case kUnsignedShortType:
metadata.size.SetWidth(ReadUint16(value_ptr, is_big_endian));
break;
case kUnsignedLongType:
metadata.size.SetWidth(ReadUint32(value_ptr, is_big_endian));
break;
}
break; break;
case ExifTags::kPixelYDimensionTag: case ExifTags::kPixelYDimensionTag:
if (type == kUnsignedShortType && count == 1) if (count != 1)
metadata.size.SetHeight(ReadUint16(value_ptr, is_big_endian)); break;
switch (type) {
case kUnsignedShortType:
metadata.size.SetHeight(ReadUint16(value_ptr, is_big_endian));
break;
case kUnsignedLongType:
metadata.size.SetHeight(ReadUint32(value_ptr, is_big_endian));
break;
}
break; break;
case ExifTags::kExifOffsetTag: case ExifTags::kExifOffsetTag:
......
...@@ -384,6 +384,7 @@ crbug.com/997202 external/wpt/css/css-images/image-orientation/svg-image-orienta ...@@ -384,6 +384,7 @@ crbug.com/997202 external/wpt/css/css-images/image-orientation/svg-image-orienta
crbug.com/997202 external/wpt/css/css-images/image-orientation/svg-image-orientation-aspect-ratio.html [ Failure ] crbug.com/997202 external/wpt/css/css-images/image-orientation/svg-image-orientation-aspect-ratio.html [ Failure ]
crbug.com/997202 external/wpt/css/css-images/image-orientation/image-orientation-background-position.html [ Failure ] crbug.com/997202 external/wpt/css/css-images/image-orientation/image-orientation-background-position.html [ Failure ]
crbug.com/997202 external/wpt/css/css-images/image-orientation/image-orientation-background-properties.html [ Failure ] crbug.com/997202 external/wpt/css/css-images/image-orientation/image-orientation-background-properties.html [ Failure ]
crbug.com/997202 external/wpt/density-size-correction/density-corrected-image-svg-aspect-ratio.html [ Failure ]
# These have filtering antialiasing artifacts on one side only, but otherwise are passing. The tests # These have filtering antialiasing artifacts on one side only, but otherwise are passing. The tests
# should be updated with the fuzzy meta data once enabled in the test driver. # should be updated with the fuzzy meta data once enabled in the test driver.
......
<html>
<head>
<title>Density corrected size: canvas</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<meta name="assert" content="Assert that density-corrected size in EXIF is taken into account for images when drawn in canvas">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<p>You should see green/yellow gradient boxes</p>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D u_image;
varying vec2 v_texCoord;
uniform float u_frame;
void main(void) {
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 a_vertex;
attribute vec2 a_tex;
varying vec2 v_texCoord;
void main(void) {
gl_Position = vec4(a_vertex, 0, 1) ;
v_texCoord = a_tex;
}
</script>
<script>
const dim = 3
function drawImage2d(context, image, srcRect) {
context.drawImage(image, ...srcRect, 0, 0, dim, dim)
return context.getImageData(0, 0, dim, dim)
}
function drawImage3d(gl, image, srcRect) {
const vshader = gl.createShader(gl.VERTEX_SHADER)
const fshader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(vshader, document.querySelector("#shader-vs").textContent)
gl.shaderSource(fshader, document.querySelector("#shader-fs").textContent)
gl.compileShader(vshader)
gl.compileShader(fshader)
const program = gl.createProgram()
gl.attachShader(program, vshader)
gl.attachShader(program, fshader)
gl.linkProgram(program)
gl.useProgram(program)
const a_vertex = gl.getAttribLocation(program, "a_vertex")
const a_tex = gl.getAttribLocation(program, "a_tex")
const buf_vertex = gl.createBuffer()
const buf_tex = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buf_tex)
const [sx, sy, sw, sh] = [srcRect[0] / image.naturalWidth, srcRect[1] / image.naturalHeight, srcRect[2] / image.naturalWidth, srcRect[3] / image.naturalHeight]
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
sx, sy, sx + sw, sy, sx, sy + sh, sx + sw, sy + sh
]), gl.STATIC_DRAW)
gl.bindBuffer(gl.ARRAY_BUFFER, buf_vertex)
var vertices = [-1, 1, 1, 1, -1, -1, 1, -1]
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
var texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.viewport(0, 0, dim, dim)
gl.enableVertexAttribArray(a_vertex)
gl.enableVertexAttribArray(a_tex)
gl.uniform1i(gl.getUniformLocation(program, "u_image"), 0)
gl.bindBuffer(gl.ARRAY_BUFFER, buf_tex)
gl.vertexAttribPointer(a_tex, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, buf_vertex)
gl.vertexAttribPointer(a_vertex, 2, gl.FLOAT, false, 0, 0)
gl.clearColor(1, 1, 1, 1)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
const pixels = new Uint8ClampedArray(dim * dim * 4)
gl.readPixels(0, 0, dim, dim, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
return {data: pixels, width: dim, height: dim}
}
function createCanvasWithImage(src, contextType, srcRect) {
const canvas = document.createElement('canvas')
canvas.width = canvas.height = dim
const image = new Image()
image.src = src
return new Promise(resolve => {
image.onload = () => {
const context = canvas.getContext(contextType)
if (contextType === '2d')
resolve(drawImage2d(context, image, srcRect))
else
resolve(drawImage3d(context, image, srcRect))
}
})
}
async function checkCanvasImage(src, contextType, srcRect, reference) {
const data = await createCanvasWithImage(src, contextType, srcRect)
assert_array_approx_equals(data.data, reference.data, 32)
}
promise_test(async () => {
const reference = await createCanvasWithImage('resources/exif-resolution-none.jpg', '2d', [40, 5, 20, 5]);
await checkCanvasImage("resources/exif-resolution-valid-hires.jpg", '2d', [20, 5, 10, 1], reference)
await checkCanvasImage("resources/exif-resolution-valid-lores.jpg", '2d', [80, 5, 40, 10], reference)
await checkCanvasImage("resources/exif-resolution-none.jpg", 'webgl', [40, 5, 20, 5], reference)
await checkCanvasImage("resources/exif-resolution-valid-hires.jpg", 'webgl', [20, 5, 10, 1], reference)
await checkCanvasImage("resources/exif-resolution-valid-lores.jpg", 'webgl', [80, 5, 40, 1], reference)
})
</script>
</body>
</html>
<html>
<head>
<title>Density Corrected Size: various elements</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
</head>
<body>
<svg style="width: 800px; height: 800px">
<image xlink:href="resources/exif-resolution-none.jpg" x="0" y="0" width="120" height="120" preserveAspectRatio="xMinYMin meet"></image>
<image xlink:href="resources/exif-resolution-preapplied-lores.jpg" x="200" y="0" width="120" height="120" preserveAspectRatio="xMidYMin meet"></image>
<image xlink:href="resources/exif-resolution-none.jpg" x="400" y="0" width="120" height="120" preserveAspectRatio="xMaxYMin meet"></image>
<image xlink:href="resources/exif-resolution-valid-non-uniform.jpg" x="630" y="0" width="60" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-preapplied-lores.jpg" x="0" y="150" width="120" height="120" preserveAspectRatio="xMinYMid slice"></image>
<image xlink:href="resources/exif-resolution-none.jpg" x="200" y="150" width="120" height="120" preserveAspectRatio="xMidYMid slice"></image>
<image xlink:href="resources/exif-resolution-preapplied-lores.jpg" x="400" y="150" width="120" height="120" preserveAspectRatio="xMaxYMid slice"></image>
<image xlink:href="resources/exif-resolution-preapplied-non-uniform.jpg" x="600" y="150" width="120" height="120" preserveAspectRatio="xMidYMid slice"></image>
<image xlink:href="resources/exif-resolution-none.jpg" x="200" y="300" width="120" height="120" preserveAspectRatio="xMidYMax meet"></image>
<image xlink:href="resources/exif-resolution-preapplied-lores.jpg" x="400" y="300" width="120" height="120" preserveAspectRatio="xMaxYMax meet"></image>
<image xlink:href="resources/exif-resolution-preapplied-non-uniform.jpg" x="600" y="300" width="120" height="120" preserveAspectRatio="xMaxYMid slice"></image>
<image xlink:href="resources/exif-resolution-none.jpg" x="0" y="450" width="120" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-preapplied-lores.jpg" x="200" y="450" width="120" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-preapplied-non-uniform.jpg" x="400" y="450" width="120" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-preapplied-non-uniform.jpg" x="600" y="450" width="120" height="120" preserveAspectRatio="xMaxYMax meet"></image>
</svg>
</div>
</body>
</html>
<html>
<head>
<title>Density Corrected Size: SVG aspect ratio</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="match" href="density-corrected-image-svg-aspect-ratio-ref.html" />
<meta name="assert" content="Assert that SVG aspect ratio is applied correctly on density-corrected images">
<meta name=fuzzy content="10;100">
</head>
<body>
<svg style="width: 800px; height: 800px">
<image xlink:href="resources/exif-resolution-valid-hires.jpg" x="0" y="0" width="120" height="120" preserveAspectRatio="xMinYMin meet"></image>
<image xlink:href="resources/exif-resolution-valid-lores.jpg" x="200" y="0" width="120" height="120" preserveAspectRatio="xMidYMin meet"></image>
<image xlink:href="resources/exif-resolution-valid-hires.jpg" x="400" y="0" width="120" height="120" preserveAspectRatio="xMaxYMin meet"></image>
<image xlink:href="resources/exif-resolution-valid-non-uniform.jpg" x="600" y="0" width="120" height="120" preserveAspectRatio="xMidYMin meet"></image>
<image xlink:href="resources/exif-resolution-valid-lores.jpg" x="0" y="150" width="120" height="120" preserveAspectRatio="xMinYMid slice"></image>
<image xlink:href="resources/exif-resolution-valid-hires.jpg" x="200" y="150" width="120" height="120" preserveAspectRatio="xMidYMid slice"></image>
<image xlink:href="resources/exif-resolution-valid-lores.jpg" x="400" y="150" width="120" height="120" preserveAspectRatio="xMaxYMid slice"></image>
<image xlink:href="resources/exif-resolution-valid-non-uniform.jpg" x="600" y="150" width="120" height="120" preserveAspectRatio="xMinYMid slice"></image>
<image xlink:href="resources/exif-resolution-valid-hires.jpg" x="200" y="300" width="120" height="120" preserveAspectRatio="xMidYMax meet"></image>
<image xlink:href="resources/exif-resolution-valid-lores.jpg" x="400" y="300" width="120" height="120" preserveAspectRatio="xMaxYMax meet"></image>
<image xlink:href="resources/exif-resolution-valid-non-uniform.jpg" x="600" y="300" width="120" height="120" preserveAspectRatio="xMaxYMid slice"></image>
<image xlink:href="resources/exif-resolution-valid-hires.jpg" x="0" y="450" width="120" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-valid-lores.jpg" x="200" y="450" width="120" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-valid-non-uniform.jpg" x="400" y="450" width="120" height="120" preserveAspectRatio="none"></image>
<image xlink:href="resources/exif-resolution-valid-non-uniform.jpg" x="600" y="450" width="120" height="120" preserveAspectRatio="xMaxYMax meet"></image>
</svg>
</div>
</body>
</html>
<html>
<head>
<title>Density Corrected Size: SVG</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
</head>
<body>
<svg width="200" height="200">
<image href="resources/exif-resolution-none.jpg" />
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-none.jpg" preserveAspectRatio="none" width="50" height="25"/>
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-none.jpg" preserveAspectRatio="none" width="200" height="100" />
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-none.jpg" preserveAspectRatio="none" width="50" height="100" />
</svg>
<svg width="200" height="200" style="transform-origin: 25% 25%; transform: rotate(90deg)">
<image href="resources/exif-resolution-none.jpg" preserveAspectRatio="none" width="200" height="100" />
</svg>
</div>
</body>
</html>
<html>
<head>
<title>Density Corrected Size: SVG</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="match" href="density-corrected-image-svg-ref.html" />
<meta name="assert" content="Assert that density-corrected size in EXIF is taken into account for SVG <image>">
</head>
<body>
<svg width="200" height="200">
<image href="resources/exif-resolution-none.jpg" />
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-valid-hires.jpg" />
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-valid-lores.jpg" />
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-valid-non-uniform.jpg" />
</svg>
<svg width="200" height="200">
<image href="resources/exif-resolution-with-orientation.jpg" />
</svg>
</div>
</body>
</html>
<!DOCTYPE html>
<body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./third_party/piexif/piexif.js"></script>
<script src="./resources/exify.js"></script>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<script>
async function test_valid(input) {
const image = await createImageWithMetadata(input)
assert_equals(image.naturalWidth, input.preferredWidth)
assert_equals(image.naturalHeight, input.preferredHeight)
}
async function test_invalid(input) {
const image = await createImageWithMetadata(input)
assert_equals(image.naturalWidth, input.width)
assert_equals(image.naturalHeight, input.height)
}
async function test() {
await test_valid({width: 10, height: 20, preferredWidth: 20, preferredHeight: 40, resolutionX: 36, resolutionY: 36, resolutionUnit: 2})
await test_valid({width: 10, height: 20, preferredWidth: 2, preferredHeight: 4, resolutionX: 360, resolutionY: 360, resolutionUnit: 2})
await test_valid({width: 10, height: 20, preferredWidth: 20, preferredHeight: 10, resolutionX: 36, resolutionY: 144, resolutionUnit: 2})
await test_valid({width: 10, height: 20, preferredWidth: 10, preferredHeight: 40, resolutionX: 72, resolutionY: 36, resolutionUnit: 2})
await test_valid({width: 30, height: 30, preferredWidth: 90, preferredHeight: 30, resolutionX: 24, resolutionY: 72, resolutionUnit: 2})
await test_invalid({width: 10, height: 20, preferredWidth: 20, preferredHeight: 30, resolutionX: 36, resolutionY: 36, resolutionUnit: 2})
await test_invalid({width: 10, height: 20, preferredWidth: 33, preferredHeight: 40, resolutionX: 36, resolutionY: 36, resolutionUnit: 2})
await test_invalid({width: 10, height: 20, preferredHeight: 40, resolutionX: 36, resolutionY: 36, resolutionUnit: 2})
await test_invalid({width: 10, height: 20, preferredWidth: 20, resolutionX: 36, resolutionY: 36, resolutionUnit: 2})
await test_invalid({width: 30, height: 30, preferredWidth: 90, preferredHeight: 30, resolutionY: 72, resolutionUnit: 2})
await test_invalid({width: 30, height: 30, preferredWidth: 90, preferredHeight: 30, resolutionX: 24, resolutionUnit: 2})
await test_invalid({width: 30, height: 30, preferredWidth: 90, preferredHeight: 30, resolutionX: 24, resolutionY: 72, resolutionUnit: 1})
await test_invalid({width: 3, height: 3, preferredHeight: 2000000, preferredWidth: 50})
}
promise_test(test)
</script>
</body>
</html>
<!DOCTYPE html>
<head>
<title>Density corrected size: background images</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
</head>
<body>
<style>
body {
--lores: url(resources/exif-resolution-valid-lores.jpg);
--hires: url(resources/exif-resolution-valid-hires.jpg);
--default: url(resources/exif-resolution-none.jpg);
--non-uniform: url(resources/exif-resolution-valid-non-uniform.jpg);
}
.default-bg {background-image: var(--default); }
.lores-bg {background-image: var(--lores); }
.hires-bg {background-image: var(--hires); }
.invalid-bg {background-image: var(--invalid); }
.non-uniform-bg {background-image: var(--non-uniform); }
.box { width: 200px; height: 200px; display: inline-block; }
.tiled {background-repeat: repeat; }
.stretch {background-repeat: no-repeat; background-size: contain; }
</style>
<div class="default-bg tiled box"></div>
<div class="lores-bg tiled box"></div>
<div class="hires-bg tiled box"></div>
<div class="non-uniform-bg tiled box"></div>
<br/>
<div class="lores-bg stretch box"></div>
<div class="default-bg stretch box"></div>
<div class="non-uniform-bg stretch box"></div>
</body>
</html>
<!DOCTYPE html>
<head>
<title>Density corrected size: background images</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="match" href="density-corrected-size-bg-ref.html" />
<meta name="assert" content="Assert that background images with EXIF density-corrected-size are rendered correctly">
</head>
<body>
<style>
body {
--lores: url(resources/exif-resolution-valid-lores.jpg);
--hires: url(resources/exif-resolution-valid-hires.jpg);
--default: url(resources/exif-resolution-none.jpg);
--non-uniform: url(resources/exif-resolution-valid-non-uniform.jpg);
}
.default-bg {background-image: var(--default); }
.lores-bg {background-image: var(--lores); }
.hires-bg {background-image: var(--hires); }
.invalid-bg {background-image: var(--invalid); }
.non-uniform-bg {background-image: var(--non-uniform); }
.box { width: 200px; height: 200px; display: inline-block; }
.tiled {background-repeat: repeat; }
.stretch {background-repeat: no-repeat; background-size: contain; }
</style>
<div class="default-bg tiled box"></div>
<div class="lores-bg tiled box"></div>
<div class="hires-bg tiled box"></div>
<div class="non-uniform-bg tiled box"></div>
<br/>
<div class="lores-bg stretch box"></div>
<div class="default-bg stretch box"></div>
<div class="non-uniform-bg stretch box"></div>
</body>
</html>
<!DOCTYPE html>
<body>
<img src="resources/exif-resolution-none.jpg" width="100" height="50" />
<img src="resources/exif-resolution-none.jpg" width="50" height="25" />
<img src="resources/exif-resolution-none.jpg" width="200" height="100"/>
<img src="resources/exif-resolution-none.jpg" width="50" height="100"/>
</body>
</html>
<!DOCTYPE html>
<head>
<title>Density corrected size: rendering</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="match" href="density-corrected-size-img-ref.html" />
<meta name="assert" content="Assert that images with EXIF density-corrected-size are rendered correctly">
</head>
<body>
<img src="resources/exif-resolution-none.jpg" />
<img src="resources/exif-resolution-valid-hires.jpg" />
<img src="resources/exif-resolution-valid-lores.jpg" />
<img src="resources/exif-resolution-valid-non-uniform.jpg" />
</body>
</html>
<!DOCTYPE html>
<head>
<title>Density corrected size: pseudo-elements</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
</head>
<body>
<style>
body {
--lores: url(resources/exif-resolution-valid-lores.jpg);
--default: url(resources/exif-resolution-none.jpg);
}
.lores-after::after {content: var(--lores); width: 200px; height: 100px; }
.default-after::after {content: var(--default); }
</style>
<div class="lores-after box"></div>
<div class="default-after box"></div>
</body>
</html>
<!DOCTYPE html>
<head>
<title>Density corrected size: pseudo-elements</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="match" href="density-corrected-size-pseudo-elements-ref.html" />
<meta name="assert" content="Assert that images with EXIF density-corrected-size are rendered correctly when in a pseudo element">
</head>
<body>
<style>
body {
--lores: url(resources/exif-resolution-valid-lores.jpg);
--default: url(resources/exif-resolution-none.jpg);
}
.lores-after::after {content: var(--lores); }
.default-after::after {content: var(--default); }
</style>
<div class="lores-after box"></div>
<div class="default-after box"></div>
</body>
</html>
<html>
<head>
<title>Density Corrected Size: various elements</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<style>
.row {
display: flex;
}
.row > * {
object-fit: none;
object-position: top left;
margin: 5px;
width: 100px;
height: 50px;
background: #CCCCCC;
}
</style>
</head>
<body>
<p>There following boxes should be identical (the colorful boxes at the top-left quarter)</p>
<div class="row">
<div>
<img src="resources/exif-resolution-none.jpg" width="50" height="25" />
</div>
<div>
<img src="resources/exif-resolution-none.jpg" width="50" height="25" />
</div>
<div>
<img src="resources/exif-resolution-none.jpg" width="50" height="25" />
</div>
</div>
</body>
</html>
<html>
<head>
<title>Density Corrected Size: various elements</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="match" href="density-corrected-various-elements-ref.html" />
<meta name="assert" content="Assert that density-corrected size in EXIF is taken into account for images in all relevant elements (input/video-poster)">
<style>
.row {
display: flex;
}
.row > * {
object-fit: none;
object-position: top left;
margin: 5px;
width: 100px;
height: 50px;
background: #CCCCCC;
}
</style>
</head>
<body>
<p>There following boxes should be identical (the colorful boxes at the top-left quarter)</p>
<div class="row">
<div>
<img src="resources/exif-resolution-none.jpg" width="50" height="25" />
</div>
<video poster="resources/exif-resolution-valid-hires.jpg"></video>
<div>
<input type="image" src="resources/exif-resolution-valid-hires.jpg" />
</div>
</div>
</body>
</html>
function createImageWithMetadata({
width,
height,
preferredWidth,
preferredHeight,
resolutionX,
resolutionY,
resolutionUnit,
orientation
}) {
const canvas = document.createElement('canvas')
canvas.width = width || 100
canvas.height = height || 100
const ctx = canvas.getContext('2d')
ctx.fillColor = 'green'
ctx.fillRect(0, 0, canvas.width, canvas.height)
const original = canvas.toDataURL('image/jpeg')
const root = {}
const exif = {}
if (orientation !== undefined)
root[piexif.ExifIFD.Orientation] = orientation
if (resolutionX !== undefined)
root[piexif.ImageIFD.XResolution] = [resolutionX, 1]
if (resolutionY !== undefined)
root[piexif.ImageIFD.YResolution] = [resolutionY, 1]
if (resolutionUnit !== undefined)
root[piexif.ImageIFD.ResolutionUnit] = resolutionUnit
if (preferredWidth !== undefined)
exif[piexif.ExifIFD.PixelXDimension] = preferredWidth
if (preferredHeight !== undefined)
exif[piexif.ExifIFD.PixelYDimension] = preferredHeight
const exifString = piexif.dump({'0th': root, 'Exif': exif})
const newDataUrl = piexif.insert(exifString, original)
const image = new Image()
image.src = newDataUrl
return new Promise(resolve => {
image.onload = () => resolve(image);
})
}
\ No newline at end of file
The MIT License (MIT)
Copyright (c) 2014 Hiroaki Matoba
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment