下の画像のように html の img 要素の src 属性に url が指定されてブラウザ上に表示されている状態で、その画像データを JavaScript で取得し、さらにサーバーにアップロードする方法を書きます。
img 要素から直接画像データを JavaScript で取得する方法は無さそうです。少なくとも自分は見つけられませんでした。なので、img 要素の画像を一旦 canvas に描画し、canvas から画像データを取得するようにしました。
なお、この記事の方法は src 属性の url が同一オリジンになっている場合に限りますので注意してください。
クロスドメインになっている場合は、canvas から画像データを取得する toDataURL メソッドまたは toBlob メソッドで DOMException がスローされます。エラーメッセージは、例えば Chrome で toDataURL メソッドを使った場合、以下のようになります。
DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
以下にコード例を載せます。ASP.NET Core MVC の View のものですが、html とJavaScript のコードはそのまま使えるはずです。
コードのコメントに書いた「その1」は toDataURL メソッドを使って画像データを DataURL 形式の文字列として取得し JSON 文字列として送信するものです。データは BASE64 でエンコードされるのでバイナリ形式よりサイズが約 1.3 倍大きくなることに注意してください。
「その2」は toBlob メソッドを使って画像データをバイナリ形式で取得し multipart/form-data 形式で送信するものです。toBlob メソッドは非同期で動き、バイナリデータは引数のコールバックに渡されることに注意してください。
@{
ViewData["Title"] = "UploadImage";
}
<h1>UploadImage</h1>
<input id="button1" type="button" value="Upload DataUrl" />
<input id="button2" type="button" value="Upload Blob" />
<br />
<img id="image1" src="/images/911GT2_2.jpg" alt="" />
<div id="result"></div>
@section Scripts {
<script type="text/javascript">
//<![CDATA[
let myImage, uploadDataUrl, uploadBlob, myCanvas, resultDiv;
// DOMContentLoaded イベントのリスナ設定
window.addEventListener('DOMContentLoaded', () => {
uploadDataUrl = document.getElementById("button1");
uploadBlob = document.getElementById("button2");
myImage = document.getElementById("image1");
resultDiv = document.getElementById("result");
myCanvas = document.createElement("canvas");
uploadDataUrl.addEventListener('click', uploadDataUrlImage);
uploadBlob.addEventListener('click', uploadBlobImage);
});
// その1
// 上の img 要素の画像を canvas に描画、canvas の画像データ
// を DataURL 形式の文字列として取得し JSON 文字列を組み立
// て、それを fetch API を使���てサーバーに送信。
const uploadDataUrlImage = async () => {
const context = myCanvas.getContext('2d');
myCanvas.setAttribute('width', myImage.width);
myCanvas.setAttribute('height', myImage.height);
context.drawImage(myImage, 0, 0);
const dataUrl = myCanvas.toDataURL("image/jpeg");
const params = {
method: "POST",
body: '{"imgBase64":"' + dataUrl + '"}',
headers: { 'Content-Type': 'application/json' }
}
const response = await fetch("/api/Canvas", params);
if (response.ok) {
const message = await response.text();
resultDiv.innerText = message;
} else {
resultDiv.innerText = "アップロード失敗";
}
}
// その2
// img 要素の画像を canvas に描画、canvas の画像データを
// Blob 形式で取得し、それを fetch API を使ってサーバー
// に送信。
const uploadBlobImage = async () => {
const context = myCanvas.getContext('2d');
myCanvas.setAttribute('width', myImage.width);
myCanvas.setAttribute('height', myImage.height);
context.drawImage(myImage, 0, 0);
const data = await getBlobFromCanvas();
const formData = new FormData();
formData.append("postedFile", data);
const param = {
method: "POST",
body: formData
}
const response = await fetch("/api/Upload", param);
if (response.ok) {
const message = await response.text();
resultDiv.innerText = message;
} else {
resultDiv.innerText = "アップロード失敗";
}
}
// toBlob メソッドは非同期で動き、結果の Blob データは引数
// のコールバックに渡されるので、結果は以下のように Promise
// にラップして返す
const getBlobFromCanvas = () => {
return new Promise((resolve) => {
myCanvas.toBlob((blob) => resolve(blob), "image/jpeg");
});
};
//]]>
</script>
}
上のコードで送信された画像データを受け取るサーバー側のコードは、「その1」の Data Url 形式のデータを JSON 文字列で受け取る場合は先の記事「canvas の画像をアップロード (その 2)」の下の方に例がありますので見てください。
「その2」のコードでは以下の Fiddler でのキャプチャ画像のようにバイナリデータが multipart/form-data 形式で送信されます。
サーバー側のコードはそれを受け取って処理できるように書く必要があります。ASP.NET Core Web API でのコード例を下に載せておきます。
using Microsoft.AspNetCore.Mvc;
namespace MvcCore6App3.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UploadController : ControllerBase
{
// 物理パスの取得用
private readonly IWebHostEnvironment _hostingEnvironment;
public UploadController(IWebHostEnvironment hostingEnvironment)
{
this._hostingEnvironment = hostingEnvironment;
}
// .NET 6.0 で作ったプロジェクトはnull許容参照型がデフォルトで有効に
// 設定してあるので、引数を NULL 許容にしておかないと、クライアントでファ
// イルを選択しないで送信した場合、HTTP 400 Bad Request エラーになって
// "The postedFile field is required."というエラーメッセージが返ってくる
[HttpPost]
public async Task<string> Post([FromForm] IFormFile? postedFile)
{
string result = "";
if (postedFile != null && postedFile.Length > 0)
{
// アップロードされたファイル名を取得
string filename = System.IO.Path.GetFileName(postedFile.FileName);
// アプリケーションルートの物理パスを取得
string contentRootPath = _hostingEnvironment.ContentRootPath;
string filePath = $"{contentRootPath}\\UploadedFiles\\" +
$"{filename}{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg";
// フォルダ UploadedFile に画像ファイルを保存
using (var stream = new FileStream(filePath, FileMode.Create))
{
await postedFile.CopyToAsync(stream);
}
result = $"{filename} ({postedFile.ContentType}) - " +
$"{postedFile.Length} bytes アップロード完了";
}
else
{
result = "ファイルアップロードに失敗しました";
}
return result;
}
}
}
JavaScript はブラウザ依存なところがありますが、Edge 109.0.1518.70, Chrome 109.0.5414.120, Firefox 109.0, Opera 94.0.4606.76 で期待通り動くことは確認しました。