WebGLRenderingContext.texImage2D() should respect EXIF orientation
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Dec 2019 05:36:15 +0000 (05:36 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Dec 2019 05:36:15 +0000 (05:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=205141

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2019-12-16
Reviewed by Simon Fraser.

Source/WebCore:

If image orientation is not the default, WebGLRenderingContext.texImage2D()
needs to draw this image into an ImageBuffer, makes a temporary Image
from the ImageBuffer then draw this temporary Image to the WebGL texture.

Test: fast/images/exif-orientation-webgl-texture.html

* html/canvas/WebGLRenderingContextBase.cpp:
(WebCore::WebGLRenderingContextBase::texSubImage2D):
(WebCore::WebGLRenderingContextBase::texImage2D):
* platform/graphics/BitmapImage.h:
* platform/graphics/Image.h:
(WebCore::Image::orientation const):

LayoutTests:

The test page uses images with different EXIF orientation. The expected
page uses a single image with no EXIF orientation then it transforms the
<canvas> elements such that it matches the image in the test page.

WebGLRenderingContext uses a trick when drawing an SVG image or images
with EXIF orientation to a WebGL texture. It draws the Image to an Image-
Buffer, creates another Image out of the ImageBuffer and then draws the
other Image to the WebGL texture.

But there can be small glitches between drawing an Image directly versus
doing the ImageBuffer trick. So the expected page will use an SVG image
to ensure the same code path is used for both the test and the expected
pages.

This SVG image includes the jpeg image with no EXIF orientation but as a
data uri. Also the script has to wait after loading the SVG image till
the bitmap image is loaded from the data uri encoded data.

* fast/images/exif-orientation-webgl-texture-expected.html: Added.
* fast/images/exif-orientation-webgl-texture.html: Added.
* fast/images/resources/webgl-draw-image.js: Added.
* platform/win/TestExpectations:
All webgl tests are skipped on Windows.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@253610 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/images/exif-orientation-webgl-texture-expected.html [new file with mode: 0644]
LayoutTests/fast/images/exif-orientation-webgl-texture.html [new file with mode: 0644]
LayoutTests/fast/images/resources/webgl-draw-image.js [new file with mode: 0644]
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
Source/WebCore/platform/graphics/BitmapImage.h
Source/WebCore/platform/graphics/Image.h

index 56f7709..ffdfe1f 100644 (file)
@@ -1,3 +1,34 @@
+2019-12-16  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        WebGLRenderingContext.texImage2D() should respect EXIF orientation
+        https://bugs.webkit.org/show_bug.cgi?id=205141
+
+        Reviewed by Simon Fraser.
+
+        The test page uses images with different EXIF orientation. The expected
+        page uses a single image with no EXIF orientation then it transforms the
+        <canvas> elements such that it matches the image in the test page.
+
+        WebGLRenderingContext uses a trick when drawing an SVG image or images 
+        with EXIF orientation to a WebGL texture. It draws the Image to an Image-
+        Buffer, creates another Image out of the ImageBuffer and then draws the
+        other Image to the WebGL texture.
+
+        But there can be small glitches between drawing an Image directly versus
+        doing the ImageBuffer trick. So the expected page will use an SVG image 
+        to ensure the same code path is used for both the test and the expected
+        pages.
+
+        This SVG image includes the jpeg image with no EXIF orientation but as a
+        data uri. Also the script has to wait after loading the SVG image till
+        the bitmap image is loaded from the data uri encoded data.
+
+        * fast/images/exif-orientation-webgl-texture-expected.html: Added.
+        * fast/images/exif-orientation-webgl-texture.html: Added.
+        * fast/images/resources/webgl-draw-image.js: Added.
+        * platform/win/TestExpectations:
+        All webgl tests are skipped on Windows.
+
 2019-12-16  Truitt Savell  <tsavell@apple.com>
 
         REGRESSION: [ Catalina wk1 ] editing/mac/input/firstrectforcharacterrange-styled.html is failing
diff --git a/LayoutTests/fast/images/exif-orientation-webgl-texture-expected.html b/LayoutTests/fast/images/exif-orientation-webgl-texture-expected.html
new file mode 100644 (file)
index 0000000..3fd56a5
--- /dev/null
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<head>
+<style>
+    div.container {
+        display: inline-block;
+        margin-right: 20px; 
+        margin-bottom: 10px; 
+        width: 100px; 
+        vertical-align: top;
+    }
+    div.horizontal {
+        width: 102px;
+        height: 52px;
+    }
+    div.vertical {
+        width: 52px;
+        height: 102px;
+    }
+    canvas {
+        border: 1px solid black;
+        width: 100px;
+        height: 50px;
+    }
+</style>
+</head>
+<script id="vertexShaderSource" type="text/glsl">
+    attribute vec4 a_position;
+    varying vec2 v_texturePosition;
+
+    void main() {
+        v_texturePosition = vec2((a_position.x + 1.0) / 2.0, (a_position.y + 1.0) / 2.0);
+        gl_Position = a_position;
+    }
+</script>
+<script id="fragmentShaderSource" type="text/glsl">
+    precision mediump float;
+
+    varying vec2 v_texturePosition;
+
+    uniform sampler2D texture;
+
+    void main() {
+        gl_FragColor = texture2D(texture, v_texturePosition);
+    }
+</script>
+<script src="resources/webgl-draw-image.js"></script>
+<body>
+    <b>WebGLRenderingContext.texImage2D() should rotate the images respecting their EXIF orientation.</b>
+    <br>
+    <br>
+    <div class ="container">
+        <div class ="horizontal">
+            <canvas class="horizontal" id="canvas2" style="transform: scaleX(-1);"></canvas>
+        </div>
+        <br>Flipped horizontally
+    </div>
+    <div class ="container">
+        <div class ="horizontal">
+            <canvas class="horizontal" id="canvas3" style="transform: rotate(180deg);"></canvas>
+        </div>
+        <br>Rotated 180&deg;
+    </div>
+    <div class ="container">
+        <div class ="horizontal">
+            <canvas class="horizontal" id="canvas4" style="transform: scaleX(-1) rotate(180deg);"></canvas>
+        </div>
+        <br>Flipped vertically
+    </div>
+    <br>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas5" style="transform: translate(-25px, 25px) rotate(90deg) scaleY(-1);"></canvas>
+        </div>
+        <br>Rotated 90&deg; CCW and flipped vertically
+    </div>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas6" style="transform: translate(-25px, 25px) rotate(90deg);"></canvas>
+        </div>
+        <br>Rotated 90&deg; CCW
+    </div>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas7" style="transform: translate(-25px, 25px) rotate(270deg) scaleY(-1);"></canvas>
+        </div>
+        <br>Rotated 90&deg; CW and flipped vertically
+    </div>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas8" style="transform: translate(-25px, 25px) rotate(270deg);"></canvas>
+        </div>
+        <br>Rotated 90&deg; CW
+    </div>
+    <br>
+    <script>
+        if (window.testRunner)
+            window.testRunner.waitUntilDone();
+
+        window.onload = function() {
+            document.querySelectorAll("canvas").forEach(canvas => {
+                canvas.width = 100 * window.devicePixelRatio;
+                canvas.height = 50 * window.devicePixelRatio;
+            });
+
+            var image = new Image;
+            image.src = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='50'><image href='data:image/jpeg;base64,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' width='100' height='50'/></svg>";
+            image.decode().then(() => {
+                setTimeout(function() {
+                    document.querySelectorAll("canvas").forEach(canvas => {
+                        canvas.width = image.width * window.devicePixelRatio;
+                        canvas.height = image.height * window.devicePixelRatio;
+                        webglDrawImage(canvas, image);
+                    });
+
+                    if (window.testRunner)
+                        window.testRunner.notifyDone();
+                }, 100);
+            });
+        }
+    </script>
+</body>
diff --git a/LayoutTests/fast/images/exif-orientation-webgl-texture.html b/LayoutTests/fast/images/exif-orientation-webgl-texture.html
new file mode 100644 (file)
index 0000000..50cbecc
--- /dev/null
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<head>
+<style>
+    div.container {
+        display: inline-block;
+        margin-right: 20px;
+        margin-bottom: 10px;
+        width: 100px;
+        vertical-align: top;
+    }
+    div.horizontal {
+        width: 102;
+        height: 52px;
+    }
+    div.vertical {
+        width: 52px;
+        height: 102px;
+    }
+    canvas {
+        border: 1px solid black;
+    }
+    canvas.horizontal {
+        width: 100px;
+        height: 50px;
+    }
+    canvas.vertical {
+        width: 50px;
+        height: 100px;
+    }
+</style>
+</head>
+<script id="vertexShaderSource" type="text/glsl">
+    attribute vec4 a_position;
+    varying vec2 v_texturePosition;
+
+    void main() {
+        v_texturePosition = vec2((a_position.x + 1.0) / 2.0, (a_position.y + 1.0) / 2.0);
+        gl_Position = a_position;
+    }
+</script>
+<script id="fragmentShaderSource" type="text/glsl">
+    precision mediump float;
+
+    varying vec2 v_texturePosition;
+
+    uniform sampler2D texture;
+
+    void main() {
+        gl_FragColor = texture2D(texture, v_texturePosition);
+    }
+</script>
+<script src="resources/webgl-draw-image.js"></script>
+<body>
+    <b>WebGLRenderingContext.texImage2D() should rotate the images respecting their EXIF orientation.</b>
+    <br>
+    <br>
+    <div class ="container">
+        <div class ="horizontal">
+            <canvas class="horizontal" id="canvas2"></canvas>
+        </div>
+        <br>Flipped horizontally
+    </div>
+    <div class ="container">
+        <div class ="horizontal">
+            <canvas class="horizontal" id="canvas3"></canvas>
+        </div>
+        <br>Rotated 180&deg;
+    </div>
+    <div class ="container">
+        <div class ="horizontal">
+            <canvas class="horizontal" id="canvas4"></canvas>
+        </div>
+        <br>Flipped vertically
+    </div>
+    <br>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas5"></canvas>
+        </div>
+        <br>Rotated 90&deg; CCW and flipped vertically
+    </div>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas6"></canvas>
+        </div>
+        <br>Rotated 90&deg; CCW
+    </div>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas7"></canvas>
+        </div>
+        <br>Rotated 90&deg; CW and flipped vertically
+    </div>
+    <div class ="container">
+        <div class ="vertical">
+            <canvas class="vertical" id="canvas8"></canvas>
+        </div>
+        <br>Rotated 90&deg; CW
+    </div>
+    <br>
+    <script>
+        if (window.testRunner)
+            window.testRunner.waitUntilDone();
+
+        window.onload = function() {
+            var names = [
+                { resource: "resources/exif-orientation-2-ur.jpg",  id : "canvas2" },
+                { resource: "resources/exif-orientation-3-lr.jpg",  id : "canvas3" },
+                { resource: "resources/exif-orientation-4-lol.jpg", id : "canvas4" },
+                { resource: "resources/exif-orientation-5-lu.jpg",  id : "canvas5" },
+                { resource: "resources/exif-orientation-6-ru.jpg",  id : "canvas6" },
+                { resource: "resources/exif-orientation-7-rl.jpg",  id : "canvas7" },
+                { resource: "resources/exif-orientation-8-llo.jpg", id : "canvas8" },
+            ];
+
+            var drawCount = 0;
+
+            names.forEach(function(name) {
+                var image = new Image;
+                image.src = name.resource;
+                image.decode().then(() => {
+                    let canvas = document.getElementById(name.id);
+                    canvas.width = image.width * window.devicePixelRatio;
+                    canvas.height = image.height * window.devicePixelRatio;
+                    webglDrawImage(canvas, image);
+                    if (++drawCount == names.length) {
+                        if (window.testRunner)
+                            window.testRunner.notifyDone();
+                    }
+                });
+            });
+        }
+    </script>
+</body>
diff --git a/LayoutTests/fast/images/resources/webgl-draw-image.js b/LayoutTests/fast/images/resources/webgl-draw-image.js
new file mode 100644 (file)
index 0000000..8887eed
--- /dev/null
@@ -0,0 +1,110 @@
+function webglDrawImage(canvas, image) {
+
+    var gl = canvas.getContext("webgl");
+
+    // Set the clear color.
+    gl.clearColor(0, 0, 0, 1);
+
+    // Create the vertex shader object.
+    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
+
+    // The source code for the shader is extracted from the <script> element above.
+    gl.shaderSource(vertexShader, document.getElementById("vertexShaderSource").textContent);
+
+    // Compile the shader.
+    gl.compileShader(vertexShader);
+    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
+        // We failed to compile. Output to the console and quit.
+        console.error("Vertex Shader failed to compile.");
+        console.log(gl.getShaderInfoLog(vertexShader));
+        return;
+    }
+
+    // Do the fragment shader.
+    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
+    gl.shaderSource(fragmentShader, document.getElementById("fragmentShaderSource").textContent);
+    gl.compileShader(fragmentShader);
+    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
+        console.error("Fragment Shader failed to compile.");
+        console.log(gl.getShaderInfoLog(fragmentShader));
+        return;
+    }
+
+    // Make the program.
+    var program = gl.createProgram();
+    gl.attachShader(program, vertexShader);
+    gl.attachShader(program, fragmentShader);
+    gl.linkProgram(program);
+
+    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+        console.error("Unable to link shaders into program.");
+        return;
+    }
+
+    gl.useProgram(program);
+
+    var textureUniform = gl.getUniformLocation(program, "texture");
+
+    var positionAttribute = gl.getAttribLocation(program, "a_position");
+    gl.enableVertexAttribArray(positionAttribute);
+
+    // The default coordinate space is a square from -1 to 1.
+    // The triangle is three points in that square.
+    var vertices = new Float32Array([
+        -1, -1,
+         1, -1,
+         1,  1,
+         1,  1,
+        -1,  1,
+        -1, -1
+    ]);
+
+    // Create the buffer that will hold the vertex data above.
+    var triangleBuffer = gl.createBuffer();
+
+    // Load the vertex data into the buffer.
+    gl.bindBuffer(gl.ARRAY_BUFFER, triangleBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+
+    var stillToDraw = false;
+
+    var updateTexture = function (texture, data) {
+        gl.bindTexture(gl.TEXTURE_2D, texture);
+        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
+        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.bindTexture(gl.TEXTURE_2D, null);
+        stillToDraw = true;
+    }
+
+    var texture = gl.createTexture();
+    updateTexture(texture, image);
+
+    // Clear to black.
+    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    var drawFunction = function () {
+        if (!stillToDraw) {
+            window.requestAnimationFrame(drawFunction);
+        }
+
+        // Clear to black.
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        gl.activeTexture(gl.TEXTURE0);
+        gl.bindTexture(gl.TEXTURE_2D, texture);
+        gl.uniform1i(textureUniform, 0);
+
+        // Bind the vertex attributes for the draw operation.
+        gl.bindBuffer(gl.ARRAY_BUFFER, triangleBuffer);
+        gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
+
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+    };
+
+    drawFunction();
+}
index 4eabd08..a429e64 100644 (file)
@@ -2014,6 +2014,7 @@ http/tests/webgl/ [ Skip ]
 http/tests/canvas/webgl/ [ Skip ]
 compositing/webgl/webgl-background-color.html [ Skip ]
 compositing/webgl/webgl-reflection.html [ Skip ]
