### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: Downscaling with Lanczos  (Read 2594 times)

#### domasz

• Sr. Member
• Posts: 471
##### Downscaling with Lanczos
« on: May 25, 2024, 09:14:27 am »
I am trying to implement Lanczos resampling. Just the simplest possible algorithm, no optimizations. Works fine when upscaling but when downscaling the resulting image is too sharp. My input image is 1500x2000 px. I am downscaling to 50x50. For each output pixel the algo finds a source pixel and its 48 neighbors (7x7 block). This means some source pixels are ignored by design (50x50 * 7x7 = 350x350 pixels of source image).

What am I missing?

Code: Pascal  [Select][+][-]
1. unit Unit1;
2.
3. {\$mode objfpc}{\$H+}
4.
5. interface
6.
7. uses
8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Math;
9.
10. type
11.   TResampleFilter = function(Value: Single): Single;
12.   { TForm1 }
13.
14.   TForm1 = class(TForm)
15.     Button1: TButton;
16.     procedure Button1Click(Sender: TObject);
17.   private
18.
19.   public
20.
21.   end;
22.
23. var
24.   Form1: TForm1;
25.
26. implementation
27.
28. {\$R *.lfm}
29.
30. { TForm1 }
31.
32. function Sinc(Value: Single): Single;
33. begin
34.   if Value <> 0.0 then begin
35.     Value := Value * Pi;
36.     Result := Sin(Value) / Value;
37.   end
38.   else Result := 1.0;
39. end;
40.
41. function Lanczos3Filter(Value: Single): Single;
42. begin
43.   if Value < 0.0 then Value := -Value;
44.
45.   if Value < 3.0 then
46.     Result := SinC(Value) * SinC(Value / 3.0)
47.   else
48.     Result := 0.0;
49. end;
50.
51. function Limit(Min, Max, Val: Integer): Integer;
52. begin
53.   if Val < Min then Exit(Min);
54.   if Val > Max then Exit(Max);
55.   Exit(Val);
56. end;
57.
58. function Clip(Val: Extended): Byte;
59. begin
60.   if Val > 255 then Exit(255);
61.   if Val < 0 then Exit(0);
62.   Result := Round(Val);
63. end;
64.
65. function Resample(Bmp: TBitmap; AWidth, AHeight: Integer; Filter: TResampleFilter): TBitmap;
66. var x,y: Integer;
67.     P: TColor;
68.     PP: array[0..3] of Byte absolute P;
69.
70.     i,j: Integer;
71.     ratioX,ratioY: Extended;
72.     SumR, SumG, SumB: Extended;
73.     xx,yy: Extended;
74.     xxx,yyy: Integer;
75.     xxxx,yyyy: Integer;
76.     Tmp: Single;
78. begin
79.   Result := TBitmap.Create;
80.   Result.PixelFormat := pf32bit;
81.   Result.SetSize(AWidth, AHeight);
82.
83.   ratioX := AWidth/ Bmp.Width;
84.   ratioY := AHeight/ Bmp.Height;
85.
86.   for y:=0 to AHeight-1 do begin
87.     for x:=0 to AWidth-1 do begin
88.
89.       xx := x / ratioX;
90.       yy := y / ratioY;
91.
92.       xxx := Floor(xx);
93.       yyy := Floor(yy);
94.
95.       SumR := 0;
96.       SumG := 0;
97.       SumB := 0;
98.
99.       for j:=-3 to 3 do
100.         for i:=-3 to 3 do begin
101.           RadX := xx - (xxx + i);
102.           RadY := yy - (yyy + j);
104.
105.           xxxx := Limit(0, Bmp.Width-1, xxx+i);
106.           yyyy := Limit(0, Bmp.Height-1, yyy+j);
107.
108.           P := Bmp.Canvas.Pixels[xxxx, yyyy];
109.
110.           SumR := SumR + PP[0]*Tmp ;
111.           SumG := SumG + PP[1]*Tmp ;
112.           SumB := SumB + PP[2]*Tmp ;
113.         end;
114.
115.       Result.Canvas.Pixels[x,y] := RGBToColor( Clip(SumR), Clip(SumG), Clip(SumB) );
116.     end;
117.   end;
118. end;
119.
120.
121. procedure TForm1.Button1Click(Sender: TObject);
122. var InB, OutB: TBitmap;
123. begin
124.   InB := TBitmap.Create;
125.   InB.LoadFromFile('1.bmp'); //should be 32bpp
126.
127.   OutB := Resample(InB, 50, 50, @Lanczos3Filter);
128.   OutB.SaveToFile('2.bmp');
129.   OutB.Free;
130.   InB.Free;
131. end;
132.
133. end.
134.
« Last Edit: May 25, 2024, 09:37:08 am by domasz »

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #1 on: May 25, 2024, 09:29:55 am »
Here are test images.
1.jpg is the source image. You should convert it to bmp.
result.png is the result of the program. The image is too sharp.
should.png is how an image viewer downscaled 1.bmp using Lanczos. As you can see the image is smooth.

