質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

Q&A

解決済

2回答

3123閲覧

CanvasにdrawImageで描写した画像をtoDataURLで取得したい

退会済みユーザー

退会済みユーザー

総合スコア0

canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

0グッド

0クリップ

投稿2019/08/23 01:15

編集2019/08/23 04:51

複数のcanvasを別のcanvas上でdrawImageを使って結合し、結合したcanvasのbase64を取得してサーバーに送信、png画像として保存したいのですが結合したcanvasのbase64がうまく取得できません。
base64っぽい文字列は取得できているのですが、保存した画像を見ると真っ白になっています。

let c = document.getElementById("canvas"); let ctx = c.getContext("2d"); let y = 0;//結合する画像のcanvasでの表示位置 //結合に使用するcanvasの大きさを設定 $("#canvas").attr({ height: ($("#img").height() * items.length) });//高さは保存した全ページを縦に表示できる高さにする $("#canvas").attr({ width: $("#img").width() }); _.forEach(items, (data) => {//itemsに結合したいcanvasのデータが格納されている let img = new Image(); img.src = data.imageData;//結合したいcanvasのbase64 img.onload = function () { ctx.drawImage(img, 0, y); y += $("#canvas").height(); } }); result = c.toDataURL("image/png");

色々試したところ、最後のbase64の取得を5秒待って実行されるようにすると正常に画像が保存できました。

setTimeout(function () { result = c.toDataURL("image/png"); },5000)

おそらくdrawImageでの描写が完全に終わる前に、base64を取得していたのが原因だと思われますが、
描写が完全に終わってからtoDataURLを実行する方法はあるのでしょうか?
上記のように5秒も待ってしまうと処理が遅くなりますし、待ち時間を短くすると画像の描写が完全に終わる前にbase64を取得してしまうことがあるかもしれないので、そのようなイベントやメソッドがあればいいのですが見つかりませんでした…

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

_ というのは Lodash(underscore.js?) を使っているのでしょうか?
https://lodash.com/docs/4.17.4#forEach
items が配列であれば第二引数にインデックスが来るので取得しておきます。

JavaScript