+fast/images/exif-orientation-webgl-texture.html [ Skip ]
 fast/images/webgl-teximage2d.html [ Skip ]
 #[ Release ] fast/canvas/webgl [ Skip ]
 http/tests/security/webgl-remote-read-remote-image-allowed.html [ Skip ]
index 5b98a03..2331328 100644 (file)
@@ -1,3 +1,23 @@
+2019-12-16  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        WebGLRenderingContext.texImage2D() should respect EXIF orientation
+        https://bugs.webkit.org/show_bug.cgi?id=205141
+
+        Reviewed by Simon Fraser.
+
+        If image orientation is not the default, WebGLRenderingContext.texImage2D()
+        needs to draw this image into an ImageBuffer, makes a temporary Image
+        from the ImageBuffer then draw this temporary Image to the WebGL texture.
+
+        Test: fast/images/exif-orientation-webgl-texture.html
+
+        * html/canvas/WebGLRenderingContextBase.cpp:
+        (WebCore::WebGLRenderingContextBase::texSubImage2D):
+        (WebCore::WebGLRenderingContextBase::texImage2D):
+        * platform/graphics/BitmapImage.h:
+        * platform/graphics/Image.h:
+        (WebCore::Image::orientation const):
+
 2019-12-16  Ryosuke Niwa  <rniwa@webkit.org>
 
         TextManipulationController should observe newly inserted or displayed contents
