Как проверить, испорчен ли элемент холста?

Основной сценарий

Я загружаю несколько изображений на стороне клиента. Некоторые из них из другого домена, некоторые - нет. Некоторые из них могут быть доступны с помощью атрибута crossOrigin, некоторые из них не являются.

Основным требованием является получение dataURL для изображений, где это возможно.

Вопрос

После рисования изображения в элемент canvas (который мне нужно, чтобы получить dataURL, правильно?), как я могу проверить без блока try ... catch, было ли пятно испорчено? Если полотно испорчено, я больше не могу использовать toDataURL() (см. MDN).

var image = new Image(),
    canvas = document.createElement( 'canvas' ),
    context = canvas.getContext( '2d' );

image.onload = function(){

  // adjust dimensions of canvas
  canvas.width = image.width;
  canvas.height = image.height;

  // insert image
  context.drawImage( image, 0, 0 );

  // how to check here?

  // get dataurl
  dataurl = tmpCanvas.toDataURL();

  // do stuff with the dataurl
};
image.src = src;  // some cross origin image

Ответ 1

Вот решение, которое не добавляет свойства к собственным объектам:

function isTainted(ctx) {
    try {
        var pixel = ctx.getImageData(0, 0, 1, 1);
        return false;
    } catch(err) {
        return (err.code === 18);
    }
}

Теперь просто проверьте, сделав это:

if (isTainted(ctx)) alert('Sorry, canvas is tainted!');

Изменить: СЕЙЧАС я вижу, что вам нужно решение без try-catch. Тем не менее, это правильный способ проверить, нет ли источника чистого фона, открытого пользователю (только для внутреннего использования). Не рекомендуется добавлять свойства к исходному объекту.

Ответ 2

Здесь косвенный тест на CORS tainting, который не использует try-catch:

A Демо: http://jsfiddle.net/m1erickson/uDt2K/

Он работает, устанавливая флаг image.tainted=true перед загрузкой изображения

Затем в image.onload, context.getImageData триггеры/не запускают нарушение CORS.

Если никакого нарушения не происходит, флаг tainted устанавливается на false (image.tainted=false).

var img=new Image();

// set a "tainted flag to the image to true (initialize as tainted)
img.tainted=true;

img.onload=function(){

    // try an action that triggers CORS security

    var i=ctx.getImageData(1,1,1,1);

    // if we don't get here, we violated CORS and "tainted" remains true

    // if we get here, CORS is happy so set the "tainted" flag to false

    img.tainted=false;

};

// test with tainted image

img.src="http://pp-group.co.uk/wp/wp-content/uploads/2013/10/house-illustration-web.gif";

Поскольку image.onload является асинхронным, ваш код вне image.onload будет выполняться даже после нарушения CORS.

Вот пример кода, который:

  • создает объект изображения
  • проверяет, соответствует ли изображение CORS
  • выполняет один обратный вызов, если изображение соответствует требованиям.
  • выполняет другой обратный вызов, если изображение не соответствует

Пример кода:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // known CORS violator
    var src1="http://pp-group.co.uk/wp/wp-content/uploads/2013/10/house-illustration-web.gif";
    // known CORS compliant
    var src2="https://dl.dropboxusercontent.com/u/139992952/houseIcon.png";

    // callbacks depending on if the image causes tainted canvas
    function tainted(img){console.log("tainted:",img.src);}
    function notTainted(img){console.log("not tainted:",img.src);}

    // testing
    var image1=newImage(src1,tainted,notTainted);
    var image2=newImage(src2,tainted,notTainted);


    function newImage(src,callWhenTainted,callWhenNotTainted){

        // tmpCanvas to test CORS
        var tmpCanvas=document.createElement("canvas");
        var tmpCtx=tmpCanvas.getContext("2d");
        // tmpCanvas just tests CORS, so make it 1x1px
        tmpCanvas.width=tmpCanvas.height=1;

        var img=new Image();
        // set the cross origin flag (and cross our fingers!)
        img.crossOrigin="anonymous";
        img.onload=function(){
            // add a tainted property to the image 
            // (initialize it to true--is tainted)
            img.tainted=true;
            // draw the img on the temp canvas
            tmpCtx.drawImage(img,0,0);
            // just in case this onload stops on a CORS error...
            // set a timer to call afterOnLoad shortly
            setTimeout(function(){
                afterOnLoad(img,callWhenTainted,callWhenNotTainted);
            },1000);  // you can probably use less than 1000ms
            // try to violate CORS
            var i=tmpCtx.getImageData(1,1,1,1);
            // if we get here, CORS is OK so set tainted=false
            img.tainted=false;
        };
        img.src=src;
        return(img);
    }

    // called from image.onload
    // at this point the img.tainted flag is correct
    function afterOnLoad(img,callWhenTainted,callWhenOK){
        if(img.tainted){
            // it tainted
            callWhenTainted(img);
        }else{
            // it OK, do dataURL stuff
            callWhenOK(img);
        }
    }


}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=360 height=281></canvas>
</body>
</html>