8.4K Views
August 28, 23
スライド概要
HTML5 Canvas を使った画像加工の超初心者向け入門
HTML5 Canvas 画像加工入門 © 2023/08/25(Fri) “よや”<[email protected]>
ターゲット • 画像処理やった事ない人 • ブラウザ環境だけで画像処理をやってみたい人 • ブラウザへの画像の渡し方が分からない人 • ライブラリ使うの面倒で素の JavaScript が良い人 • HTML Canvas にチャレンジして環境作れず挫折した人
一応、自己紹介 • https://github.com/yoya https://yoya.github.io/ • https://qiita.com/yoya/ • 主に JavaScript がお仕事 • 画像処理が趣味 • 特に Canvas で遊ぶのが好き • ImageMagick マニア
実験サンプル • ソースコード • https://github.com/yoya/canvas.js/tree/main/intro • デモサイト • https://yoya.github.io/canvas.js/intro/
HTML Canvas とは • <canvas> で描画可能 HTML <canvas> </canvas> JavaScript const canvas = document.querySelector('canvas’); const ctx = canvas.getContext('2d’); ctx.fillStyle = ‘green’; // 緑色の塗りで ctx.fillRect(10, 10, 100, 100); // 四角を描画 デモサイトの intro/00_canvas.html
画像処理の準備 (画像入力1)
• img src に画像ファイルを渡す
intro/01_imgsrc.html
• 画像が用意できると onload 発火
• canvas に drawImage で貼り付ける
const image = new Image();
image.src = "fujisan.jpg";
image.onload = (e) => {
const canvas = document.querySelector('canvas’);
const ctx = canvas.getContext('2d’);
const { width, height } = image;
Object.assign(canvas, {width, height});
ctx.drawImage(image, 0, 0, width, height);
}
画像処理の準備 (画像入力2) (1/3)
• drop で画像ファイルを渡す
• まず、邪魔なデフォルト処理を無効化する
intro/02_imgdrop.html
document.addEventListener("dragover" , (e) => {
e.preventDefault(); // デフォルトの動作を無効化
}, false);
画像処理の準備 (画像入力2) (2/3)
• drop で画像ファイルを渡す
intro/02_imgdrop.html
• dataTransfer file => FileReader => dataURL => img.src
document.addEventListener("drop", function(e) {
e.preventDefault(); // デフォルトの動作を無効化
const file = e.dataTransfer.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
const url = reader.result;
const image = new Image();
image.src = url;
image.onload = (e) => {
const canvas = document.querySelector('canvas’);
(image を受け取れたので、後は imgsrc と同じ)
画像処理の準備 (画像入力2) (3/3) • drop で画像ファイルを渡す • 入れ子が辛いので await を使う intro/03_imgdropawait.html document.addEventListener("drop", async function(e) { e.preventDefault(); // デフォルトの動作を無効化 const file = e.dataTransfer.files[0]; const reader = new FileReader(); reader.readAsDataURL(file); await new Promise(resolve => reader.onload = () => resolve()); const url = reader.result; const image = new Image(); image.src = url; // await new Promise(resolve => image.onload = () => resolve()); await image.decode(); // ↑ これと同じ (image を受け取れたので、後は imgsrc と同じ)
画像処理の準備 (画像入力3) • copy & paste で画像ファイルを渡す intro/04_imgpaste.html document.addEventListener(”paste", async function(e) { const contents = await navigator.clipboard.read(); const item = contents[0]; const blob = await item.getType("image/png"); const url = URL.createObjectURL(blob); const image = new Image(); image.src = url; await image.decode(); (image を受け取れたので、後は imgsrc と同じ)
画像処理の準備 (画像入力)
• 毎回書くの面倒なので、function に押し込める
function imageinput(canvas, callback) {
const image = new Image();
image.onload = () => {
const ctx = canvas.getContext('2d’);
const { width, height } = image;
Object.assign(canvas, {width, height});
ctx.drawImage(image, 0, 0, width, height);
callback();
}
image.src = "img/fujisan.jpg";
document.addEventListener("drop", async function(e) {
画像処理の準備 (画像コピー) (1/3)
• 画像をコピーする
intro/05_imagecopy.html
• コピーさえできれば途中にフィルタつけるのも簡単
• canvas1 に画像を貼る => canvas1 の画像を canvas2 にコピー
const [canvas1, canvas2] = document.querySelectorAll('canvas');
imageinput(canvas1, () => {
copyImage(canvas1, canvas2);
});
const copyImage = (canvas1, canvas2) => {
(ここに画像コピーの処理を記述する)
}
画像処理の準備 (画像コピー) (2/3)
• 画像をコピーする。配列を set
intro/05_imagecopy.html
const copyImage = (canvas1, canvas2) => {
const { width , height } = canvas1;
Object.assign(canvas2, {width, height}); // src,dstサイズ合わせ
const ctx1 = canvas1.getContext('2d');
const ctx2 = canvas2.getContext('2d’);
// canvas から画像 RGBA データを取得
const imageData1 = ctx1.getImageData(0, 0, width, height);
const imageData2 = new ImageData(width, height);
//
imageData2.data.set(imageData1.data); // 配列全コピ
// canvas に RGBA データを書き込む
ctx2.putImageData(imageData2, 0, 0);
}
画像処理の準備 (画像コピー) (3/3)
• 画像をコピーする
• RGBAを意識してコピー
• RGBA は 4byte 構成
intro/05_imagecopy.html
const copyImage = (canvas1, canvas2) => {
(略)
// imageData2.data.set(imageData1.data); // 配列全コピ
for (let i = 0; i < width * height; i++) {
const rgba = imageData1.data.subarray(i*4, i*4 + 4);
imageData2.data.set(rgba, i*4);
}
画像フィルタ処理 (画像の赤強調)
• 画像フィルタ
• 赤だけ色を強調する
• ImageData の data 配列は代入時に 0〜255 内自動 clamp する
• Uint8ClampedArray
• 255 を超える心配をせずに掛け算して良い!
const redImage = (canvas1, canvas2) => {
(略)
for (let i = 0; i < width * height; i++) {
const [r, g, b, a] = imageData1.data.subarray(i*4, i*4 + 4);
const rr = r * 1.5;
imageData2.data.set([rr, g, b, a], i*4);
}
intro/06_imagered.html
画像フィルタ処理 (画像のセピア化)
• 画像フィルタ
• CSS のセピアを真似して計算
intro/07_imagesepia.html
const copyImage = (canvas1, canvas2) => {
(略)
for (let i = 0; i < width * height; i++) {
let [r, g, b, a] = imageData1.data.subarray(i*4, i*4 + 4);
// CSS sepia coefficient
const rr = r * 0.393 + g * 0.769 + b * 0.189;
const gg = r * 0.349 + g * 0.686 + b * 0.168;
const bb = r * 0.272 + g * 0.534 + b * 0.131;
imageData2.data.set([rr, gg, bb, a], i*4);
}
画像座標処理 (画像の回転)
• 180度回転
• 逆向き index で代入
intro/08_imagereverse.html
const copyImage = (canvas1, canvas2) => {
(略)
for (let i = 0; i < width * height; i++) {
const rgba = imageData1.data.subarray(i*4, i*4 + 4);
imageData2.data.set(rgba, (width * height - i - 1)*4);
}
画像座標処理 (画像の回転) 失敗例 • 180度回転 • reverse を使うとどうなるか。 intro/08_imagereverse.html const copyImage = (canvas1, canvas2) => { (略) imageData2.data.set(imageData1.data.reverse()); • RGBA が ABGR にひっくり返る • 色が壊れる
画像座標処理 (画像の回転) Uint32 • 180度回転 • Uint32Array でラップする。 intro/06_imagereverse.html const copyImage = (canvas1, canvas2) => { (略) const data1_u32 = new Uint32Array(imageData1.data.buffer) const data2_u32 = new Uint32Array(imageData2.data.buffer); data2_u32.set(data1_u32.reverse()); // copy array all • こっちなら RGBA 順が維持されて問題なし! • (注) uint8 で subarray をとる等して buffer のオフセットが 4 の倍数でなくなっ た場合には大抵エラーになるので注意。
まとめ • HTML Canvas の紹介 • 描画命令で好きにできる。画像を貼り付ける事もできる。 • 画像の入力方法 • img src、drop、paste の3種類 • 画像の変換サンプル • 色フィルタと座標変換の超シンプルな例 • 次回予告 • 実際の canvas 応用例 • ここでデモ公開してるアルゴリズム。 • https://yoya.github.io/image.js/