Help us understand the problem. What is going on with this article?

Chromium で Canvas.toBlob が試験的に利用可能になっています

More than 3 years have passed since last update.

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 にあったような苦役から開放されます。

  1. Base64 変換が不要に ( atob )
  2. JavaScript 側でのバッファの確保が不要に ( new Uint8Array )
  3. charCodeAt のヘビーループが不要に ( for 〜 charCodeAt )
  4. これら一連の同期処理が非同期化される

特に非同期化です。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

uupaa
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away