Image Processing
Aspect Ratio    Lab Report

Chinese Translation by Hector Xiang

 "Landscape" TBitmap displayed in various TImage rectangles while maintaining proper aspect ratio

Purpose
The purpose of this project is to demonstrate how to display a rectangular TBitmap in a rectangular TImage of any size, while preserving the original aspect ratio.

Background
The ratio of a picture's width-to-height is known as its aspect ratio.  An image with an aspect ratio greater than 1, is called a "landscape" image.  An image with an aspect ratio less than 1, is called a "portrait" image.  When the aspect ratio is exactly 1, the image is square.

This image has an aspect ratio of 4 to 3:

width = 200

height = 150

Aspect Ratio = width / height = 200/150 = 4/3
(a "landscape" picture)

A larger or smaller "similar" version of a picture can be obtained by stretching both dimensions by the same factor, which keeps the aspect ratio the same.  For example, if you divide each of the dimensions of the above picture by 2, the resulting image has the same aspect ratio:

width = 100

height = 75

Aspect Ratio = width / height = 100/75 = 4/3
(a "landscape" picture)

A distorted picture results from stretching one dimension more or less than the other dimension.  If the 200-by-150 image is stretched only in the vertical dimension, the picture is obviously distorted:

width = 200

height = 50

Aspect Ratio = width / height = 200/50 = 4

Likewise, if the 200-by-150 image is only stretched in the horizontal dimension, the picture is obviously distorted also:

width = 50

height = 150

Aspect Ratio = width / height = 50/150 = 1/3
(a "portrait" picture)

Many common computer display modes, and digital camera image sizes, involve a 4-to3 aspect ratio with square pixels:

Common 4-to-3 Aspect Ratio Sizes

width height comments
640 480 standard VGA display
800 600  
1024 768 XGA Nikon CoolPix 990 image size
1280 1024  
1400 1050 Dell Inspiron display mode on 15" LCD
1600 1200  
2048 1536 "Full" Nikon CoolPix 990 image size

Displaying a picture with a 4-to-3 aspect ratio full screen in a 4-to-3 display mode gives good results.  Displaying a "landscape" picture in a "portrait" display area, or vice versa, often does not give acceptable results using Delphi's TImage component.  Sometimes setting the Stretch property of a TImage can be used to fill an area, but this stretching results in distortion since each dimension may be be stretched by a different factor.

Finding a method to display any rectangular picture in a TImage of any size, AND preserving the aspect ratio, is very desirable. 

Materials and Equipment

Software Requirements
Windows 95/98/NT/2000
Delphi 3/4/5 (to recompile)
AspectRatio.EXE

Hardware Requirements
VGA display with high color or true color.

Procedure

  1. Click on the AspectRatio.EXE icon to start the program.  The program starts with a default "square" bitmap.
  2. Press on the Load Picture button and select a picture (.BMP, .JPG, ...) to display from the OpenPictureDialog.

  3. Observe how the picture is displayed in landscape, square and portrait orientations.  The AspectRatio program shows each picture in two different sizes of landscape, square and portrait TImages.

  4. If desired, change the "fill color" for the areas that are not covered by preserving the aspect ratio.

  5. Observe all possible picture types and how they are displayed:

TBitmap  Type

TImage Type

Example

Landscape Square Portrait
Landscape Best     "Sunflower" image (above)
Square   Best   "Smiley" image (below)
Portrait     Best "Ski Lift" image (below)

 

"Square" picture displayed in various rectangles while maintaining proper aspect ratio

 

"Portrait" picture displayed in various rectangles while maintaining proper aspect ratio

Discussion

See the source code for complete details of how the AspectRatio program works.  The highlights are shown here.

Pressing the Load Picture button invokes the ButtonLoadPictureClick method shown in Listing 1.

Listing 1.  Processing "Load Picture" Button Click

procedure TFormAspectRatio.ButtonLoadPictureClick(Sender: TObject);
begin
  IF   OpenPictureDialog.Execute
  THEN BEGIN
    Bitmap.Free;  // get rid of old bitmap
    Bitmap := LoadGraphicsFile(OpenPictureDialog.Filename);
    LabelFilename.Caption := OpenPictureDialog.Filename +  '  (' +
                             IntToStr(Bitmap.Width) + ' by ' +
                             IntToStr(Bitmap.Height) + ' pixels)';
    UpdateAllImages
  END
end;

The LoadGraphicsFile routine uses a TPicture object to load a graphic of any registered file type.  Before the picture can be manipulated in any way, it must be converted to a TBitmap, as shown in Listing 2.

Listing 2.  Read TPicture and converting to TBitmap

// Based on suggestions from Anders Melander.  
// See Magnifier Lab Report.
FUNCTION LoadGraphicsFile(CONST Filename: STRING):  TBitmap;
  VAR
    Picture: TPicture;