#### KodeZwerg

• Hero Member
• Posts: 2269
• Fifty shades of code.
##### Re: Downscaling with Lanczos
« Reply #2 on: May 25, 2024, 10:26:41 am »
If a different approach is welcomed not using custom interpolation with the result you wanted:
Code: Pascal  [Select][+][-]
1. procedure AntiAliasedStretchDrawBitmap(SourceBitmap, DestBitmap: TCustomBitmap);
2. var
3.   DestIntfImage, SourceIntfImage: TLazIntfImage;
4.   DestWidth, DestHeight: Integer;
5.   DestCanvas: TLazCanvas;
6. begin
7.   DestWidth := DestBitmap.Width;
8.   DestHeight := DestBitmap.Height;
9.   DestIntfImage := TLazIntfImage.Create(0, 0);
10.   try
12.     DestCanvas := TLazCanvas.Create(DestIntfImage);
13.     try
14.       SourceIntfImage := SourceBitmap.CreateIntfImage;
15.       try
16.         DestCanvas.Interpolation := TFPBaseInterpolation.Create;
17.         try
18.           DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);
20.         finally
21.           DestCanvas.Interpolation.Free;
22.         end;
23.       finally
24.         SourceIntfImage.Free;
25.       end;
26.     finally
27.       DestCanvas.Free;
28.     end;
29.   finally
30.     DestIntfImage.Free;
31.   end;
32. end;
33.
34. procedure TForm1.Button1Click(Sender: TObject);
35. var
36.   Source,
37.   Dest: TBitmap;
38. begin
39.   Source := TBitmap.Create;
40.   try
41.     Source.Assign(Image2.Picture.Bitmap); // image2 got "1.jpg" assigned in designtime
42.     Dest := TBitmap.Create;
43.     try
44.       Dest.SetSize(50, 50);
45.       AntiAliasedStretchDrawBitmap(Source, Dest);
46.       Image1.Picture.Assign(Dest);
47.     finally
48.       Dest.Free;
49.     end;
50.   finally
51.     Source.Free;
52.   end;
53. end;
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #3 on: May 25, 2024, 11:27:25 am »
Thanks for the code, KodeZwerg, might be handy but I really need to know what I did wrong to sleep well

#### wp

• Hero Member
• Posts: 12067
##### Re: Downscaling with Lanczos
« Reply #4 on: May 25, 2024, 02:11:37 pm »
I've never done this myself, so this answer may be nonsense. But anyway: I am not sure whether you apply the filter function correctly. You have i and j running over x and y and you calculate the filter function at the integer values -3, -2, -1, 0, 1, 2, 3 (for i and j). Now let's look at a plot of the Lanczos filter - see screenshot, where you can find that these values are always near the zero-crossings and thus close to zero, except for the center value which is 1. In total, only the center value really contributes to the sum - a complicated way of just selecting the center value in the filter interval.

In my opinion you should map the intervals [-3..3] to the original image (i.e. multiply i,j by the ratio of source image width to destination image width, height accordingly), and iterate over all original pixels in this area, apply the Lanczos function to them and sum them up.

As a consequence you'll notice that the program will probably become much slower (because many more pixels have to be evaluated). This is because accessing Canvas.Pixels is a very slow process. You should convert the bitmap to a TLazIntfImage, or load it into an even simpler TFPMemoryImage, where the access to individual pixels is much faster. And doing so, you might consider using the interpolation functions built into fcl-image - there's even a TLanczosInterpolation... Here is some code how to apply it:
Code: Pascal  [Select][+][-]
1. uses
2.   fpimage, fpcanvas, fpimgCanv, fpreadjpeg, fpwritebmp, extinterpolation;
3.
4. procedure TForm1.Button2Click(Sender: TObject);
5. var
6.   srcImg, destImg: TFPMemoryImage;
7.   canv: TFPImageCanvas;
8. begin
9.   // Create and load the source image
10.   srcImg := TFPMemoryImage.Create(0, 0);
12.
13.   // Create the destination image in the requested size
14.   destimg := TFPMemoryImage.Create(50, 50);
15.
16.   // Create a canvas for the destination image ...
17.   canv := TFPImageCanvas.Create(destImg);
18.
19.   // ... and an interpolation
20.   canv.Interpolation := TLanczosInterpolation.Create;
21.
22.   // Use the interpolation to stretch the source image to the new size
23.   canv.StretchDraw(0, 0, destImg.Width, destImg.Height, srcImg);
24.
25.   // Save destination image to file...
26.   destImg.SaveToFile('2a.bmp');
27.   // ... and display it in a TImage component
28.   Image2.Picture.Assign(destImg);
29.
30.   // Clean up
31.   canv.Interpolation.Free;
32.   canv.Free;
33.   destImg.Free;
34.   srcImg.Free;
35. end;

