Bug 16954: Support putImageData
authoroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 2 Mar 2008 06:55:44 +0000 (06:55 +0000)
committeroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 2 Mar 2008 06:55:44 +0000 (06:55 +0000)
Reviewed by Sam Weinig.

Implement support for HTML5's putImageData for the CG port.  All other ports
are currently just using stubs for the final blit.

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

14 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/canvas/canvas-putImageData-expected.txt [new file with mode: 0644]
LayoutTests/fast/canvas/canvas-putImageData.html [new file with mode: 0644]
LayoutTests/fast/canvas/canvas-putImageData.js [new file with mode: 0644]
WebCore/ChangeLog
WebCore/bindings/js/JSCanvasRenderingContext2DCustom.cpp
WebCore/html/CanvasPixelArray.cpp
WebCore/html/CanvasRenderingContext2D.cpp
WebCore/html/CanvasRenderingContext2D.h
WebCore/platform/graphics/ImageBuffer.h
WebCore/platform/graphics/cairo/ImageBufferCairo.cpp
WebCore/platform/graphics/cg/ImageBufferCG.cpp
WebCore/platform/graphics/qt/ImageBufferQt.cpp
WebCore/platform/graphics/wx/ImageBufferWx.cpp

index 6bf7a351c998b52000ee8a2abaaa7d201e03ecd7..cfdf6d071b60d33f845545bc58be0f3a67399df3 100644 (file)
@@ -1,3 +1,15 @@
+2008-03-01  Oliver Hunt  <oliver@apple.com>
+
+        Reviewed by Sam Weinig.
+
+        Bug 16954: Support putImageData
+
+        Nice simple testcase for putImageData covering the exciting 
+        little bits of behaviour present in the current spec.
+
+        * fast/canvas/canvas-putImageData-expected.txt: Added.
+        * fast/canvas/canvas-putImageData.html: Added.
+
 2008-03-01  Sam Weinig  <sam@webkit.org>
 
         Reviewed by Darin Adler.
