Jump to content
Renate Schaaf

Parallel Resampling of (VCL-) Bitmaps

Recommended Posts

13 hours ago, Anders Melander said:

FastBlur is actually a box blur and not a true Gaussian blur. This is just fine for some setups, but not so great for others.

I was fooled by the names in the example-project. I just had a closer look and, yes it's box blur.

As far as I can see  GaussianBlur isn't Gaussian either, it just introduces weights of 1/square_distance_from_center.

They do handle the alpha-channel, though. I would have liked to see the alpha-blended result, but I couldn't get the ImageView to show it.

Seeing as my (unthreaded) routine is truly Gaussian, and that it outperforms GR32.GaussianBlur by a factor of roughly 2-3, would it make sense for me to suggest it as a replacement in GR32?

How can I better test the quality?

Where can I find your routine then?

Quality usually takes precedence over performance for me, unless the performance annoys me, well it's also fun to get something faster 🙂

 

 

 

Edited by Renate Schaaf

Share this post


Link to post
On 10/11/2023 at 11:33 AM, Renate Schaaf said:

They do handle the alpha-channel, though. I would have liked to see the alpha-blended result, but I couldn't get the ImageView to show it.

You really do need to handle the alpha channel if you are going to operate on 32-bit bitmaps. Basically, it's the same as what you did for the resampler: Premultiply, blur, unpremultiply.

Enable the checkerboard pattern in TImgView32.Background and you should be able to see it.

 

On 10/11/2023 at 11:33 AM, Renate Schaaf said:

Seeing as my (unthreaded) routine is truly Gaussian, and that it outperforms GR32.GaussianBlur by a factor of roughly 2-3, would it make sense for me to suggest it as a replacement in GR32?

Sure, that would be nice; Go ahead. Just make a version that has the same API as the existing ones:

procedure Blur(Bitmap32: TBitmap32; Radius: TFloat); overload;

and optionally:

procedure Blur(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect); overload;
procedure Blur(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint); overload;

If you start by posting to your own repository I can do the work required to get it integrated into Graphics32.

 

On 10/11/2023 at 11:33 AM, Renate Schaaf said:

How can I better test the quality?

My test bench validates the following:

  • Edge handling
    Does the "outside" of the bitmap bleed into the blurred bitmap. Basically, I just blur a white bitmap and verify that all pixels are still white.
  • Alpha bleed
    Does the color of fully transparent pixels bleed into semi-tranparent pixels.
  • Color overflow
    No R, G, or B values should increase. A minor decrease, when blurring a solid color bitmap, is allowed.
  • Loss/gain due to premultiplication
    I test the average color loss of blur of a solid color bitmap, with varying alpha. This mostly tests the precision of the premultiplication.
  • Average RGB error and lightness change
    I blur a gradient and compare the average RGB-, and lightness change.
  • Horizontal and vertical symmetry
    I blur some horizon and vertical lines and verify that they have been blurred symmetrically.
  • Gaussian error
    I blur a line and verify that the blurred result corresponds to the Gaussian curve. RMS error and signal loss are validated.
  • Vertical error
    I blur a vertical line and verify that all rows contain the same color value.
  • Horizontal error
    I blur a horizontal line and verify that all columns contain the same color value.
  • Uniform RGB
    I blur a sequence of solid color bitmaps, with increasing R=G=B and verify that the result doesn't contain noise. This usually catches simple over/underflows.

The result looks like this:

