LoginSignup
36

More than 5 years have passed since last update.

posted at

updated at

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
36