diff --git a/LayoutTests/fast/canvas/canvas-putImageData-expected.txt b/LayoutTests/fast/canvas/canvas-putImageData-expected.txt
new file mode 100644 (file)
index 0000000..e34bdd6
--- /dev/null
@@ -0,0 +1,170 @@
+
+This test ensures that putImageData works correctly, the end result should be a 100x100px green square.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS getPixel(0,0) is [0,128,0,255]
+PASS getPixel(25,25) is [0,128,0,255]
+PASS getPixel(49,0) is [0,128,0,255]
+PASS getPixel(0,49) is [0,128,0,255]
+PASS getPixel(49,49) is [0,128,0,255]
+PASS getPixel(50,0) is [0,0,0,0]
+PASS getPixel(0,50) is [0,0,0,0]
+PASS getPixel(50,50) is [0,0,0,0]
+PASS getPixel(0,50) is [0,128,0,255]
+PASS getPixel(25,75) is [0,128,0,255]
+PASS getPixel(49,50) is [0,128,0,255]
+PASS getPixel(0,99) is [0,128,0,255]
+PASS getPixel(49,99) is [0,128,0,255]
+PASS getPixel(50,50) is [0,128,0,255]
+PASS getPixel(75,75) is [0,128,0,255]
+PASS getPixel(99,99) is [0,128,0,255]
+PASS getPixel(50,49) is [0,0,0,0]
+PASS getPixel(50,0) is [0,128,0,255]
+PASS getPixel(50,5) is [0,128,0,255]
+PASS getPixel(50,15) is [0,128,0,255]
+PASS getPixel(50,25) is [0,128,0,255]
+PASS getPixel(50,35) is [0,128,0,255]
+PASS getPixel(50,45) is [0,128,0,255]
+PASS getPixel(55,0) is [0,128,0,255]
+PASS getPixel(55,5) is [0,128,0,255]
+PASS getPixel(55,15) is [0,128,0,255]
+PASS getPixel(55,25) is [0,128,0,255]
+PASS getPixel(55,35) is [0,128,0,255]
+PASS getPixel(55,45) is [0,128,0,255]
+PASS getPixel(65,0) is [0,128,0,255]
+PASS getPixel(65,5) is [0,128,0,255]
+PASS getPixel(65,15) is [0,128,0,255]
+PASS getPixel(65,25) is [0,128,0,255]
+PASS getPixel(65,35) is [0,128,0,255]
+PASS getPixel(65,45) is [0,128,0,255]
+PASS getPixel(75,0) is [0,128,0,255]
+PASS getPixel(75,5) is [0,128,0,255]
+PASS getPixel(75,15) is [0,128,0,255]
+PASS getPixel(75,25) is [0,128,0,255]
+PASS getPixel(75,35) is [0,128,0,255]
+PASS getPixel(75,45) is [0,128,0,255]
+PASS getPixel(85,0) is [0,128,0,255]
+PASS getPixel(85,5) is [0,128,0,255]
+PASS getPixel(85,15) is [0,128,0,255]
+PASS getPixel(85,25) is [0,128,0,255]
+PASS getPixel(85,35) is [0,128,0,255]
+PASS getPixel(85,45) is [0,128,0,255]
+PASS getPixel(95,0) is [0,128,0,255]
+PASS getPixel(95,5) is [0,128,0,255]
+PASS getPixel(95,15) is [0,128,0,255]
+PASS getPixel(95,25) is [0,128,0,255]
+PASS getPixel(95,35) is [0,128,0,255]
+PASS getPixel(95,45) is [0,128,0,255]
+PASS getPixel(0,25) is [0,128,0,255]
+PASS getPixel(99,25) is [0,128,0,255]
+PASS getPixel(25,0) is [0,128,0,255]
+PASS getPixel(25,99) is [0,128,0,255]
+PASS getPixel(50,0) is [0,128,0,255]
+PASS getPixel(50,5) is [0,128,0,255]
+PASS getPixel(50,15) is [0,128,0,255]
+PASS getPixel(50,25) is [0,128,0,255]
+PASS getPixel(50,35) is [0,128,0,255]
+PASS getPixel(50,45) is [0,128,0,255]
+PASS getPixel(55,0) is [0,128,0,255]
+PASS getPixel(55,5) is [0,128,0,255]
+PASS getPixel(55,15) is [0,128,0,255]
+PASS getPixel(55,25) is [0,128,0,255]
+PASS getPixel(55,35) is [0,128,0,255]
+PASS getPixel(55,45) is [0,128,0,255]
+PASS getPixel(65,0) is [0,128,0,255]
+PASS getPixel(65,5) is [0,128,0,255]
+PASS getPixel(65,15) is [0,128,0,255]
+PASS getPixel(65,25) is [0,128,0,255]
+PASS getPixel(65,35) is [0,128,0,255]
+PASS getPixel(65,45) is [0,128,0,255]
+PASS getPixel(75,0) is [0,128,0,255]
+PASS getPixel(75,5) is [0,128,0,255]
+PASS getPixel(75,15) is [0,128,0,255]
+PASS getPixel(75,25) is [0,128,0,255]
+PASS getPixel(75,35) is [0,128,0,255]
+PASS getPixel(75,45) is [0,128,0,255]
+PASS getPixel(85,0) is [0,128,0,255]
+PASS getPixel(85,5) is [0,128,0,255]
+PASS getPixel(85,15) is [0,128,0,255]
+PASS getPixel(85,25) is [0,128,0,255]
+PASS getPixel(85,35) is [0,128,0,255]
+PASS getPixel(85,45) is [0,128,0,255]
+PASS getPixel(95,0) is [0,128,0,255]
+PASS getPixel(95,5) is [0,128,0,255]
+PASS getPixel(95,15) is [0,128,0,255]
+PASS getPixel(95,25) is [0,128,0,255]
+PASS getPixel(95,35) is [0,128,0,255]
+PASS getPixel(95,45) is [0,128,0,255]
+PASS getPixel(0,25) is [0,128,0,255]
+PASS getPixel(0,50) is [0,128,0,255]
+PASS getPixel(0,75) is [0,128,0,255]
+PASS getPixel(99,25) is [0,128,0,255]
+PASS getPixel(99,50) is [0,128,0,255]
+PASS getPixel(99,75) is [0,128,0,255]
+PASS getPixel(25,0) is [0,128,0,255]
+PASS getPixel(50,0) is [0,128,0,255]
+PASS getPixel(75,0) is [0,128,0,255]
+PASS getPixel(25,99) is [0,128,0,255]
+PASS getPixel(50,99) is [0,128,0,255]
+PASS getPixel(75,99) is [0,128,0,255]
+PASS getPixel(0,25) is [0,128,0,255]
+PASS getPixel(0,50) is [0,128,0,255]
+PASS getPixel(0,75) is [0,128,0,255]
+PASS getPixel(10,25) is [0,128,0,255]
+PASS getPixel(10,50) is [0,128,0,255]
+PASS getPixel(10,75) is [0,128,0,255]
+PASS getPixel(99,25) is [0,128,0,255]
+PASS getPixel(99,50) is [0,128,0,255]
+PASS getPixel(99,75) is [0,128,0,255]
+PASS getPixel(89,25) is [0,128,0,255]
+PASS getPixel(89,50) is [0,128,0,255]
+PASS getPixel(89,75) is [0,128,0,255]
+PASS getPixel(25,0) is [0,128,0,255]
+PASS getPixel(50,0) is [0,128,0,255]
+PASS getPixel(75,0) is [0,128,0,255]
+PASS getPixel(25,10) is [0,128,0,255]
+PASS getPixel(50,10) is [0,128,0,255]
+PASS getPixel(75,10) is [0,128,0,255]
+PASS getPixel(25,99) is [0,128,0,255]
+PASS getPixel(50,99) is [0,128,0,255]
+PASS getPixel(75,99) is [0,128,0,255]
+PASS getPixel(25,89) is [0,128,0,255]
+PASS getPixel(50,89) is [0,128,0,255]
+PASS getPixel(75,89) is [0,128,0,255]
+PASS getPixel(11,11) is [0,128,0,255]
+PASS getPixel(1,1) is [0,128,0,255]
+PASS getPixel(1,1) is [0,128,0,255]
+PASS getPixel(10,10) is [0,128,0,255]
+PASS getPixel(1,1) is [0,128,0,255]
+PASS getPixel(10,10) is [0,128,0,255]
+PASS getPixel(1,1) is [0,128,0,255]
+PASS getPixel(9,9) is [0,128,0,255]
+PASS getPixel(1,1) is [0,128,0,255]
+PASS getPixel(9,9) is [0,128,0,255]
+PASS getPixel(1,1) is [0,128,0,255]
+PASS getPixel(9,9) is [0,128,0,255]
+PASS context.putImageData({}, 0, 0) threw exception Error: TYPE_MISMATCH_ERR: DOM Exception 17.
+PASS context.putImageData(buffer, NaN, 0, 0, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, NaN, 0, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, NaN, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, NaN, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, 0, NaN, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, 0, 0, NaN) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, Infinity, 0, 0, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, Infinity, 0, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, Infinity, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, Infinity, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, 0, Infinity, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, 0, 0, Infinity) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, undefined, 0, 0, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, undefined, 0, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, undefined, 0, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, undefined, 0, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, 0, undefined, 0) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS context.putImageData(buffer, 0, 0, 0, 0, 0, undefined) threw exception Error: INDEX_SIZE_ERR: DOM Exception 1.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/canvas/canvas-putImageData.html b/LayoutTests/fast/canvas/canvas-putImageData.html
new file mode 100644 (file)
index 0000000..e36e9b2
--- /dev/null
@@ -0,0 +1,7 @@
+<link rel="stylesheet" href="../js/resources/js-test-style.css">
+<script src="../js/resources/js-test-pre.js"></script>
+<canvas id="canvas" width="100" height="100"></canvas><br/>
+<p id="description"></p>
+<div id="console"></div>
+<script src="canvas-putImageData.js"></script>
+<script src="../js/resources/js-test-post.js"></script>
diff --git a/LayoutTests/fast/canvas/canvas-putImageData.js b/LayoutTests/fast/canvas/canvas-putImageData.js
new file mode 100644 (file)
index 0000000..c56f4bc
--- /dev/null
@@ -0,0 +1,224 @@
+description("This test ensures that putImageData works correctly, the end result should be a 100x100px green square.");
+
+function fillRect(imageData, x, y, width, height, r, g, b, a)
+{
+    var bytesPerRow = imageData.width * 4;
+    var data =imageData.data;
+    for (var i = 0; i < height; i++) {
+        var rowOrigin = (y+i) * bytesPerRow;
+        rowOrigin += x * 4;
+        for (var j = 0; j < width; j++) {
+            var position = rowOrigin + j * 4;
+            data[position + 0] = r;
+            data[position + 1] = g;
+            data[position + 2] = b;
+            data[position + 3] = a;
+        }
+    }
+}
+
+function dataToArray(data) {
+    var result = new Array(data.length)
+    for (var i = 0; i < data.length; i++)
+        result[i] = data[i];
+    return result;
+}
+
+function getPixel(x, y) {
+    var data = context.getImageData(x,y,1,1);
+    if (!data) // getImageData failed, which should never happen
+        return [-1,-1,-1,-1];
+    return dataToArray(data.data);
+}
+
+function pixelShouldBe(x, y, colour) {
+    shouldBe("getPixel(" + [x, y] +")", "["+colour+"]");
+}
+
+var canvas = document.getElementById("canvas");
+var context = canvas.getContext("2d");
+
+if (!context.createImageData)
+    context.createImageData = function(w,h) { 
+        var data = this.getImageData(0, 0, w, h);
+        for (var i = 0; i < data.data.length; i++)
+            data.data[i] = 0;
+    }
+var buffer = context.createImageData(100,100);
+// Fill top left corner
+fillRect(buffer, 0, 0, 50, 50, 0, 128,0,255);
+context.putImageData(buffer, 0, 0);
+pixelShouldBe( 0,  0, [0, 128,0,255]);
+pixelShouldBe(25, 25, [0, 128,0,255]);
+pixelShouldBe(49,  0, [0, 128,0,255]);
+pixelShouldBe( 0, 49, [0, 128,0,255]);
+pixelShouldBe(49, 49, [0, 128,0,255]);
+pixelShouldBe(50,  0, [0, 0, 0, 0]);
+pixelShouldBe( 0, 50, [0, 0, 0, 0]);
+pixelShouldBe(50, 50, [0, 0, 0, 0]);
+
+// Test positioned drawing -- make bottom right green
+context.putImageData(buffer, 0, 50);
+pixelShouldBe( 0, 50, [0, 128,0,255]);
+pixelShouldBe(25, 75, [0, 128,0,255]);
+pixelShouldBe(49, 50, [0, 128,0,255]);
+pixelShouldBe( 0, 99, [0, 128,0,255]);
+pixelShouldBe(49, 99, [0, 128,0,255]);
+
+// Test translation doesn't effect putImageData
+context.translate(50, -50);
+context.putImageData(buffer, 50, 50);
+pixelShouldBe(50, 50, [0, 128,0,255]);
+pixelShouldBe(75, 75, [0, 128,0,255]);
+pixelShouldBe(99, 99, [0, 128,0,255]);
+pixelShouldBe(50, 49, [0, 0, 0, 0]);
+context.translate(-50, 50);
+
+// Test dirty rect handling
+buffer = context.createImageData(50,50);
+fillRect(buffer, 0, 0, 50, 50, 0, 128, 0, 255);
+context.putImageData(buffer, 50, 0);
+fillRect(buffer, 0, 0, 50, 50, 255, 0, 0, 255);
+context.putImageData(buffer, 50, 0, 15, 15, 20, 20);
+context.fillStyle="rgb(0,128,0)";
+context.fillRect(65, 15, 20, 20);
+var points = [0, 5, 15, 25, 35, 45];
+for (var x = 0; x < points.length; x++)
+    for (var y = 0; y < points.length; y++)
+        pixelShouldBe(points[x] + 50, points[y], [0, 128, 0, 255]);
+
+// Test drawing outside the canvas border
+fillRect(buffer, 0, 0, 50, 50, 255, 0, 0, 255);
+context.putImageData(buffer, -50, 0);
+pixelShouldBe(0, 25, [0, 128,0,255]);
+context.putImageData(buffer, 100, 0);
+pixelShouldBe(99, 25, [0, 128,0,255]);
+context.putImageData(buffer, 0, -50);
+pixelShouldBe(25, 0, [0, 128,0,255]);
+context.putImageData(buffer, 0, 100);
+pixelShouldBe(25, 99, [0, 128,0,255]);
+
+// test drawing with non-intersecting dirty rect
+context.putImageData(buffer, 50, 0,  50, 0, 100, 100);
+context.putImageData(buffer, 50, 0, -50, 0, 50, 100);
+context.putImageData(buffer, 50, 0,  0, 50, 100, 100);
+context.putImageData(buffer, 50, 0,  50, -50, 100, 100);
+for (var x = 0; x < points.length; x++)
+    for (var y = 0; y < points.length; y++)
+        pixelShouldBe(points[x] + 50, points[y], [0, 128, 0, 255]);
+
+// Test drawing to region intersect edge of canvas
+buffer = context.createImageData(100, 100);
+fillRect(buffer, 0, 0, 100, 100, 0, 128, 0, 255);
+fillRect(buffer, 10, 10, 80, 80, 255, 0, 0, 255);
+
+//left edge
+context.putImageData(buffer, -90, 0);
+pixelShouldBe(0, 25, [0, 128,0,255]);
+pixelShouldBe(0, 50, [0, 128,0,255]);
+pixelShouldBe(0, 75, [0, 128,0,255]);
+//right edge
+context.putImageData(buffer, 90, 0);
+pixelShouldBe(99, 25, [0, 128,0,255]);
+pixelShouldBe(99, 50, [0, 128,0,255]);
+pixelShouldBe(99, 75, [0, 128,0,255]);
+//top edge
+context.putImageData(buffer, 0, -90);
+pixelShouldBe(25, 0, [0, 128,0,255]);
+pixelShouldBe(50, 0, [0, 128,0,255]);
+pixelShouldBe(75, 0, [0, 128,0,255]);
+//bottom edge
+context.putImageData(buffer, 0, 90);
+pixelShouldBe(25, 99, [0, 128,0,255]);
+pixelShouldBe(50, 99, [0, 128,0,255]);
+pixelShouldBe(75, 99, [0, 128,0,255]);
+
+// Test drawing with only part of the dirty region intersecting the window
+// left edge
+context.putImageData(buffer, 0, 0, -90, 0, 100, 100);
+pixelShouldBe(0, 25, [0, 128,0,255]);
+pixelShouldBe(0, 50, [0, 128,0,255]);
+pixelShouldBe(0, 75, [0, 128,0,255]);
+pixelShouldBe(10, 25, [0, 128,0,255]);
+pixelShouldBe(10, 50, [0, 128,0,255]);
+pixelShouldBe(10, 75, [0, 128,0,255]);
+//right edge
+context.putImageData(buffer, 0, 0, 90, 0, 100, 100);
+pixelShouldBe(99, 25, [0, 128,0,255]);
+pixelShouldBe(99, 50, [0, 128,0,255]);
+pixelShouldBe(99, 75, [0, 128,0,255]);
+pixelShouldBe(89, 25, [0, 128,0,255]);
+pixelShouldBe(89, 50, [0, 128,0,255]);
+pixelShouldBe(89, 75, [0, 128,0,255]);
+// top edge
+context.putImageData(buffer, 0, 0, 0, -90, 100, 100);
+pixelShouldBe(25, 0, [0, 128,0,255]);
+pixelShouldBe(50, 0, [0, 128,0,255]);
+pixelShouldBe(75, 0, [0, 128,0,255]);
+pixelShouldBe(25, 10, [0, 128,0,255]);
+pixelShouldBe(50, 10, [0, 128,0,255]);
+pixelShouldBe(75, 10, [0, 128,0,255]);
+//bottom edge
+context.putImageData(buffer, 0, 0, 0, 90, 100, 100);
+pixelShouldBe(25, 99, [0, 128,0,255]);
+pixelShouldBe(50, 99, [0, 128,0,255]);
+pixelShouldBe(75, 99, [0, 128,0,255]);
+pixelShouldBe(25, 89, [0, 128,0,255]);
+pixelShouldBe(50, 89, [0, 128,0,255]);
+pixelShouldBe(75, 89, [0, 128,0,255]);
+
+// Test clamping of dx/dy
+var smallbuffer = context.createImageData(10, 10);
+fillRect(smallbuffer, 0, 0, 10, 10, 255, 0, 0, 255);
+context.putImageData(smallbuffer, 1.5, 1);
+pixelShouldBe(11, 11, [0, 128,0,255]);
+fillRect(smallbuffer, 0, 0, 10, 10, 0, 128, 0, 255);
+context.putImageData(smallbuffer, 1.5, 1);
+pixelShouldBe(1, 1, [0, 128,0,255]);
+
+// test clamping of dirtyX/Y/Width/Height
+fillRect(smallbuffer, 0, 0, 10, 10, 0, 128, 0, 255);
+context.fillStyle = "red";
+context.fillRect(1, 1, 9, 9);
+context.putImageData(smallbuffer, 1, 1, 0.5, 0.5, 8.5, 8.5);
+pixelShouldBe(1, 1, [0, 128,0,255]);
+pixelShouldBe(10, 10, [0, 128,0,255]);
+context.fillRect(1, 1, 9, 9);
+context.putImageData(smallbuffer, 1, 1, 0.25, 0.25, 9, 9);
+pixelShouldBe(1, 1, [0, 128,0,255]);
+pixelShouldBe(10, 10, [0, 128,0,255]);
+context.fillRect(1, 1, 8, 8);
+context.putImageData(smallbuffer, 1, 1, 0.0, 0.0, 8.5, 8.5);
+pixelShouldBe(1, 1, [0, 128,0,255]);
+pixelShouldBe(9, 9, [0, 128,0,255]);
+context.fillRect(1, 1, 8, 8);
+context.putImageData(smallbuffer, 1, 1, 0.0, 0.0, 8.25, 8.25);
+pixelShouldBe(1, 1, [0, 128,0,255]);
+pixelShouldBe(9, 9, [0, 128,0,255]);
+context.fillRect(1, 1, 7, 7);
+context.putImageData(smallbuffer, 1, 1, 0.5, 0.5, 7.9, 7.9);
+pixelShouldBe(1, 1, [0, 128,0,255]);
+pixelShouldBe(9, 9, [0, 128,0,255]);
+
+
+shouldThrow("context.putImageData({}, 0, 0)", "'Error: TYPE_MISMATCH_ERR: DOM Exception 17'");
+shouldThrow("context.putImageData(buffer, NaN, 0, 0, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, NaN, 0, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, NaN, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, NaN, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, 0, NaN, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, 0, 0, NaN)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, Infinity, 0, 0, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, Infinity, 0, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, Infinity, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, Infinity, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, 0, Infinity, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, 0, 0, Infinity)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, undefined, 0, 0, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, undefined, 0, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, undefined, 0, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, undefined, 0, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, 0, undefined, 0)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+shouldThrow("context.putImageData(buffer, 0, 0, 0, 0, 0, undefined)", "'Error: INDEX_SIZE_ERR: DOM Exception 1'");
+
+var successfullyParsed = true;
index ad89d9a8491f140057309ca1c72391766d937aef..56ba9f36e3867f947110710716a31dbb60a6751f 100644 (file)
@@ -1,3 +1,26 @@
+2008-03-01  Oliver Hunt  <oliver@apple.com>
+
+        Reviewed by Sam Weinig.
+
+        Bug 16954: Support putImageData
+        
+        Implement support for HTML5's putImageData for the CG port.  All other ports
+        are currently just using stubs for the final blit.
+
+        Test: fast/canvas/canvas-putImageData.html
+
+        * bindings/js/JSCanvasRenderingContext2DCustom.cpp:
+        (WebCore::JSCanvasRenderingContext2D::putImageData):
+        * html/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::putImageData):
+        * html/CanvasRenderingContext2D.h:
+        * platform/graphics/ImageBuffer.h:
+        * platform/graphics/cairo/ImageBufferCairo.cpp:
+        * platform/graphics/cg/ImageBufferCG.cpp:
+        (WebCore::ImageBuffer::putImageData):
+        * platform/graphics/qt/ImageBufferQt.cpp:
+        * platform/graphics/wx/ImageBufferWx.cpp:
+
 2008-03-01  Jon Honeycutt  <jhoneycutt@apple.com>
 
         Reviewed by Darin.