FastBlur (box), Angus Johnson [1024 x 1024, 10]
  PASS: Edge handling
  PASS: 100% alpha bleed:   0.00% bleed, 0 errors
  PASS: Overflows: 0, max swell: 0.00%, 0 bleeds
  PASS: Alpha:  31, Average loss, RGB:   0.92 %, A:   0.00%
  PASS: Alpha:  63, Average loss, RGB:   0.52 %, A:   0.00%
  PASS: Alpha:  95, Average loss, RGB:   0.26 %, A:   0.00%
  PASS: Alpha: 127, Average loss, RGB:   0.26 %, A:   0.00%
  PASS: Alpha: 159, Average loss, RGB:   0.13 %, A:   0.00%
  PASS: Alpha: 191, Average loss, RGB:   0.00 %, A:   0.00%
  PASS: Alpha: 223, Average loss, RGB:   0.00 %, A:   0.00%
  PASS: Alpha: 255, Average loss, RGB:   0.00 %, A:   0.00%
  PASS: Average RGB error:   0.48 %
      : Lightness change:  -0.50 %
  PASS: Horizontal symmetry error:   0.00
  PASS: Vertical symmetry error:   0.00
  PASS: Gaussian RMS error:   7.94
  FAIL: Gaussian signal loss:   6.67 %
  PASS: Vertical errors:      0
  PASS: Horizontal errors:      0
  PASS: Uniform RGB[ 31] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[ 63] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[ 95] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[127] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[159] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[191] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[223] errors:      0 (  0.00 %), noise:      0
  PASS: Uniform RGB[255] errors:      0 (  0.00 %), noise:      0

 

On 10/11/2023 at 11:33 AM, Renate Schaaf said:

Where can I find your routine then?

In your mailbox in a few days, if you're lucky :classic_cool:

I'll include the test bench too.

 

By the way, I can really recommend that you profile your implementation with VTune. This is an area that usually benefits greatly from that.

  • Thanks 1

Share this post


Link to post
22 minutes ago, Anders Melander said:

Premultiply, blur, unpremultiply.

I already did.

24 minutes ago, Anders Melander said:

If you start by posting to your own repository I can do the work required to get it integrated into Graphics32.

I meanwhile learned how to do pull requests 🙂

Looking forward to the mailman!

Renate

Share this post


Link to post
37 minutes ago, Alexander Sviridenkov said:

Here is compiled sample: delphihtmlcomponents.com/blur.zip

Source or it didn't happen 🙂

Share this post


Link to post
1 hour ago, Alexander Sviridenkov said:

delphihtmlcomponents.com/blur.zip

I managed to get it, source or not. For the same amount of "blurriness" my parallel version needs about 1.5 times the time of yours. Source would still be nice, I'm sure we'd learn something.

Renate

Share this post


Link to post
6 minutes ago, Renate Schaaf said:

For the same amount of "blurriness" my parallel version needs about 1.5 times the time of yours.

A box blur is almost always faster. Almost...

Share this post


Link to post
12 hours ago, Anders Melander said:

A box blur is almost always faster.

For me, the fastest blur is created by taking off my glasses...

  • Like 1
  • Haha 6

Share this post


Link to post

To my past working on resampling and resizing up and down, i found that Mitchell and Robidoux filter is superior to Lanczos(3) in speed and quality specially in halo reduction and color defects.

https://legacy.imagemagick.org/Usage/filter/#mitchell

https://legacy.imagemagick.org/Usage/filter/nicolas/

 

I added them to AlphaSkin few years back and it seems i failed to deliver them to the author, shame on me after all that work.

 

Anyway found my old test project, and here a sample of the a result

image.thumb.png.fc10ba6566be0cb5fcdbc8124a151133.png

With Lanczos3 the halo effect is very visible inside the boxes and there a skyblue color above the T also there a yellow on left side of the red.

 

While Mitchell and Robidoux (default, sharp and soft) , doesn't have halo and blue but the yellow exist

 image.thumb.png.e2695cf23f814870803c4b29845b235d.pngimage.thumb.png.e7e51f8726afbf4076bfa3c30938b442.pngimage.thumb.png.824d7d5f2f97c628b826d1c4dfaa6b3d.pngimage.thumb.png.b0b9e031095633999254327b8cbba7f8.png

 

Downsizing

image.png.ee4599841b235de3a21c8af6e128d4f6.pngimage.png.feb900892cd43dd835b199c404cc2ac9.pngimage.png.0c19f398f7d44834f4dfb29984c11c37.pngimage.thumb.png.aa8ca229ba5f4031927f8b750e4225b2.pngimage.png.7ceb7c795ac6880134484bf8be3ac6bf.png

 

 

 

