QA@IT

ASP.NETで、canvas上に描画した画像をサーバにアップロードする方法

10578 PV

【開発・テスト環境】
Microsoft Visual Studio 2013 Community Edhition のWebForm
Windows8.1
です。
【問題点】
ボタン "btnSend"を押したとき、所定の場所にcanvas上の画像(1/6に縮小)を送りたいのですが、元の画像が送られてしまうようです。
【コード部分】

Imports System.Data.SqlClient
Imports System.IO

Public Class Canvas
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    End Sub

    Protected Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click

        Dim SendPath As String

        Dim i As Integer

        For i = 0 To Request.Files.Count - 1

            Dim file = Request.Files(i)

            If Path.GetFileName(file.FileName) = "" Then
                'lblExclamation.Text = "写真が選択されていません"
                Exit Sub
            End If

            SendPath = "C:\Users\Hoge\Desktop\"


            file.SaveAs(SendPath & "_" & CStr(i) & Path.GetFileName(file.FileName))

        Next


    End Sub
End Class

【ソース部分】

<%@ Page Title="" Language="vb" AutoEventWireup="false" MasterPageFile="~/Site.Mobile.Master" CodeBehind="Canvas.aspx.vb" Inherits="CanvasTest.Canvas" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
    <style type="text/css">

        .fileInput {
            width: 302px;
            height: 42px;
        }
    </style>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="FeaturedContent" runat="server">
       <script src="http://code.jquery.com/jquery-latest.js" type="text/javascript"></script>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server" enctype="multipart/form-data">
    <input id="uploadFile" runat="server" class="fileInput" height="600px" name="userfile" type="file" /><br />
    <asp:Button ID="btnSend" runat="server" BackColor="#FF9933" Font-Size="X-Large" Height="40px" Text="送信" Width="100px" />
&nbsp;
    <br /> 
    <%--<div style="margin:auto; width:100%; border:2px solid #808080; height: 238px;"  >--%>
    <canvas id="myCanvas" ; style="-ms-touch-action:none; " width="568" ; height="568"> 
         <%--style=" width: 100%; "--%>       <%-- width="350" ; height="350"--%>
    </canvas><br /> 

<%--</div>--%> 

    <script>

        $('.fileInput').change(function () {

            var canvas = $("#myCanvas");
            var ctx = canvas[0].getContext("2d");

            // 選択されたファイルを取得
            var file = this.files[0];

            // 画像ファイル以外は処理中止
            if (!file.type.match(/^image\/(png|jpeg|gif)$/)) return;

            var image = new Image();
            var reader = new FileReader();

            // File APIを使用し、ローカルファイルを読み込む
            reader.onload = function (evt) {

                // 画像がloadされた後に、canvasに描画する
                image.onload = function () {

                //一旦クリア
                ctx.clearRect(0, 0, canvas.width, canvas.height);


              //  alert(image.naturalWidth + " " + image.naturalHeight);

                    // 元画像のサイズを取得
                    var imgWidth = image.naturalWidth;
                    var imgHeight = image.naturalHeight;

                    // 縮小する
                    imgWidth = imgWidth / 6       
                    imgHeight = imgHeight / 6

                    ctx.drawImage(image, 0, 0, imgWidth, imgHeight);

                }

                // 画像のURLをソースに設定
                image.src = evt.target.result;
            }

            // ファイルを読み込み、データをBase64でエンコードされたデータURLにして返す
            reader.readAsDataURL(file);

        });
     </script>

    <br />
</asp:Content>

【その他】
iPhone6Plus から開発環境のIISに接続し、Safari(ブラウザ)に表示された「ファイル選択」ボタンを押すと、「写真またはビデオを撮る」「フォトライブラリ」の選択項目が出ます。
「写真またはビデオを撮る」を選択しカメラを起動すると、撮った写真をすぐに取り込むことができます。
このとき、iPhoneを横にして写真を撮ると良いのですが、iPhoneを縦にして撮った写真は、反時計周りに90度回転したものになってしまいます。何を直せば良いでしょうか。
【2015/7/6 追記】
以下のようなコードで、canvasに表示された画像のみが送信されております。

        Dim SendPath As String

        ' 出力先
        SendPath = "\\Hoge\MyApp\file\"
        If uploadFile.PostedFile.ContentLength > 0 Then

            ' hiddenのデータを取得、canvasのBase64のデータの様であれば保存する。
            Dim uploaded = canvasData.Value
            If uploaded IsNot Nothing AndAlso uploaded.StartsWith("data:image/png;base64,") Then

                uploaded = uploaded.Replace("data:image/png;base64,", "")

                System.IO.File.WriteAllBytes(SendPath + "AA" + uploadFile.PostedFile.FileName, Convert.FromBase64String(uploaded))



            End If
        End If