index 939abb8..b21ea8a 100644 (file)
@@ -4014,7 +4014,7 @@ ExceptionOr<void> WebGLRenderingContextBase::texSubImage2D(GC3Denum target, GC3D
         if (!imageForRender)
             return { };
 
-        if (imageForRender->isSVGImage())
+        if (imageForRender->isSVGImage() || imageForRender->orientation() != ImageOrientation::None)
             imageForRender = drawImageIntoBuffer(*imageForRender, image->width(), image->height(), 1);
 
         auto texture = validateTextureBinding("texSubImage2D", target, true);
@@ -4569,7 +4569,7 @@ ExceptionOr<void> WebGLRenderingContextBase::texImage2D(GC3Denum target, GC3Dint
         if (!imageForRender)
             return { };
 
-        if (imageForRender->isSVGImage())
+        if (imageForRender->isSVGImage() || imageForRender->orientation() != ImageOrientation::None)
             imageForRender = drawImageIntoBuffer(*imageForRender, image->width(), image->height(), 1);
 
         if (!imageForRender || !validateTexFunc("texImage2D", TexImage, SourceHTMLImageElement, target, level, internalformat, imageForRender->width(), imageForRender->height(), 0, format, type, 0, 0))
index 2ad7f1c..bcf0ad2 100644 (file)
@@ -82,6 +82,7 @@ public:
 
     // FloatSize due to override.
     FloatSize size(ImageOrientation orientation = ImageOrientation::FromImage) const override { return m_source->size(orientation); }
+    ImageOrientation orientation() const override { return m_source->orientation(); }
     Color singlePixelSolidColor() const override { return m_source->singlePixelSolidColor(); }
     bool frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(size_t index, const DecodingOptions& decodingOptions) const { return m_source->frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, decodingOptions); }
     DecodingStatus frameDecodingStatusAtIndex(size_t index) const { return m_source->frameDecodingStatusAtIndex(index); }
index e304894..d7cb6e5 100644 (file)
@@ -116,6 +116,7 @@ public:
     float width() const { return size().width(); }
     float height() const { return size().height(); }
     virtual Optional<IntPoint> hotSpot() const { return WTF::nullopt; }
+    virtual ImageOrientation orientation() const { return ImageOrientation::FromImage; }
 
     WEBCORE_EXPORT EncodedDataStatus setData(RefPtr<SharedBuffer>&& data, bool allDataReceived);
     virtual EncodedDataStatus dataChanged(bool /*allDataReceived*/) { return EncodedDataStatus::Unknown; }