JavaScript
HTML5
Chrome
canvas
blob

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

More than 1 year has 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