処理がほぼ一定のぼかし処理

以前「どんな度合いに対しても処理時間がほぼ一定なぼかし」という話がありました。
考えたら、以前とよく似た方法で実現できそうです。


・考え方
ぼかしの主な処理は任意ピクセル周囲にある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;