しかし、以下のようなコードでは二重に送信されてしまいます。(加工済みのコードで申し訳ないですが。)どこに違いがあるのか分からない状態です。


        Dim Directory As String
        Dim SendPath As String

        Dim 得意先CD As Long
        Dim 現場CD As Long
        Dim 修理CD As String

        Dim FileName As String
        Dim FileNameBaseElement As String

        Dim SendPathElement As String = Session("フォルダ管理PATH")



        Directory = Session("フォルダ管理PATH") & "\ファイル管理\現場\" & Session("得意先CD").PadLeft(7, "0"c) & "-" & Session("現場CD").PadLeft(4, "0"c)
        SendPath = Directory & "\" & Right("0000000" & Session("得意先CD"), 7) & "_" & Right("0000" & Session("現場CD"), 4) & "_" & Now.ToString("yyyyMMddHHmmss")

        得意先CD = IIf(Session("得意先CD") = vbNullString, 0, Session("得意先CD"))
        現場CD = IIf(Session("現場CD") = vbNullString, 0, Session("現場CD"))
        修理CD = CStr(Session("修理CD"))

        FileNameBaseElement = Right("0000000" & Session("得意先CD"), 7) & "_" & Right("0000" & Session("現場CD"), 4) & "_" & Now.ToString("yyyyMMddHHmmss")

        Debug.Print(Directory)

        If System.IO.Directory.Exists(Directory) Then

        Else
            System.IO.Directory.CreateDirectory(Directory)
        End If

        If uploadFile.PostedFile.ContentLength > 0 Then

            ' hiddenのデータを取得、canvasのBase64のデータの様であれば保存する。
            Dim uploaded = canvasData.Value
            If uploaded IsNot Nothing AndAlso uploaded.StartsWith("data:image/png;base64,") Then

                uploaded = uploaded.Replace("data:image/png;base64,", "")

                System.IO.File.WriteAllBytes(SendPath + uploadFile.PostedFile.FileName, Convert.FromBase64String(uploaded))



            End If
        End If
  • canvas の画像データを送る案を解答欄に追記しました。 -
  • jQuery.Ajax を使って送信する案のサンプルを解答欄に追記しました。 -
  • サンプルコードをありがとうございます。やってみます。 -
  • サンプルを実際に動かして試すことができるよう、自分の使っているサーバーにアップしました。URL は解答欄の【2015/7/3 追記】を見てください。 -
  • uploadFile.PostedFile.ContentLength > 0 が true になるのはどういう意味ですかね? Dim uploaded = canvasData.Value の uploaded を base64 デコードして保存できるってのはどういう意味ですかね? 両方アップロードするという無駄なことをしているのではないのですか?
    -
  • flied_onion さん、説明していただけないですか? -
  • 質問者さんには、私が書いたサンプルコードを実際に動かして試すことができるようサーバーにアップたものを試して、結果を連絡いただけると嬉しいのですが。
    http://surferonwww.info/Test/0118-CanvasImageUpload2.aspx
    -
  • ごめんなさい、返信はちょっと待って下さい。
    とりあえず、サーバー上で使っているかどうかに関わらずuploadFileにも元画像データは飛んできているはずです。
    SurferOnWwwさんも指摘してますが、uploadFileのPostedFileが空ではないので。
    fileタグ使えば回避できるんじゃないかと思いますが、それについてはまた書きます。
    -
  • structure さんが言っている 「しかし、以下のようなコードでは二重に送信されてしまいます。」の二重とは、元画像と縮小画像の両方という意味ですか? -
  • 回答に追記しました。 -
  • flied_onion さんがせっかく書いてくださったのに、structure さんからは無しのつぶてですね・・・ -
  • 気にして下さってありがとうございます。利用者の都合もあるでしょうから私は即返答はそんなに期待していませんよ。
    もちろんクローズされずに最後まで放置は困ってしまいますけどね。
    -
  • flied_onion さんに説明をお願いした手前、こういう形になってしまったのはちょっと心苦しかったのですが、そう言っていただけると気が楽になります。質問者さんでなくても、誰かの役に立つと期待します。(自分の回答も) -
  • 反応が遅くなってしまい、誠に申し訳ありませんでした。 -
  • 質問者さんには、iPh0ne で、私が書いたサンプルコードを実際に動かして試すことができるようサーバーにアップたもの(URL 下記)を試して、結果を連絡いただけると嬉しいのですが。
    http://surferonwww.info/Test/0118-CanvasImageUpload2.aspx
    -
  • 了解いたしました。 -
  • 1)「ファイルを選択」を押すと、「写真またはビデオを撮る」と「フォトライブラリ」のどちらかを選ぶように項目が出ます。2)「写真またはビデオを撮る」を選択するとカメラが起動します。3)シャッターを切ってから「写真を使用」で選択すると、ページに画像が表示される…はずなのですが、「ファイルのサイズが制限の500000バイトを超えています」ということで、表示できませんでした。 -
  • なお、「フォトライブラリ」を選択し、フォトライブラリ内に既にあった画像のうち、(iPhoneのカメラで撮ったものではなく)より容量の小さい画像を選択した場合には、uploadできるようです。「ブラウザから送信されたDataurl文字列"data:image/jpeg:base64,/9j/4AAQSkZ"を2015/07/10 15:56:04に受信しました。」と表示されました。 -
  • iPhoneでは、撮った写真を加工したり他のカメラアプリを使う場合には画像の容量を調節できますが、カメラを直接起動した場合は解像度の調整はできないようです。撮ったものを見たところ、2.8Mを超えるものもありました。 -
  • iPhone で検証いただきありがとうございました。ファイルサイズ 500,000 バイトの制限も、jQuery.Ajax でサーバーに送信し、日付を追加してサーバーからから返ってきた文字列もプログラムしたとおり動いているということを示しています。 -
  • ソースを見ていただくを分かると思いますが、500,000バイトの制限は変更または削除できますので、カメラで取った画像も送信できます。自分のサイトに大きなデータを送られるのは困るので、サーバーに送信するデータは差し替えてますが、差し替えを止めれば canvas 画像がサーバーに送信されます。 -
  • flied_onion さんのサンプルとの大きな違いは、flied_onion さんのサンプルが隠しフィールドにセットした画像データをポストバックして(同期で)サーバーに送信しているのに対し、私のサンプルは jQuery.Ajax を使って非同期でサーバーに送信しているところです。 -
  • その他、1/6 に縮小しているところと、500 x 500 の枠(変更できます)に入るように縮小しているところも違います。
    両方を検討して、ご自分の目的にあった方法(特に、同期ポストバックで送るか、Ajax で非同期で送るかという点)を考えていただければと思います。
    -
  • 私のコードも質問のコードをベースにしていますので最初から 1/6に縮小していますよ(違うと言っているのがその事でなければすいません)。 -
  • flied_onion さんのサンプルは 1/6 に縮小している、私が自分のサイトにアップしたサンプルは 500 x 500 の枠(注:下の回答にアップしたサンプルは 800 x 800 の枠)に入るように縮小している・・・と言ったつもりですが? -
  • #ここで書くような話ではないかもしれませんが・・・
    コメントがあったときにはメールで通知されるのですが、以前はコメントがあれば即メールが届いたのですが、最近は 2, 3 日かかったり来なかったりする事があります。自分だけでしょうか? 皆さんの状況を教えていただけると幸いです。
    -
  • すいません、勘違いでした。「1/6 に縮小しているところと、」は対してという意味だったんですね。ANDの意味かと呼んでしまいました。
    そして、SurferOnWwwさんのサンプルが サイズを 1/6にしてみて枠に収まらなければさらに枠に収まるように縮小率を再計算しているのかと勝手に思ってました。失礼しました。
    -
  • 通知メールは私は気にしてていないので時間差はわかりませんが、いつの間にかページのフッターで意見要望投稿できるようになったようなのでそちらに投げてみては?
    昔はたしか自分が投稿した回答・質問にコメントついた時しかこなかったですよね(例えばここだと質問者にしかいかなかった気がします)。
    ここでこの議論伸ばしても structure さんにご迷惑なのでメールについてはこのコメントのみにします。
    -