1_.forEach(items, (data, index) => {

すべての画像の load を待ってからでないと実行できないので、Promise(Deferred) を使います。
drawImage() の dy の計算を間違えていないか確認してください。

JavaScript

1const height = $("#img").height(); 2const promises = [];

jQuery

1 const deferred = new $.Deferred(); 2 promises.push(deferred.promise()); 3 let img = new Image(); 4 img.onload = function () { 5 ctx.drawImage(img, 0, height * index); 6 deferred.resolve(); 7 } 8 9 img.src = data.imageData;

jQuery

1$.when.apply($, promises).done(() => { 2 result = c.toDataURL("image/png"); 3});

https://api.jquery.com/category/deferred-object/
https://foreignkey.toyao.net/archives/1147

投稿2019/08/23 07:02

x_x

総合スコア13749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/08/23 08:29

ご回答ありがとうございます! _ は Lodashです。 ちょっと来週になってしまいそうですが、 promissの使い方などもあまりわかっていないので、調べて試してみます!
退会済みユーザー

退会済みユーザー

2019/08/26 06:03

教えていただいた正常にbase64形式の文字列が取得できました! 迅速にご回答いただき助かりました。 promiseって複数の結果を受け取れるんですね。 ありがとうございました!
guest

0

以下の記事は参考になりませんか?

canvas の画像をアップロード
http://surferonwww.info/BlogEngine/post/2015/07/02/upload-image-drawn-on-html5-canvas.aspx

不明な点があれば聞いてください。

【追記】

下の 2019/08/26 13:14 の私のコメントに「もう少し考えてみました・・・回答欄にそのあたりを追記しておきます」と書きましたが、それを以下に追記します。

コメント欄に「複数の画像を扱うということが何か関係してる」と書きましたがそんなことはなくて、ごく単純に一枚だけ扱った場合でも canvas.toDataURL("image/png"); を image.onload のリスナの外に記述すると Data Url は取得できません。

逆に言うと、一枚でも複数でも image.onload のリスナの中に canvas.toDataURL("image/png"); を書けば Data Url は取得できます。もちろん setTimeout で待つ必要は有りません。

複数の画像の場合、複数の image.onload イベントに複数のリスナがアタッチされますが、そのリスナにどのように canvas.toDataURL("image/png"); を記述するかが考えどころです。

画像の読み込みにかかる時間が画像のサイズなどによって異なるので、load イベント発生の順番は不定となりますが、それに対応するために最後の load イベントの発生を待って、そのイベントリスナで希望する順番に各 image を drawImage で canvas に書き込むのがよさそうです。

そうすれば、そのリスナで canvas に書き込んだ後、同じリスナ内で canvas.toDataURL("image/png"); によって Data Url を取得できます。

検証に使ったコードを以下にアップしておきます。元の画像は 226px x 150px の .png または .jpg 画像です。(ASP.NET のページを利用していますが、そこは本題とは関係ないので気にしないでください)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="0076-Canvas.aspx.cs" Inherits="_0076_Canvas" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script type="text/javascript"> window.onload = function () { var srcs = [ "/Images/image1.png", "/Images/image2.jpg", "/Images/image3.jpg", "/Images/image4.jpg", "/Images/image5.jpg" ]; var canvas = document.getElementById('mycanvas'); var context = canvas.getContext('2d'); // 単純に一枚の画像の場合 // これは OK だが、canvas.toDataURL("image/png") をリスナの外に出すとダメ //var image = new Image(); //image.src = "/Images/image1.png"; //image.onload = function () { // context.drawImage(image, 0, 0); // var result = canvas.toDataURL("image/png"); // document.getElementById("image1").src = result; //}; // 質問者さんのコードと似たような感じにしてみましたが、load イベントの発生タイミング // は i の順番にならず、canvas に描かれる順番が保証されないのでダメ //var y = 0; //for (let i = 0; i < srcs.length; i++) { // let image = new Image(); // image.src = srcs[i]; // image.onload = function () { // context.drawImage(image, 0, y); // y += 150; // } //} // canvas に描かれる順番を保証するには以下のようにするのがよさそう。そうすれば // リスナの中に canvas.toDataURL("image/png"); を記述でき Data Url が取得できる var images = []; var loadCount = 0; for (let i = 0; i < srcs.length; i++) { images[i] = new Image(); images[i].src = srcs[i]; images[i].onload = function () { loadCount++; if (loadCount == images.length) { for (let j = 0; j < images.length; j++) { context.drawImage(images[j], 0, j * 150); } // image.onload のリスナの中でないと Data Url が取得できない var result = canvas.toDataURL("image/png"); document.getElementById("image1").src = result; } }; } // ここではダメ。Data Url は取得できない //var result = canvas.toDataURL("image/png"); //document.getElementById("image1").src = result; } </script> </head> <body> <form id="form1" runat="server"> <div style="float: right;"> <canvas id="mycanvas" width="266" height="750"></canvas> </div> <div> <img id="image1" alt="" /> </div> </form> </body> </html>

結果は以下のようになります。

イメージ説明

投稿2019/08/23 01:25

編集2019/08/26 05:10
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/08/23 01:59

コードを読んでみましたが、画面上のcanvasに画像を描写した後、ユーザーがボタンを押したらtoDataURLを実行してデータを取得という流れのようなので、画像が描写されたらボタンを押すなどの操作をせずtoDataURLを実行したいという今回の要望には合わないようです… すみません、ご回答ありがとうございます!
退会済みユーザー

退会済みユーザー

2019/08/23 02:11 編集

お役に立てずすみませんでした。 5 秒待つと取得できるそうなので関係ないかもしれませんが、ちょっと気になった点を書いておきます。 アップされていたコード let c = document.getElementById("#canvas"); の "#canvas" の # は不要では? 同じくアップされていたコード result = c.toDataURL("image/png"); の result に Data url 形式の文字列を取得したいのだと思いますが、であれば以下のようにするのでは? var context = document.getElementById('canvas').getContext('2d'); var result = context.canvas.toDataURL("image/png");
退会済みユーザー

退会済みユーザー

2019/08/23 03:03

>let c = document.getElementById("#canvas"); >の "#canvas" の # は不要では? 混乱させて申し訳ありません。 わかりやすいかと思い実際のコードと変数名など少し変えたときに#を付けてしまいました。 実際は#はついておりません。 >result = c.toDataURL("image/png"); >の result に Data url 形式の文字列を取得したいのだと思いますが、であれば以下のようにするのでは? >var context = document.getElementById('canvas').getContext('2d'); >var result = context.canvas.toDataURL("image/png"); その方法でもできるのでしょうか? 検索すると canvasのエレメントオブジェクトを取得してtoDataURL()で取得する方法が多いので この方法で取得していました。 参考: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL https://syncer.jp/javascript-reference/canvas-todataurl
退会済みユーザー

退会済みユーザー

2019/08/23 03:56 編集

toDataUrl メソッドの件ですが、紹介した記事にも書きましたように、実際自分は上のコメントに書いたようにしています。(自分のコードでは jpeg ですがそこは png でも同じことかと) ただ、上にも書きましたが、5 秒待つと取得できるそうですので、関係ないかも。
退会済みユーザー

退会済みユーザー

2019/08/23 04:01

結合などの操作をしないで、単純に一枚の画像から Data Url 文字列を取得してみるとか、切り分けしてどこに問題があるか探ってみてはいかがでしょう。
退会済みユーザー

退会済みユーザー

2019/08/23 05:15

toDataUrlでのDataUrl文字列取得部分の書き方の修正、結合の操作をしないでtoDataUrlを実行する、 両方それぞれ試しましたがやはり保存した画像が真っ白になってしまいます。 また試しに画像描写処理後5秒ではなく1ミリ秒待つように修正したところ、正常に画像が保存できました。 たった1ミリ秒ですが、 1ミリ秒待たずに取得したDataUrl文字列と1ミリ秒待ってから取得したDataUrl文字列を比較したところ違っていたので、 やはりcanvasへdrawImageメソッドで画像が描写されるのが遅いのが原因な気がします。 最悪1秒くらい待てば画像が正常に保存できないことは防げるかとは思いますが、やはりcanvasへの画像の描写が完全に終わってから DataUrl文字列取得する、という書き方のほうが正しい気がします… もう少し調べようと思います。
退会済みユーザー

退会済みユーザー

2019/08/24 05:17 編集

ごく単純に一枚だけ扱ってもダメでしたか? drawImage は同期メソッドのはずなのですが・・・ 後で検証してみます。
退会済みユーザー

退会済みユーザー

2019/08/24 14:01

上に書いたように検証してみましたが、ごく単純に一枚だけ扱った場合は問題ありませんでした(drawImage は同期メソッドなので、そもそも待つ必要はないようです)。 複数の画像を扱うということが何か関係してるのかもしれませんね。こういう記事 http://seckie.hatenablog.com/entry/2014/12/24/120104 もありました。もう少し考えてみます。
退会済みユーザー

退会済みユーザー

2019/08/26 04:14

もう少し考えてみました。 上に「複数の画像を扱うということが何か関係してる」と書きましたがそんなことはなくて、ごく単純に一枚だけ扱った場合でも canvas.toDataURL("image/png"); を image.onload のリスナの外に記述すると Data Url は取得できません。 回答欄にそのあたりを追記しておきます。
退会済みユーザー

退会済みユーザー

2019/08/26 06:05

上記の方法で正常に画像を保存できました。 また、詳しくご説明いただいたおかげでやっと原因を理解できました。 複数のimage.onloadイベントのリスナに記述されたcanvas.toDataURL("image/png")がすべて実行される前に canvas.toDataURL("image/png")でbase64形式の文字列を取得していたため正常な文字列が取得できなかった、ということですね。 複数のImageオブジェクトに対して一括でonloadイベントを呼び出せるのは初めて知りました。 ありがとうございました!
退会済みユーザー

退会済みユーザー

2019/08/26 08:36

> 複数のimage.onloadイベントのリスナに記述されたcanvas.toDataURL("image/png")がすべて実行される前にcanvas.toDataURL("image/png")でbase64形式の文字列を取得していたため正常な文字列が取得できなかった、ということですね。 いえ、そうではなくて、質問者さんのコードで言うと ctx.drawImage(img, 0, y); が実行される前に result = c.toDataURL("image/png"); が実行されたので Data Url が取得できなかったということだと思います。 forEach ループで各 image の onload イベントにリスナ function () { ... } をアタッチしていますが、それは単にリスナをアタッチしただけで、リスナの中の ctx.drawImage(img, 0, y); が実行されるのはもっと後の onload イベントが発生した時です。 質問者さんのコードは、リスナをアタッチした後、イベントの発生を待たず、即 c.toDataURL("image/png"); 実行することになります。その時点では canvas に画像が drawImage されていないので Data Url が取得できないということです。 それから、回答にも書きましたが、「画像の読み込みにかかる時間が画像のサイズなどによって異なるので、load イベント発生の順番は不定となります」という点には十分注意してください。今の質問者さんのコードではその問題が回避できません。Promise(Deferred) を使っても、そこは同じだと思われます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問