5年前から多くの注目を集めていた Chromium の Issue 67587 に進捗があり、 Canvas.toBlob が Chrome Canary 47.0.2498.0 で試験的に利用可能になりました。
追記: Safari 11 にも Canvas.toBlob が実装されました。
本題
★ Issue 67587: Add toBlob to Canvas element.
174 people starred this issue.Currently, the html5 canvas element has a toDataURL method, which outputs a DOM string representing the backing bitmap. With the File API now widely implemented, it is appropriate to add a similar getBlob method, one which would return a Blob object. Passing Blob pointers around is quite a bit more efficient than passing large base64 encoded strings.
https://code.google.com/p/chromium/issues/detail?id=67587#c113
interface HTMLCanvasElement : HTMLElement {
   [RaisesException, RuntimeEnabled=ExperimentalCanvasFeatures] void toBlob(FileCallback? _callback, optional DOMString type = null, optional any arguments);
}
Canvas.toBlob の実装は以下のようになっています。
void HTMLCanvasElement::toBlob(FileCallback* callback, const String& mimeType, const ScriptValue& qualityArgument, ExceptionState& exceptionState) const
{
    if (!originClean()) {
        exceptionState.throwSecurityError("Tainted canvases may not be exported.");
        return;
    }
    File* resultBlob = nullptr;
    if (!isPaintable()) {
        // If the canvas element's bitmap has no pixels
        return;
    }
    double quality;
    if (!qualityArgument.isEmpty()) {
        v8::Local<v8::Value> v8Value = qualityArgument.v8Value();
        if (v8Value->IsNumber()) {
            quality = v8Value.As<v8::Number>()->Value();
        }
    }
    String encodingMimeType = toEncodingMimeType(mimeType);
    ImageData* imageData = toImageData(BackBuffer);
    ScopedDisposal<ImageData> disposer(imageData);
    // Perform image encoding
    Vector<char> encodedImage;
    ImageDataBuffer(imageData->size(), imageData->data()->data()).encodeImage(encodingMimeType, &quality, &encodedImage);
    resultBlob = File::create(encodedImage.data(), encodedImage.size(), encodingMimeType);
    Platform::current()->mainThread()->postTask(FROM_HERE, bind(&FileCallback::handleEvent, callback, resultBlob));
}
Canvas.toDataURL を駆使して Canvas.toBlob 相当の結果を得るコーディング(polyfill_Canvas_toBlob)は可能だったのですが、それはただ単に可能というだけであり、とてもとてもつらい状態でした。
function polyfill_Canvas_toBlob(canvas, callback, type, encoderOptions) {
    type = type || "image/png";
    var url = canvas.toDataURL(type, encoderOptions);
    var bin = atob(url.split(",")[1]);
    var u8a = new Uint8Array(bin.length);
    for (var i = 0, iz = bin.length; i < iz; ++i) {
        u8a[i] = bin.charCodeAt(i);
    }
    callback( new Blob([u8a.buffer], { "type": type }) );
}
Canvas.toBlob では、polyfill_Canvas_toBlob にあったような苦役から開放されます。
- Base64 変換が不要に ( atob)
- JavaScript 側でのバッファの確保が不要に ( new Uint8Array)
- charCodeAt のヘビーループが不要に ( for 〜 charCodeAt)
- これら一連の同期処理が非同期化される
特に非同期化です。Canvas のキャッシュを作成する際に UI Thread が固まらなくなります。
モバイル端末の画素数が激増する昨今において、これは大きな進歩です。
Sample Code
Canvas.toBlob と、toBlob の代わりに toDataURL を使ったコードはこのようなります。
chrome://flags/#enable-experimental-canvas-features を有効にすると試用できます。
<canvas width="150"></canvas>
<script>
window.onload = function() {
    var canvas = document.querySelector("canvas");
    var ctx = canvas.getContext("2d");
    draw(ctx);
    if (canvas.toBlob) {
        canvas.toBlob(_copyCanvas); // native
    }
    polyfill_Canvas_toBlob(canvas, _copyCanvas);
};
function _copyCanvas(blob) {
    var img = document.createElement("img");
    var blobURL = URL.createObjectURL(blob);
    img.onload = function() {
        URL.revokeObjectURL(blobURL); // GC
    };
    img.src = blobURL;
    document.body.appendChild(img);
}
function polyfill_Canvas_toBlob(canvas, callback, type, encoderOptions) {
    type = type || "image/png";
    var url = canvas.toDataURL(type, encoderOptions);
    var bin = atob(url.split(",")[1]);
    var u8a = new Uint8Array(bin.length);
    for (var i = 0, iz = bin.length; i < iz; ++i) {
        u8a[i] = bin.charCodeAt(i);
    }
    callback( new Blob([u8a.buffer], { "type": type }) );
}
function draw(ctx) {
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, 100, 100);
    ctx.fillStyle = "blue";
    ctx.fillRect(10, 10, 100, 100);
}
</script>
Result
Conclusion
- Canvas.toBlob 良い。とても良い
- Firefox や IE 10+ では既に利用可能な状態です
- 
残りは Safari だけ。WebKit も追従してほしい
