var tests = {};

tests['clearRect()'] = function(ctx){
  ctx.fillRect(25,25,100,100);
  ctx.clearRect(45,45,60,60);
  ctx.fillRect(50,50,50,50);
};

tests['strokeRect()'] = function(ctx){
  ctx.fillRect(25,25,100,100);
  ctx.clearRect(45,45,60,60);
  ctx.strokeRect(50,50,50,50);
};

tests['fillRect()'] = function(ctx){

  function render(level){
    ctx.fillStyle = getPointColour(122,122);
    ctx.fillRect(0,0,240,240);
    renderLevel(level,81,0);
  }

  function renderLevel(minimumLevel,level,y){
    for (var x=0; x < 243 / level; ++x) {
      drawBlock(x,y,level);
    }
    for (var x=0; x < 243 / level; x+=3){
      drawBlock(x,y+1,level);
      drawBlock(x+2,y+1,level);
    }
    for (var x=0; x < 243 / level; ++x){
      drawBlock(x,y+2,level);
    }
    if ((y += 3) >= 243 / level){
      y=0;
      level /= 3;
    }
    if (level >= minimumLevel){
      renderLevel(minimumLevel, level, y);
    }
  }

  function drawBlock(x,y,level){
    ctx.fillStyle = getPointColour(
        x * level + (level-1) / 2
      , y * level + (level-1) / 2);

    ctx.fillRect(
        x * level
      , y * level
      , level
      , level
      );
  }

  function getPointColour(x,y){
    x= x / 121.5 - 1;
    y= -y / 121.5 + 1;
    var x2y2 = x * x + y * y;
    if (x2y2 > 1){
      return '#000';
    } else {
      var root = Math.sqrt(1 - x2y2);
      var x3d = x * 0.7071067812 + root / 2 - y / 2;
      var y3d = x * 0.7071067812 - root / 2 + y / 2;
      var z3d = 0.7071067812 * root + 0.7071067812 * y;
      var brightness= -x / 2 + root * 0.7071067812 + y / 2;
      if (brightness < 0) brightness = 0;
      return ''
        + 'rgb(' + Math.round(brightness * 127.5 * (1 - y3d))
        + ',' + Math.round(brightness * 127.5 * (x3d + 1))
        + ',' + Math.round(brightness * 127.5 * (z3d + 1))
        + ')'
        ;
   }
  }

  render(1);
};

tests['lineTo()'] = function(ctx){
  // Filled triangle
  ctx.beginPath();
  ctx.moveTo(25.5,25);
  ctx.lineTo(105,25);
  ctx.lineTo(25,105);
  ctx.fill();

  // Stroked triangle
  ctx.beginPath();
  ctx.moveTo(125,125);
  ctx.lineTo(125,45);
  ctx.lineTo(45,125);
  ctx.closePath();
  ctx.stroke();
};

tests['arc()'] = function(ctx){
  ctx.beginPath();
  ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
  ctx.moveTo(110,75);
  ctx.arc(75,75,35,0,Math.PI,false);   // Mouth
  ctx.moveTo(65,65);
  ctx.arc(60,65,5,0,Math.PI*2,true);  // Left eye
  ctx.moveTo(95,65);
  ctx.arc(90,65,5,0,Math.PI*2,true);  // Right eye
  ctx.stroke();
};

tests['arc() 2'] = function(ctx){
  for(var i=0;i<4;i++){
    for(var j=0;j<3;j++){
      ctx.beginPath();
      var x              = 25+j*50;               // x coordinate
      var y              = 25+i*50;               // y coordinate
      var radius         = 20;                    // Arc radius
      var startAngle     = 0;                     // Starting point on circle
      var endAngle       = Math.PI+(Math.PI*j)/2; // End point on circle
      var anticlockwise  = i%2==0 ? false : true; // clockwise or anticlockwise

      ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);

      if (i>1){
        ctx.fill();
      } else {
        ctx.stroke();
      }
    }
  }
};

tests['arcTo()'] = function(ctx){
  ctx.fillStyle = '#08C8EE';
  ctx.translate(-50,-50);
  ctx.moveTo(120, 100);
  ctx.lineTo(180, 100);
  ctx.arcTo(200, 100, 200, 120, 5);
  ctx.lineTo(200, 180);
  ctx.arcTo(200, 200, 180, 200, 20);
  ctx.lineTo(120, 200);
  ctx.arcTo(100, 200, 100, 180, 20);
  ctx.lineTo(100, 120);
  ctx.arcTo(100, 100, 120, 100, 20);
  ctx.fill();

  ctx.font = 'bold 25px Arial';
  ctx.fillStyle = '#fff';
  ctx.fillText('node', 120, 155);
};

tests['bezierCurveTo()'] = function(ctx){
  ctx.beginPath();
  ctx.moveTo(75,40);
  ctx.bezierCurveTo(75,37,70,25,50,25);
  ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
  ctx.bezierCurveTo(20,80,40,102,75,120);
  ctx.bezierCurveTo(110,102,130,80,130,62.5);
  ctx.bezierCurveTo(130,62.5,130,25,100,25);
  ctx.bezierCurveTo(85,25,75,37,75,40);
  ctx.fill();
};