index 4a6390f81f9d19338a96adeda022c9451e82c3b8..c5256d9977d0afaf54a33c43232ff7043ed6b932 100644 (file)
 #include "FloatRect.h"
 #include "HTMLCanvasElement.h"
 #include "HTMLImageElement.h"
+#include "ImageData.h"
 #include "JSCanvasGradient.h"
 #include "JSCanvasPattern.h"
 #include "JSHTMLCanvasElement.h"
 #include "JSHTMLImageElement.h"
+#include "JSImageData.h"
 #include "kjs_html.h"
 
 using namespace KJS;
@@ -335,6 +337,19 @@ JSValue* JSCanvasRenderingContext2D::createPattern(ExecState* exec, const List&
 
 JSValue* JSCanvasRenderingContext2D::putImageData(ExecState* exec, const List& args)
 {
+    // putImageData has two variants
+    // putImageData(ImageData, x, y)
+    // putImageData(ImageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
+    CanvasRenderingContext2D* context = impl();
+
+    ExceptionCode ec = 0;
+    if (args.size() >= 7)
+        context->putImageData(toImageData(args[0]), args[1]->toFloat(exec), args[2]->toFloat(exec), 
+                              args[3]->toFloat(exec), args[4]->toFloat(exec), args[5]->toFloat(exec), args[6]->toFloat(exec), ec);
+    else
+        context->putImageData(toImageData(args[0]), args[1]->toFloat(exec), args[2]->toFloat(exec), ec);
+
+    setDOMException(exec, ec);
     return jsUndefined();
 }
 
index 66aae3dbc924ee1f6624168dfda917ef763b63cd..82bc27b2e6e03255dd0a5d9819e9b423fac5d39f 100644 (file)
@@ -39,6 +39,7 @@ PassRefPtr<CanvasPixelArray> CanvasPixelArray::create(unsigned size)
 CanvasPixelArray::CanvasPixelArray(unsigned size)
     : m_data(size)
 {
+    ASSERT((reinterpret_cast<size_t>(m_data.data()) & 3) == 0);
 }
 
 }