BEGIN
  RESULT := NIL;
  IF   FileExists(Filename)
  THEN BEGIN
    RESULT := TBitmap.Create;
    TRY
      Picture := TPicture.Create;
      TRY
        Picture.LoadFromFile(Filename);
        // Try converting picture to bitmap
        TRY
          Result.Assign(Picture.Graphic);
        EXCEPT
          // Picture didn't support conversion to TBitmap.
          // Draw picture on bitmap instead.
          RESULT.Width  := Picture.Graphic.Width;
          RESULT.Height := Picture.Graphic.Height;
          RESULT.PixelFormat := pf24bit;
          RESULT.Canvas.Draw(0, 0, Picture.Graphic);
        END
      FINALLY
        Picture.Free
      END
    EXCEPT
      RESULT.Free;
      RAISE
    END
  END
END {LoadGraphicFile};

LoadGraphicsFile (from Listing 2) is used to create the Bitmap object (in Listing 1).  UpdateAllImages shown in Listing 1 displays this Bitmap object in each of the various TImages, which is shown in Listing 3.

Listing 3.  Display Bitmap in various TImages

PROCEDURE TFormAspectRatio.UpdateAllImages;
BEGIN
  DisplayBitmap(Bitmap, ImageLandscape);
  DisplayBitmap(Bitmap, ImageSquare);
  DisplayBitmap(Bitmap, ImagePortrait);
  DisplayBitmap(Bitmap, ImageLandscapeNarrow);
  DisplayBitmap(Bitmap, ImageSquareSmall);
  DisplayBitmap(Bitmap, ImagePortraitNarrow);
END {UpdateAllImages};

The DisplayBitmap routine creates a new bitmap that is the same size as the target TImage.  Then DisplayBitmap copies the original bitmap to the new bitmap in such as way as to maintain the original aspect ratio and keep the new version as large as possible, as shown in Listing 4.

Listing 4.  Create New Bitmap to Fill TImage

// Display Bitmap in Image.  Keep the TBitmap as large as possible
// in the TImage while maintaining the correct aspect ratio.
PROCEDURE TFormAspectRatio.DisplayBitmap(CONST Bitmap:  TBitmap;
                                         CONST Image :  TImage);
  VAR
    Half      :  INTEGER;
    Height    :  INTEGER;
    NewBitmap :  TBitmap;
    TargetArea:  TRect;
    Width     :  INTEGER;
BEGIN
  NewBitmap := TBitmap.Create;
  TRY
    NewBitmap.Width  := Image.Width;
    NewBitmap.Height := Image.Height;
    NewBitmap.PixelFormat := pf24bit;
    NewBitmap.Canvas.Brush := ShapeFill.Brush;
    NewBitmap.Canvas.FillRect(NewBitmap.Canvas.ClipRect);
    // "equality" (=) case can go either way in this comparison
    IF   Bitmap.Width / Bitmap.Height  <  Image.Width / Image.Height
    THEN BEGIN
      // Stretch Height to match.
      TargetArea.Top    := 0;
      TargetArea.Bottom := NewBitmap.Height;
      // Adjust and center Width.
      Width := MulDiv(NewBitmap.Height, Bitmap.Width, Bitmap.Height);
      Half := (NewBitmap.Width - Width) DIV 2;
      TargetArea.Left  := Half;
      TargetArea.Right := TargetArea.Left + Width;
    END
    ELSE BEGIN
      // Stretch Width to match.
      TargetArea.Left    := 0;
      TargetArea.Right   := NewBitmap.Width;
      // Adjust and center Height.
      Height := MulDiv(NewBitmap.Width, Bitmap.Height, Bitmap.Width);
      Half := (NewBitmap.Height - Height) DIV 2;
      TargetArea.Top    := Half;
      TargetArea.Bottom := TargetArea.Top + Height
    END;
    NewBitmap.Canvas.StretchDraw(TargetArea, Bitmap);
    Image.Picture.Graphic := NewBitmap
  FINALLY
    NewBitmap.Free
  END
END {DisplayBitmap};

The Canvas.StretchDraw method is used to stretch the bitmap, as shown in Listing 4 above.  Other algorithms to stretch images can be found under Resampling on the Delphi Image Processing Algorithms page.  In particular, use StretchDIBits to print a bitmap instead of StretchDraw shown above.

When the aspect ratio is fixed when the bitmap is stretched, part of the new bitmap may not be filled with the original bitmap.  The FillRect call above fills the entire new bitmap with the color of defined by the brush of the TShape.  This color remains in the areas not filled with the original bitmap.  

Since a TShape does not have an OnClick method, the TShape's OnMouseDown is used to assign the color from a ColorDialog.  Just click on the TShape to change this fill color.

Conclusion
Displaying a TBitmap in a TImage of the same aspect ratio gives the best and easiest results, but with slight adjustments, a bitmap  can be displayed in any TImage while preserving the aspect ratio.


Keywords
aspect ratio, landscape orientation, portrait orientation, StretchDraw, TPicture, TBitmap, TImage, TShape


Download 
Delphi 3/4/5 Source and EXE (188 KB):  AspectRatio.ZIP


Updated 14 Jun 2009
Since 23 Mar 2001