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

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

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

Electronは、HTML5とNode.jsというWebの技術を用いてデスクトップアプリケーションを作成できるクロスプラットフォームな実行環境です。

canvas

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

JavaScript

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

Q&A

解決済

1回答

8239閲覧

canvasのtoDataURLが想定している内容を返さない

violet_ncnc

総合スコア7

Electron

Electronは、HTML5とNode.jsというWebの技術を用いてデスクトップアプリケーションを作成できるクロスプラットフォームな実行環境です。

canvas

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

JavaScript

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

0グッド

1クリップ

投稿2017/11/07 21:34

編集2017/11/07 21:38

###前提・実現したいこと
electronでローカルファイルを複数のcanvasに分けて表示し、あとからcanvasを結合してpng形式でダウンロードする処理を書いています

###発生している問題・エラーメッセージ
結合後のcanvasのtoDataURL()でブランクの画像データが取得され、canvasの内容が取得できない(canvasは正常に表示できています)

###該当のソースコード

javascript

1 let imgBase = new Image(); 2 let imgMoment = new Image(); 3 if(document.getElementById('base').checked === true){ 4 imgBase.src = document.getElementById(cvsId + 'base').toDataURL(); 5 } 6 let radio = document.querySelectorAll("input[type='radio']"); 7 for(let i = 0; i < radio.length; i++){ 8 if(radio[i].checked === true){ 9 imgMoment.src = document.getElementById(cvsId + radio[i].id).toDataURL(); 10 } 11 } 12 let output = document.createElement('canvas'); 13 let cntOut = output.getContext('2d'); 14 output.width = document.getElementById(cvsId + 'base').width; 15 output.height = document.getElementById(cvsId + 'base').height; 16 imgBase.onload = () => { 17 if(imgBase !== null){ 18 cntOut.drawImage(imgBase, 0, 0, output.width, output.height); 19 } 20 } 21 imgMoment.onload = () => { 22 if(imgMoment !== null){ 23 cntOut.drawImage(imgMoment, 0, 0, output.width, output.height); 24 } 25 } 26 document.getElementById('main').appendChild(output) 27 let aLink = document.createElement('a'); 28 aLink.href = output.toDataURL(); 29 aLink.download = 'output.png'; 30 aLink.innerHTML = 'test'; 31 document.getElementById('main').appendChild(aLink); 32 aLink.dispatchEvent(new CustomEvent('click'));

###試したこと
上記の通りcanvasは表示できていることを確認済み
コンソールから直接結合後のcanvasのtoDataURL()を取得すると想定している画像が取得できる

こちらで質問するのは初めてですので至らない点等あるかと思いますが、皆様のお力をお借りしたく思います
よろしくお願いします

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

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

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

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

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

guest

回答1

0

ベストアンサー

主な問題点は次の3つです.
0. Image.srcに画像への参照URLを設定したあとでImage.onloadを設定している.
Image(HTMLImageElement)オブジェクトによる画像読み込みはスクリプトの背後で同時並行的に行われます. そのため, Image.srcプロパティにURLを設定した, Image.onloadにリスナ関数を登録するに画像の読み込みが完了してしまうと, リスナ関数を呼び出すことが出来ません.

  1. toDataURLメソッドの呼び出しがImage.onloadリスナ関数内部で行われていない.

Image.onloadリスナ関数の呼び出しは非同期的に行われるため, リスナ関数の登録直後toDataURLメソッドを呼び出すことは, リスナ関数の実行前に呼び出すことと同義です. 結果リスナ関数内部で行っている画像描画処理がスキップされているように見えるのです.

  1. そもそもdrawImageメソッドは引数としてHTMLCanvasElementを利用できる.

ご提示のコードではcanvas要素に描いたグラフィックをわざわざtoDataURLでデータURIスキーム化し, Imageオブジェクトに設定してからdrawImageメソッドでcanvas要素に描こうとしていますが, canvas要素には別のcanvas要素(HTMLCanvasElementオブジェクト)を引数として利用できます. (canvas要素には別のcanvas要素を直接描ける)

上記1,2に関しては, canvas要素にImageオブジェクトの内容を描く場合の慣例句を憶えておくべきです.

JavaScript

1const canvas = document.createElement("canvas"); 2const ctx = canvas.getContext("2d"); 3const image = new Image(); 4//まずはじめにリスナ関数を登録する 5image.onload = e => { 6 ctx.drawImage(image, 0, 0); 7 //画像描き込み後の処理は以下に書く 8 const url = canvas.toDataURL(); 9}; 10//リスナ関数の登録が終わったのでsrcを設定する 11image.src = "some url";

一方3を念頭に入れると, 上記1,2で発生している画像読み込みに伴う非同期処理にまつわる問題は全て回避できます.


以上を踏まえてコードを修正した場合, 次のようになります. ここでは問題を引き起こしている非同期処理を全て同期処理に置き換えています.

JavaScript

1let imgBase = null; 2let imgMoment = null; 3if(document.getElementById('base').checked === true){ 4 imgBase = document.getElementById(cvsId + 'base');//canvas要素 5} 6let radio = document.querySelectorAll("input[type='radio']"); 7for(let i = 0; i < radio.length; i++){ 8 if(radio[i].checked === true){ 9 imgMoment = document.getElementById(cvsId + radio[i].id);//canvas要素 10 } 11} 12let output = document.createElement('canvas'); 13let cntOut = output.getContext('2d'); 14output.width = document.getElementById(cvsId + 'base').width; 15output.height = document.getElementById(cvsId + 'base').height; 16if(imgBase !== null){ 17 cntOut.drawImage(imgBase, 0, 0, output.width, output.height);//canvas要素 18} 19if(imgMoment !== null){ 20 cntOut.drawImage(imgMoment, 0, 0, output.width, output.height);//canvas要素 21} 22document.getElementById('main').appendChild(output) 23let aLink = document.createElement('a'); 24aLink.href = output.toDataURL(); 25aLink.download = 'output.png'; 26aLink.innerHTML = 'test'; 27document.getElementById('main').appendChild(aLink); 28aLink.dispatchEvent(new CustomEvent('click'));

投稿2017/11/07 22:33

defghi1977

総合スコア4756

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

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

violet_ncnc

2017/11/08 11:48

丁寧に解説していただきありがとうございます。 canvasにはcanvasを直接指定できるのですね。大変勉強になりました。 おかげさまでやりたかった処理が実現できそうです。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問