回答

6分の1にならない方ですが、canvasに6分の1で描画していても、Imageのsrcに設定しているのが元々の画像だからじゃないでしょうか。

// 画像のURLをソースに設定
image.src = evt.target.result;

検証してないですけど、以下の様にしてみてはどうでしょうか?

// 画像のURLをソースに設定
image.src = canvas[0].toDataURL();

追記:

表示の問題かと思っていたのですが、送信で躓いていたのですね。失礼しました。

以下の様に、hiddenコントロールで転送してはどうでしょうか。
質問のコードをベースにしています。

コードビハインド側はクリックイベントのみ書いておきます。
出力先が変わっているので注意してください。

** 修正コードを後に追記した関係で長くなる、この部分は削除しました。 **
** 参照したい場合は履歴から見てください。 **

aspx側の変更点は

マークアップの

<%-- 画像送信用のhiddenコントロール --%>
<asp:HiddenField runat="server" ID="canvasData" ClientIDMode="Static"/>

および Scriptの

// 画像送信用 hiddenに設定
$('#canvasData').val(canvas[0].toDataURL('image/png'));

の2か所だけだったと思います。

このままだと利用しない元の画像も転送されてしまうのと、
複数同時アップロードも考慮してませんがサンプルという事で。

画像の向きに関しては「EXIF 回転 javascript」などで検索すれば出てくると思います。
ライブラリを使う事になりそうですね。


