スタートページJavascript

ローカル画像の加工

ローカル画像を 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カラーモデル
  出力画素 = (0.299 * 赤 + 0.587 * 緑 + 0.114 * 青)
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 ┐
     │ 1/9 1/9 1/9 │
     └ 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];
        }
    }
}

色の変化を強調した画像になります。
フィルタ ┌ 0 -1  0 ┐
     │-1  5 -1 │
     └ 0 -1  0 ┘
function sharp() {
    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]
                     =  5*画素1[i][j][c]
                     - (画素1[i-1][j][c]
                          + 画素1[i][j-1][c]
                          + 画素1[i][j+1][c]
                          + 画素1[i+1][j][c]);
            }
            画素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;
            }
        }
    }
}



任意のフィルタにより変換します。→★参照
フィルタとは、周辺の画素の色強度に重みをつけて、   それを中心の画素の強度とする操作です。    左上 上  右上    左  中心 右    左下 下  右下 対象画素およびそのまわりの画素に次の重みをつける ul uc ur cl cc cr dl dc dr 各要素の値は-5~5程度が適切 合計値=1:全体の明るさが元と同程度 合計値=0:全体の明るさがやや暗くなる
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); 
}