blob: 5069680c13f950c588af93882428942f5e7f9e1c [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library canvas_rendering_context_2d_test;
import '../../pkg/unittest/lib/unittest.dart';
import '../../pkg/unittest/lib/html_config.dart';
import 'dart:html';
import 'dart:math';
// Some rounding errors in the browsers.
checkPixel(List<int> pixel, List<int> expected) {
expect(pixel[0], closeTo(expected[0], 2));
expect(pixel[1], closeTo(expected[1], 2));
expect(pixel[2], closeTo(expected[2], 2));
expect(pixel[3], closeTo(expected[3], 2));
}
main() {
useHtmlConfiguration();
group('canvasRenderingContext2d', () {
var canvas;
var context;
var otherCanvas;
var otherContext;
void createOtherCanvas() {
otherCanvas = new CanvasElement();
otherCanvas.width = 10;
otherCanvas.height = 10;
otherContext = otherCanvas.context2d;
otherContext.fillStyle = "red";
otherContext.fillRect(0, 0, otherCanvas.width, otherCanvas.height);
}
setUp(() {
canvas = new CanvasElement();
canvas.width = 100;
canvas.height = 100;
context = canvas.context2d;
createOtherCanvas();
});
tearDown(() {
canvas = null;
context = null;
otherCanvas = null;
otherContext = null;
});
List<int> readPixel(int x, int y) {
var imageData = context.getImageData(x, y, 1, 1);
return imageData.data;
}
/// Returns true if the pixel has some data in it, false otherwise.
bool isPixelFilled(int x, int y) => readPixel(x,y).any((p) => p != 0);
String pixelDataToString(int x, int y) {
var data = readPixel(x, y);
return '[${data.join(", ")}]';
}
String _filled(bool v) => v ? "filled" : "unfilled";
void expectPixelFilled(int x, int y, [bool filled = true]) {
expect(isPixelFilled(x, y), filled, reason:
'Pixel at ($x, $y) was expected to'
' be: <${_filled(filled)}> but was: <${_filled(!filled)}> with data: '
'${pixelDataToString(x,y)}');
}
void expectPixelUnfilled(int x, int y) {
expectPixelFilled(x, y, false);
}
test('setFillColorRgb', () {
context.setFillColorRgb(255, 0, 255, 1);
context.fillRect(0, 0, canvas.width, canvas.height);
expect(readPixel(2, 2), [255, 0, 255, 255]);
});
test('setFillColorHsl hue', () {
context.setFillColorHsl(0, 100, 50);
context.fillRect(0, 0, canvas.width, canvas.height);
checkPixel(readPixel(2, 2), [255, 0, 0, 255]);
});
test('setFillColorHsl hue 2', () {
context.setFillColorHsl(240, 100, 50);
context.fillRect(0, 0, canvas.width, canvas.height);
checkPixel(readPixel(2, 2), [0, 0, 255, 255]);
});
test('setFillColorHsl sat', () {
context.setFillColorHsl(0, 0, 50);
context.fillRect(0, 0, canvas.width, canvas.height);
checkPixel(readPixel(2, 2), [127, 127, 127, 255]);
});
test('setStrokeColorRgb', () {
context.setStrokeColorRgb(255, 0, 255, 1);
context.lineWidth = 10;
context.strokeRect(0, 0, canvas.width, canvas.height);
expect(readPixel(2, 2), [255, 0, 255, 255]);
});
test('setStrokeColorHsl hue', () {
context.setStrokeColorHsl(0, 100, 50);
context.lineWidth = 10;
context.strokeRect(0, 0, canvas.width, canvas.height);
expect(readPixel(2, 2), [255, 0, 0, 255]);
});
test('setStrokeColorHsl hue 2', () {
context.setStrokeColorHsl(240, 100, 50);
context.lineWidth = 10;
context.strokeRect(0, 0, canvas.width, canvas.height);
expect(readPixel(2, 2), [0, 0, 255, 255]);
});
test('setStrokeColorHsl sat', () {
context.setStrokeColorHsl(0, 0, 50);
context.lineWidth = 10;
context.strokeRect(0, 0, canvas.width, canvas.height);
checkPixel(readPixel(2, 2), [127, 127, 127, 255]);
});
test('fillStyle', () {
context.fillStyle = "red";
context.fillRect(0, 0, canvas.width, canvas.height);
checkPixel(readPixel(2, 2), [255, 0, 0, 255]);
});
test('strokeStyle', () {
context.strokeStyle = "blue";
context.lineWidth = 10;
context.strokeRect(0, 0, canvas.width, canvas.height);
expect(readPixel(2, 2), [0, 0, 255, 255]);
});
test('fillStyle linearGradient', () {
var gradient = context.createLinearGradient(0,0,20,20);
gradient.addColorStop(0,'red');
gradient.addColorStop(1,'blue');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
expect(context.fillStyle is CanvasGradient, isTrue);
});
test('putImageData', () {
ImageData expectedData = context.getImageData(0, 0, 10, 10);
expectedData.data[0] = 25;
expectedData.data[1] = 65;
expectedData.data[2] = 255;
// Set alpha to 255 to make the pixels show up.
expectedData.data[3] = 255;
context.fillStyle = 'green';
context.fillRect(0, 0, canvas.width, canvas.height);
context.putImageData(expectedData, 0, 0);
var resultingData = context.getImageData(0, 0, 10, 10);
// Make sure that we read back what we wrote.
expect(resultingData.data, expectedData.data);
});
test('default arc should be clockwise', () {
context.beginPath();
final r = 10;
// Center of arc.
final cx = 20;
final cy = 20;
// Arc centered at (20, 20) with radius 10 will go clockwise
// from (20 + r, 20) to (20, 20 + r), which is 1/4 of a circle.
context.arc(cx, cy, r, 0, PI/2);
context.strokeStyle = 'green';
context.lineWidth = 2;
context.stroke();
// Center should not be filled.
expectPixelUnfilled(cx, cy);
// (cx + r, cy) should be filled.
expectPixelFilled(cx + r, cy, true);
// (cx, cy + r) should be filled.
expectPixelFilled(cx, cy + r, true);
// (cx - r, cy) should be empty.
expectPixelFilled(cx - r, cy, false);
// (cx, cy - r) should be empty.
expectPixelFilled(cx, cy - r, false);
// (cx + r/SQRT2, cy + r/SQRT2) should be filled.
expectPixelFilled((cx + r/SQRT2).toInt(), (cy + r/SQRT2).toInt(), true);
// (cx - r/SQRT2, cy - r/SQRT2) should be empty.
expectPixelFilled((cx - r/SQRT2).toInt(), (cy + r/SQRT2).toInt(), false);
// (cx + r/SQRT2, cy + r/SQRT2) should be empty.
expectPixelFilled((cx - r/SQRT2).toInt(), (cy - r/SQRT2).toInt(), false);
// (cx - r/SQRT2, cy - r/SQRT2) should be empty.
expectPixelFilled((cx + r/SQRT2).toInt(), (cy - r/SQRT2).toInt(), false);
});
test('arc anticlockwise', () {
context.beginPath();
final r = 10;
// Center of arc.
final cx = 20;
final cy = 20;
// Arc centered at (20, 20) with radius 10 will go anticlockwise
// from (20 + r, 20) to (20, 20 + r), which is 3/4 of a circle.
// Because of the way arc work, when going anti-clockwise, the end points
// are not included, so small values are added to radius to make a little
// more than a 3/4 circle.
context.arc(cx, cy, r, .1, PI/2 - .1, true);
context.strokeStyle = 'green';
context.lineWidth = 2;
context.stroke();
// Center should not be filled.
expectPixelUnfilled(cx, cy);
// (cx + r, cy) should be filled.
expectPixelFilled(cx + r, cy, true);
// (cx, cy + r) should be filled.
expectPixelFilled(cx, cy + r, true);
// (cx - r, cy) should be filled.
expectPixelFilled(cx - r, cy, true);
// (cx, cy - r) should be filled.
expectPixelFilled(cx, cy - r, true);
// (cx + r/SQRT2, cy + r/SQRT2) should be empty.
expectPixelFilled((cx + r/SQRT2).toInt(), (cy + r/SQRT2).toInt(), false);
// (cx - r/SQRT2, cy - r/SQRT2) should be filled.
expectPixelFilled((cx - r/SQRT2).toInt(), (cy + r/SQRT2).toInt(), true);
// (cx + r/SQRT2, cy + r/SQRT2) should be filled.
expectPixelFilled((cx - r/SQRT2).toInt(), (cy - r/SQRT2).toInt(), true);
// (cx - r/SQRT2, cy - r/SQRT2) should be filled.
expectPixelFilled((cx + r/SQRT2).toInt(), (cy - r/SQRT2).toInt(), true);
});
// Draw an image to the canvas from an image element.
test('drawImage from img element with 3 params', () {
var dataUrl = otherCanvas.toDataUrl('image/gif');
var img = new ImageElement();
img.onLoad.listen(expectAsync1((_) {
context.drawImage(img, 50, 50);
expectPixelFilled(50, 50);
expectPixelFilled(55, 55);
expectPixelFilled(59, 59);
expectPixelUnfilled(60, 60);
expectPixelUnfilled(0, 0);
expectPixelUnfilled(70, 70);
}));
img.onError.listen((_) {
guardAsync(() {
fail('URL failed to load.');
});
});
img.src = dataUrl;
});
// Draw an image to the canvas from an image element and scale it.
test('drawImage from img element with 5 params', () {
var dataUrl = otherCanvas.toDataUrl('image/gif');
var img = new ImageElement();
img.onLoad.listen(expectAsync1((_) {
context.drawImageAtScale(img, new Rect(50, 50, 20, 20));
expectPixelFilled(50, 50);
expectPixelFilled(55, 55);
expectPixelFilled(59, 59);
expectPixelFilled(60, 60);
expectPixelFilled(69, 69);
expectPixelUnfilled(70, 70);
expectPixelUnfilled(0, 0);
expectPixelUnfilled(80, 80);
}));
img.onError.listen((_) {
guardAsync(() {
fail('URL failed to load.');
});
});
img.src = dataUrl;
});
// Draw an image to the canvas from an image element and scale it.
test('drawImage from img element with 9 params', () {
otherContext.fillStyle = "blue";
otherContext.fillRect(5, 5, 5, 5);
var dataUrl = otherCanvas.toDataUrl('image/gif');
var img = new ImageElement();
img.onLoad.listen(expectAsync1((_) {
// This will take a 6x6 square from the first canvas from position 2,2
// and then scale it to a 20x20 square and place it to the second canvas
// at 50,50.
context.drawImageAtScale(img, new Rect(50, 50, 20, 20),
sourceRect: new Rect(2, 2, 6, 6));
checkPixel(readPixel(50, 50), [255, 0, 0, 255]);
checkPixel(readPixel(55, 55), [255, 0, 0, 255]);
checkPixel(readPixel(60, 50), [255, 0, 0, 255]);
checkPixel(readPixel(65, 65), [0, 0, 255, 255]);
checkPixel(readPixel(69, 69), [0, 0, 255, 255]);
expectPixelFilled(50, 50);
expectPixelFilled(55, 55);
expectPixelFilled(59, 59);
expectPixelFilled(60, 60);
expectPixelFilled(69, 69);
expectPixelUnfilled(70, 70);
expectPixelUnfilled(0, 0);
expectPixelUnfilled(80, 80);
}));
img.onError.listen((_) {
guardAsync(() {
fail('URL failed to load.');
});
});
img.src = dataUrl;
});
// These base64 strings are the same video, representing 2 frames of 8x8 red
// pixels.
// The videos were created with:
// convert -size 8x8 xc:red blank1.jpg
// convert -size 8x8 xc:red blank2.jpg
// avconv -f image2 -i "blank%d.jpg" -c:v libx264 small.mp4
// python -m base64 -e small.mp4
// avconv -i small.mp4 small.webm
// python -m base64 -e small.webm
var mp4VideoUrl =
'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZn'
'JlZQAAAsdtZGF0AAACmwYF//+X3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDEyMCByMj'
'E1MSBhM2Y0NDA3IC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMj'
'AxMSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYm'
'FjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibW'
'U9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm'
'9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3'
'Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTE4IHNsaWNlZF90aHJlYWRzPT'
'AgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYW'
'luZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTAgYl9hZGFwdD0xIGJfYmlhcz0wIG'
'RpcmVjdD0xIHdlaWdodGI9MCBvcGVuX2dvcD0xIHdlaWdodHA9MiBrZXlpbnQ9MjUwIGtleW'
'ludF9taW49MjUgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MC'
'ByYz1jcmYgbWJ0cmVlPTEgY3JmPTUxLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IH'
'Fwc3RlcD00IGlwX3JhdGlvPTEuMjUgYXE9MToxLjAwAIAAAAARZYiEB//3aoK5/tP9+8yeuI'
'EAAAAHQZoi2P/wgAAAAzxtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAUAABAAABAA'
'AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAA'
'AAAAAAAAAAAAAAAAAAAAAAAAACAAAAGGlvZHMAAAAAEICAgAcAT/////7/AAACUHRyYWsAAA'
'BcdGtoZAAAAA8AAAAAAAAAAAAAAAEAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAA'
'AAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAACAAAAAgAAAAAACRlZHRzAAAAHGVsc3QAAA'
'AAAAAAAQAAAFAAAAABAAEAAAAAAchtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAAAZAAAAAl'
'XEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFzbW'
'luZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybC'
'AAAAABAAABM3N0YmwAAACXc3RzZAAAAAAAAAABAAAAh2F2YzEAAAAAAAAAAQAAAAAAAAAAAA'
'AAAAAAAAAACAAIAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
'AAAAAY//8AAAAxYXZjQwFNQAr/4QAYZ01ACuiPyy4C2QAAAwABAAADADIPEiUSAQAGaOvAZS'
'yAAAAAGHN0dHMAAAAAAAAAAQAAAAIAAAABAAAAFHN0c3MAAAAAAAAAAQAAAAEAAAAYY3R0cw'
'AAAAAAAAABAAAAAgAAAAEAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAHHN0c3oAAA'
'AAAAAAAAAAAAIAAAK0AAAACwAAABhzdGNvAAAAAAAAAAIAAAAwAAAC5AAAAGB1ZHRhAAAAWG'
'1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAK2lsc3QAAAAjqX'
'RvbwAAABtkYXRhAAAAAQAAAABMYXZmNTMuMjEuMQ==';
var webmVideoUrl =
'data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBCEKChHdlYm1Ch'
'4ECQoWBAhhTgGcBAAAAAAAB/hFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEsT'
'buMU6uEHFO7a1OsggHk7AEAAAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEEq17GDD0JATYCLTGF2ZjUzL'
'jIxLjFXQYtMYXZmNTMuMjEuMXOkkJatuHwTJ7cvFLSzBSmxbp5EiYhAVAAAAAAAABZUrmsBA'
'AAAAAAAR64BAAAAAAAAPteBAXPFgQGcgQAitZyDdW5khoVWX1ZQOIOBASPjg4QCYloA4AEAA'
'AAAAAASsIEIuoEIVLCBCFS6gQhUsoEDH0O2dQEAAAAAAABZ54EAo72BAACA8AIAnQEqCAAIA'
'ABHCIWFiIWEiAICAnWqA/gD+gINTRgA/v0hRf/kb+PnRv/I4//8WE8DijI//FRAo5WBACgAs'
'QEAARAQABgAGFgv9AAIAAAcU7trAQAAAAAAAA67jLOBALeH94EB8YIBfw==';
// test('drawImage from video element with 3 params', () {
// var video = new VideoElement();
//
// video.onLoadedData.listen(expectAsync1((_) {
// context.drawImage(video, 50, 50);
//
// expectPixelFilled(50, 50);
// expectPixelFilled(54, 54);
// expectPixelFilled(57, 57);
// expectPixelUnfilled(58, 58);
// expectPixelUnfilled(0, 0);
// expectPixelUnfilled(70, 70);
// }));
// video.onError.listen((_) {
// guardAsync(() {
// fail('URL failed to load.');
// });
// });
//
// if(video.canPlayType('video/webm; codecs="vp8.0, vorbis"', '') != '') {
// video.src = webmVideoUrl;
// } else if(video.canPlayType('video/mp4; codecs="avc1.4D401E, mp4a.40.2"',
// null) != '') {
// video.src = mp4VideoUrl;
// } else {
// logMessage('Video is not supported on this system.');
// }
// });
//
// test('drawImage from video element with 5 params', () {
// var video = new VideoElement();
//
// video.onLoadedData.listen(expectAsync1((_) {
// context.drawImageAtScale(video, new Rect(50, 50, 20, 20));
//
// expectPixelFilled(50, 50);
// expectPixelFilled(55, 55);
// expectPixelFilled(59, 59);
// expectPixelFilled(60, 60);
// expectPixelFilled(69, 69);
// expectPixelUnfilled(70, 70);
// expectPixelUnfilled(0, 0);
// expectPixelUnfilled(80, 80);
// }));
// video.onError.listen((_) {
// guardAsync(() {
// fail('URL failed to load.');
// });
// });
//
// if(video.canPlayType('video/webm; codecs="vp8.0, vorbis"', '') != '') {
// video.src = webmVideoUrl;
// } else if(video.canPlayType('video/mp4; codecs="avc1.4D401E, mp4a.40.2"',
// null) != '') {
// video.src = mp4VideoUrl;
// } else {
// // TODO(amouravski): Better fallback?
// logMessage('Video is not supported on this system.');
// }
// });
//
// test('drawImage from video element with 9 params', () {
// var video = new VideoElement();
//
// video.onLoadedData.listen(expectAsync1((_) {
// context.drawImageAtScale(video, new Rect(50, 50, 20, 20),
// sourceRect: new Rect(2, 2, 6, 6));
//
// expectPixelFilled(50, 50);
// expectPixelFilled(55, 55);
// expectPixelFilled(59, 59);
// expectPixelFilled(60, 60);
// expectPixelFilled(69, 69);
// expectPixelUnfilled(70, 70);
// expectPixelUnfilled(0, 0);
// expectPixelUnfilled(80, 80);
// }));
// video.onError.listen((_) {
// guardAsync(() {
// fail('URL failed to load.');
// });
// });
//
// if(video.canPlayType('video/webm; codecs="vp8.0, vorbis"', '') != '') {
// video.src = webmVideoUrl;
// } else if(video.canPlayType('video/mp4; codecs="avc1.4D401E, mp4a.40.2"',
// null) != '') {
// video.src = mp4VideoUrl;
// } else {
// // TODO(amouravski): Better fallback?
// logMessage('Video is not supported on this system.');
// }
// });
test('drawImage from canvas element with 3 params', () {
// Draw an image to the canvas from a canvas element.
context.drawImage(otherCanvas, 50, 50);
expectPixelFilled(50, 50);
expectPixelFilled(55, 55);
expectPixelFilled(59, 59);
expectPixelUnfilled(60, 60);
expectPixelUnfilled(0, 0);
expectPixelUnfilled(70, 70);
});
test('drawImage from canvas element with 5 params', () {
// Draw an image to the canvas from a canvas element.
context.drawImageAtScale(otherCanvas, new Rect(50, 50, 20, 20));
expectPixelFilled(50, 50);
expectPixelFilled(55, 55);
expectPixelFilled(59, 59);
expectPixelFilled(60, 60);
expectPixelFilled(69, 69);
expectPixelUnfilled(70, 70);
expectPixelUnfilled(0, 0);
expectPixelUnfilled(80, 80);
});
test('drawImage from canvas element with 9 params', () {
// Draw an image to the canvas from a canvas element.
otherContext.fillStyle = "blue";
otherContext.fillRect(5, 5, 5, 5);
context.drawImageAtScale(otherCanvas, new Rect(50, 50, 20, 20),
sourceRect: new Rect(2, 2, 6, 6));
checkPixel(readPixel(50, 50), [255, 0, 0, 255]);
checkPixel(readPixel(55, 55), [255, 0, 0, 255]);
checkPixel(readPixel(60, 50), [255, 0, 0, 255]);
checkPixel(readPixel(65, 65), [0, 0, 255, 255]);
checkPixel(readPixel(69, 69), [0, 0, 255, 255]);
expectPixelFilled(50, 50);
expectPixelFilled(55, 55);
expectPixelFilled(59, 59);
expectPixelFilled(60, 60);
expectPixelFilled(69, 69);
expectPixelUnfilled(70, 70);
expectPixelUnfilled(0, 0);
expectPixelUnfilled(80, 80);
});
});
}