As seen above and as i remember with many images, Robidoux sharp was the best for upsizing, and Robidoux soft was the best in downsizing.

 

And here are the parameters for Robidoux 

        ftRobidoux:
          if Param < 1.0 then
            W := Sqr(Param) * (1.1218 * Param - 1.9327) + 0.8739
          else
            W := Sqr(Param) * (-0.3739 * Param + 1.9327) - 3.2436 * Param + 1.7478;

        ftRobidouxSharp:
          if Param < 1.0 then
            W := Sqr(Param) * (1.238  * Param - 2.107) + 0.9126
          else
            W := Sqr(Param) * (-0.4126 * Param + 2.1069) - 3.4759 * Param + 1.8253;

        ftRobidouxSoft:
          if Param < 1.0 then
            W := Sqr(Param) * (0.8204 *  Param -1.4806) + 0.7734
          else
            W := Sqr(Param) * (-0.2734 * Param + 1.4806) - 2.6408 * Param + 1.5469;

No Mitchell though because it is already in AlphaSkin and i don't want to paste code that is not mine.

  • Like 1
  • Thanks 1

Share this post


Link to post
1 hour ago, Kas Ob. said:

To my past working on resampling and resizing up and down, i found that Mitchell and Robidoux filter is superior to Lanczos(3) in speed and quality specially in halo reduction and color defects.

Thanks very much for the input, I hadn't looked at those filters more closely before, should be easy to test them out.

 

18 hours ago, Anders Melander said:

I'll include the test bench too.

Thank you!

Here is a first result for radius 10. It only passed the Gauss-RMS-test after I changed the sigma-to-radius-ratio to the same as yours. Need to give that a closer look.

For other radii my routine failed some of the uniform color tests, (and edge detection as a consequence,) so it's back to the drawing board for that.

Validation.thumb.jpg.7eaf8a244442496088d5123f9c914f5d.jpg

Share this post


Link to post
35 minutes ago, Renate Schaaf said:

Here is a first result for radius 10.

Pretty nice validation result.

 

Extrapolating from your benchmark result, I'm guessing your CPU is about twice as fast as mine and that my blur would process something like 20,000,000 pix/sec on your system. Can that be right?

 

45 minutes ago, Renate Schaaf said:

Need to give that a closer look.

There's no guarantee that my validation algorithm is correct (I cooked it up myself). You might want to verify that before you start mangling your code.

Share this post


Link to post
3 hours ago, Anders Melander said:

I'm guessing your CPU is about twice as fast as mine

It's probably not. Your blur is slightly slower than mine for small radii. For large radii, yours is much faster. I'll mail you my benchmark-unit, then you can see for yourself.

Share this post


Link to post
On 10/13/2023 at 12:15 PM, Kas Ob. said:

And here are the parameters for Robidoux

What's the function these parameters need to be plugged into? All I can gather is that it might be some kind of cubic spline, and I don't feel like reading all of this guy's papers :). Would you mind posting the formula for the kernel?

Share this post


Link to post
43 minutes ago, Renate Schaaf said:

What's the function these parameters need to be plugged into? All I can gather is that it might be some kind of cubic spline, and I don't feel like reading all of this guy's papers :). Would you mind posting the formula for the kernel?

It is intimidating at first glance, i offered them because you are using contributors, things for me fall in place when i looked at how imagemagick implemented.

 

Yes these are BiCubic filter, here are the raw formula https://en.wikipedia.org/wiki/Mitchell–Netravali_filters 

Though you don't need that formula in particular, as the implementation will calculate the contributors, so you need B and C which are mentioned in wikipedia for Mitchell and in https://www.imagemagick.org/Usage/filter/#cubics

