| 1 |
|
|---|
| 2 | var
|
|---|
| 3 | blurRowY,blurRowX: packed array of NativeUInt;
|
|---|
| 4 | iRadiusX,iRadiusY: NativeInt;
|
|---|
| 5 | weightFactor: NativeUInt;
|
|---|
| 6 |
|
|---|
| 7 | { Compute weights of pixels in a row }
|
|---|
| 8 | procedure ComputeBlurRow;
|
|---|
| 9 | var
|
|---|
| 10 | i: NativeInt;
|
|---|
| 11 | ofs: single;
|
|---|
| 12 | begin
|
|---|
| 13 | SetLength(blurRowX, 2*iRadiusX+1);
|
|---|
| 14 | if frac(radiusX)=0 then ofs := 1 else ofs := frac(radiusX);
|
|---|
| 15 | for i := 0 to iRadiusX do
|
|---|
| 16 | begin
|
|---|
| 17 | blurRowX[i] := round((i+ofs)*weightFactor);
|
|---|
| 18 | blurRowX[high(blurRowX)-i] := blurRowX[i];
|
|---|
| 19 | end;
|
|---|
| 20 | SetLength(blurRowY, 2*iRadiusY+1);
|
|---|
| 21 | if frac(radiusY)=0 then ofs := 1 else ofs := frac(radiusY);
|
|---|
| 22 | for i := 0 to iRadiusY do
|
|---|
| 23 | begin
|
|---|
| 24 | blurRowY[i] := round((i+ofs)*weightFactor);
|
|---|
| 25 | blurRowY[high(blurRowY)-i] := blurRowY[i];
|
|---|
| 26 | end;
|
|---|
| 27 | end;
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 | var
|
|---|
| 31 | srcDelta,
|
|---|
| 32 | verticalWeightShift, horizontalWeightShift: NativeInt;
|
|---|
| 33 | ys1,ys2: NativeInt;
|
|---|
| 34 |
|
|---|
| 35 | { Compute blur result in a vertical direction }
|
|---|
| 36 | procedure ComputeVerticalRow(psrc: PBGRAPixel; var sums: TRowSum; pw: PNativeUInt; count: NativeInt);
|
|---|
| 37 | var w: NativeUInt;
|
|---|
| 38 | c: DWord;
|
|---|
| 39 | begin
|
|---|
| 40 | while count > 0 do
|
|---|
| 41 | with sums do
|
|---|
| 42 | begin
|
|---|
| 43 | dec(count);
|
|---|
| 44 | w := pw^; //apply pixel weight
|
|---|
| 45 | inc(pw);
|
|---|
| 46 | c := PDWord(psrc)^;
|
|---|
| 47 | inc(PByte(psrc),srcDelta);
|
|---|
| 48 | aDiv += w;
|
|---|
| 49 | w *= ((c shr TBGRAPixel_AlphaShift) and $ff);
|
|---|
| 50 | sumA += w;
|
|---|
| 51 | w := w shr verticalWeightShift;
|
|---|
| 52 | rgbDiv += w;
|
|---|
| 53 | {$hints off}
|
|---|
| 54 | sumR += ((c shr TBGRAPixel_RedShift) and $ff)*w;
|
|---|
| 55 | sumG += ((c shr TBGRAPixel_GreenShift) and $ff)*w;
|
|---|
| 56 | sumB += ((c shr TBGRAPixel_BlueShift) and $ff)*w;
|
|---|
| 57 | {$hints on}
|
|---|
| 58 | end;
|
|---|
| 59 | end;
|
|---|
| 60 |
|
|---|
| 61 | var
|
|---|
| 62 | psum, psumEnd: PRowSum;
|
|---|
| 63 | sums: packed array of TRowSum;
|
|---|
| 64 | sumStartIndex: NativeInt;
|
|---|
| 65 | total: TRowSum;
|
|---|
| 66 | extendedTotal: TExtendedRowSum;
|
|---|
| 67 | yb,xb,xs,x,xEnd: NativeInt;
|
|---|
| 68 | w: NativeUInt;
|
|---|
| 69 | pw: PNativeUInt;
|
|---|
| 70 | psrc,pdest: PBGRAPixel;
|
|---|
| 71 | bmpWidth,bmpHeight : NativeInt;
|
|---|
| 72 | accumulationFactor: double;
|
|---|
| 73 | bounds: TRect;
|
|---|
| 74 | highSum: NativeInt;
|
|---|
| 75 | tempDest: TBGRACustomBitmap;
|
|---|
| 76 |
|
|---|
| 77 | begin
|
|---|
| 78 | radiusX := round(radiusX*10)*0.1;
|
|---|
| 79 | radiusY := round(radiusY*10)*0.1;
|
|---|
| 80 | if (radiusX <= 0) and (radiusY <= 0) then
|
|---|
| 81 | begin
|
|---|
| 82 | ADestination.PutImage(0,0,bmp,dmSet);
|
|---|
| 83 | exit;
|
|---|
| 84 | end;
|
|---|
| 85 | iRadiusX := ceil(radiusX);
|
|---|
| 86 | iRadiusY := ceil(radiusY);
|
|---|
| 87 | if (frac(radiusX)=0) and (frac(radiusY)=0) then
|
|---|
| 88 | weightFactor:= 1
|
|---|
| 89 | else
|
|---|
| 90 | weightFactor:= 10;
|
|---|
| 91 | bmpWidth := bmp.Width;
|
|---|
| 92 | bmpHeight := bmp.Height;
|
|---|
| 93 | //create output
|
|---|
| 94 | if (ADestination.Width <> bmp.Width) or (ADestination.Height <> bmp.Height) then
|
|---|
| 95 | raise exception.Create('Dimension mismatch');
|
|---|
| 96 | bounds := bmp.GetImageBounds;
|
|---|
| 97 | if IsRectEmpty(bounds) then exit;
|
|---|
| 98 | bounds.Left := max(0, bounds.Left - iRadiusX);
|
|---|
| 99 | bounds.Top := max(0, bounds.Top - iRadiusY);
|
|---|
| 100 | bounds.Right := min(bmp.Width, bounds.Right + iRadiusX);
|
|---|
| 101 | bounds.Bottom := min(bmp.Height, bounds.Bottom + iRadiusY);
|
|---|
| 102 | if not IntersectRect(bounds,bounds,ABounds) then exit;
|
|---|
| 103 |
|
|---|
| 104 | if radiusX*radiusY >= 100 then
|
|---|
| 105 | begin
|
|---|
| 106 | tempDest := ADestination.NewBitmap(ADestination.Width,ADestination.Height);
|
|---|
| 107 | FilterBlurBox(bmp,bounds,radiusX/3.2,radiusY/3.2,tempDest);
|
|---|
| 108 | FilterBlurBox(tempDest,bounds,radiusX/2.9,radiusY/2.9,ADestination);
|
|---|
| 109 | FilterBlurBox(ADestination,bounds,radiusX/3.2,radiusY/3.2,tempDest);
|
|---|
| 110 | FilterBlurBox(tempDest,bounds,radiusX/2.3,radiusY/2.3,ADestination, ACheckShouldStop);
|
|---|
| 111 | tempDest.Free;
|
|---|
| 112 | exit;
|
|---|
| 113 | end;
|
|---|
| 114 |
|
|---|
| 115 | accumulationFactor := (iRadiusY+2)*(iRadiusY+1) div 2 + (iRadiusY+1)*iRadiusY div 2;
|
|---|
| 116 | accumulationFactor *= sqr(weightFactor);
|
|---|
| 117 | verticalWeightShift := 0;
|
|---|
| 118 | while accumulationFactor > (high(NativeUInt) shr 16) + 1 do
|
|---|
| 119 | begin
|
|---|
| 120 | inc(verticalWeightShift);
|
|---|
| 121 | accumulationFactor *= 0.5;
|
|---|
| 122 | end;
|
|---|
| 123 | horizontalWeightShift:= 0;
|
|---|
| 124 | accumulationFactor *= ((iRadiusX+2)*(iRadiusX+1) div 2 + (iRadiusX+1)*iRadiusX div 2);
|
|---|
| 125 | accumulationFactor *= sqr(weightFactor);
|
|---|
| 126 | while accumulationFactor > (high(NativeUInt) shr 16) + 1 do
|
|---|
| 127 | begin
|
|---|
| 128 | inc(horizontalWeightShift);
|
|---|
| 129 | accumulationFactor *= 0.5;
|
|---|
| 130 | end;
|
|---|
| 131 | ComputeBlurRow;
|
|---|
| 132 | //current vertical sums
|
|---|
| 133 | setlength(sums, 2*iRadiusX+1);
|
|---|
| 134 | highSum := high(Sums);
|
|---|
| 135 | psumEnd := @sums[highSum];
|
|---|
| 136 | inc(psumEnd);
|
|---|
| 137 | if bmp.LineOrder = riloTopToBottom then
|
|---|
| 138 | srcDelta := bmpWidth*sizeof(TBGRAPixel) else
|
|---|
| 139 | srcDelta := -bmpWidth*sizeof(TBGRAPixel);
|
|---|
| 140 |
|
|---|
| 141 | xEnd := bounds.left-iRadiusX+highSum;
|
|---|
| 142 | if xEnd >= bmpWidth then xEnd := bmpWidth-1;
|
|---|
| 143 | //loop through destination bitmap
|
|---|
| 144 | for yb := bounds.top to bounds.bottom-1 do
|
|---|
| 145 | begin
|
|---|
| 146 | if (ACheckShouldStop <> nil) and ACheckShouldStop(yb) then break;
|
|---|
| 147 | //evalute available vertical range
|
|---|
| 148 | if yb - iRadiusY < 0 then
|
|---|
| 149 | ys1 := iRadiusY - yb
|
|---|
| 150 | else
|
|---|
| 151 | ys1 := 0;
|
|---|
| 152 | if yb + iRadiusY >= bmpHeight then
|
|---|
| 153 | ys2 := bmpHeight-1 - yb + iRadiusY
|
|---|
| 154 | else
|
|---|
| 155 | ys2 := 2*iRadiusY;
|
|---|
| 156 |
|
|---|
| 157 | { initial vertical rows are computed here. Later,
|
|---|
| 158 | for each pixel, vertical sums are shifted, so there
|
|---|
| 159 | is only one vertical sum to calculate }
|
|---|
| 160 | fillchar(sums[0],sizeof(TRowSum)*length(sums),0);
|
|---|
| 161 | x := bounds.left-iRadiusX;
|
|---|
| 162 | if x < 0 then
|
|---|
| 163 | begin
|
|---|
| 164 | xs := -x;
|
|---|
| 165 | x := 0;
|
|---|
| 166 | end else
|
|---|
| 167 | xs := 0;
|
|---|
| 168 | psrc := bmp.ScanLine[yb-iRadiusY+ys1]+x;
|
|---|
| 169 | psum := @sums[xs];
|
|---|
| 170 | pw := @blurRowY[ys1];
|
|---|
| 171 | while true do
|
|---|
| 172 | begin
|
|---|
| 173 | ComputeVerticalRow(psrc,psum^,pw,ys2-ys1+1);
|
|---|
| 174 | inc(x);
|
|---|
| 175 | inc(psrc);
|
|---|
| 176 | if x > xEnd then break;
|
|---|
| 177 | inc(psum);
|
|---|
| 178 | end;
|
|---|
| 179 | sumStartIndex := 0;
|
|---|
| 180 |
|
|---|
| 181 | pdest := ADestination.scanline[yb]+bounds.left;
|
|---|
| 182 | for xb := bounds.left to bounds.right-1 do
|
|---|
| 183 | begin
|
|---|
| 184 | //add vertical rows
|
|---|
| 185 | pw := @blurRowX[0];
|
|---|
| 186 | psum := @sums[sumStartIndex];
|
|---|
| 187 | if horizontalWeightShift > 4 then
|
|---|
| 188 | begin //we don't want to loose too much precision
|
|---|
| 189 | fillchar({%H-}extendedTotal,sizeof(extendedTotal),0);
|
|---|
| 190 | for xs := highSum downto 0 do
|
|---|
| 191 | with psum^ do
|
|---|
| 192 | begin
|
|---|
| 193 | w := pw^;
|
|---|
| 194 | inc(pw);
|
|---|
| 195 | extendedTotal.sumA += TExtendedRowValue(sumA)*w;
|
|---|
| 196 | extendedTotal.aDiv += TExtendedRowValue(aDiv)*w;
|
|---|
| 197 | extendedTotal.sumR += TExtendedRowValue(sumR)*w;
|
|---|
| 198 | extendedTotal.sumG += TExtendedRowValue(sumG)*w;
|
|---|
| 199 | extendedTotal.sumB += TExtendedRowValue(sumB)*w;
|
|---|
| 200 | extendedTotal.rgbDiv += TExtendedRowValue(rgbDiv)*w;
|
|---|
| 201 | inc(psum);
|
|---|
| 202 | if psum >= psumEnd then pSum := @sums[0];
|
|---|
| 203 | end;
|
|---|
| 204 | if (extendedTotal.aDiv > 0) and (extendedTotal.rgbDiv > 0) then
|
|---|
| 205 | pdest^:= ComputeExtendedAverage(extendedTotal)
|
|---|
| 206 | else
|
|---|
| 207 | pdest^:= BGRAPixelTransparent;
|
|---|
| 208 | end else
|
|---|
| 209 | if horizontalWeightShift > 0 then
|
|---|
| 210 | begin //lossy but efficient way
|
|---|
| 211 | fillchar({%H-}total,sizeof(total),0);
|
|---|
| 212 | for xs := highSum downto 0 do
|
|---|
| 213 | with psum^ do
|
|---|
| 214 | begin
|
|---|
| 215 | w := pw^;
|
|---|
| 216 | inc(pw);
|
|---|
| 217 | total.sumA += sumA*w shr horizontalWeightShift;
|
|---|
| 218 | total.aDiv += aDiv*w shr horizontalWeightShift;
|
|---|
| 219 | total.sumR += sumR*w shr horizontalWeightShift;
|
|---|
| 220 | total.sumG += sumG*w shr horizontalWeightShift;
|
|---|
| 221 | total.sumB += sumB*w shr horizontalWeightShift;
|
|---|
| 222 | total.rgbDiv += rgbDiv*w shr horizontalWeightShift;
|
|---|
| 223 | inc(psum);
|
|---|
| 224 | if psum >= psumEnd then pSum := @sums[0];
|
|---|
| 225 | end;
|
|---|
| 226 | if (total.aDiv > 0) and (total.rgbDiv > 0) then
|
|---|
| 227 | pdest^:= ComputeClampedAverage(total)
|
|---|
| 228 | else
|
|---|
| 229 | pdest^:= BGRAPixelTransparent;
|
|---|
| 230 | end else
|
|---|
| 231 | begin //normal way
|
|---|
| 232 | {$hints off}
|
|---|
| 233 | fillchar(total,sizeof(total),0);
|
|---|
| 234 | {$hints on}
|
|---|
| 235 | for xs := highSum downto 0 do
|
|---|
| 236 | with psum^ do
|
|---|
| 237 | begin
|
|---|
| 238 | w := pw^;
|
|---|
| 239 | inc(pw);
|
|---|
| 240 | total.sumA += sumA*w;
|
|---|
| 241 | total.aDiv += aDiv*w;
|
|---|
| 242 | total.sumR += sumR*w;
|
|---|
| 243 | total.sumG += sumG*w;
|
|---|
| 244 | total.sumB += sumB*w;
|
|---|
| 245 | total.rgbDiv += rgbDiv*w;
|
|---|
| 246 | inc(psum);
|
|---|
| 247 | if psum >= psumEnd then pSum := @sums[0];
|
|---|
| 248 | end;
|
|---|
| 249 | if (total.aDiv > 0) and (total.rgbDiv > 0) then
|
|---|
| 250 | pdest^ := ComputeAverage(total)
|
|---|
| 251 | else
|
|---|
| 252 | pdest^:= BGRAPixelTransparent;
|
|---|
| 253 | end;
|
|---|
| 254 | inc(pdest);
|
|---|
| 255 | //shift vertical rows
|
|---|
| 256 | psum := @sums[sumStartIndex];
|
|---|
| 257 | fillchar(psum^,sizeof(TRowSum),0);
|
|---|
| 258 | if x < bmpWidth then
|
|---|
| 259 | begin
|
|---|
| 260 | ComputeVerticalRow(psrc,psum^,@blurRowY[ys1],ys2-ys1+1);
|
|---|
| 261 | inc(x);
|
|---|
| 262 | inc(psrc);
|
|---|
| 263 | end;
|
|---|
| 264 | inc(sumStartIndex);
|
|---|
| 265 | if sumStartIndex > highSum then sumStartIndex := 0;
|
|---|
| 266 | end;
|
|---|
| 267 | end;
|
|---|
| 268 | ADestination.InvalidateBitmap;
|
|---|
| 269 | end;
|
|---|
| 270 |
|
|---|