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

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

ただいまの
回答率

91.03%

  • JavaScript

    13761questions

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

  • canvas

    228questions

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

canvasで画像保存がうまくいきません

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 286

kihokutarou

score 13

canvasでローカル画像を呼び出し、加工(絵画)、これを画像として出力したいのですがうまくいきません。chromeエラーによると
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
とありますので、「汚れている(?)」ということなのか、つまりcanvasに画像を載せている段階で汚れている判定なのでしょうか。。。完全にローカル環境だけで完結させたいのでそのせいもあるのか、まったくわかりません。

ちなみにchromeの場合、canvas領域を右クリックで画像をpng形式で保存できます。結局これと同様に画像化したものを貼り付けたいだけなのですが・・・。

コードにはオブジェクトを動かす記述など不必要なものもたくさん含まれます。
なにが悪いか、もしくは修正点はどこかご指摘いただけるとありがたいです。

onload = function() {
  draw();
};
function draw() {
  var canvas = document.getElementById("drawArea");
  if ( ! canvas || ! canvas.getContext ) { return false; }
  var ctx = canvas.getContext("2d");
  /* Imageオブジェクトを生成 */
  var img = new Image();
  img.src = "a.jpg?" + new Date().getTime();
  /* 画像が読み込まれるのを待ってから処理を続行 */
  img.onload = function() {
    ctx.drawImage(img, 0, 0,ctx.canvas.width, ctx.canvas.height);
  }
}

var canvas = document.getElementById("drawArea");
var ctx = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var x = 0, y = 0;
var color = "#000000";
var line = 13;
canvas.width = width;
canvas.height = height - 70;

canvas.addEventListener("mousedown", touchStartHandler, false);
canvas.addEventListener("mouseup", touchEndHandler, false);

canvas.addEventListener("touchstart", ttouchStartHandler, false);
canvas.addEventListener("touchend", ttouchEndHandler, false);

function touchStartHandler(e) {
    e.preventDefault();
    getTouchPoint(e);
    ctx.beginPath();
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctx.moveTo(x, y);
    canvas.addEventListener("mousemove", touchMoveHandler, false);
}

function touchMoveHandler(e) {
    e.preventDefault();
    getTouchPoint(e);
    ctx.lineWidth = line; //線の太さ
    ctx.strokeStyle = color; //線の色
    ctx.lineTo(x, y);
    ctx.stroke();
}

function touchEndHandler(e) {
    e.preventDefault();
    ctx.closePath();
    canvas.removeEventListener("mousemove", touchMoveHandler, false);
}

function getTouchPoint(e) {
    x = e.clientX - canvas.offsetLeft;
    y = e.clientY - canvas.offsetTop;
}

function ttouchStartHandler(e) {
    e.preventDefault();
    getTTouchPoint(e);
    ctx.beginPath();
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctx.moveTo(x, y);
    canvas.addEventListener("touchmove", ttouchMoveHandler, false);
}

function ttouchMoveHandler(e) {
    e.preventDefault();
    getTTouchPoint(e);
    ctx.lineWidth = line; //線の太さ
    ctx.strokeStyle = color; //線の色
    ctx.lineTo(x, y);
    ctx.stroke();
}

function ttouchEndHandler(e) {
    e.preventDefault();
    ctx.closePath();
    canvas.removeEventListener("touchmove", ttouchMoveHandler, false);
}

function getTTouchPoint(e) {
    var touch = e.touches[0];
    x = touch.pageX - canvas.offsetLeft;
    y = touch.pageY - canvas.offsetTop;
}

function spread() {
    ctx.fillStyle = color;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    document.getElementById("ClickSound").play();
}

function clr() {
    cntx = canvas.getContext("2d");
    cntx.clearRect(0, 0, canvas.width, canvas.height);
    document.getElementById("ClickSound").play();
}

function change_black() {
    color = "#000000";
    document.getElementById("ClickSound").play();
}
function change_red() {
    color = "#ff0000";
    document.getElementById("ClickSound").play();
}
function change_green() {
    color = "#00ff00";
    document.getElementById("ClickSound").play();
}
function change_blue() {
    color = "#0000ff";
    document.getElementById("ClickSound").play();
}
function change_white() {
    color = "#ffffff";
    document.getElementById("ClickSound").play();
}
function change_8() {
    line = 8;
    document.getElementById("ClickSound").play();
}
function change_16() {
    line = 16;
    document.getElementById("ClickSound").play();
}
//canvasデータを画像に変換にする関数
function chgImg()
{
  var png = canvas.toDataURL("image/png");
  img.crossOrigin = 'Anonymous'
  document.getElementById("newImg").src = png;
}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>汎用お絵かきチャレンジ</title>
    <link rel="stylesheet" type="text/css" href="wboard.css">
</head>
<body>

<div class="wrapper">
<form>
<p>
&nbsp;&nbsp;色&nbsp;
<input type="button" class="b1" style="background: #000000;" value=" " onclick="change_black()">
<input type="button" class="b1" style="background: #ff0000;" value=" " onclick="change_red()">
<input type="button" class="b1" style="background: #00ff00;" value=" " onclick="change_green()">
<input type="button" class="b1" style="background: #0000ff;" value=" " onclick="change_blue()">
<input type="button" class="b1" style="background: #ffffff;" value=" " onclick="change_white()">
&nbsp;&nbsp;線幅&nbsp;
<input type="button" class="b2" value="8px" onclick="change_8()">
<input type="button" class="b2" value="16px" onclick="change_16()">
&nbsp;&nbsp;
<input type="button" class="b2" value="全面" onclick="spread()">
<input type="button" class="b2" value="消去" onclick="clr()">
<font size="5" color="#ff0000"><a href="javascript:location.reload();">最初から</a></font>
&nbsp;&nbsp;画像&nbsp;
<input id="ufile" name="ufile" type="file" accept="image/jpeg,image/png">
<input type="button" value="戻る" id="backBtn">
<input type="button" value="進む" id="forwardBtn">
<input type="button" value="画像に変換" onclick="chgImg()">
</form>