My code for calculate the contributors are very simple

  procedure Calc(B, C: Single);
  begin
    Memo1.Lines.Add(FloatToStr((1 / 6) * (12 - 9 * B - 6 * C)) +
      ' * Sqr(Param) * Param + ' + FloatToStr((1 / 6) * (-18 + 12 * B + 6 * C)) +
      ' * Sqr(Param) + ' +
      FloatToStr((1 / 6) * (6 - 2 * B)));
    Memo1.Lines.Add(FloatToStr((1 / 6) * (-B - 6 * C)) + ' * Sqr(Param) * Param + ' +
      FloatToStr((1 / 6) * (6 * B + 30 * C)) + ' * Sqr(Param) + ' + FloatToStr((1 / 6) * (-
      12 * B - 48 * C)) + ' * Param + ' + FloatToStr((1 / 6) * (8 * B + 24 * C)));
    Memo1.Lines.Add('');
  end;

var
  B, C, X: Single;
begin
  Calc(0.3782, 0.3109);
  Calc(0.262, 0.3690);
  Calc(0.6796, 0.1602);
  Calc(1, 0);  // for comparison
end;

the result

1.12180000543594 * Sqr(Param) * Param + -1.93270000815392 * Sqr(Param) + 0.873933335145315
-0.373933335145315 * Sqr(Param) * Param + 1.93270000815392 * Sqr(Param) + -3.24360001087189 * Param + 1.74786667029063

1.23800002038479 * Sqr(Param) * Param + -2.1070000231266 * Sqr(Param) + 0.912666668494542
-0.412666653593381 * Sqr(Param) * Param + 2.10699993371964 * Sqr(Param) + -3.47599989175797 * Param + 1.82533327738444

0.82039999961853 * Sqr(Param) * Param + -1.4805999994278 * Sqr(Param) + 0.77346666653951
-0.27346666653951 * Sqr(Param) * Param + 1.4805999994278 * Sqr(Param) + -2.64079999923706 * Param + 1.54693333307902

0.5 * Sqr(Param) * Param + -1 * Sqr(Param) + 0.666666666666667
-0.166666666666667 * Sqr(Param) * Param + 1 * Sqr(Param) + -2 * Param + 1.33333333333333

These are w (weight) and where param is delta x, i added them on AlphaSkin as i mention and the implementation here doesn't scale by $800 like yours, yet i see no problem in doing so.

 

I suggested to have have a look at ImageMagick, yet i don't see you need to, did nice job with cylindrical filter (Lanczos), and switching to bicubic will be minimum, i think your adjustments will touch only the contributors.

  • Thanks 1

Share this post


Link to post

There is this, irrelevant to the above

    pT.rgbBlue := Min((max(Total.b, 0) + $1FFFFF) shr 22, 255);
    pT.rgbGreen := Min((max(Total.g, 0) + $1FFFFF) shr 22, 255);
    pT.rgbRed := Min((max(Total.r, 0) + $1FFFFF) shr 22, 255);
    pT.rgbReserved := Min((max(Total.a, 0) + $1FFFFF) shr 22, 255);

That is easier to read and understand as simple clamping, but Min and Max are not inlined functions and will waste hell of time just jumping and retunting for simple IF, i suggest to get rid of them with either inlined if possible or just old fashion IF's, these in assembly will be few instructions without any jumping, and in SIMD will be will be one pass for the four colors, but that for later talk.

Share this post


Link to post
26 minutes ago, Kas Ob. said:

but Min and Max are not inlined functions

They are in System.Math:

function Min(const A, B: Integer): Integer; overload; inline;

I had coded it with ifs in a previous version, but I changed that after I noticed the inlining, looks a bit less stupid.

1 hour ago, Kas Ob. said:

here are the raw formula

Oh, W is the weight you compute, and param is the x of the kernel. So you *did* post the kernel code, I was just too dense to see it. I think Maple and me can take it from there.

 

1 hour ago, Kas Ob. said:

things for me fall in place when i looked at how imagemagick implemented

I tried to find something in their source code, but gave up. Looks like you had a bit more stamina :).

  • Like 1

Share this post


Link to post
5 hours ago, Renate Schaaf said:

I tried to find something in their source code, but gave up. Looks like you had a bit more stamina :).