tests['quadraticCurveTo()'] = function(ctx){
  ctx.beginPath();
  ctx.moveTo(75,25);
  ctx.quadraticCurveTo(25,25,25,62.5);
  ctx.quadraticCurveTo(25,100,50,100);
  ctx.quadraticCurveTo(50,120,30,125);
  ctx.quadraticCurveTo(60,120,65,100);
  ctx.quadraticCurveTo(125,100,125,62.5);
  ctx.quadraticCurveTo(125,25,75,25);
  ctx.stroke();
};


tests['transform()'] = function(ctx){
  var sin = Math.sin(Math.PI/6);
  var cos = Math.cos(Math.PI/6);
  ctx.translate(100, 100);
  ctx.scale(.5,.5);
  var c = 0;
  for (var i=0; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }
};

tests['rotate()'] = function(ctx){
  ctx.rotate(0.4);
  ctx.translate(30,0);
  ctx.rect(0,0,50,50);
  ctx.stroke();
};

tests['rotate() 2'] = function(ctx){
  ctx.translate(75,75);

  for (var i=1;i<6;i++){ // Loop through rings (from inside to out)
    ctx.save();
    ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';

    for (var j=0;j<i*6;j++){ // draw individual dots
      ctx.rotate(Math.PI*2/(i*6));
      ctx.beginPath();
      ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
      ctx.fill();
    }

    ctx.restore();
  }
};

tests['translate()'] = function(ctx){
  ctx.fillRect(0,0,300,300);
  for (var i=0;i<3;i++) {
    for (var j=0;j<3;j++) {
      ctx.save();
      ctx.strokeStyle = "#9CFF00";
      ctx.translate(50+j*100,50+i*100);
      drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10);
      ctx.restore();
    }
  }
  function drawSpirograph(ctx,R,r,O){
    var x1 = R-O;
    var y1 = 0;
    var i  = 1;
    ctx.beginPath();
    ctx.moveTo(x1,y1);
    do {
      if (i>20000) break;
      var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
      var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
      ctx.lineTo(x2,y2);
      x1 = x2;
      y1 = y2;
      i++;
    } while (x2 != R-O && y2 != 0 );
    ctx.stroke();
  }
};

tests['scale()'] = function(ctx){
  ctx.strokeStyle = "#fc0";
  ctx.lineWidth = 1.5;
  ctx.fillRect(0,0,300,300);

  // Uniform scaling
  ctx.save()
  ctx.translate(50,50);
  drawSpirograph(ctx,22,6,5);  // no scaling

  ctx.translate(100,0);
  ctx.scale(0.75,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(133.333,0);
  ctx.scale(0.75,0.75);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

  // Non-uniform scaling (y direction)
  ctx.strokeStyle = "#0cf";
  ctx.save()
  ctx.translate(50,150);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(100,0);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(100,0);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

  // Non-uniform scaling (x direction)
  ctx.strokeStyle = "#cf0";
  ctx.save()
  ctx.translate(50,250);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(133.333,0);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(177.777,0);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();
  function drawSpirograph(ctx,R,r,O){
    var x1 = R-O;
    var y1 = 0;
    var i  = 1;
    ctx.beginPath();
    ctx.moveTo(x1,y1);
    do {
      if (i>20000) break;
      var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
      var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
      ctx.lineTo(x2,y2);
      x1 = x2;
      y1 = y2;
      i++;
    } while (x2 != R-O && y2 != 0 );
    ctx.stroke();
  }
};

tests['rect()'] = function(ctx){
  ctx.rect(5,5,50,50);
  ctx.strokeStyle = 'yellow';
  ctx.fill();
  ctx.stroke();
};

tests['clip()'] = function(ctx){
  ctx.arc(50,50,50,0,Math.PI * 2,false);
  ctx.stroke();
  ctx.clip();
  ctx.fillStyle = 'rgba(0,0,0,.5)';
  ctx.fillRect(0,0,100,100);
};

tests['clip() 2'] = function(ctx){
  ctx.fillRect(0,0,150,150);
  ctx.translate(75,75);

  // Create a circular clipping path
  ctx.beginPath();
  ctx.arc(0,0,60,0,Math.PI*2,true);
  ctx.clip();

  // draw background
  var lingrad = ctx.createLinearGradient(0,-75,0,75);
  lingrad.addColorStop(0, '#232256');
  lingrad.addColorStop(1, '#143778');

  ctx.fillStyle = lingrad;
  ctx.fillRect(-75,-75,150,150);

  // draw stars
  for (var j=1;j<50;j++){
    ctx.save();
    ctx.fillStyle = '#fff';
    ctx.translate(75-Math.floor(Math.random()*150),
                  75-Math.floor(Math.random()*150));
    drawStar(ctx,Math.floor(Math.random()*4)+2);
    ctx.restore();
  }
  function drawStar(ctx,r){
    ctx.save();
    ctx.beginPath()
    ctx.moveTo(r,0);
    for (var i=0;i<9;i++){
      ctx.rotate(Math.PI/5);
      if(i%2 == 0) {
        ctx.lineTo((r/0.525731)*0.200811,0);
      } else {
        ctx.lineTo(r,0);
      }
    }
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }
};

tests['createLinearGradient()'] = function(ctx){
  var lingrad = ctx.createLinearGradient(0,0,0,150);
  lingrad.addColorStop(0, '#00ABEB');
  lingrad.addColorStop(0.5, '#fff');
  lingrad.addColorStop(0.5, '#26C000');
  lingrad.addColorStop(1, '#fff');

  var lingrad2 = ctx.createLinearGradient(0,50,0,95);
  lingrad2.addColorStop(0.5, '#000');
  lingrad2.addColorStop(1, 'rgba(0,0,0,0)');

  ctx.fillStyle = lingrad;
  ctx.strokeStyle = lingrad2;

  ctx.fillRect(10,10,130,130);
  ctx.strokeRect(50,50,50,50);
};

tests['createRadialGradient()'] = function(ctx){
  // Create gradients
  var radgrad = ctx.createRadialGradient(45,45,10,52,50,30);
  radgrad.addColorStop(0, '#A7D30C');
  radgrad.addColorStop(0.9, '#019F62');
  radgrad.addColorStop(1, 'rgba(1,159,98,0)');

  var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50);
  radgrad2.addColorStop(0, '#FF5F98');
  radgrad2.addColorStop(0.75, '#FF0188');
  radgrad2.addColorStop(1, 'rgba(255,1,136,0)');

  var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40);
  radgrad3.addColorStop(0, '#00C9FF');
  radgrad3.addColorStop(0.8, '#00B5E2');
  radgrad3.addColorStop(1, 'rgba(0,201,255,0)');

  var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90);
  radgrad4.addColorStop(0, '#F4F201');
  radgrad4.addColorStop(0.8, '#E4C700');
  radgrad4.addColorStop(1, 'rgba(228,199,0,0)');

  // draw shapes
  ctx.fillStyle = radgrad4;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad3;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad2;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad;
  ctx.fillRect(0,0,150,150);
};