<canvas id="drawArea"></canvas>


<p><font size="6" color="#ff0000">
<a href="#" onClick="toggle_view1();return false;">カードの表示</a>
<a href="#" onClick="toggle_hidden1();return false;">カードの非表示</a>
</font>
</p>
<div id="card"  style="display: none;">
<table><tr>
<td><p id="move1"><img src="img/1.JPG" width="150" height="120"></p></td>

<td><p id="move2"><img src="img/2.JPG" width="150" height="120"></p></td>

<td><p id="move3"><img src="img/3.JPG" width="150" height="120"></p></td>

<td><p id="move4"><img src="img/4.JPG" width="150" height="120"></p></td>

<td><p id="move5"><img src="img/5.JPG" width="150" height="120"></p></td>

<td><p id="move6"><img src="img/6.JPG" width="150" height="120"></p></td>

<td><p id="move7"><img src="img/7.JPG" width="150" height="120"></p></td>

<td><p id="move8"><img src="img/8.png" width="80" height="80"></p></td>

</tr></table>

<div><img id="newImg"></div>


<script src="wboard.js"></script>
<script src="jquery.min.js"></script>
<script type="text/javascript" src="jquery-ui-1.8.12.custom.min.js"></script>
<script type="text/javascript" src="jquery.ui.touch-punch.js"></script>
<script type="text/javascript" src="move.js"></script>

 <script>
         $(function(){

             // id="ufile"の変化でコールバック
             $("#ufile").change(function(){
                 // 選択ファイルの有無をチェック
                 if (!this.files.length) {
                     alert('ファイルが選択されていません');
                     return;
                 }

                 // Formからファイルを取得
                 var file = this.files[0];

                 // (1) HTMLのCanvas要素の取得
                 var canvas = $("#drawArea");

                 // (2) getContext()メソッドで描画機能を有効にする
                 var ctx = canvas[0].getContext('2d');

                 // 描画イメージインスタンス化
                 var image = new Image();

                 // File API FileReader Objectでローカルファイルにアクセス
                 var fr = new FileReader();

                 // ファイル読み込み読み込み完了後に実行 [非同期処理]
                 fr.onload = function(evt) {

                     // 画像がロードされた後にcanvasに描画を行う [非同期処理]
                     image.onload = function() {
                         // (3) プレビュー(Cnavas)のサイズを指定
                         var cnvsH = ctx.canvas.height;
                         var cnvsW = image.naturalWidth*cnvsH/image.naturalHeight;
                         // (4) Cnavasにサイズアトリビュートを設定する
                         canvas.attr('width', cnvsW);
                         canvas.attr('height', cnvsH);
                         // (5) 描画
                         ctx.drawImage(image, 0, 0, cnvsW, cnvsH);
                     }
                     // 読み込んだ画像をimageのソースに設定
                     image.src = evt.target.result;
                 }

                 // fileを読み込む データはBase64エンコードされる
                 fr.readAsDataURL(file);
             })
        })
</script>
<script language="JavaScript" type="text/javascript">
<!--
var elem1 = document.getElementById("card");
function toggle_view1() {
  elem1.style.display = "";
}
function toggle_hidden1() {
  elem1.style.display = "none";
}
-->
</script>
</body>
</html>
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+5

chromeではローカル環境(HTML文書のURLがfile://から始まるもの)におけるtoDataURL(toBlob,getImageData,captureStream)メソッドの実行が許可されていません.(実行するとセキュリティエラーが発生する)

これは悪意あるスクリプトをローカル環境で実行することによる情報漏えい(ローカル画像のパスを直接指定し, canvas要素を使ってURL文字列化して外部に送信する手法)を防ぐためのものです.

NOTE:
canvas要素ではこの「危険な画像の出力」をorigin clean flagと呼ばれる内部フラグで管理しており, 同一生成元ポリシーに反する操作(例えば異なるオリジンにある画像を書き込む)を行うことでこのフラグがfalseに設定され, この状態をしばしばオリジンクリーンフラグが汚染されたと形容します. オリジンクリーンフラグが汚染されたcanvas要素に対して上記メソッド群を実行するとエラーとなります. 

NOTE:
Fileオブジェクトを介したローカル画像の書き込みはユーザーの操作によって書き込まれたことが明らかであることからオリジンクリーンフラグを汚染しません.

NOTE:
ローカル環境での動作はブラウザごとに対処が異なります. 例えばFireFoxではHTML文書が保管されているパスよりも上位のディレクトリの画像を参照した場合に限り同様の問題が発生します.


解決策としては, 次の二つが考えられます.

  • HTML文書をWEBサーバー上に配置して実行する.
  • chromeを--allow-file-access-from-filesフラグ付きで起動し, HTML文書を実行する.

NOTE:
右クリックによる画像の保存はユーザーが実行したことが明らかであるため, エラーは発生しません.

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 91.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    13761questions

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

  • canvas

    228questions

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