Dear, you already did all the right things and implemented contributors, i think you are overthinking it, your implementation is great and you are missing just few things to point you right, all these papers are talking about the weight of the neighbors and reduce them to specific formulas represented mathematically, in this case like here https://en.wikipedia.org/wiki/Mitchell–Netravali_filters , P(d) is referenced by P0, P1, P2, P3 the points in 4 direction from p the point you are calculating, while k(x) is a function calculating the weight, which you are already implemented and using with Lanczos, this implementation is almost universal for all filters as it is the best.

 

now re-read these links, with that in mind and you don't need any source as reference, specially you are going to like this 

image.thumb.png.4eb459a70694fbbb6de59be35c001e36.png

but it is not so clear as 

[IM Output] and image.png.6c7520cb3816631ff838e4b910cd239a.png

 

Now you can use the same filter to generate blur and antialiasing or force ringing (halo's and ripple), and can't remember what was the blocking :classic_blush:.

 

I browsed your code in "View file" in WinRar , i saw MakeContributors and AntiNLanczos and i confident you can just duplicate AntiNLanczos for all the above in the table with B and C or replace them with the calculated value like mine for faster calculation, while MakeContributors most likely doesn't need any change or a may be a little, i am sure you can solve this as it is way easier than how it sound and look.

 

 In AntiNLanczos

const
  sqrt3 = 1.7320508075688;
  b = -27 * sqrt3 / 2 / Pi;
  d = -1.4 * 0.8933976645E-1;
  e = 1.0 * 0.1650337606E-1;
  a = 0.5 - e - d;

function AntiNLanczos(x: double): double;
var
  s: integer;
begin
  s := 1;
  if x < 0 then
  begin
    x := -x;
    s := -1;
  end;
  if x < 1 / 3 then
    Result := (3 + (-162 + 3 / 2 * b + 270 * a + (648 - 9 * b - 1215 * a +
      (-729 + 27 / 2 * b + 1458 * a) * x) * x) * sqr(x)) * x
  else if x < 2 / 3 then
    Result := 1 / 2 * b - 31 * d + a +
      (-16 / 3 * b + 360 * d + (177 / 8 * b - 1620 * d + (-357 / 8 * b + 3510 *
      d + (351 / 8 * b - 3645 * d + (-135 / 8 * b + 1458 * d) * x) * x) *
      x) * x) * x
  else if x < 1 then
    Result := -3 / 2 * b - 512 * e + a + d +
      (9 * b + 3240 * e + (-171 / 8 * b - 8100 * e + (201 / 8 * b + 9990 * e +
      (-117 / 8 * b - 6075 * e + (27 / 8 * b + 1458 * e) * x) * x) * x) * x) * x
  else
    Result := 0.5;
  Result := s * Result;
end;

You need these constants a, b, d and e, with Mitchell–Netravali filters you will need only B and C, result is the weigth (w) and x is param , simple like that, only the difference is you are scaling first to do the clamping with shr 22  later , you can drop it or keep it, just adjust the values accordingly.

 

and good luck, waiting to see your result images, as your sample and demos also not working on older Delphi, and please don't waste your time on compatibility now, i will nag you for that later.

  • Like 1

Share this post


Link to post
1 hour ago, Kas Ob. said:

I browsed your code in "View file" in WinRar , i saw MakeContributors and AntiNLanczos and i confident you can just duplicate AntiNLanczos for all the above in the table with B and C or replace them with the calculated value like mine for faster calculation, while MakeContributors most likely doesn't need any change or a may be a little, i am sure you can solve this as it is way easier than how it sound and look.

I know! Just missed the B and C-values for the Robidoux in the table-image you post. And then I just have to rescale the functions to have support in [-1,1], make sure it's integral is 1. Bang. It plugs right in.

 

Wish I could edit my original post and delete the attachment, it's ancient now, and include a link to my GitHub-repo. The AntiNLanczos is a spline to approximate the antiderivative of Lanczos, all that stuff isn't needed anymore.

Edited by Renate Schaaf
  • Like 1

Share this post


Link to post

OK, Maple computed the following simplified filters, to implement them was just a matter of extending the TFilter-Enum. I'll update my repo some time tomorrow, the new filters need to be implemented in the demos. Right now I feel more like surviving a few more days on The Long Dark.

 

// The following filters are based on the Mitchell-Netravali filters with
// restricting the parameters B and C to the "good" line B + 2*C = 1.
// We have eliminated B this way and scaled the filter to [-1,1].
// See https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters

const
  C_M = 1 / 3;

  // Mitchell filter used by ImageMagick
function Mitchell(x: double): double; inline;
begin
  x := abs(x);
  if x < 0.5 then
    Result := (8 + 32 * C_M) * x * x * x - (8 + 24 * C_M) * x * x + 4 / 3 +
      4 / 3 * C_M
  else if x < 1 then
    Result := -(8 / 3 + 32 / 3 * C_M) * x * x * x + (8 + 24 * C_M) * x * x -
      (8 + 16 * C_M) * x + 8 / 3 + 8 / 3 * C_M
  else
    Result := 0;
end;

const
  C_R = 0.3109;

  // Robidoux filter
function Robidoux(x: double): double; inline;
begin
  x := abs(x);
  if x < 0.5 then
    Result := (8 + 32 * C_R) * x * x * x - (8 + 24 * C_R) * x * x + 4 / 3 +
      4 / 3 * C_R
  else if x < 1 then
    Result := -(8 / 3 + 32 / 3 * C_R) * x * x * x + (8 + 24 * C_R) * x * x -
      (8 + 16 * C_R) * x + 8 / 3 + 8 / 3 * C_R
  else
    Result := 0;
end;

.... and so on. Just one function with different constants.

  • Like 1

Share this post


Link to post

I have update the repo on GitHub

https://github.com/rmesch/Parallel-Bitmap-Resampler

 

Changes made to the "modern" VCL- and FMX-version in the folder BitmapScaling:

   New resampling filters: Mitchell, Robidoux, RobidouxSharp, RobidouxSoft.
   Simplified and corrected MakeGaussContributors in uScaleCommon.pas.

@Anders Melander: It will pass the uniform color tests now. But it will fail the Gauss-RMS, since I changed to RadiusToSigma back.
   Tried to make Gamma-correction a little more precise.

 

On 10/18/2023 at 7:27 PM, Kas Ob. said:

please don't waste your time on compatibility now

I tried nonetheless. You already spent so much time digging through that ancient attachment, give the repo a shot. I also added the option in DemoScale.dpr to use a test-bitmap similar to yours. I can't see any of the color-artefacts you describe, though.  
 

Share this post


Link to post
12 hours ago, Renate Schaaf said:

I tried nonetheless. You already spent so much time digging through that ancient attachment, give the repo a shot.

Thank you !, and as you asked for, it still refuse to compile on XE8 or Seattle, the problem is with this part, so i worked around to make it work at minimum.

image.thumb.png.c26db0968e008c5afacef9b8935035e2.png

 

12 hours ago, Renate Schaaf said:

I also added the option in DemoScale.dpr to use a test-bitmap similar to yours.

Well, you missed my point of test, in my opinion when testing for artefacts or ringing or color bleeding should start at the simplest and smallest details, your TextAndLines doesn't follow my test, my lines were at 1 pixel every 5, i wanted to see text and their readability in this one, and it did in fact showed me a lot.

So here my Lines and text

procedure LinesAndTextBitmap(const bmp: TBitmap; w: integer);
var
  X, Y: Integer;
begin
    bmp.PixelFormat := pf32bit;
    bmp.SetSize(w,w);
    bmp.Canvas.Font.Size := 24;
    bmp.Canvas.Font.Color := clRed;
    bmp.Canvas.TextOut(10, 10, 'Test');
    bmp.Canvas.Font.Size := 48;
    bmp.Canvas.Font.Color := clGreen;
    bmp.Canvas.TextOut(50, 50, 'Test');
    bmp.Canvas.Font.Size := 32;
    bmp.Canvas.Font.Color := clBlue;
    bmp.Canvas.TextOut(150, 150, 'Test');

    X := 5;
    while X < w do
    begin
      bmp.Canvas.MoveTo(X, 5);
      bmp.Canvas.LineTo(X, bmp.Height - 5);
      Inc(X, 5);
    end;

    Y := 5;
    while Y < w do
    begin
      bmp.Canvas.MoveTo(5, Y);
      bmp.Canvas.LineTo(bmp.Width - 5, Y);
      Inc(Y, 5);
    end;
end;

You can adjust the 5 also to make it target the little details, there is the devil. 

12 hours ago, Renate Schaaf said:

I can't see any of the color-artefacts you describe, though.  

Now to the result and you gonna love it

image.thumb.png.96c67834418bd259fac949ce5fa39501.pngimage.thumb.png.883cdf98d1a4d5eb8b29e9542a4eae26.pngimage.thumb.png.1a9f8c28584295d078abca5dd6f38771.png  

 

As for speed, it is waayyyy slow and very eligible for optimization, a lot of tweaking and definitely assembly will give it a great push, so i will find time and more important than time, wait until my OCD kick and will dig into it, heck i have many of those functions already somewhere written in fast SIMD, no promises though, just i have too many dark days, unlike your The long Dark, i am in a dark place for many years now, coding sometimes helps to see some light in a tunnel.

 

If you would please, just skip using anonymous methods, this will push the compatibility many Delphi versions back, as for short code, well not worth it for open source and great library.

 

Also as you already did it and got it, why not add all these mentioned filters (in Wikipedia and the ImageMagick links) it is merely copy and paste, right ?, so you will have a filter like Adobe and What every the hell is Paint Net, and so fourth, cost nothing performance like.

Share this post


Link to post
On 10/19/2023 at 8:39 PM, Renate Schaaf said:

It will pass the uniform color tests now. But it will fail the Gauss-RMS, since I changed to RadiusToSigma back.

Okay, but why?

 

As you know, my calculation is based on FWHM:

Quote

Ratio between Radius and Sigma
Calculated as:
   Ratio = 1 / FWHM (Full Width at Half Maximum)
         = 1 / (2 * Sqrt(2 * Ln(2)))
         = 0.424660891294479
Note: Gimp uses the value 0.30038663 (1 / 3,329042969) = 1 / Sqrt(-2 * Ln(1/255)).
The Gimp value is quoted in a lot of places - most often with a comment that it's incorrect...

and the value you use now is the same as Gimp's but I don't understand the rationale for that calculation.

 

Of course, one can use any radius/sigma factor one chooses since it's kind of an abstract value (for some algorithms) but it makes it a bit difficult to compare blurs if they don't use the same scale.

 

The Gaussian RMS test isn't that important, BTW. AFAIR, it does a blur of a single-pixel vertical line and then compares the horizontal pixel values to the ideal Gaussian curve. If your Sigma/Radius differs from the one I use for the test curve it will show in the RMS. For example, a single pass box filter will have a high RMS. Two passes will produce a lower RMS, and three passes even lower, etc., as the filter approximates closer and closer to the Gaussian.

 

The more important test is the Gaussian signal loss. In that, I sum the blurred pixel values and compare that against the summed Gaussian values. I.e. I compare the integral of the filter and the integral of the ideal curve. In theory, they should both equal 255. In practice, rounding errors make this difficult to test precisely. For example, the sum of the ideal curve is 252 for a radius of 10.
Your algorithm has a loss of zero against the theoretical ideal curve (i.e. you have a sum of 255). This test should not be affected by the Sigma/Radius value.

 

FWIW:

Share this post


Link to post
6 minutes ago, Anders Melander said:
On 10/19/2023 at 8:39 PM, Renate Schaaf said:

It will pass the uniform color tests now. But it will fail the Gauss-RMS, since I changed to RadiusToSigma back.

Okay, but why?

Oh, I think I see what you are doing.

 

Is it correct that...

Quote

2 * Ceil(Sigma * Sqrt(-2 * Ln(1/255)))

...gives you the smallest size of a kernel containing all values >= 1/255?

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×