index dab38793fe4949b8fd1e44ed5bb07b5075e0c7a2..26509acbff0f7aeb28cc4c197d0757b1b3aefbd9 100644 (file)
@@ -1202,4 +1202,55 @@ PassRefPtr<ImageData> CanvasRenderingContext2D::getImageData(float sx, float sy,
     return buffer->getImageData(scaledRect);
 }
 
+void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, ExceptionCode& ec)
+{
+    if (!data) {
+        ec = TYPE_MISMATCH_ERR;
+        return;
+    }
+    putImageData(data, dx, dy, 0, 0, data->width(), data->height(), ec);
+}
+
+void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, float dirtyX, float dirtyY, 
+                                            float dirtyWidth, float dirtyHeight, ExceptionCode& ec)
+{
+    if (!data) {
+        ec = TYPE_MISMATCH_ERR;
+        return;
+    }
+    if (!isfinite(dx) || !isfinite(dy) || !isfinite(dirtyX) || 
+        !isfinite(dirtyY) || !isfinite(dirtyWidth) || !isfinite(dirtyHeight)) {
+        ec = INDEX_SIZE_ERR;
+        return;
+    }
+
+    ImageBuffer* buffer = m_canvas ? m_canvas->buffer() : 0;
+    if (!buffer)
+        return;
+
+    if (dirtyWidth < 0) {
+        dirtyX += dirtyWidth;
+        dirtyWidth = -dirtyWidth;
+    }
+
+    if (dirtyHeight < 0) {
+        dirtyY += dirtyHeight;
+        dirtyHeight = -dirtyHeight;
+    }
+
+    FloatRect clipRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
+    clipRect.intersect(IntRect(0, 0, data->width(), data->height()));
+    IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy));
+    IntRect sourceRect = enclosingIntRect(clipRect);
+    sourceRect.move(destOffset);
+    sourceRect.intersect(IntRect(IntPoint(), buffer->size()));
+    if (sourceRect.isEmpty())
+        return;
+    willDraw(sourceRect);
+    sourceRect.move(-destOffset);
+    IntPoint destPoint(destOffset.width(), destOffset.height());
+    
+    buffer->putImageData(data, sourceRect, destPoint);
+}
+
 } // namespace WebCore