P.S.
Ah, sorry, KodeZwerg, I did not read your post before sending mine because you are doing essentially the same...
« Last Edit: May 25, 2024, 02:17:53 pm by wp »

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #5 on: May 25, 2024, 06:14:18 pm »
In my opinion you should map the intervals [-3..3] to the original image (i.e. multiply i,j by the ratio of source image width to destination image width, height accordingly), and iterate over all original pixels in this area, apply the Lanczos function to them and sum them up.
Thanks, wp!

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #6 on: May 26, 2024, 01:22:09 pm »
This is a problem with today scientific articles, presentations and the alike. I have seen dozens about Lanczos interpolation but they rarely give any code, pseudocode or even describe the nuances. Just focus on comparing Lanczos to Bilinear and other filters and how to use it in a given language.

Here's what I found in the book from 1992 (Graphic Gems 3):

"During magnification, we are stretching our reconstructed signal, lowering its component frequencies. However,
during minification, we are shrinking our reconstructed signal, raising its component frequencies, and possibly exceeding the Nyquist frequency of
our new sampling rate. To create proper samples, we must eliminate all frequency components above the resampling Nyquist frequency. This can
be accomplished by stretching the filter function by the image reduction factor."

So basically my algo is fine for upsampling but for downsampling what wp said is right.

#### runewalsh

• Jr. Member
• Posts: 84
##### Re: Downscaling with Lanczos
« Reply #7 on: May 26, 2024, 07:03:06 pm »
Downscale in several steps, each 7× at most (not sure if 2× will be better or not).

#### KodeZwerg

• Hero Member
• Posts: 2269
• Fifty shades of code.
##### Re: Downscaling with Lanczos
« Reply #8 on: May 26, 2024, 08:06:15 pm »
This is a problem with today scientific articles, presentations and the alike. I have seen dozens about Lanczos interpolation but they rarely give any code, pseudocode or even describe the nuances. Just focus on comparing Lanczos to Bilinear and other filters and how to use it in a given language.

Here's what I found in the book from 1992 (Graphic Gems 3):

"During magnification, we are stretching our reconstructed signal, lowering its component frequencies. However,
during minification, we are shrinking our reconstructed signal, raising its component frequencies, and possibly exceeding the Nyquist frequency of
our new sampling rate. To create proper samples, we must eliminate all frequency components above the resampling Nyquist frequency. This can
be accomplished by stretching the filter function by the image reduction factor."

So basically my algo is fine for upsampling but for downsampling what wp said is right.
Take a look at Lánczos interpolation explained, its pretty in-depth with math formulas and some demo images to understand differences at usage.
For sourcecode you can take a look at ImageMagick (C Language), watch at resize, it offer a lot interpolation "filters".
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #9 on: May 26, 2024, 08:13:08 pm »
Thx, runewalsh. That might be an idea.

Generally speaking - the quality of code around us is total shit.

In 1992 Dale Schumacher published in Graphics Gems III (p. 8-16) his algorithm with source code (filter.c) which resamples images using Lanczos, Bilinear and a few other filters. About 95% lanczos resamplers out there are just ports of this code.
In 1997-98  Anders Melander released a Delphi port (resample.pas).
GraphicEx, Alcinoe, JEDI JCL, GXScene and many others all use that code (or ported filter.c themselves).

Lazarus also comes with a Lanczos resampler (extinterpolation.pas) but seems it's a different algorithm.

All of these claim to support also other filters (Bilinear, Nearest Neighbor, Bell, Mitchell). Just look at attachments of some quick tests...
« Last Edit: May 26, 2024, 08:28:21 pm by domasz »

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #10 on: May 26, 2024, 08:18:26 pm »
And here's Lazarus (extinterpolation.pas)

#### domasz

• Sr. Member
• Posts: 471
##### Re: Downscaling with Lanczos
« Reply #11 on: May 26, 2024, 08:25:07 pm »
And my slow code from the first post (which doesn't handle well downscaling too much).

So this is why you should reinvent the wheel sometimes.
« Last Edit: May 26, 2024, 08:27:41 pm by domasz »

#### wp

• Hero Member
• Posts: 12067
##### Re: Downscaling with Lanczos
« Reply #12 on: May 26, 2024, 11:47:21 pm »
I would not swear that the fcl-image interpolation is bug-free. Some time ago I wrote a small test application which up- and downscales some test images using all built-in interpolation filters (see attachment). I have the impression that most of these filters do not support upscaling at all because they do not produce a smoothed image. And some images look really strange, such as the small Lena image upscaled to 600x600 using the Hermite filter.

• Hero Member
• Posts: 14787
• Censorship about opinions does not belong here.
##### Re: Downscaling with Lanczos
« Reply #13 on: May 27, 2024, 04:41:54 pm »
Isn't graphics32 an option? It has fast and has also good filters. And it works on Lazarus.
bitrate is always calculated like this:sample rate * bitdepth * number of channels.

#### jamie

• Hero Member
• Posts: 6301
##### Re: Downscaling with Lanczos
« Reply #14 on: May 27, 2024, 07:44:02 pm »
has anyone questioned the popup doc for the canvas.Draw(.....) ?

It states it calls the StretchDraw and so on, read the rest.
The only true wisdom is knowing you know nothing