Link to home
Start Free TrialLog in
Avatar of bobbysdog
bobbysdog

asked on

In Delphi - I need an algorithm to resample a BitMap to thumbnail size - BiLinear preferred

Thanks for reading

I've been using the below algorithm to resample bitmaps in Delphi.

But when it comes to making small thumbnails, it results in grainy images.

Anyone see a good algorithm around to do a better job?  Or do you want to recommend a package to buy?

This is literally the only "image processing" i need so i'd hate to buy a whole package for just one function...

Thanks!

procedure SmoothResize(Src, Dst: TBitmap);
var
x, y: Integer;
xP, yP: Integer;
xP2, yP2: Integer;
SrcLine1, SrcLine2: pRGBArray;
t3: Integer;
z, z2, iz2: Integer;
DstLine: pRGBArray;
DstGap: Integer;
w1, w2, w3, w4: Integer;
begin
Src.PixelFormat := pf24Bit;
Dst.PixelFormat := pf24Bit;

if (Src.Width = Dst.Width) and (Src.Height = Dst.Height) then
Dst.Assign(Src)
else
begin
DstLine := Dst.ScanLine[0];
DstGap := Integer(Dst.ScanLine[1]) - Integer(DstLine);

xP2 := MulDiv(pred(Src.Width), $10000, Dst.Width);
yP2 := MulDiv(pred(Src.Height), $10000, Dst.Height);
yP := 0;

for y := 0 to pred(Dst.Height) do
begin
xP := 0;

SrcLine1 := Src.ScanLine[yP shr 16];

if (yP shr 16 < pred(Src.Height)) then
SrcLine2 := Src.ScanLine[succ(yP shr 16)]
else
SrcLine2 := Src.ScanLine[yP shr 16];

z2 := succ(yP and $FFFF);
iz2 := succ((not yp) and $FFFF);
for x := 0 to pred(Dst.Width) do
begin
t3 := xP shr 16;
z := xP and $FFFF;
w2 := MulDiv(z, iz2, $10000);
w1 := iz2 - w2;
w4 := MulDiv(z, z2, $10000);
w3 := z2 - w4;
DstLine[x].rgbtRed := (SrcLine1[t3].rgbtRed * w1 +
SrcLine1[t3 + 1].rgbtRed * w2 +
SrcLine2[t3].rgbtRed * w3 + SrcLine2[t3 + 1].rgbtRed * w4) shr 16;
DstLine[x].rgbtGreen :=
(SrcLine1[t3].rgbtGreen * w1 + SrcLine1[t3 + 1].rgbtGreen * w2 +

SrcLine2[t3].rgbtGreen * w3 + SrcLine2[t3 + 1].rgbtGreen * w4) shr 16;
DstLine[x].rgbtBlue := (SrcLine1[t3].rgbtBlue * w1 +
SrcLine1[t3 + 1].rgbtBlue * w2 +
SrcLine2[t3].rgbtBlue * w3 +
SrcLine2[t3 + 1].rgbtBlue * w4) shr 16;
Inc(xP, xP2);
end; {for}
Inc(yP, yP2);
DstLine := pRGBArray(Integer(DstLine) + DstGap);
end; {for}
end; {if}
end;

Open in new window

Avatar of jimyX
jimyX

Probably you should give this component a try:
http://plsoft.users.btopenworld.com/
If you want to avoid granularity and strange effects when you downsize an Image, you should always filter out all the higher frequencies that will no longer fit into the smaller image area... Removing higher frequencies in an image basically means that you "blur" the image...

Since I like image processing, I took some time to make a small sample for you how this can be made...

Run the sample code and then resize the window... If you make the window small enough you will see the aliasing effects on the unfiltered image, where the higher frequencies no longer fit in the image, which makes the downsized image distorted...

If you then mark the checkbox (which tells the application to show the filtered image instead), you'll see the difference with the Anti aliasing effect enabled...

Hope this helps...

/Hypo
object Form5: TForm5
  Left = 0
  Top = 0
  Caption = 'Form5'
  ClientHeight = 551
  ClientWidth = 527
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  OnPaint = FormPaint
  OnResize = FormResize
  DesignSize = (
    527
    551)
  PixelsPerInch = 96
  TextHeight = 13
  object Bevel1: TBevel
    Left = 8
    Top = 31
    Width = 512
    Height = 512
    Anchors = [akLeft, akTop, akRight, akBottom]
  end
  object CheckBox1: TCheckBox
    Left = 8
    Top = 8
    Width = 97
    Height = 17
    Caption = 'Filterscale'
    TabOrder = 0
    OnClick = CheckBox1Click
  end
end

Open in new window

unit Unit5;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Math;

type
  TForm5 = class(TForm)
    Bevel1: TBevel;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    FOriginalBitmap : TBitmap;
    FScaledBitmap : TBitmap;
    procedure ScaleBitmap(ASource, ATarget : TBitmap; AWidth, AHeight : integer);
  end;