tests['globalAlpha'] = function(ctx){
  ctx.globalAlpha = 0.5;
  ctx.fillStyle = 'rgba(0,0,0,0.5)';
  ctx.strokeRect(0,0,50,50);

  ctx.globalAlpha = 0.8;
  ctx.fillRect(20,20,20,20);

  ctx.fillStyle = 'black';
  ctx.globalAlpha = 1;
  ctx.fillRect(25,25,10,10);
};

tests['globalAlpha 2'] = function(ctx){
  ctx.fillStyle = '#FD0';
  ctx.fillRect(0,0,75,75);
  ctx.fillStyle = '#6C0';
  ctx.fillRect(75,0,75,75);
  ctx.fillStyle = '#09F';
  ctx.fillRect(0,75,75,75);
  ctx.fillStyle = '#F30';
  ctx.fillRect(75,75,150,150);
  ctx.fillStyle = '#FFF';

  ctx.globalAlpha = 0.2;

  for (var i=0;i<7;i++){
      ctx.beginPath();
      ctx.arc(75,75,10+10*i,0,Math.PI*2,true);
      ctx.fill();
  }
};

tests['fillStyle'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
};

tests['strokeStyle'] = function(ctx){
  for (var i=0;i<6;i++){
    for (var j=0;j<6;j++){
      ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ')';
      ctx.beginPath();
      ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
      ctx.stroke();
    }
  }
};

tests['fill with stroke'] = function(ctx){
  ctx.beginPath();
  ctx.arc(75,75,50,0,Math.PI*2,true);
  ctx.fill();
  ctx.closePath();
  ctx.beginPath();
  ctx.fillStyle = 'red';
  ctx.strokeStyle = 'yellow';
  ctx.arc(75,75,30,0,Math.PI*2,true);
  ctx.fill();
  ctx.stroke();
};

tests['floating point coordinates'] = function(ctx){
  ctx.lineCap = 'square';
  for (var i=0; i<70; i+=3.05) {
    ctx.rect(i+3, 10.5, 0, 130);
    ctx.moveTo(i+77, 10.5);
    ctx.lineTo(i+77, 140.5);
  }
  ctx.stroke();
}

tests['lineWidth'] = function(ctx){
  for (var i = 0; i < 10; i++){
    ctx.lineWidth = 1+i;
    ctx.beginPath();
    ctx.moveTo(5+i*14,5);
    ctx.lineTo(5+i*14,140);
    ctx.stroke();
  }
};