追記 (7/8)

コメントにも書きましたが質問に追記された「しかし、以下のようなコードでは二重に送信されてしまいます。」については
ざっとコードを見た限りどう二重に送信されているのか現象がよくわかりませんでした。

さて、先に私が提示したサンプルコードの説明で「元の画像も転送されてしまう」と書きました。
あのコードではサーバー上(VBコード)でファイルがアップロードされたかどうかを確認するために、input[type=file]であるuploadFileで送信されたファイル(PostedFile)のContentLengthが 0より大きいかという判定をしています。これはすなわち、uploadFile経由でもファイルが送信されていることを意味します(そしてuploadFileに設定しているのは縮小前の元の画像です)。
せっかく縮小した画像を送信しているのに元のサイズも送信してしまっているのは無駄です。

元画像のアップロード、加えて送信したHidden(Canvasデータ)の値が戻ってきてしまう問題を修正したバージョンに書き換えました。
前回同様 出力先(SendPathの値)、aspx の 1行目の Inheritsは質問のコードと異なります。

[変更点]とあるコメントの近くが前回との差分です。

   Protected Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click

        Dim SendPath As String

        ' 出力先
        'SendPath = "C:\Users\Hoge\Desktop\"
        SendPath = "c:\temp\"

        ' [変更点]
        ' uploadFileはコントロールではなくなったので以下は無効
        'If uploadFile.PostedFile.ContentLength > 0 Then
        If Not String.IsNullOrEmpty(canvasFileName.Value) Then

            ' hiddenのデータを取得、canvasのBase64のデータの様であれば保存する。
            Dim uploaded = canvasData.Value
            If uploaded IsNot Nothing AndAlso uploaded.StartsWith("data:image/png;base64,") Then
                uploaded = uploaded.Replace("data:image/png;base64,", "")

                ' [変更点]
                ' uploadFileからはFileNameが取得できないので、Hidenからファイル名を取得する。
                ' windows 以外の環境からアップロードされる場合、この部分は工夫が必要かもしれません
                Dim fileName = (New System.IO.FileInfo(canvasFileName.Value)).Name
                System.IO.File.WriteAllBytes(SendPath + fileName, Convert.FromBase64String(uploaded))

            End If
        End If

        ' [変更点]
        ' Hiddenデータがページに戻されてしまうのでクリア
        canvasFileName.Value = ""
        canvasData.Value = ""
    End Sub
<%@ Page Title="" Language="vb" AutoEventWireup="false" MasterPageFile="~/Site.Mobile.Master" CodeBehind="Canvas.aspx.vb" Inherits="WebApplication1.Canvas" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
    <style type="text/css">

        .fileInput {
            width: 302px;
            height: 42px;
        }
    </style>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="FeaturedContent" runat="server">
       <script src="http://code.jquery.com/jquery-latest.js" type="text/javascript"></script>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server" enctype="multipart/form-data">
    <%-- [変更点] uploadFile を input[type=file]に変更。縮小前の画像がアップロードされない様にname属性も付与しない。 --%>
    <input id="uploadFile" class="fileInput" height="600px" type="file" /><br />
    <asp:Button ID="btnSend" runat="server" BackColor="#FF9933" Font-Size="X-Large" Height="40px" Text="送信" Width="100px" />
&nbsp;
    <br /> 
    <canvas id="myCanvas" style="-ms-touch-action:none; " width="568" height="568"> 
    </canvas><br /> 
    <%-- 画像送信用のhiddenコントロール --%>
    <asp:HiddenField runat="server" ID="canvasFileName" ClientIDMode="Static"/>
    <asp:HiddenField runat="server" ID="canvasData" ClientIDMode="Static"/>

    <script>
        // [変更点] uploadFile のみにイベントを設定
        $('#uploadFile').change(function () {
            var canvas = $("#myCanvas");
            var ctx = canvas[0].getContext("2d");            

            var file = this.files[0];
            if (!file.type.match(/^image\/(png|jpeg|gif)$/)) return;

            var image = new Image();
            var reader = new FileReader();

            reader.onload = function (evt) {
                image.onload = function () {
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    var imgWidth = image.naturalWidth / 6;
                    var imgHeight = image.naturalHeight / 6;

                    ctx.drawImage(image, 0, 0, imgWidth, imgHeight);
                    // 画像送信用 hiddenに設定
                    $('#canvasData').val(canvas[0].toDataURL('image/png'));
                    // [変更点] ファイル名もHiddenにて送信する。
                    $('#canvasFileName').val($('#uploadFile').val()); 
                }
                image.src = evt.target.result;
            }
            reader.readAsDataURL(file);
        });
    </script>
    <br />