var
  Form5: TForm5;

implementation

{$R *.dfm}


procedure TForm5.FormCreate(Sender: TObject);
var x, y, v : integer;
    r, g, b : Byte;
    d : double;
begin
  FScaledBitmap := TBitmap.Create;
  FScaledBitmap.PixelFormat := pf32bit;
  // Generate the sourceimage...
  FOriginalBitmap := TBitmap.Create;
  FOriginalBitmap.SetSize(512, 512);
  FOriginalBitmap.PixelFormat := pf32bit;
  for y := 0 to 511 do
    for x := 0 to 511 do begin
      // Calculate color for circular pattern...
      d := Sqrt(Sqr(256-X)+Sqr(256-Y));
      v := Trunc(128 + 127*Sin(d));
      r := v;
      g := v;
      b := v;
      // Add chessboard pattern...
      if ((x and 32) <> 0) xor ((y and 32) <> 0) then begin
        R := 0;
        G := 0;
      end;
      // Set the pixel...
      FOriginalBitmap.Canvas.Pixels[X, Y] := RGB(R, G, B);
    end;
end;

procedure TForm5.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FOriginalBitmap);
  FreeAndNil(FScaledBitmap);
end;

procedure TForm5.CheckBox1Click(Sender: TObject);
begin
  Repaint;
end;

procedure TForm5.FormPaint(Sender: TObject);
begin
  if CheckBox1.Checked
    then Canvas.StretchDraw(Bevel1.BoundsRect, FScaledBitmap)
    else Canvas.StretchDraw(Bevel1.BoundsRect, FOriginalBitmap);
end;

procedure TForm5.FormResize(Sender: TObject);
begin
  ScaleBitmap(FOriginalBitmap, FScaledBitmap, Bevel1.Width, Bevel1.Height);
end;

procedure TForm5.ScaleBitmap(ASource, ATarget: TBitmap; AWidth, AHeight : Integer);
var X, Y, sX, sY, tX, tY : integer;
    wScale, hScale : Double;
    aColor : TColor;
    R, G, B : Byte;
    aIndex : integer;
    fX, fY : Double;
    d : double;
    aResult : array of single;
begin
  if (AWidth > 0) and (AHeight > 0) then begin
    SetLength(aResult, AWidth*AHeight * 4);
    try
      wScale := AWidth / ASource.Width;
      hScale := AHeight / ASource.Height;
      for Y := 0 to ASource.Width-1 do begin
        fY := Y * hScale;
        tY := Round(fY);
        for X := 0 to ASource.Height-1 do begin
          fX := X * wScale;  // fX & fY contains the float coordinate to ATarget...
          tX := Round(fX);   // tX & tY contains the integer center-coordinate to ATarget...
          // get Color-components...
          aColor := ASource.Canvas.Pixels[X, Y];
          R := GetRValue(aColor);
          G := GetGValue(aColor);
          B := GetBValue(aColor);
          // Process each pixel and it's surroundings...
          for sY := -1 to 1 do begin
            if (tY + sY >= 0) and (tY + sY < AHeight) then
              for sX := -1 to 1 do begin
                if (tX + sX >= 0) and (tX + sX < AWidth) then begin
                  aIndex := (tY + sY) * AWidth + tX + sX;
                  d := Sqrt(Sqr(fX - (tX + sX)) + Sqr(fY - (tY + sY)));
                  // d contains the distance to the pixel...
                  if d < 1 then begin
                    aResult[aIndex * 4 + 0] := aResult[aIndex * 4 + 0] + R * (1-d);
                    aResult[aIndex * 4 + 1] := aResult[aIndex * 4 + 1] + G * (1-d);
                    aResult[aIndex * 4 + 2] := aResult[aIndex * 4 + 2] + B * (1-d);
                    aResult[aIndex * 4 + 3] := aResult[aIndex * 4 + 3] + (1-d);
                  end;
                end;
              end;
          end;
        end;
      end;
      ATarget.SetSize(AWidth, AHeight);
      for Y := 0 to AHeight-1 do begin
        for X := 0 to AWidth-1 do begin
          aIndex := Y * AWidth + X;
          d := aResult[aIndex * 4 + 3];
          if d <> 0 then begin
            R := Trunc(aResult[aIndex * 4 + 0] / d);
            G := Trunc(aResult[aIndex * 4 + 1] / d);
            B := Trunc(aResult[aIndex * 4 + 2] / d);
            ATarget.Canvas.Pixels[X, Y] := RGB(R, G, B);
          end;
        end;
      end;
    finally
      aResult := nil;
    end;
  end;
end;


end.

Open in new window

Avatar of bobbysdog

ASKER

Thank Hypo!

I tried your code and i compared thumbnails to my previous algorithm. And it looks like yours are coming out much better.

Awesome