tests['line caps'] = function(ctx){
  var lineCap = ['butt','round','square'];

  ctx.strokeStyle = '#09f';
  ctx.beginPath();
  ctx.moveTo(10,10);
  ctx.lineTo(140,10);
  ctx.moveTo(10,140);
  ctx.lineTo(140,140);
  ctx.stroke();

  ctx.strokeStyle = 'black';
  for (var i=0;i<lineCap.length;i++){
    ctx.lineWidth = 15;
    ctx.lineCap = lineCap[i];
    ctx.beginPath();
    ctx.moveTo(25+i*50,10);
    ctx.lineTo(25+i*50,140);
    ctx.stroke();
  }
};

tests['line join'] = function(ctx){
  var lineJoin = ['round','bevel','miter'];
  ctx.lineWidth = 10;
  for (var i=0;i<lineJoin.length;i++){
    ctx.lineJoin = lineJoin[i];
    ctx.beginPath();
    ctx.moveTo(-5,5+i*40);
    ctx.lineTo(35,45+i*40);
    ctx.lineTo(75,5+i*40);
    ctx.lineTo(115,45+i*40);
    ctx.lineTo(155,5+i*40);
    ctx.stroke();
  }
};

tests['lineCap default'] = function(ctx){
  ctx.beginPath();
  ctx.lineWidth = 10.0;
  ctx.moveTo(50, 50);
  ctx.lineTo(50, 100);
  ctx.lineTo(80, 120);
  ctx.stroke();
};

tests['lineCap'] = function(ctx){
  ctx.beginPath();
  ctx.lineWidth = 10.0;
  ctx.lineCap = 'round';
  ctx.moveTo(50, 50);
  ctx.lineTo(50, 100);
  ctx.lineTo(80, 120);
  ctx.stroke();
};

tests['lineJoin'] = function(ctx){
  ctx.beginPath();
  ctx.lineWidth = 10.0;
  ctx.lineJoin = 'round';
  ctx.moveTo(50, 50);
  ctx.lineTo(50, 100);
  ctx.lineTo(80, 120);
  ctx.stroke();
};

tests['states'] = function(ctx){
  ctx.save();
  ctx.rect(50, 50, 100, 100);
  ctx.stroke();

  ctx.restore();
  ctx.save();
  ctx.translate(50,50);
  ctx.scale(.5,.5);
  ctx.strokeRect(51, 51, 100, 100);

  ctx.restore();
  ctx.translate(95,95);
  ctx.fillRect(0,0,10,10);
};

tests['states with stroke/fill/globalAlpha'] = function(ctx){
  ctx.fillRect(0,0,150,150);
  ctx.save();

  ctx.fillStyle = '#09F'
  ctx.fillRect(15,15,120,120);

  ctx.save();
  ctx.fillStyle = '#FFF'
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30,30,90,90);

  ctx.restore();
  ctx.fillRect(45,45,60,60);

  ctx.restore();
  ctx.fillRect(60,60,30,30);
};

tests['path through fillRect/strokeRect/clearRect'] = function(ctx){
  // left: fillRect()
  ctx.beginPath();
  ctx.rect(0, 50, 50, 50);
  ctx.fillStyle = '#F00';
  ctx.fillRect(10, 60, 30, 30);
  ctx.fillStyle = '#0F0';
  ctx.fill();

  // center: strokeRect()
  ctx.beginPath();
  ctx.rect(50, 50, 50, 50);
  ctx.strokeStyle = '#F00';
  ctx.lineWidth = 5;
  ctx.strokeRect(60, 60, 30, 30);
  ctx.fillStyle = '#0F0';
  ctx.fill();

  // right: clearRect()
  ctx.beginPath();
  ctx.rect(100, 50, 50, 50);
  ctx.fillStyle = '#0F0';
  ctx.fill();
  ctx.clearRect(110, 60, 30, 30);
  ctx.fill();
}

tests['invalid stroke/fill styles'] = function(ctx){
  ctx.fillStyle = 'red';
  ctx.strokeStyle = 'yellow';
  ctx.rect(50,50,50,50);
  ctx.fill();
  ctx.stroke();
  ctx.beginPath();
  ctx.fillStyle = 'asdf';
  ctx.strokeStyle = 'asdf';
  ctx.rect(100,80,15,15);
  ctx.fill();
  ctx.stroke();
};

tests['fillText()'] = function(ctx){
  ctx.font = '30px Arial';
  ctx.rotate(.1);
  ctx.lineTo(10,10);
  ctx.fillText("Awesome!", 50, 100);

  var te = ctx.measureText('Awesome!');

  ctx.strokeStyle = 'rgba(0,0,0,0.5)';
  ctx.lineTo(50, 102);
  ctx.lineTo(50 + te.width, 102);
  ctx.stroke();
};

tests['fillText() transformations'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.font = 'bold 12px Helvetica';

  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.beginPath();
  ctx.lineTo(100,0);
  ctx.lineTo(100,200);
  ctx.stroke();

  ctx.rotate(0.2);
  ctx.fillText("foo", 150, 100);
  ctx.font = 'normal 30px Arial';
  ctx.fillText("bar", 50, 100);
};

