| import QtQuick 2.0 |
| |
| Item { |
| id: container |
| width: 200 |
| height: 200 |
| |
| CanvasTestCase { |
| id:testCase |
| name: "canvas" |
| function init_data() { return testData("2d"); } |
| |
| function test_canvasSize(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| verify(ctx); |
| |
| tryCompare(c, "availableChangedCount", 1); |
| //by default canvasSize is same with canvas' actual size |
| // when canvas size changes, canvasSize should be changed as well. |
| compare(c.canvasSize.width, c.width); |
| compare(c.canvasSize.height, c.height); |
| c.width = 20; |
| compare(c.canvasSize.width, 20); |
| compare(c.canvasSizeChangedCount, 1); |
| c.height = 5; |
| compare(c.canvasSizeChangedCount, 2); |
| compare(c.canvasSize.height, 5); |
| |
| //change canvasSize manually, then canvasSize detaches from canvas |
| //actual size. |
| c.canvasSize.width = 100; |
| compare(c.canvasSizeChangedCount, 3); |
| compare(c.canvasSize.width, 100); |
| compare(c.width, 20); |
| c.canvasSize.height = 50; |
| compare(c.canvasSizeChangedCount, 4); |
| compare(c.canvasSize.height, 50); |
| compare(c.height, 5); |
| |
| c.width = 10; |
| compare(c.canvasSizeChangedCount, 4); |
| compare(c.canvasSize.width, 100); |
| compare(c.canvasSize.height, 50); |
| |
| c.height = 10; |
| |
| compare(c.canvasSizeChangedCount, 4); |
| compare(c.canvasSize.width, 100); |
| compare(c.canvasSize.height, 50); |
| c.destroy(); |
| } |
| function test_tileSize(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| verify(ctx); |
| tryCompare(c, "availableChangedCount", 1); |
| |
| compare(c.tileSize.width, c.width); |
| compare(c.tileSize.height, c.height); |
| c.width = 20; |
| compare(c.tileSize.width, 20); |
| compare(c.tileSizeChangedCount, 1); |
| c.height = 5; |
| compare(c.tileSizeChangedCount, 2); |
| compare(c.tileSize.height, 5); |
| |
| c.tileSize.width = 100; |
| compare(c.tileSizeChangedCount, 3); |
| compare(c.tileSize.width, 100); |
| compare(c.width, 20); |
| c.tileSize.height = 50; |
| compare(c.tileSizeChangedCount, 4); |
| compare(c.tileSize.height, 50); |
| compare(c.height, 5); |
| |
| c.width = 10; |
| compare(c.tileSizeChangedCount, 4); |
| compare(c.tileSize.width, 100); |
| compare(c.tileSize.height, 50); |
| |
| c.height = 10; |
| compare(c.tileSizeChangedCount, 4); |
| compare(c.tileSize.width, 100); |
| compare(c.tileSize.height, 50); |
| c.destroy(); |
| |
| } |
| |
| function test_canvasWindow(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| verify(ctx); |
| |
| tryCompare(c, "availableChangedCount", 1); |
| compare(c.canvasWindow.x, 0); |
| compare(c.canvasWindow.y, 0); |
| compare(c.canvasWindow.width, c.width); |
| compare(c.canvasWindow.height, c.height); |
| |
| c.width = 20; |
| compare(c.canvasWindow.width, 20); |
| compare(c.canvasWindowChangedCount, 1); |
| c.height = 5; |
| compare(c.canvasWindowChangedCount, 2); |
| compare(c.canvasWindow.height, 5); |
| |
| c.canvasWindow.x = 5; |
| c.canvasWindow.y = 6; |
| c.canvasWindow.width = 10; |
| c.canvasWindow.height =20; |
| compare(c.canvasWindowChangedCount, 6); |
| compare(c.canvasWindow.width, 10); |
| compare(c.canvasWindow.height, 20); |
| compare(c.canvasWindow.x, 5); |
| compare(c.canvasWindow.y, 6); |
| c.destroy(); |
| |
| } |
| |
| function test_save(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| |
| tryCompare(c, "availableChangedCount", 1); |
| |
| c.requestPaint(); |
| var imagePath = applicationDirPath + "/c.png"; |
| verify(c.save(imagePath)); |
| c.loadImage(imagePath); |
| tryVerify(function() { return c.isImageLoaded(imagePath) }) |
| verify(!c.isImageLoading(imagePath)); |
| verify(!c.isImageError(imagePath)); |
| c.destroy(); |
| } |
| |
| function test_toDataURL(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| verify(ctx); |
| |
| tryCompare(c, "availableChangedCount", 1); |
| |
| var imageTypes = [ |
| {mimeType:"image/png"}, |
| {mimeType:"image/bmp"}, |
| {mimeType:"image/jpeg"}, |
| {mimeType:"image/x-portable-pixmap"}, |
| {mimeType:"image/xpm"}, |
| ]; |
| if (hasImageFormats) |
| imageTypes.push({ mimeType: "image/tiff" }); |
| |
| for (var i = 0; i < imageTypes.length; i++) { |
| ctx.fillStyle = "red"; |
| ctx.fillRect(0, 0, c.width, c.height); |
| |
| var dataUrl = c.toDataURL(); |
| verify(dataUrl !== "data:,"); |
| dataUrl = c.toDataURL("image/invalid"); |
| verify(dataUrl === "data:,"); |
| |
| dataUrl = c.toDataURL(imageTypes[i].mimeType); |
| verify(dataUrl !== "data:,"); |
| |
| ctx.save(); |
| ctx.fillStyle = "blue"; |
| ctx.fillRect(0, 0, c.width, c.height); |
| ctx.restore(); |
| |
| var dataUrl2 = c.toDataURL(imageTypes[i].mimeType); |
| verify (dataUrl2 !== "data:,"); |
| verify (dataUrl2 !== dataUrl); |
| } |
| c.destroy(); |
| |
| } |
| function test_paint(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| tryCompare(c, "availableChangedCount", 1); |
| //scene graph could be available immediately |
| //in this case, we force waiting a short while until the init paint finished |
| tryCompare(c, "paintedCount", 0); |
| ctx.fillRect(0, 0, c.width, c.height); |
| c.toDataURL(); |
| tryCompare(c, "paintedCount", 1); |
| tryCompare(c, "paintCount", 1); |
| // implicit repaint when visible and resized |
| testCase.visible = true; |
| c.width += 1; |
| c.height += 1; |
| tryCompare(c, "paintCount", 2); |
| tryCompare(c, "paintedCount", 1); |
| // allow explicit repaint even when hidden |
| testCase.visible = false; |
| c.requestPaint(); |
| tryCompare(c, "paintCount", 3); |
| tryCompare(c, "paintedCount", 1); |
| // no implicit repaint when resized but hidden |
| c.width += 1; |
| c.height += 1; |
| waitForRendering(c); |
| compare(c.paintCount, 3); |
| tryCompare(c, "paintedCount", 1); |
| c.destroy(); |
| } |
| function test_loadImage(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| verify(ctx); |
| |
| tryCompare(c, "availableChangedCount", 1); |
| |
| verify(!c.isImageLoaded("red.png")); |
| c.loadImage("red.png"); |
| tryVerify(function() { return c.isImageLoaded("red.png") }); |
| verify(!c.isImageLoading("red.png")); |
| verify(!c.isImageError("red.png")); |
| |
| c.unloadImage("red.png"); |
| verify(!c.isImageLoaded("red.png")); |
| verify(!c.isImageLoading("red.png")); |
| verify(!c.isImageError("red.png")); |
| c.destroy(); |
| |
| } |
| |
| function test_getContext(row) { |
| var c = createCanvasObject(row); |
| verify(c); |
| var ctx = c.getContext("2d"); |
| verify(ctx); |
| tryCompare(c, "availableChangedCount", 1); |
| |
| compare(ctx.canvas, c); |
| ctx = c.getContext('2d'); |
| verify(ctx); |
| compare(ctx.canvas, c); |
| ctx = c.getContext('2D'); |
| verify(ctx); |
| compare(ctx.canvas, c); |
| ignoreWarning(Qt.resolvedUrl("CanvasComponent.qml") + ":6:9: QML Canvas: Canvas already initialized with a different context type"); |
| ctx = c.getContext('invalid'); |
| verify(!ctx); |
| c.destroy(); |
| |
| } |
| |
| Image { |
| id: image |
| source: "anim-gr.png" |
| } |
| |
| /* |
| Ensures that extra arguments to functions are ignored, |
| by checking that drawing, clearing, etc. still occurs. |
| */ |
| function test_extraArgumentsIgnored_data() { |
| var extra = 0; |
| return [ |
| { |
| tag: "arc", |
| test: function(ctx) { |
| ctx.arc(10, 10, 5, 0, Math.PI * 2, true, extra); |
| ctx.fill(); |
| comparePixel(ctx, 10, 10, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "arcTo", |
| test: function(ctx) { |
| ctx.translate(-50, -25); |
| ctx.moveTo(20,20); |
| ctx.arcTo(150, 20, 100, 70, 50, extra); |
| ctx.fill(); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "bezierCurveTo", |
| test: function(ctx) { |
| ctx.beginPath(); |
| ctx.moveTo(-20, -20); |
| ctx.bezierCurveTo(20, 100, 100, 100, 100, 20, extra); |
| ctx.fill(); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "clearRect", |
| test: function(ctx) { |
| ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| ctx.clearRect(0, 0, 10, 10, extra); |
| comparePixel(ctx, 0, 0, 0, 0, 0, 0); |
| } |
| }, |
| { |
| tag: "createConicalGradient", |
| test: function(ctx) { |
| verify(ctx.createConicalGradient(0, 0, 0, extra) !== ctx); |
| } |
| }, |
| { |
| tag: "createLinearGradient", |
| test: function(ctx) { |
| verify(ctx.createLinearGradient(0, 0, 10, 10, extra) !== ctx); |
| } |
| }, |
| { |
| tag: "createRadialGradient", |
| test: function(ctx) { |
| verify(ctx.createRadialGradient(0, 0, 10, 20, 20, 10, extra) !== ctx); |
| } |
| }, |
| { |
| tag: "createPattern-image", |
| test: function(ctx) { |
| verify(ctx.createPattern(image, "repeat", extra) !== undefined); |
| } |
| }, |
| { |
| tag: "createPattern-color", |
| test: function(ctx) { |
| verify(ctx.createPattern("red", Qt.SolidPattern, extra) !== undefined); |
| } |
| }, |
| { |
| tag: "drawImage-9-args", |
| test: function(ctx) { |
| ctx.drawImage(image, 0, 0, image.sourceSize.width, image.sourceSize.height, |
| 0, 0, image.sourceSize.width, image.sourceSize.height, extra); |
| comparePixel(ctx, 10, 10, 0, 255, 0, 255); |
| } |
| }, |
| { |
| tag: "drawImage-5-args", |
| test: function(ctx) { |
| ctx.drawImage(image, 0, 0, image.sourceSize.width, image.sourceSize.height, extra); |
| comparePixel(ctx, 10, 10, 0, 255, 0, 255); |
| } |
| }, |
| { |
| tag: "drawImage-3-args", |
| test: function(ctx) { |
| ctx.drawImage(image, 0, 0, extra); |
| comparePixel(ctx, 10, 10, 0, 255, 0, 255); |
| } |
| }, |
| { |
| tag: "ellipse", |
| test: function(ctx) { |
| ctx.ellipse(0, 0, 10, 10); |
| ctx.fill(); |
| comparePixel(ctx, 5, 5, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "fillRect", |
| test: function(ctx) { |
| ctx.fillRect(0, 0, 10, 10, extra); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255, 200); |
| } |
| }, |
| { |
| tag: "fillText", |
| test: function(ctx) { |
| ctx.font = "100px sans-serif"; |
| ctx.fillText("Hello", -10, 10, extra); |
| for (var x = 0; x < 100; ++x) { |
| var c = ctx.getImageData(x,0,1,1).data; |
| if (c[0] === 255 && c[1] === 0 && c[2] === 0 && c[3] === 255) |
| return; |
| } |
| qtest_fail("No red pixel found"); |
| } |
| }, |
| { |
| tag: "getImageData", |
| test: function(ctx) { |
| verify(ctx.getImageData(0, 0, 1, 1, extra) !== null); |
| } |
| }, |
| { |
| tag: "isPointInPath", |
| test: function(ctx) { |
| ctx.moveTo(0, 0); |
| ctx.lineTo(10, 10); |
| verify(ctx.isPointInPath(0, 0, extra)); |
| } |
| }, |
| { |
| tag: "lineTo", |
| test: function(ctx) { |
| ctx.lineWidth = 5; |
| ctx.moveTo(0, 0); |
| ctx.lineTo(10, 10, extra); |
| ctx.stroke(); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "measureText", |
| test: function(ctx) { |
| var textMetrics = ctx.measureText("Hello", extra); |
| verify(textMetrics !== undefined); |
| verify(textMetrics.width > 0); |
| } |
| }, |
| { |
| tag: "moveTo", |
| test: function(ctx) { |
| ctx.lineWidth = 5; |
| ctx.moveTo(10, 10, extra); |
| ctx.lineTo(20, 20, extra); |
| ctx.stroke(); |
| comparePixel(ctx, 0, 0, 0, 0, 0, 0); |
| comparePixel(ctx, 10, 10, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "putImageData", |
| test: function(ctx) { |
| ctx.drawImage(image, 0, 0); |
| comparePixel(ctx, 0, 0, 0, 255, 0, 255); |
| var imageData = ctx.getImageData(0, 0, 1, 1); |
| // Swap green with red. |
| imageData.data[0] = 255; |
| imageData.data[1] = 0; |
| ctx.putImageData(imageData, 0, 0, 0, 0, ctx.canvas.width, ctx.canvas.height, extra); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "quadraticCurveTo", |
| test: function(ctx) { |
| ctx.lineWidth = 5; |
| ctx.moveTo(0, 0); |
| ctx.quadraticCurveTo(20, 100, 100, 20, extra); |
| ctx.stroke(); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "rect", |
| test: function(ctx) { |
| ctx.rect(0, 0, 1, 1, extra); |
| ctx.fill(); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "rotate", |
| test: function(ctx) { |
| // If we don't rotate, it should be red in the middle. |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 255, 0, 0, 255); |
| |
| ctx.reset(); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| |
| // If we do rotate, it shouldn't be there. |
| ctx.rotate(Math.PI / 4, extra); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| } |
| }, |
| { |
| tag: "roundedRect", |
| test: function(ctx) { |
| ctx.roundedRect(0, 0, 50, 50, 5, 5, extra); |
| ctx.fill(); |
| comparePixel(ctx, 25, 25, 255, 0, 0, 255); |
| } |
| }, |
| { |
| tag: "scale", |
| test: function(ctx) { |
| // If we don't scale, it should be red in the middle. |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 255, 0, 0, 255); |
| |
| ctx.reset(); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| |
| // If we do scale, it shouldn't be there. |
| ctx.scale(1.25, 1.25, extra); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| } |
| }, |
| { |
| tag: "setTransform", |
| test: function(ctx) { |
| // The same as the scale test, except with setTransform. |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 255, 0, 0, 255); |
| |
| ctx.reset(); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| |
| ctx.setTransform(1.25, 0, 0, 1.25, 0, 0, extra); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| } |
| }, |
| { |
| tag: "shear", |
| test: function(ctx) { |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 255, 0, 0, 255); |
| |
| ctx.reset(); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| |
| ctx.shear(0.5, 0, extra); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| } |
| }, |
| { |
| tag: "strokeRect", |
| test: function(ctx) { |
| ctx.strokeRect(0, 0, 10, 10, extra); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255, 200); |
| } |
| }, |
| { |
| tag: "strokeText", |
| test: function(ctx) { |
| ctx.font = "10px sans-serif"; |
| ctx.strokeText("Hello", -1, 5, extra); |
| comparePixel(ctx, 0, 5, 255, 0, 0, 255, 200); |
| } |
| }, |
| { |
| tag: "text", |
| test: function(ctx) { |
| ctx.font = "100px sans-serif"; |
| ctx.text(".", -15, 8, extra); |
| ctx.fill(); |
| comparePixel(ctx, 0, 0, 255, 0, 0, 255, 200); |
| } |
| }, |
| { |
| tag: "transform", |
| test: function(ctx) { |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 255, 0, 0, 255); |
| |
| ctx.reset(); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| |
| ctx.transform(1.25, 0, 0, 1.25, 0, 0, extra); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| } |
| }, |
| { |
| tag: "translate", |
| test: function(ctx) { |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 255, 0, 0, 255); |
| |
| ctx.reset(); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| |
| ctx.translate(1, 1, extra); |
| ctx.fillRect(50, 50, 1, 1); |
| comparePixel(ctx, 50, 50, 0, 0, 0, 0); |
| } |
| } |
| ]; |
| } |
| |
| function test_extraArgumentsIgnored(data) { |
| var canvas = Qt.createQmlObject("import QtQuick 2.3; Canvas { onPaint: {} }", testCase); |
| verify(canvas); |
| canvas.width = 100; |
| canvas.height = 100; |
| waitForRendering(canvas); |
| |
| var ctx = canvas.getContext("2d"); |
| |
| ctx.beginPath(); |
| ctx.fillStyle = "red"; |
| ctx.strokeStyle = "red"; |
| data.test(ctx); |
| |
| canvas.destroy(); |
| } |
| |
| function test_getContextOnDestruction_data() { |
| // We want to test all possible combinations deemed valid by the testcase, |
| // but we can't test FramebufferObject due to difficulties ignoring the "available" warning. |
| var allData = testData("2d"); |
| var ourData = []; |
| |
| for (var i = 0; i < allData.length; ++i) { |
| if (allData[i].properties.renderTarget !== Canvas.FramebufferObject) { |
| var row = allData[i].properties; |
| row.tag = allData[i].tag; |
| ourData.push(row); |
| } |
| } |
| |
| return ourData; |
| } |
| |
| function test_getContextOnDestruction(data) { |
| try { |
| var canvasWindow = Qt.createQmlObject(" |
| import QtQuick 2.4\n |
| import QtQuick.Window 2.2\n |
| Window {\n |
| function test() {\n |
| loader.active = true\n |
| loader.active = false\n |
| }\n |
| Loader {\n |
| id: loader\n |
| active: false\n |
| sourceComponent: Canvas {\n |
| renderStrategy: " + renderStrategyToString(data.renderStrategy) + "\n |
| Component.onDestruction: getContext(\"2d\") |
| }\n |
| }\n |
| }\n", |
| testCase); |
| verify(canvasWindow); |
| canvasWindow.test(); |
| // Shouldn't crash when destruction is done. |
| wait(0); |
| } catch (exception) { |
| fail(exception.message); |
| } |
| } |
| |
| property Component implicitlySizedComponent: Item { |
| implicitWidth: 32 |
| implicitHeight: implicitWidth |
| anchors.centerIn: parent |
| |
| property alias canvas: canvas |
| |
| Canvas { |
| id: canvas |
| width: Math.max(1, Math.min(parent.width, parent.height)) |
| height: width |
| |
| onPaint: { |
| var ctx = getContext("2d"); |
| ctx.reset(); |
| ctx.beginPath(); |
| ctx.fillRect(0, 0, width, height); |
| } |
| } |
| } |
| |
| function test_implicitlySizedParent() { |
| var implicitlySizedItem = implicitlySizedComponent.createObject(container); |
| verify(implicitlySizedItem); |
| |
| var xCenter = implicitlySizedItem.width / 2; |
| var yCenter = implicitlySizedItem.height / 2; |
| waitForRendering(implicitlySizedItem); |
| comparePixel(implicitlySizedItem.canvas.context, xCenter, yCenter, 0, 0, 0, 255); |
| } |
| |
| Component { |
| id: simpleTextureNodeUsageComponent |
| |
| Canvas { |
| id: canvas |
| anchors.fill: parent |
| |
| onPaint: { |
| var ctx = canvas.getContext("2d"); |
| ctx.clearRect(0, 0, 200, 200); |
| } |
| } |
| } |
| |
| function test_simpleTextureNodeUsage() { |
| var canvas = simpleTextureNodeUsageComponent.createObject(container); |
| verify(canvas); |
| wait(0); |
| // Shouldn't crash. |
| canvas.requestPaint(); |
| } |
| } |
| } |