The only thing is, I seem to get white bars on my resultant images. I think I might be sending bad data to the AWidth, AHeight variables.

but i'm not sure...

I'm so unskilled about such things...

Do you think you could tweak your algo to accept 2 variables - e.g. to have the same 2 variables as input as the algo I listed at the top of this post?

procedure SmoothResize(Src, Dst: TBitmap);

I would forever thankful!!!
Sure... one thing about the algorithm I wrote is that it's written to scale down images... If you scale up the images it gives bad results right now... but I'll modify the code as to how you want it....

Looking at your code, I assume that you set Width and Height of the Dst bitmap before you call the SmoothResize function... so the Src image will be scaled down to whatever size Dst image is, correct?
Ok,
so I've modified the function somewhat... do the results look ok for you now?

/Hypo
procedure SmoothScale(Src, Dst : TBitmap);
var X, Y, sX, sY, tX, tY : integer;
    wScale, hScale : Double;
    aColor : TColor;
    R, G, B : Byte;
    aIndex, aWidth, aHeight : integer;
    fX, fY : Double;
    d : double;
    aResult : array of single;
begin
  aWidth := Dst.Width;
  aHeight := Dst.Height;
  if (aWidth > 0) and (aHeight > 0) then begin
    wScale := aWidth / Src.Width;
    hScale := aHeight / Src.Height;
    if (wScale >= 1) or (hScale >= 1) then begin
      Dst.Canvas.StretchDraw(Rect(0, 0, aWidth, aHeight), Src);
    end;
    if (wScale < 1) or (hScale < 1) then begin
      SetLength(aResult, aWidth*aHeight * 4);
      try
        for Y := 0 to Src.Width-1 do begin
          fY := Y * hScale;
          tY := Round(fY);
          for X := 0 to Src.Height-1 do begin
            fX := X * wScale;  // fX & fY contains the float coordinate to Dst...
            tX := Round(fX);   // tX & tY contains the integer center-coordinate to Dst...
            // get Color-components...
            aColor := Src.Canvas.Pixels[X, Y];
            R := GetRValue(aColor);
            G := GetGValue(aColor);
            B := GetBValue(aColor);
            // Process each pixel and it's surroundings...
            for sY := -1 to 1 do begin
              if (tY + sY >= 0) and (tY + sY < aHeight) then
                for sX := -1 to 1 do begin
                  if (tX + sX >= 0) and (tX + sX < aWidth) then begin
                    aIndex := (tY + sY) * aWidth + tX + sX;
                    d := Sqrt(Sqr(fX - (tX + sX)) + Sqr(fY - (tY + sY)));
                    // d contains the distance to the pixel...
                    if d < 1 then begin
                      aResult[aIndex * 4 + 0] := aResult[aIndex * 4 + 0] + R * (1-d);
                      aResult[aIndex * 4 + 1] := aResult[aIndex * 4 + 1] + G * (1-d);
                      aResult[aIndex * 4 + 2] := aResult[aIndex * 4 + 2] + B * (1-d);
                      aResult[aIndex * 4 + 3] := aResult[aIndex * 4 + 3] + (1-d);
                    end;
                  end;
                end;
            end;
          end;
        end;
        for Y := 0 to aHeight-1 do begin
          for X := 0 to aWidth-1 do begin
            aIndex := Y * aWidth + X;
            d := aResult[aIndex * 4 + 3];
            if d <> 0 then begin
              R := Trunc(aResult[aIndex * 4 + 0] / d);
              G := Trunc(aResult[aIndex * 4 + 1] / d);
              B := Trunc(aResult[aIndex * 4 + 2] / d);
              Dst.Canvas.Pixels[X, Y] := RGB(R, G, B);
            end;
          end;
        end;
      finally
        aResult := nil;
      end;
    end;
  end;
end;

Open in new window

Thanks man

I plugged in your above code and the program did indeed run.

but it still seems to be cropping the images in a weird fashion: - e.g. inserting white bars

You can see an example of a pic i tried to resize here:

http://img98.imageshack.us/img98/9400/whyoriginal.jpg
http://img23.imageshack.us/img23/1319/whyresult.jpg

ya there isnt anything too exotic about the new bitmap. Heres the code i use to call it.

NewBitmap := TBitmap.Create;
NewBitmap.Width  := MyNewWidth;
NewBitmap.Height := MulDiv(MaxWidth, OldBitmap.Height, OldBitmap.Width);

The program still works fine with my originally quoted algorithm.

So this leads me to believe that there is some math error somewhere that is knockin the canvas over for some reason????

Thanks!!!
ASKER CERTIFIED SOLUTION
Avatar of Hypo
Hypo
Flag of Sweden image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Following this thread. Good stuff here Hypo.
Thx Thieving,
It's just a rough piece of code I threw together... but still, I love these kinds of problems... :)
woo hoo - thanks so much - it works great! - Cr*p i wish i was that smart! thanks!!!!