tests['strokeText()'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.beginPath();
  ctx.lineTo(100,0);
  ctx.lineTo(100,200);
  ctx.stroke();

  ctx.strokeStyle = 'red';
  ctx.font = 'normal 50px Arial';
  ctx.strokeText("bar", 100, 100);
};

tests['textAlign right'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.beginPath();
  ctx.lineTo(100,0);
  ctx.lineTo(100,200);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textAlign = 'right';
  ctx.fillText("right", 100, 100);
};

tests['textAlign left'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.beginPath();
  ctx.lineTo(100,0);
  ctx.lineTo(100,200);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textAlign = 'left';
  ctx.fillText("left", 100, 100);
};

tests['textAlign center'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.beginPath();
  ctx.lineTo(100,0);
  ctx.lineTo(100,200);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("center", 100, 100);
};

tests['textBaseline alphabetic'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textBaseline = 'alphabetic';
  ctx.textAlign = 'center';
  ctx.fillText("alphabetic", 100, 100);
};

tests['textBaseline top'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textBaseline = 'top';
  ctx.textAlign = 'center';
  ctx.fillText("top", 100, 100);
};

tests['textBaseline hanging'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textBaseline = 'hanging';
  ctx.textAlign = 'center';
  ctx.fillText("hanging", 100, 100);
};

tests['textBaseline middle'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textBaseline = 'middle';
  ctx.textAlign = 'center';
  ctx.fillText("middle", 100, 100);
};

tests['textBaseline ideographic'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textBaseline = 'ideographic';
  ctx.textAlign = 'center';
  ctx.fillText("ideographic", 100, 100);
};

tests['textBaseline bottom'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 20px Arial';
  ctx.textBaseline = 'bottom';
  ctx.textAlign = 'center';
  ctx.fillText("bottom", 100, 100);
};

tests['font size px'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 14px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("normal 14px Arial", 100, 100);
};

tests['font size pt'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 14pt Arial';
  ctx.textAlign = 'center';
  ctx.fillText("normal 14pt Arial", 100, 100);
};

tests['font size mm'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 3mm Arial';
  ctx.textAlign = 'center';
  ctx.fillText("normal 3mm Arial", 100, 100);
};

tests['font size cm'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'normal 0.6cm Arial';
  ctx.textAlign = 'center';
  ctx.fillText("normal 0.6cm Arial", 100, 100);
};

tests['font weight bold'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'bold 14px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("bold 14px Arial", 100, 100);
};

tests['font weight lighter'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'lighter 14px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("lighter 14px Arial", 100, 100);
};

tests['font weight lighter italic'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = 'lighter italic 14px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("lighter italic 14px Arial", 100, 100);
};

tests['font weight 200'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = '200 14px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("200 14px Arial", 100, 100);
};

tests['font weight 800'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = '800 14px Arial';
  ctx.textAlign = 'center';
  ctx.fillText("800 14px Arial", 100, 100);
};

tests['font family serif'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = '14px serif';
  ctx.textAlign = 'center';
  ctx.fillText("14px serif", 100, 100);
};

tests['font family sans-serif'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = '14px sans-serif';
  ctx.textAlign = 'center';
  ctx.fillText("14px sans-serif", 100, 100);
};

tests['font family Impact'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = '18px Impact';
  ctx.textAlign = 'center';
  ctx.fillText("18px Impact", 100, 100);
};

tests['font family invalid'] = function(ctx){
  ctx.strokeStyle = '#666';
  ctx.strokeRect(0,0,200,200);
  ctx.lineTo(0,100);
  ctx.lineTo(200,100);
  ctx.stroke();

  ctx.font = '14px Foo, Invalid, Impact, sans-serif';
  ctx.textAlign = 'center';
  ctx.fillText("14px Invalid, Impact", 100, 100);
};

tests['globalCompositeOperation source-over'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'source-over';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation source-in'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'source-in';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation source-out'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'source-out';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation destination-in'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'destination-in';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation source-atop'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'source-atop';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation destination-out'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'destination-out';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation destination-atop'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'destination-atop';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation xor'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'xor';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation copy'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'copy';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation lighter'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'lighter';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation darker'] = function(ctx){
  ctx.fillStyle = 'blue';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'darker';
  ctx.fillStyle = 'red';
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation multiply'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'multiply';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation screen'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'screen';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation overlay'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'overlay';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation hard-light'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'hard-light';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation hsl-hue'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'hsl-hue';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation hsl-saturation'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'hsl-saturation';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation hsl-color'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'hsl-color';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['globalCompositeOperation hsl-luminosity'] = function(ctx){
  ctx.fillStyle = 'rgba(0,0,255,0.6)';
  ctx.fillRect(0,0,100,100);
  ctx.globalCompositeOperation = 'hsl-luminosity';
  var grad = ctx.createRadialGradient(80,80,5,60,60,60);
  grad.addColorStop(0, 'yellow');
  grad.addColorStop(0.2, 'red');
  grad.addColorStop(1, 'black');
  ctx.fillStyle = grad;
  ctx.arc(80,80,50,0,Math.PI * 2,false);
  ctx.fill();
};

