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