index f4cc93819d1138df04668089efc0dc25dde364d7..5f79d53110603592f5c0843d580157fca3fa4890 100644 (file)
@@ -166,6 +166,8 @@ namespace WebCore {
         
         PassRefPtr<ImageData> createImageData(float width, float height) const;
         PassRefPtr<ImageData> getImageData(float sx, float sy, float sw, float sh) const;
+        void putImageData(ImageData*, float dx, float dy, ExceptionCode&);
+        void putImageData(ImageData*, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight, ExceptionCode&);
         
         void reset();
         void detachCanvas() { m_canvas = 0; }
index 869cd712ee71bc4c0d016e94a96e72522c2d6c9e..2ac2b4e7b1def0405922812262f60d889aa2088a 100644 (file)
@@ -49,6 +49,7 @@ namespace WebCore {
 
     class GraphicsContext;
     class ImageData;
+    class IntPoint;
     class IntRect;
     class RenderObject;
 
@@ -67,8 +68,10 @@ namespace WebCore {
 #elif PLATFORM(CAIRO)
         cairo_surface_t* surface() const;
 #endif
-
+        
         PassRefPtr<ImageData> getImageData(const IntRect& rect) const;
+        void putImageData(ImageData* source, const IntRect& sourceRect, const IntPoint& destPoint);
+
     private:
         void* m_data;
         IntSize m_size;
index d8d6ad017b422d2ad5e9026f12a36472563496f1..392e9dfd9f464e563741448cd6ff556367090f7c 100644 (file)
@@ -80,4 +80,9 @@ PassRefPtr<ImageData> ImageBuffer::getImageData(const IntRect&) const
     return 0;
 }
 
+void ImageBuffer::putImageData(ImageData*, const IntRect&, const IntPoint&)
+{
+    notImplemented();
+}
+
 }
index f456ec574d22c036cef2311c830ec2768ed6f26a..2e48ceb3a8b0f8e2e1b4095d5f35fa43f6536a6b 100644 (file)
@@ -75,6 +75,7 @@ ImageBuffer::ImageBuffer(void* imageData, const IntSize& size, auto_ptr<Graphics
     , m_context(context.release())
     , m_cgImage(0)
 {
+    ASSERT((reinterpret_cast<size_t>(imageData) & 2) == 0);
 }
 
 ImageBuffer::~ImageBuffer()
@@ -136,7 +137,7 @@ PassRefPtr<ImageData> ImageBuffer::getImageData(const IntRect& rect) const
     unsigned srcBytesPerRow = 4 * m_size.width();
     unsigned destBytesPerRow = 4 * rect.width();
     
-    // -originy to handle the accursed flipped y axis
+    // m_size.height() - originy to handle the accursed flipped y axis in CG backing store
     unsigned char* srcRows = reinterpret_cast<unsigned char*>(m_data) + (m_size.height() - originy - 1) * srcBytesPerRow + originx * 4;
     unsigned char* destRows = data + desty * destBytesPerRow + destx * 4;
     for (int y = 0; y < numRows; ++y) {
@@ -156,4 +157,56 @@ PassRefPtr<ImageData> ImageBuffer::getImageData(const IntRect& rect) const
     return result;
 }
 
+void ImageBuffer::putImageData(ImageData* source, const IntRect& sourceRect, const IntPoint& destPoint)
+{
+    ASSERT(sourceRect.width() > 0);
+    ASSERT(sourceRect.height() > 0);
+
+    int originx = sourceRect.x();
+    int destx = destPoint.x() + sourceRect.x();
+    ASSERT(destx >= 0);
+    ASSERT(destx < m_size.width());
+    ASSERT(originx >= 0);
+    ASSERT(originx <= sourceRect.right());
+
+    int endx = destPoint.x() + sourceRect.right();
+    ASSERT(endx <= m_size.width());
+
+    int numColumns = endx - destx;
+    
+    int originy = sourceRect.y();
+    int desty = destPoint.y() + sourceRect.y();
+    ASSERT(desty >= 0);
+    ASSERT(desty < m_size.height());
+    ASSERT(originy >= 0);
+    ASSERT(originy <= sourceRect.bottom());
+
+    int endy = destPoint.y() + sourceRect.bottom();
+    ASSERT(endx <= m_size.height());
+    int numRows = endy - desty;
+
+    unsigned srcBytesPerRow = 4 * source->width();
+    unsigned destBytesPerRow = 4 * m_size.width();
+    
+    unsigned char* srcRows = source->data()->data().data() + originy * srcBytesPerRow + originx * 4;
+
+    // -desty to handle the accursed flipped y axis
+    unsigned char* destRows = reinterpret_cast<unsigned char*>(m_data) + (m_size.height() - desty - 1) * destBytesPerRow + destx * 4;
+    for (int y = 0; y < numRows; ++y) {
+        for (int x = 0; x < numColumns; x++) {
+            unsigned char alpha = srcRows[x * 4 + 3];
+            if (alpha != 255) {
+                destRows[x * 4 + 0] = (srcRows[0] * alpha) / 255;
+                destRows[x * 4 + 1] = (srcRows[1] * alpha) / 255;
+                destRows[x * 4 + 2] = (srcRows[2] * alpha) / 255;
+                destRows[x * 4 + 3] = alpha;
+            } else {
+                reinterpret_cast<uint32_t*>(destRows + x * 4)[0] = reinterpret_cast<uint32_t*>(srcRows + x * 4)[0];
+            }
+        }
+        destRows -= destBytesPerRow;
+        srcRows += srcBytesPerRow;
+    }
+}
+
 }
index 217242f4a4edc328a9d07ff30f8f4826d3f7836f..449677ef1a76324e5a7786d24a851d7f1c5997cd 100644 (file)
@@ -77,4 +77,9 @@ PassRefPtr<ImageData> ImageBuffer::getImageData(const IntRect&) const
     return 0;
 }
 
+void ImageBuffer::putImageData(ImageData*, const IntRect&, const IntPoint&)
+{
+    notImplemented();
+}
+
 }
index 3e96f1dee5d136d89b34bbc5696c24ba7c6df4c9..0c832ea973a6d3ce8eb9a4d2276206b754b1d165 100644 (file)
@@ -52,4 +52,9 @@ PassRefPtr<ImageData> ImageBuffer::getImageData(const IntRect&) const
     return 0;
 }
 
+void ImageBuffer::putImageData(ImageData*, const IntRect&, const IntPoint&)
+{
+    notImplemented();
+}
+
 }