tests['shadowBlur'] = function(ctx){
  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#000';
  ctx.shadowBlur = 5;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowBlur = 0;

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadowColor'] = function(ctx){
  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#c00';
  ctx.shadowBlur = 5;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowBlur = 0;

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadowOffset{X,Y}'] = function(ctx){
  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#c00';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowBlur = 0;

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadowOffset{X,Y} large'] = function(ctx){
  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#c00';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 10;
  ctx.shadowOffsetY = 10;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowBlur = 0;

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadowOffset{X,Y} negative'] = function(ctx){
  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#c00';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = -10;
  ctx.shadowOffsetY = -10;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowBlur = 0;

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadowOffset{X,Y} transform'] = function(ctx){
  ctx.translate(100, 0);
  ctx.scale(.75,.75);
  ctx.rotate(Math.PI/4);

  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#c00';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 10;
  ctx.shadowOffsetY = 10;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowBlur = 0;

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadowBlur values'] = function(ctx){
  ctx.fillRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#c00';
  ctx.shadowBlur = 25;
  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.fillRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowColor = 'rgba(0,0,0,0)';

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.fillRect(150,150,20,20);
};

tests['shadow strokeRect()'] = function(ctx){
  ctx.strokeRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#000';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.strokeRect(20,20,100,100);

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowColor = 'rgba(0,0,0,0)';

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.strokeRect(150,150,20,20);
};

tests['shadow fill()'] = function(ctx){
  ctx.strokeRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#000';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.rect(20,20,100,100);
  ctx.fill();

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowColor = 'rgba(0,0,0,0)';

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.strokeRect(150,150,20,20);
};

tests['shadow stroke()'] = function(ctx){
  ctx.strokeRect(150,10,20,20);

  ctx.lineTo(20,5);
  ctx.lineTo(100,5);
  ctx.stroke();

  ctx.shadowColor = '#000';
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.rect(20,20,100,100);
  ctx.stroke();

  ctx.beginPath();
  ctx.lineTo(20,150);
  ctx.lineTo(100,150);
  ctx.stroke();

  ctx.shadowColor = 'rgba(0,0,0,0)';

  ctx.beginPath();
  ctx.lineTo(20,180);
  ctx.lineTo(100,180);
  ctx.stroke();

  ctx.strokeRect(150,150,20,20);
};

tests['shadow globalAlpha'] = function(ctx){
  ctx.lineTo(0,0);
  ctx.lineTo(50,0);
  ctx.lineTo(50,150);
  ctx.stroke();

  ctx.lineWidth = 5;
  ctx.globalAlpha = 0.3;
  ctx.shadowColor = '#00c';
  ctx.shadowBlur = 2;
  ctx.shadowOffsetX = 8;
  ctx.shadowOffsetY = 8;

  ctx.lineTo(0,150);
  ctx.stroke();
};

tests['shadow fillText()'] = function(ctx){
  ctx.shadowColor = '#00c';
  ctx.shadowBlur = 2;
  ctx.shadowOffsetX = 8;
  ctx.shadowOffsetY = 8;
  ctx.textAlign = 'center';
  ctx.font = '35px Arial';
  ctx.fillText("Shadow", 100, 100);
};

tests['shadow strokeText()'] = function(ctx){
  ctx.shadowColor = '#00c';
  ctx.shadowBlur = 2;
  ctx.shadowOffsetX = 8;
  ctx.shadowOffsetY = 8;
  ctx.textAlign = 'center';
  ctx.font = '35px Arial';
  ctx.strokeText("Shadow", 100, 100);
};

tests['shadow transform text'] = function(ctx){
  ctx.shadowColor = '#c0c';
  ctx.shadowBlur = 4;
  ctx.shadowOffsetX = 6;
  ctx.shadowOffsetY = 10;
  ctx.textAlign = 'center';
  ctx.font = '35px Arial';
  ctx.scale(2, 2);
  ctx.strokeText("Sha", 33, 40);
  ctx.rotate(Math.PI/2);
  ctx.fillText("dow", 50, -72);
};

tests['shadow image'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.shadowColor = '#f3ac22';
    ctx.shadowBlur = 2;
    ctx.shadowOffsetX = 8;
    ctx.shadowOffsetY = 8;
    ctx.drawImage(img, 0, 0);
    done();
  };
  img.onerror = function(){}
  img.src = 'star.png';
};

tests['scaled shadow image'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.shadowColor = '#f3ac22';
    ctx.shadowBlur = 2;
    ctx.shadowOffsetX = 8;
    ctx.shadowOffsetY = 8;
    ctx.drawImage(img, 10, 10, 80, 80);
    done();
  };
  img.onerror = function(){}
  img.src = 'star.png';
};