</asp:Content>

aspxのコメントにあるように、uploadFileは runat="server"を外してname属性も削除しています。
これによりサーバー上でファイル名が取得できなくなりますので、ファイル名格納用にHiddenを追加してCanvasに画像を描画するタイミングでその新しいHiddenにファイル名を格納するようにしています。

vb側では、ファイル名のHiddenが空でなければファイルが送信されていると判断して保存処理を行うようにしています。
コメントにもありますが、ファイル名の抽出にFileInfoクラスを使っていますので送信されているファイル名のスタイルによっては正しくとれない可能性があります。Windows以外から利用する場合は気を付けてください(特に iOSから送信する予定だったと思いますので、どういった値が送信されているかを確認して適宜変更してください)。


以下は元の画像もアップロードされてしまう事の検証について書いておきます。興味があれば読んでください。

前述のコードに検証用のコードを追加します。
VB.NETには、Protected Sub btnSend_Click...の行とDim SendPath As Stringの行に7行追加します。

   Protected Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click

        ' [検証用] 元画像もアップロードされてしまう事の確認
        ' 以下3行は最終的には不要です。
        If Request.Files.AllKeys.Contains("uploadFile") Then
            Debug.WriteLine("uploadFile byte size: {0}", Request.Files("uploadFile").ContentLength)
        Else
            Debug.WriteLine("Request.Files(""uploadFile"") not found.")
        End If

        Dim SendPath As String

aspxは、uploadFileにname属性を追加します。

<input id="uploadFile" <%-- 検証用 name属性 --%> name="uploadFile" class="fileInput" height="600px" type="file" /><br />

加えて現状のサンプルコードだと、私の環境(VS2013 + ASP.NET 4.5 + Chrome)では Content3に設定している multipart/form-dataがASP.NETに不要と判断されたのか普通のFormになってしまったので、簡単にformが multipartになるように、<asp:Content ID="Content3"...の行の下に非表示のinput[type=file] 且つ runat="server"のタグを追加します。

<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server" enctype="multipart/form-data">
    <%-- [検証用] 以下の 1行は検証用のコードです。最終的には不要なので削除してください。 --%>
    <input id="File1" runat="server" class="fileInput" height="600px" name="userfile" type="file" style="visibility: hidden" /><br/>

(※ 1行目は元々存在する行です。)

これで実行して画像を送信すると、出力ウィンドウに以下の様に表示されます。

__________070815_014048_AM.jpg

これは uploadFileのデータとして 444255byte送信されていることを意味します。
( 444255byte はスクリーンショットを撮る時に指定した画像の縮小前のサイズです)
Canvasのデータとは別に元の画像も送信されており、修正前のサンプルコードでも同様の事が起きていました。

次に、uploadFileのタグから name属性だけ削除します。

<input id="uploadFile" class="fileInput" height="600px" type="file" /><br />

ページを読み込み直して再度画像を送信すると、以下の様に表示され、uploadFileは転送されていない事がわかります。

__________070815_014630_AM.jpg

Webの標準に従っているブラウザであれば、name属性がなければデータは送信しませんのでこの様な結果になっています。
(余談ですが、ASP.NETのWebFormsのコントロール(<asp:XXXX)はデザイン時にname属性が無くてもASP.NETが自動的に追加しています。表示されたページのHTMLソースを見てみれば確認できます。)

これにより元画像まで送信されることを回避しています。

