ローカル画像を CANVAS に表示、セピア化やフィルタリングなどの画像加工をします。
・左の(説明)をクリックすると、基本的事項が下にに表示されます。
・ボタンにカーソルを載せると、その処理の説明とコードが表示されます。
さらにクリックすると、右上に処理後の画像が表示されます。
●各画素の変換
(説明)画像は細かい点(画素、ピクセル)の集まりです。 各画素は、赤・緑・青の3原色がそれぞれ0~255の段階で表現されます。 それにより、256×256×256=16万色(フルカラー)が表現されます。 赤= 0、緑= 0、青= 0:black 赤=255、緑= 0、青= 0:red 赤= 0、緑=255、青= 0:green 赤= 0、緑= 0、青=255:blue 赤=255、緑=255、青= 0:yellow 赤= 0、緑=255、青=255:aqua 赤=255、緑= 0、青=255:fuchsia 赤=255、緑=255、青=255:white 赤=127、緑=127、青=127:gray 赤=127、緑=127、青= 0:olive 赤= 0、緑=127、青=127:teal 赤=127、緑= 0、青=127:purple 元図の情報は 画像1[i][j][c]、変換後の情報は 画像2[i][j][c] に保存されます。 i:画素の縦位置 j:画素の横位置 c:0=赤、1=緑、2=青 3=不透明度 画像1[150][200][0] = 127 とは、縦150、横2000の赤の強度が127の意味 「各画素の変換」とは、 画像1[i][j][c] →(操作)→ 画像2[i][j][c] をすることです。 元図をネガ(写真のフィルム)にします。 3原色の強さは0~255なので、それを反転する。 function nega() { for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for(c=0; c<=2; c++) { 画素2[i][j][c] = 255 - 画素1[i][j][c]; } 画素2[i][j][3] = 画素1[i][j][3]; } } } 元図を白黒(モノクロ、グレイスケール)にします。 赤、緑、青の強度を同じにすると灰色になる。 しかし、人間の目の感覚が異なるので調整する。 NTSCで使用されているYIQカラーモデル function gray() { for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { var y = 0.299*画素1[i][j][0] + 0.587*画素1[i][j][1] + 0.114*画素1[i][j][2]; 画素2[i][j][0] = y; 画素2[i][j][1] = y; 画素2[i][j][2] = y; 画素2[i][j][3] = 画素1[i][j][3]; } } } 元図をセピア調(古い写真)にします。 3原色の値が同じであるとき、その色が彩度0のグレーになる。 NTSCで使用されているYIQカラーモデル y = (0.299 * 赤 + 0.587 * 緑 + 0.114 * 青) グレイスケールになる セピア色にするため、3原色の強弱をつける 赤 = (Y/255) * 240 緑 = (Y/255) * 200 青 = (Y/255) * 145 function sepia() { for (var i=0; i<縦幅; i++) { for (var j=0; ji<横幅; j++) { var y = 0.299*画素1[i][j][0] + 0.587*画素1[i][j][1] + 0.114*画素1[i][j][2]; 画素2[i][j][0] = (y/255)*240; 画素2[i][j][1] = (y/255)*200; 画素2[i][j][2] = (y/255)*145; 画素2[i][j][3] = 画素1[i][j][3]; } } }
-255~255の値を指定してください。
3原色の強さは0~255の段階で表されています。
その値に指定した値を加えます。
正のときはその色が強くなり、負のときは弱くなります。
function shift() { var p = new Array(3); p[0] = eval(document.shiftform.red.value); p[1] = eval(document.shiftform.green.value); p[2] = eval(document.shiftform.blue.value); for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for (c=0; c<=2; c++) { y = 画素1[i][j][c] + p[c]; if (y > 255) y = 255; else if(y < 0) y = 0; 画素2[i][j][c] = y; } // 透明度(単にコピーするだけ) 画素2[i][j][3] = 画素1[i][j][3]; } } } すべてをγ=1にすると左画像と同じになり、 0<γ<1とした色は明るく(濃く)なり、 1<γとした色は暗く(薄く)なります。 モニタにはそれぞれ特性があり、RGB信号を正しく表示できません。 それを解消するためにRGB信号を y=xγ に変更します。 それをγ(ガンマ)補正といいます。 モニタを最初にパソコンに接続したときに設定します。 一般には暗くなる傾向があるので、γ≒2程度が標準的になっています。 しかし、モニタの特性が標準と大きく異なっていたり、設定が不適切ですと、 モニタ画面が元の画像を忠実に表示できません。 また、プリンタにも特性があり、画面と異なる色彩になることがあり、 それを補正するために、プリンタでもγ補正を設定しています。 ここでの操作は、モニタやプリンタの設定を変更するのではなく、 元のRGB信号にγ補正をしたときの画像を表示します。 function gamma() { var p = new Array(3); p[0] = eval(document.gammaform.red.value); p[1] = eval(document.gammaform.green.value); p[2] = eval(document.gammaform.blue.value); for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for (c=0; c<=2; c++) { if (p[c] == 1) { 画素2[i][j][c] = 画素1[i][j][c]; } else { y = Math.pow((画素1[i][j][c]/255), p[c]) * 255; if (y > 255) y = 255; y = Math.round(y); 画素2[i][j][c] = y; } } 画素2[i][j][3] = 画素1[i][j][3]; } } }
3原色の強さを変更します。
0<左値<右値<255にしてください。
例:赤[50]~[200] のとき、各画素の赤強度が、
50以下→0(赤要素を消す)
100以上→255(最大強度)
50~100→ その間を直線で変換
y=255/(100-50)*(x-50)
すなわち、50~100の変化を際立たせます。
function linear() { var cmin = new Array(3); var cmax = new Array(3); cmin[0] = eval(document.linearform.rmin.value); cmax[0] = eval(document.linearform.rmax.value); cmin[1] = eval(document.linearform.gmin.value); cmax[1] = eval(document.linearform.gmax.value); cmin[2] = eval(document.linearform.bmin.value); cmax[2] = eval(document.linearform.bmax.value); for (c=0; c<=2; c++) { if ( (cmin[c] < 0) || (cmin[c] >= cmax[c] ) || (cmax[c] > 255) ) { alert("範囲エラー"); return; } } for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for(c=0; c<=2; c++) { x = 画素1[i][j][c]; if (x <= cmin[c]) y = 0; else if (x >= cmax[c]) y = 255; else y = Math.round( 255/(cmax[c]-cmin[c])*(x-cmin[c]) ); 画素2[i][j][c] = y; } 画素2[i][j][3] = 画素1[i][j][3]; } } }
色数は8、27、64から選んでください。
下図はフルカラーですが、それを、少ない色にします。
ポスターのような効果があります。
(パステルの本数が少ないような状況です)
色数27とは、赤・緑・青がそれぞれ3つの強度しかもたない場合です。
強度255を3等分して、0、127、255に変換します。
function postal() { var irosu = eval(document.postalform.irosu.value); if (irosu < 16 ) irosu = 8; else if (irosu < 40 ) irosu = 27; else irosu = 64; for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for (c=0; c<=2; c++) { x = 画素1[i][j][c]; if (irosu == 8) { if (x < 128) y = 0; else y = 255; } else if (irosu == 27) { if (x < 85) y = 0; else if (x < 170) y = 127; else y = 255; } else { if (x < 64) y = 0; else if (x < 127) y = 85; else if (x < 192) y = 170; else y = 255; } 画素2[i][j][c] = y; } 画素2[i][j][3] = 画素1[i][j][3]; } } } |
●フィルタリング
(説明)
フィルタリングとは、各画素について、
隣接画素の情報により加工する操作です。
左上 上 右上 0 -1 0
左 自分 右 について -1 5 -1
左下 下 右下 0 -1 0
重みづけ(フィルタという)を与えると、
自分は5倍になり、
上下左右は-1倍になるので
色の変化が強調された画像になります。
外枠の部分は、canvasを超える部分の画素を参照するので、
それを無視しているので、不正確です。
●代表的なフィルタ(分数形式で入力できます)
平滑化:右の[平滑化」と同じ
┌ 1/9 1/9 1/9 ┐
│ 1/9 1/9 1/9 │
└ 1/9 1/9 1/9 ┘
ガウシアン:平滑化
┌ 1/16 2/16 1/16 ┐
│ 2/16 4/16 2/16 │
└ 1/16 2/16 1/16 ┘
鮮鋭化:右の[鮮鋭化」と同じ
┌ 0 -1 0 ┐
│-1 5 -1 │
└ 0 -1 0 ┘
鮮鋭化:斜め隣接画素の影響も考慮
┌ -1 -1 -1 ┐
│ -1 9 -1 │
└ -1 -1 -1 ┘
ラプラシアン:鮮鋭化やエッジ検出、[鮮鋭化」と逆転
┌ 0 1 0 ┐
│ 1 -4 1 │
└ 0 1 0 ┘
Prewitt:エッジ検出(縦方向)
┌ -1 -1 -1 ┐
│ 0 0 0 │
└ 1 1 1 ┘
Sobel:エッジ検出(横方向)
┌ -1 0 1 ┐
│ -2 0 2 │
└ -1 0 1 ┘
エンボス:左の「エンボス」と同じ。エッジ検出の一つ
┌ 5 3 0 ┐
│ 3 1 -3 │
└ 0 -3 -5 ┘
各画素の色を、周囲の画素の平均にします。
色の変化が滑らかになり、ぼやけた感じになります。
フィルタ ┌ 1/9 1/9 1/9 ┐
function smooth() { for (var i=1; i%lt;縦幅-1; i++) { for (var j=1; j%lt;横幅-1; j++) { for (c=0; c%lt;=2; c++) { 画素2[i][j][c] = Math.round ( ( 画素1[i-1][j-1][c] + 画素1[i-1][j][c] + 画素1[i-1][j+1][c] + 画素1[i][j-1][c] + 画素1[i][j][c] + 画素1[i][j+1][c] + 画素1[i+1][j-1][c] + 画素1[i+1][j][c] + 画素1[i+1][j+1][c] ) /9); } 画素2[i][j][3] = 画素1[i][j][3]; } } } エンボスとは、輪郭を強調した画像です。 墨版画のような感じになります。 エンボスでの代表的なソーベル(Sobel)を使用 画素1をグレイスケール化して左のフィルタをかける。 さらに右のフィルタをかけて画素2にする ┌ -1 0 1 ┐ ┌ -1 -2 -1 ┐ │ -2 0 2 │ │ 0 0 0 │ └ -1 0 1 ┘ └ 1 2 1 ┘ function emboss() { // abの定義(グレイスケールなので2次元でよい) var ab = []; for (var i=0; i<縦幅; i++) { ab[i] = []; } // とりあえず画素1を画素2にグレイスケールでコピーしておく for (i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { y = Math.round( (画素1[i][j][0] + 画素1[i][j][1] + 画素1[i][j][2]) / 3); 画素2[i][j][0] = y; 画素2[i][j][1] = y; 画素2[i][j][2] = y; 画素2[i][j][3] = 画素1[i][j][3]; } } // 画素1に最初のフィルタをかけてグレイスケール化してadに書き出す for (i=1; i<縦幅-1; i++) { for (j=1; j<横幅-1; j++) { var sumy = 0; for (var c=0; c<=2; c++) { y = (画素1[i-1][j+1][c] + 2*画素1[i][j+1][c] + 画素1[i+1][j+1][c]) - (画素1[i-1][j-1][c] + 2*画素1[i][j-1][c] + 画素1[i+1][j-1][c]); sumy = sumy + y; } ab[i][j] = Math.round(sumy / 3); // 3原色の平均値 } } // abを2番目のフィルタをかけてbに書き出す for (i=1; i<縦幅-1; i++) { for (j=1; j<横幅-1; j++) { y = (ab[i+1][j-1] + 2*ab[i+1][j] + ab[i+1][j+1]) - (ab[i-1][j-1] + 2*ab[i-1][j] + ab[i-1][j+1]); for (c=0; c<=2; c++) { 画素2[i][j][c] = y; } } } } 任意のフィルタにより変換します。→★参照 function filter() { var ul = eval(document.filterform.ul.value); var uc = eval(document.filterform.uc.value); var ur = eval(document.filterform.ur.value); var cl = eval(document.filterform.cl.value); var cc = eval(document.filterform.cc.value); var cr = eval(document.filterform.cr.value); var dl = eval(document.filterform.dl.value); var dc = eval(document.filterform.dc.value); var dr = eval(document.filterform.dr.value); for (var i=1; i<縦幅-1; i++) { for (var j=1; j<横幅-1; j++) { for (var c=0; c<=2; c++) { 画素2[i][j][c] = Math.round ( ul*画素1[i-1][j-1][c] + uc*画素1[i-1][j][c] + ur*画素1[i-1][j+1][c] + cl*画素1[i][j-1][c] + cc*画素1[i][j][c] + cr*画素1[i][j+1][c] + dl*画素1[i+1][j-1][c] + dc*画素1[i+1][j][c] + dr*画素1[i+1][j+1][c] ); if (画素2[i][j][c] > 255) 画素2[i][j][c] = 255; if (画素2[i][j][c] < 0) 画素2[i][j][c] = 0; } 画素2[i][j][3] = 画素1[i][j][3]; } } } モザイクとは、いくつかの画素を同じ色にすることです。 例えば10とすると、縦10×横10=100画素が、 すべてその平均値の色になります。 小さな画像を拡大したような感じになります。 function mosaic() { var d = Math.round(eval(document.mosaicform.d.value)); if ( ( d < 1 ) || ( d > 20 ) ) { alert("画素数エラー:2~20"); return; } for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for(c=0; c<=3; c++) { 画素2[i][j][c] = 画素1[i][j][c]; } } } for (var i=0; i<=h-d; i=i+d) { for (var j=0; j<縦幅-d; j=j+d) { for (var c=0; c<=2; c++) { var t = 0; for (var ii=i; ii<i+d; ii++) { for (var jj=j; jj<=j+d; jj++) { t = t + 画素1[ii][jj][c]; } } t = Math.round(t/(d*d)); // 縦d×横dの画素の平均色 for (ii=i; ii<i+d; ii++) { for (jj=j; jj<=j+d; jj++) { 画素2[ii][jj][c] = t; } } } } } } グラデーションとは、色彩を段階的に変化させることです。 ここでは左から右にかけて、次第に赤を弱くし、青を強くしています。 function gradation() { for (var j=0; j<横幅; j++) { var 赤 = (横幅 - j) / 横幅; var 青 = j / 横幅; for (var i=0; i<縦幅; i++) { 画素2[i][j][0] = 画素1[i][j][0] * 赤; 画素2[i][j][1] = 画素1[i][j][1]; 画素2[i][j][2] = 画素1[i][j][2] * 青; 画素2[i][j][3] = 画素1[i][j][3]; } } } 透明化とは、画素の透明度を上げて背景の 画像を透かして見えるようにすることです。 画素情報 画素1[i][j][k] の k=3 は透明度を示し、 完全透明は0、透明なしは255です。 ここでは、画面の中央矩形内は元のまま、外は半透明にしています。 function opacity() { var 矩形上 = 縦幅 * (1/4); var 矩形下 = 縦幅 * (3/4); var 矩形左 = 横幅 * (1/4); var 矩形右 = 横幅 * (3/4); for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for (var c=0; c<=3; c++) { 画素2[i][j][c] = 画素1[i][j][c]; } if ( (i<矩形上) || (i>矩形下) || (j<矩形左) || (j>矩形右) ) { 画素2[i][j][3] = Math.round(画素1[i][j][3] / 4) } } } } 左右回転をします。 function mirror() { for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { var jj = 横幅 - j; if (jj < 横幅) { // canvas オーバーフローを回避 for (var c=0; c<=3; c++) { 画素2[i][jj][c] = 画素1[i][j][c]; } } } } } 右画像を左画面に移します。 これにより、各操作を連続的に行なえます。 例えば、[ネガ]→[右→左」→「ネガ」とすると、 右画面が元の画像になります。 function btoa() { var ctx1data = ctx1.createImageData(横幅, 縦幅); var outData = ctx1data.data; var k = 0; for (var i=0; i<縦幅; i++) { for (var j=0; j<横幅; j++) { for (var c=0; c<=3; c++) { outData[k] = 画素2[i][j][c]; 画素1[i][j][c] = 画素2[i][j][c]; k++; } } } // canvas1に画像表示 ctx1.putImageData(ctx1data, 0, 0); } |