tests['shadow integration'] = function(ctx){
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 10;
  ctx.shadowOffsetY = 10;
  ctx.shadowColor = '#eee';
  ctx.lineWidth = 3;

  var grad1 = ctx.createLinearGradient(105,0, 200,100);
  grad1.addColorStop(0,    'yellow');
  grad1.addColorStop(0.25, 'red');
  grad1.addColorStop(0.75, 'blue');
  grad1.addColorStop(1,    'limegreen');

  var grad2 = ctx.createRadialGradient(50,50,10,50,50,50);
  grad2.addColorStop(0,    'yellow');
  grad2.addColorStop(0.25, 'red');
  grad2.addColorStop(0.75, 'blue');
  grad2.addColorStop(1,    'limegreen');

  // linear grad box
  ctx.fillStyle = grad1;
  ctx.fillRect(105,0, 100,100);

  // skyblue box
  ctx.fillStyle = "skyblue";
  ctx.fillRect(105,101, 100,100);

  // radial grad oval
  ctx.beginPath();
  ctx.arc(50, 50, 50, 0, Math.PI*2, false);
  ctx.fillStyle = grad2;
  ctx.fill();

  // gold oval
  ctx.beginPath();
  ctx.arc(50, 151, 50, 0, Math.PI*2, false);
  ctx.fillStyle = "gold";
  ctx.fill();
};

tests['font state'] = function(ctx){
  ctx.save();
  ctx.font = '20px Impact';
  ctx.fillText('Bam!', 50, 80);

  ctx.save();
  ctx.font = '10px Arial';
  ctx.fillText('Boom!', 50, 100);

  ctx.restore();
  ctx.fillText('Bam again!', 50, 120);

  ctx.restore();
  ctx.fillText('Boom again!', 50, 140);
};

tests['drawImage(img,0,0)'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img, 0, 0);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img) jpeg'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img,0,0,100,100);
    done();
  };
  img.onerror = function(){}
  img.src = 'face.jpeg';
};

tests['drawImage(img,x,y)'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img, 5, 25);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img,x,y,w,h) scale down'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img, 25, 25, 10, 10);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img,x,y,w,h) scale up'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img, 0, 0, 200, 200);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img,x,y,w,h) scale vertical'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img, 0, 0, img.width, 200);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img,sx,sy,sw,sh,x,y,w,h)'] = function(ctx, done){
  var img = new Image;
  img.onload = function(){
    ctx.drawImage(img, 13, 13, 45, 45, 25, 25, img.width / 2, img.height / 2);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img,0,0) globalAlpha'] = function(ctx, done){
  var img = new Image;
  ctx.fillRect(50,50,30,30);
  ctx.globalAlpha = .5;
  img.onload = function(){
    ctx.drawImage(img, 0, 0);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['drawImage(img,0,0) clip'] = function(ctx, done){
  ctx.arc(50,50,50,0,Math.PI * 2,false);
  ctx.stroke();
  ctx.clip();
  var img = new Image;
  ctx.fillRect(50,50,30,30);
  ctx.globalAlpha = .5;
  img.onload = function(){
    ctx.drawImage(img, 0, 0);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['putImageData()'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,10,10);
};

tests['putImageData() 2'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  var data = ctx.getImageData(25,25,50,50);
  ctx.putImageData(data,10,10);
};

tests['putImageData() 3'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  var data = ctx.getImageData(10,25,10,50);
  ctx.putImageData(data,50,10);
};

tests['putImageData() 4'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  ctx.strokeRect(30,30,30,30);
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,30,30,10,10,30,30);
};

tests['putImageData() 5'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  ctx.strokeRect(60,60,50,30);
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,60,60,0,0,50,30);
};

tests['putImageData() 6'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  ctx.strokeRect(60,60,50,30);
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,60,60,10,0,35,30);
};

tests['putImageData() 7'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  ctx.strokeRect(60,60,50,30);
  ctx.translate(20,20);
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,60,60,10,20,35,-10);
};

tests['putImageData() 8'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  ctx.translate(20,20);
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,-10,-10,0,20,35,30);
};

tests['putImageData() 9'] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
  ctx.translate(20,20);
  var data = ctx.getImageData(0,0,50,50);
  ctx.putImageData(data,-10,-10,0,20,500,500);
};

tests['putImageData() 10'] = function(ctx){
  ctx.fillStyle = 'rgba(255,0,0,1)'
  ctx.fillRect(0,0,50,100);
  ctx.fillStyle = 'rgba(0,255,0,1)'
  ctx.fillRect(50,0,50,100);
  ctx.fillStyle = 'rgba(0,0,255,1)'
  ctx.fillRect(100,0,50,100);

  var data = ctx.getImageData(0,0,120,20);
  ctx.putImageData(data,20,120);
};

tests['putImageData() alpha'] = function(ctx){
  ctx.fillStyle = 'rgba(255,0,0,0.5)'
  ctx.fillRect(0,0,50,100);
  ctx.fillStyle = 'rgba(0,255,0,0.5)'
  ctx.fillRect(50,0,50,100);
  ctx.fillStyle = 'rgba(0,0,255,0.5)'
  ctx.fillRect(100,0,50,100);

  var data = ctx.getImageData(0,0,120,20);
  ctx.putImageData(data,20,120);
};