編集 履歴 (2)
  • ありがとうございます。
    上記の方法ですと、読み込んだときに画像が消えてしまうようです。
    -
  • すいません、勘違いしてました。回答を追記しました。 -
  • 上の回答の追記部分の「利用しない元の画像も転送されてしまう」というところを、回答を見たら一目でわかるよう、強調しておいていただければと思います。 -
  • 検索などであとからこのスレッドにたどり着いたような人が、特にこの回答がベストアンサーになっていたりすると、具体的なコードが書いてあるので、そこを知らずに飛びつくということがありそうな気がします。リソース・帯域(電波は特に)の浪費は避けるべきです。 -
  • HiddenFieldの値を設定する方法で書いてみたところ、首尾よく(目的とする画像データのみが)所定のフォルダに送信できました。
    ただ、iPhone端末から試したところ、「このWebページで問題が起きたため、再度読み込まれました」とのことで、クラッシュしてしまいました。画像の送信自体はできています。このような場合、どのようなデバッグ方法がありますでしょうか。
    -
  • 他の肉付けのスクリプトも追加しているため、悪影響が出ているのかもしれませんので、削りながら試しているところですが。 -
  • Adobe Shadowなどのツールがあるようなので、試してみます。 -
  • どこかのページで見たか覚えていないですが、サイズが大きい画像だとBase64エンコードの負荷が大きすぎて落ちるというのを見た記憶があります。でも送信自体は上手くいっているのであれば関係ないかもしれませんね。 -
  • iPhoneでは、Opera MiniやChromeでも不調でした。しかしiLunascapeでは動作が安定していてエラーが出ることはありませんでした。 -
  • 今さらながらのコメントですが・・・ 上の質問者さんのコメントで「首尾よく(目的とする画像データのみが)所定のフォルダに送信できました」とありますが、間違いないですか? flied_onion さんがアップされたコードのままでは、オリジナルの画像ファイルと canvas に描画された画像データの両方がサーバーに送信されるはずです。 -
  • 質問欄に追記しましたが、canvasに描画された画像データのみが送信されている状態です。 -
  • uploadFile.PostedFile.ContentLength > 0 が true になるのはどういう意味ですかね? Dim uploaded = canvasData.Value の uploaded を base64 デコードして保存できるってのはどういう意味ですかね? 両方アップロードするという無駄なことをしているのではないのですか?
    -
  • 一方、実際に使おうとしている、追記分の2つめのほうは二重に送信されてしまいます。 -
  • 「追記分の2つめのほう」って何ですか? -
  • 質問欄の【2015/7/6 追記】内の、2つあるコードの2番目のことです。 -
  • 追記 (7/8)で書いていただいた方法では元の画像も送信されてしまうことがなくなりました。また、"windows 以外の環境からアップロードされる場合、この部分は工夫が必要かもしれません" についてですが、iPhoneでも特に変更しないままで送信できました。当コメント欄に書いた"「このWebページで問題が起きたため、再度読み込まれました」とのことで、クラッシュ"するということもなくなりました。 -
  • 報告ありがとうございます。不安定だったのは、送受信の量が多かったためかもしれませんね。 -

html5 canvas に縮小した画像を描画しているのですよね?

その canvas の画像をアップロードすることができるのかどうか知りませんが(私が知らないだけで、できるということなら以下のレスは無視してください)、もし、できないようであればサーバー側で縮小してはいかがですか?

サイズの大きいままアップロードするのはイヤだということがあるかもしれませんが、クライアント側で縮小できないものはやむを得ないので。

サーバー側で縮小すれば、.NET のライブラリの中に補間のオプション(Graphics クラスにある InterpolationMode プロパティ)で、8 種類の補間モードが設定できるという利点もあります。その具体例は以下のページを見てください。

サムネイル画質改善
http://surferonwww.info/Hobby/Computer/PcPage16.aspx

クライアント側で HTML5 File API が使えるのであれば、size と type をチェックして制限から外れていたらアップロードさせないようにすれば、とんでもないファイルをアップロードされるのは防げると思います。その具体例は以下のページを見てください。

FileUpload と CustomValidator
http://surferonwww.info/BlogEngine/post/2015/03/10/aspnet-fileupload-and-customvalidator.aspx

ハズレでしたら(cavnas の画像を問題なくアップロードできるということでしたら)失礼しました。

【2015/6/29 追記】

ちょっと調べてみましたが、<input type="file" .../> を使って canvas に描画された画像をアップロードするのは無理だと思います。

<input type="file" .../> が保持するファイルを、クライアントスクリプトで canvas に表示されている画像に差し替えるということが可能なら話は別ですが、そういう方法は、自分が調べた限りですが、なさそうですし。

個人的には、上の回答で書いたように、オリジナルの画像ファイルを送ってサーバー側で縮小してから保存するという方法が良いと思うのですが、どうしても canvas の画像データを送りたいということであれば、以下のようにしてはいかがでしょう? (未検証です)

(1) <asp:Button ID="btnSend" ... /><input type="button" ... /> に変更する(クリックでポストバックされてはマズイので)。

(2) JavaScript の関数を追加する。それには ctx.canvas.toDataURL() で canvas の画像データを data 形式で取得し、それを jQuery.Ajax でサーバーに送信するコードを含める。

(3) 上記 (2) の関数を上記 (1) の click イベントにアタッチする。

(4) サーバー側に、上記 (2) の jQuery.Ajax の POST 先(Web サービス、静的メソッドなど)を作る。そこで、送信されてきたデータを BASE64 デコードした画像ファイルを作成し保存する。

