処理がほぼ一定のぼかし処理
以前「どんな度合いに対しても処理時間がほぼ一定なぼかし」という話がありました。
考えたら、以前とよく似た方法で実現できそうです。
・考え方
ぼかしの主な処理は任意ピクセル周囲にあるnxnピクセルの色の加算です。
普通にやると凄く重くなるのですが、大半の計算は前回計算した内容と重なる性質があるので、そこを省略して速度を稼いであげます。
・横方向の計算の省略
□□□ ■■□ □①□→■②□ □□□ ■■□ ||> ①②とぼかし処理を行った場合、■の部分の計算が重複する事になります。 ■の部分の計算を省いてみます。 >|| 1 2 3 2 3 4 □□□ ■■□ □①□→■②□ □□□ ■■□ ②=①-h[1]+h[4]
縦の列の加算値の配列『h[]』とします。
①から一番左のh[1]を減算し、②の一番左h[4]を加算させれば②の合計値を得る事が出来ます。
これで横方向に対して一定の計算回数で求める事になりました。
というのが前回までのお話。
・横方向の計算の省略
縦方向に加算した配列自体も、横方向と同じ考え方が使用できます。
forループのラインがY方向に1Pixel移動する際、再度、縦方向に加算しなおす必要はありません。
1□ 2■ 2①→3② 3□ 4□ ②=①-v[1]+v[4]
h[]から①の一番上のピクセルを減算して、②の一番下のピクセルを加算すれば求まります。
ただし、このとき、①の一番上のピクセルはぼかし処理で値が変化しているので、必ず画像のコピーをとって、そこから正確な値を引く必要があります。
フェイクとしてぼかしたいい加減な値を引くことは出来ないので、この方式では必ずコピーが必要です。
これで縦方向の計算も一定になり、ぼかしの度合いに対して、『ほぼ一定』に処理できますね。
はい終了。
っとやってみたら、ぼかし処理の四角いマスが見えちゃいますね。
ちょっと不恰好なので二回かけちゃえー
以下ソース。
TARGB8888はPixelFormat:ARGB8888をオブラートしたものなので、てきとーに解釈してくらはい。
あと、微妙にフェイクです:D
procedure TARGB8888.FakeBlur(aw, ah: Integer); var SA,SR,SG,SB : array of Integer; AddA,AddR,AddG,AddB : Integer; aa,rr,gg,bb : DWORD; Temp : TARGB8888; ww,hh,haw,hah,divvalue : Integer; i,j,n : Integer; sptr,dptr : PDWORD; begin if (bmp=nil)then exit; ww := Width; hh := Height; haw := aw div 2; hah := ah div 2; if (hah<1)and(haw<1)then exit; if (haw<1)then haw := 1; if (hah<1)then hah := 1; //コピー作成 Temp := TARGB8888.Create; Temp.Assign(self); //1024倍固定小数の逆数 a=a/9 → a=a*idd1024 shr 10 //idd1024 := Trunc(1/9*1024); divvalue := (haw*2+1) * (hah*2+1); //スタックバッファ SetLength(SA,ww); SetLength(SR,ww); SetLength(SG,ww); SetLength(SB,ww); for i:=0 to ww-1 do begin SA[i] := 0; SR[i] := 0; SG[i] := 0; SB[i] := 0; end; //スタックバッファに初期値を設定する for i:=-hah to hah do begin if (i<0)then sptr := Temp.Bmp.ScanLine[0] else if (i>hh-1)then sptr := Temp.Bmp.ScanLine[hh-1] else sptr := Temp.Bmp.ScanLine[i]; for j:=0 to ww-1 do begin inc(SA[j],sptr^ shr 24); inc(SR[j],sptr^ shr 16 and $FF); inc(SG[j],sptr^ shr 8 and $FF); inc(SB[j],sptr^ and $FF); inc(sptr); end; end; //ぼかし処理 for i:=0 to hh-1 do begin //AddBufferに初期値を設定する AddA := 0; AddR := 0; AddG := 0; AddB := 0; for j:=-haw to haw do begin n := j; if (n<0)then n:=0 else if (n>ww-1)then n:=ww-1; inc(AddA,SA[n]); inc(AddR,SR[n]); inc(AddG,SG[n]); inc(AddB,SB[n]); end; //ぼかし処理 dptr := BMP.ScanLine[i]; for j:=0 to ww-1 do begin aa := AddA div divvalue; //うひゃー rr := AddR div divvalue; gg := AddG div divvalue; bb := AddB div divvalue; dptr^ := (aa shl 24)or(rr shl 16)or(gg shl 8)or(bb); inc(dptr); //AddBufferから1つ前の値を減算する n := j-haw; if (n<0)then n := 0; dec(AddA,SA[n]); dec(AddR,SR[n]); dec(AddG,SG[n]); dec(AddB,SB[n]); //AddBufferに次の値を格納 n := j+haw+1; if (n>ww-1)then n := ww-1; inc(AddA,SA[n]); inc(AddR,SR[n]); inc(AddG,SG[n]); inc(AddB,SB[n]); end; //スタックバッファの更新 //一番上のライン分を減算 if (i-hah<0)then sptr := Temp.Bmp.ScanLine[0] else sptr := Temp.Bmp.ScanLine[i-hah]; for j:=0 to ww-1 do begin dec(SA[j],sptr^ shr 24); dec(SR[j],sptr^ shr 16 and $FF); dec(SG[j],sptr^ shr 8 and $FF); dec(SB[j],sptr^ and $FF); inc(sptr); end; //次のライン分を加算させる if (i+hah+1<hh)then sptr := Temp.Bmp.ScanLine[i+hah+1] else sptr := Temp.Bmp.ScanLine[hh-1]; for j:=0 to ww-1 do begin inc(SA[j],sptr^ shr 24); inc(SR[j],sptr^ shr 16 and $FF); inc(SG[j],sptr^ shr 8 and $FF); inc(SB[j],sptr^ and $FF); inc(sptr); end; end; Temp.Free; end;