DataURI が 2MB までしか使えなくて Canvas 画像の保存に困る
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 1
◆ DataURI 自体は 2MB 以上にもできるけど URL として開くときは 2MB まで
◆ a タグで開いて保存しようとするとエラーになる
◆ copy() で DataURI の文字列でクリップボードに保存して Node.js でファイルとして保存できる
◆ URL.createObjectURL だと 200MB 超えても保存できるので保存するときはこっちがよさそう
◆ a タグで開いて保存しようとするとエラーになる
◆ copy() で DataURI の文字列でクリップボードに保存して Node.js でファイルとして保存できる
◆ URL.createObjectURL だと 200MB 超えても保存できるので保存するときはこっちがよさそう
便利なので普段から DataURI 形式をよく使っています
ですが DataURI 形式にも欠点がありました
ファイルサイズです
何が原因だろうととりあえず img タグの src にエラーになっていた DataURI を設定してみたらちゃんと表示されました
保存するために a タグに download 属性をつけて click() しているので URL として開こうとすると制限にかかるのかもしれません
試しに window.open してみたらエラーじゃなくクラッシュしました
やっぱり Canvas で DataURI 形式で出力する部分や DataURI の扱い自体が対応していないんじゃなく URL として開くのがだめのようです
文字列の length を見ると エラーになっていたのは 2MB ちょっとのサイズで 保存できていたのは 1MB 台でした
昔何処かで Chrome は 2MB というのをみたような覚えもありますし 2MB が限度みたいです
[Download]
[preview]
今回は画像なので img タグで表示してデスクトップなどにドラッグして保存もできますがひとつひとつ手作業で画像表示してドラッグして保存はさすがに嫌です
それに今回は Canvas の画像だからできるものの 画像じゃないとできない方法です
なので DataURI のまま取り出して Node.js で画像として保存するということをやってみます
表示してコピーするのだと 2MB を超えてるのでちょっと重いです
なので
これでクリップボードに DataURI をコピーします
copy はコンソールから使えるものなので devtools で行います
次に Node.js でファイル形式で保存します
Node.js だと 2MB 超えていても関係なく保存できます
上の *** のところに DataURI 文字列をいれます
配列形式にしているので複数まとめてもできます
こんな感じです
これだと 2MB を超えたファイルも保存されました
サイズが大きいのでダウンロードが終わったら URL を破棄したいのですが a タグダウンロードで終わったら呼び出されるコールバック登録はできなかったと思うので適当に setTimeout して revokeObjectURLしています
上で書いた方法は何かの web サービスのページから個人的に画像保存したいときには使えますが 自分で作るアプリケーションに保存機能をつけるというのには向かないです
ユーザに devtools 開いたり Node.js で保存してね というのはちょっとハードル高いですからね
ですが createObjectURL だとページ内の JavaScript だけで完結できます
copy(urls) と Node.js の方法でやっていたとき まとめて保存しようとして 200MB くらいを copy しようとすると devtools が落ちてしまいました
createObjectURL だとこれでもいけるかな っとやってみると問題なく保存できました
もうちょっと大きくなって 270MB くらいまでいくと Chrome の最大文字数の都合で複数の DataURI を 1 つの文字列にしようとしたところで文字列生成できないとエラーになります
JavaScript の文字列の最大の長さ
今回は全部半角文字なので 270MB は 2億7000万文字です
通常ブラウザで 1 ファイルで 200MB 以上のファイルを作って保存なんてしないでしょうし DataURI で保存できないなら URL.createObjectURL を使えば大丈夫そうです
ですが DataURI 形式にも欠点がありました
ファイルサイズです
Chrome だと 2MB までらしい
Canvas の画像を保存していたのですが 一部のファイルだけ保存エラーになっていました何が原因だろうととりあえず img タグの src にエラーになっていた DataURI を設定してみたらちゃんと表示されました
保存するために a タグに download 属性をつけて click() しているので URL として開こうとすると制限にかかるのかもしれません
試しに window.open してみたらエラーじゃなくクラッシュしました
やっぱり Canvas で DataURI 形式で出力する部分や DataURI の扱い自体が対応していないんじゃなく URL として開くのがだめのようです
文字列の length を見ると エラーになっていたのは 2MB ちょっとのサイズで 保存できていたのは 1MB 台でした
昔何処かで Chrome は 2MB というのをみたような覚えもありますし 2MB が限度みたいです
[Download]
function download(url, filename){
const a = document.createElement("a")
a.download = filename
a.href = url
a.click()
}
const a = document.createElement("a")
a.download = filename
a.href = url
a.click()
}
[preview]
function preview(url){
const i = new Image()
i.src = url
document.body.append(i)
}
const i = new Image()
i.src = url
document.body.append(i)
}
コピーして Node.js で保存する
ブラウザでファイルを保存となると download 属性付き a タグクリックくらいしかないです今回は画像なので img タグで表示してデスクトップなどにドラッグして保存もできますがひとつひとつ手作業で画像表示してドラッグして保存はさすがに嫌です
それに今回は Canvas の画像だからできるものの 画像じゃないとできない方法です
なので DataURI のまま取り出して Node.js で画像として保存するということをやってみます
表示してコピーするのだと 2MB を超えてるのでちょっと重いです
なので
copy(canvas.toDataURL())
これでクリップボードに DataURI をコピーします
copy はコンソールから使えるものなので devtools で行います
次に Node.js でファイル形式で保存します
Node.js だと 2MB 超えていても関係なく保存できます
const fs = require("fs")
const urls = [***]
urls.forEach(save)
function save(url, num){
const b64 = url.replace(/^data:image\/png;base64,/, "")
fs.writeFile(`image${num}.png`, b64, "base64", function(err) {
err && console.log(err);
})
}
const urls = [***]
urls.forEach(save)
function save(url, num){
const b64 = url.replace(/^data:image\/png;base64,/, "")
fs.writeFile(`image${num}.png`, b64, "base64", function(err) {
err && console.log(err);
})
}
上の *** のところに DataURI 文字列をいれます
配列形式にしているので複数まとめてもできます
URL.createObjectURL()
と ここまで書いて URL.createObjectURL 使えば Node.js 使わなくてもいいんじゃない?とひらめきましたfunction dataURItoBlob(dataURI) {
const b64 = atob(dataURI.split(',')[1])
const u8 = Uint8Array.from(b64.split(""), e => e.charCodeAt())
return new Blob([u8], {type: "image/png"})
}
function download2(dataURI, filename){
const blob = dataURItoBlob(dataURI)
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.download = filename
a.href = url
a.click()
// ダウンロードの時間がわからないので多めに 最低 3s, 1MiB / sec として
// 終わった頃に revoke する
setTimeout(() => {
URL.revokeObjectURL(url)
}, Math.max(3000, 1000 * dataURI.length / 1024 * 1024))
}
const b64 = atob(dataURI.split(',')[1])
const u8 = Uint8Array.from(b64.split(""), e => e.charCodeAt())
return new Blob([u8], {type: "image/png"})
}
function download2(dataURI, filename){
const blob = dataURItoBlob(dataURI)
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.download = filename
a.href = url
a.click()
// ダウンロードの時間がわからないので多めに 最低 3s, 1MiB / sec として
// 終わった頃に revoke する
setTimeout(() => {
URL.revokeObjectURL(url)
}, Math.max(3000, 1000 * dataURI.length / 1024 * 1024))
}
こんな感じです
これだと 2MB を超えたファイルも保存されました
サイズが大きいのでダウンロードが終わったら URL を破棄したいのですが a タグダウンロードで終わったら呼び出されるコールバック登録はできなかったと思うので適当に setTimeout して revokeObjectURLしています
上で書いた方法は何かの web サービスのページから個人的に画像保存したいときには使えますが 自分で作るアプリケーションに保存機能をつけるというのには向かないです
ユーザに devtools 開いたり Node.js で保存してね というのはちょっとハードル高いですからね
ですが createObjectURL だとページ内の JavaScript だけで完結できます
文字列の最大長でも保存できた
createObjectURL ですが Chrome での文字列の最大長のテキストでも保存できましたcopy(urls) と Node.js の方法でやっていたとき まとめて保存しようとして 200MB くらいを copy しようとすると devtools が落ちてしまいました
createObjectURL だとこれでもいけるかな っとやってみると問題なく保存できました
もうちょっと大きくなって 270MB くらいまでいくと Chrome の最大文字数の都合で複数の DataURI を 1 つの文字列にしようとしたところで文字列生成できないとエラーになります
JavaScript の文字列の最大の長さ
今回は全部半角文字なので 270MB は 2億7000万文字です
通常ブラウザで 1 ファイルで 200MB 以上のファイルを作って保存なんてしないでしょうし DataURI で保存できないなら URL.createObjectURL を使えば大丈夫そうです