tests['putImageData() alpha 2'] = function(ctx){
  ctx.fillStyle = 'rgba(255,0,0,0.2)'
  ctx.fillRect(0,0,50,100);
  ctx.fillStyle = 'rgba(0,255,0,0.5)'
  ctx.fillRect(50,0,50,100);
  ctx.fillStyle = 'rgba(0,0,255,0.75)'
  ctx.fillRect(100,0,50,100);

  var data = ctx.getImageData(0,0,120,20);
  ctx.putImageData(data,20,120);
};

tests['putImageData() globalAlpha'] = function(ctx){
  ctx.globalAlpha = 0.5
  ctx.fillStyle = '#f00'
  ctx.fillRect(0,0,50,100);
  ctx.fillStyle = '#0f0'
  ctx.fillRect(50,0,50,100);
  ctx.fillStyle = '#00f'
  ctx.fillRect(100,0,50,100);

  var data = ctx.getImageData(0,0,120,20);
  ctx.putImageData(data,20,120);
};

tests['putImageData() png data'] = function(ctx, done){
  var img = new Image;
  ctx.fillRect(50,50,30,30);
  img.onload = function(){
    ctx.drawImage(img,0,0,200,200);
    var imageData = ctx.getImageData(0,0,50,50)
      , data = imageData.data;
    for (var i = 0, len = data.length; i < len; i += 4) {
      data[i + 3] = 80;
    }
    ctx.putImageData(imageData,50,50);
    done();
  };

  img.onerror = function(){}

  img.src = 'state.png';
};

tests['putImageData() png data 2'] = function(ctx, done){
  var img = new Image;
  ctx.fillRect(50,50,30,30);
  img.onload = function(){
    ctx.drawImage(img,0,0,200,200);
    var imageData = ctx.getImageData(0,0,50,50)
      , data = imageData.data;
    for (var i = 0, len = data.length; i < len; i += 4) {
      data[i + 3] = 80;
    }
    ctx.putImageData(imageData,50,50,10,10,20,20);
    done();
  };

  img.onerror = function(){}

  img.src = 'state.png';
};

tests['putImageData() png data 3'] = function(ctx, done){
  var img = new Image;
  ctx.fillRect(50,50,30,30);
  img.onload = function(){
    ctx.drawImage(img,0,0,200,200);
    var imageData = ctx.getImageData(0,0,50,50)
      , data = imageData.data;
    for (var i = 0, len = data.length; i < len; i += 4) {
      data[i + 0] = data[i + 0] * 0.2;
      data[i + 1] = data[i + 1] * 0.2;
      data[i + 2] = data[i + 2] * 0.2;
    }
    ctx.putImageData(imageData,50,50);
    done();
  };
  img.onerror = function(){}
  img.src = 'state.png';
};

tests['setLineDash']  = function(ctx){
  ctx.setLineDash([10, 5, 25, 15]);
  ctx.lineWidth = 17;

  var y=5;
  var line = function(lineDash, color){
    ctx.setLineDash(lineDash);
    if (color) ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.moveTo(0,   y);
    ctx.lineTo(200, y);
    ctx.stroke();
    y += ctx.lineWidth + 4;
  };

  line([15, 30], "blue");
  line([], "black");
  line([5,10,15,20,25,30,35,40,45,50], "purple");
  line([8], "green");
  line([3, 3, -30], "red");
  line([4, Infinity, 4]);
  line([10, 10, NaN]);
  line((function(){
    ctx.setLineDash([8]);
    var a = ctx.getLineDash();
    a[0] -= 3;
    a.push(20);
    return a;
  })(), "orange");
};

tests['lineDashOffset']  = function(ctx){
  ctx.setLineDash([10, 5, 25, 15]);
  ctx.lineWidth = 4;

  var y=5;
  var line = function(lineDashOffset, color){
    ctx.lineDashOffset = lineDashOffset;
    if (color) ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.moveTo(0,   y);
    ctx.lineTo(200, y);
    ctx.stroke();
    y += ctx.lineWidth + 4;
  };

  line(-10, "black");
  line(0);
  line(10);
  line(20);
  line(30);
  line(40, "blue");
  line(NaN)
  line(50, "green");
  line(Infinity)
  line(60, "orange");
  line(-Infinity)
  line(70, "purple");
  line(void 0)
  line(80, "black");
  line(ctx.lineDashOffset + 10);
  for (var i=0; i<10; i++)
    line(90 + i/5, "red");
}


tests['fillStyle=\'hsl(...)\''] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'hsl(' + (360-60*i) + ',' +
                       (100-16.66*j) + '%,' + (50+(i+j)*(50/12)) + '%)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
};

tests['fillStyle=\'hsla(...)\''] = function(ctx){
  for (i=0;i<6;i++){
    for (j=0;j<6;j++){
      ctx.fillStyle = 'hsla(' + (360-60*i) + ',' +
                       (100-16.66*j) + '%,50%,' + (1-0.16*j) + ')';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
};