よろしければ検討してみてください。

【2015/7/1 追記】

上の回答に書いた案のサンプルを作ってみましたので、そのコードをアップしておきます。

1/6 に縮小するのではなく、最大サイズを 800px x 800px とし(とりあえず)、それに入る場合はそのまま、入らない場合は幅・高さどちらか大きい方を 800px に縮小し他方をその縮小率と同じに縮小(要するに縦横比を保ったまま、800px x 800px に入るよう縮小)するようにしました。

アップロードは jQuery.Ajax を使い、受け側は同じページに静的メソッドを追加してそれで受けて保存するようにしています。

iPhone は使えないので試してませんが、Firefox 38.0.5, Chrome 43.0.2357.130 m, Opera 12.17 で期待通り動くことは確認しました。

Web サイトプロジェクトならコピペして、ページの名前 0118-CanvasImageUpload2.aspx と保存フォルダの名前 FileUploadTest を実際にあわせて変更すれば動くはずですので、お試しください。(Web アプリケーションプロジェクトの場合は .aspx ページと .aspx.cs ページに分ける必要があります)

<%@ Import Namespace="System.Web.Services" %>
<%@ Import Namespace="System.IO" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    // jQuery.Ajax で POST された画像データを受けるためのメソッド。
    // public static にして WebMethodAttribute 属性を付与。 
    [WebMethod]
    public static string ReceiveImage(string imgBase64)
    {
        // 文字列先頭の "data:image/png;base64," を除去。
        imgBase64 = imgBase64.Replace("data:image/jpeg;base64,", "");

        // BASE64 にエンコードされた画像データを元のバイト列に変換。
        Byte[] imgByteArray = Convert.FromBase64String(imgBase64);

        // ファイル格納フォルダ FileUploadTest の物理パスを取得。
        string savePath = HttpContext.Current.Server.MapPath("~/FileUploadTest/");

        // ファイル名を設定。
        string filename = "img" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".jpg";

        // ファイル格納フォルダ FileUploadTest に画像ファイルを保存。
        File.WriteAllBytes(savePath + filename, imgByteArray);

        return "ファイル名 " + filename + " として保存しました。";
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Canvas Image Upload</title>
    <script src="Scripts/jquery-1.8.3.js" type="text/javascript">
    </script>
    <script type="text/javascript">
    //<![CDATA[
        // 画像ファイルのサイズ制限を 500,000 bytes、タイプ制限を
        // image/jpeg とする
        var maxFileSize = 500000;
        var allowedContentType = "image/jpeg";

        // アップロードする画像のサイズを 800 x 800 以下とする
        var maxWidth = 800;
        var maxHeight = 800;


        // FileReader を利用して FileUpload で選択された画像ファイルを
        // 読み込み、その画像の Data url(data:image/jpeg;base64, ... 
        // という形式の文字列)を取得。それを image オブジェクトの src 
        // 属性に設定する
        var fileReader;
        var image = new Image();

        // image オブジェクトの src 属性に Data url が設定されると
        // onload イベントが発生する。そのイベントにリスナをアタッチ。
        // そのリスナの中で image オブジェクトの画像を canvas に描画す
        // る。その際、canvas の機能を利用してサイズ制限以下に縮小する。
        image.onload = DrawImageOnCanvas;

        $(function () {
            // HTML5 File API がサポートされていることを確認
            if (window.File && window.FileReader && window.FileList) {
                fileReader = new FileReader();

                // 下の関数 readAsDataURL で読み込みが完了すると
                // onloadend イベントが発生する。非同期なので、そのリスナ
                // で FileReader から Data url を取得し、それを image
                // オブジェクトの src 属性に設定する。
                fileReader.onloadend = function () {
                    image.src = fileReader.result;
                };

                $("#<%=FileUpload1.ClientID%>").change(function () {
                    var fileUpload =
                        document.getElementById("<%=FileUpload1.ClientID%>");

                    // ファイルの選択・タイプ/サイズを確認
                    if (ClientValidate(fileUpload) == false) {
                        return;
                    }

                    // FileUpload で選択された画像ファイルを読み込み
                    fileReader.readAsDataURL(fileUpload.files[0]);
                });
            }
            else {
                alert("ブラウザは HTML5 File API をサポートしていません。");
            }
        });

        // ファイルの選択・サイズ・タイプの確認のためのヘルパ関数
        function ClientValidate(fileUpload) {
            if (fileUpload.files[0] == null) {
                alert("ファイルが未選択です。");
                return false;
            }

            if (fileUpload.files[0].type != allowedContentType) {
                alert("選択されたファイルのタイプが" + allowedContentType + "ではありません。");
                return false;
            }

            if (fileUpload.files[0].size > maxFileSize) {
                alert("ファイルのサイズが制限の " + maxFileSize + " バイトを超えています。");
                return false;
            }

            return true;
        }


        // 上で定義した image オブジェクトの src 属性に Data url が設定さ
        // れると発生する onload イベントのリスナ。ここで image 要素から 
        // canvas を描画する。
        function DrawImageOnCanvas( )
        {
            // オリジナル画像のサイズ
            var w = image.width;
            var h = image.height;
            var targetW, targetH;
            var context = document.getElementsByTagName('canvas')[0].getContext('2d');

            if (w <= maxWidth && h <= maxHeight) {
                // w, h ともに制限 maxWidth, maxHeight 以内 ⇒ そのままのサイズで canvas に描画
                $('#mycanvas').attr('width', w);
                $('#mycanvas').attr('height', h);
                context.drawImage(image, 0, 0);
            }
            else if (w < h) {
                // w, h どちらかが制限オーバーで h の方が大きい ⇒ 高さを maxHeight に縮小
                targetH = maxHeight;
                // 幅は高さの縮小比率で縮小
                targetW = Math.floor(w * targetH / h);
                $('#mycanvas').attr('width', targetW);
                $('#mycanvas').attr('height', targetH);
                context.drawImage(image, 0, 0, targetW, targetH);
            }
            else {
                // w, h どちらかが制限オーバーで w の方が大きい ⇒ 幅を maxWidth に縮小
                targetW = maxWidth;
                // 高さは幅の縮小比率で縮小
                targetH = Math.floor(h * targetW / w);
                $('#mycanvas').attr('width', targetW);
                $('#mycanvas').attr('height', targetH);
                context.drawImage(image, 0, 0, targetW, targetH);
            }
        }   

        // canvas の画像データを取得して jQuery.Ajax で送信。
        // クライアントからサーバーへ送信できる JSON 文字列の
        // 長さは、デフォルトで 102,400 文字に制限されている。
        // 制限を越える場合 web.config の jsonSerialization 
        // 要素の設定によって変更が必要。
        function uploadImage() {
            var context = document.getElementsByTagName('canvas')[0].getContext('2d');
            var url = context.canvas.toDataURL("image/jpeg");

            $.ajax({
                type: "POST",
                url: "0118-CanvasImageUpload2.aspx/ReceiveImage",
                data: '{"imgBase64":"' + url + '"}',
                contentType: "application/json; charset=utf-8",

                success: function (data) {
                    // .NET 3.5 で追加された d パラメータの処置
                    if (data.hasOwnProperty('d')) {
                        data = data.d;
                    }
                    $('#result').text(data);
                },

                error: function (jqXHR, textStatus, errorThrown) {
                    $('#result').text('textStatus: ' + textStatus +
                        ', errorThrown: ' + errorThrown);
                }
            });
        }
    //]]>
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:FileUpload ID="FileUpload1" runat="server" />
        <input id="Button1" type="button" value="Upload" 
            onclick="javascript:uploadImage();" />
        <hr />
        <canvas id="mycanvas" width="500" height="500"></canvas>
        <div id="result"></div>
    </div>
    </form>
</body>
</html>

【2015/7/3 追記】

上に書いたコードを実際に動かして試すことができるよう、自分の使っているサーバーにアップしました。

http://surferonwww.info/Test/0118-CanvasImageUpload2.aspx

いたずら防止のため上のコードとは少し変えています。サイズ制限も 500 x 500 に変えました。肝心の部分は変えていませんが、変更後の JavaScript コードが見たい場合は上の URL のページの HTML ソースを見てください。

ファイルのアップロードと保存もできませんが(代わりごく短い JSON 文字列を送信し、応答は返すようにしています)、よろしければ試してみてください。

編集 履歴 (4)
  • ありがとうございます。
    「iPhoneで写真を撮る→表示された画像に○などのイメージを描きこむ→サーバにアップロードする」という流れですので、 canvas の画像データを送りたいところです。
    お教えいただいているのは、送信方法を変える必要があるということですね。
    -
  • Button クリックで form を submit するのではなくて、Ajax を使って canvas の画像データを送信するということです。canvas upload などをキーワードにググると参考になる記事が見つかるはずですのでやってみてください。 -
  • ご丁寧にありがとうございます。デモページを拝見しました。コードでも書いてみます。 -
  • 自分のサイトのアップしたものは、いたずら防止のため上のコードとは少し変えていますのでご注意ください。IE などでアクセスして、ソースを表示すれば分かります。 -
  • WEBアプリケーションプロジェクトでまだ自分では検証していないのですが、一つずつ確かめていきたいとおもいます。まことにありがとうございました。
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。