Image Processing
Magnify.gif (969 bytes) Magnifying Glass  Lab Report

Chinese Translation by Hector Xiang

A Square or Circular Digital Magnifying Glass
MandrillRightEye.jpg (83816 bytes)

Purpose
The purpose of the Magnifier program is to demonstrate how to create a digital magnifying glass to enlarge a small selected area of an image.

An alternate and better solution using TPicture/TGraphic polymorphism by Anders Melander will also be discussed.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3 or 4 (to recompile)
Magnifier.EXE

GIF Support:  Requires Anders Melander's TGIFImage (from www.melander.dk/delphi/gifimage) and a "GIF" conditional to be set before compilation.  

Hardware Requirements
Super VGA display with 800-by-600 screen in high/true color display mode

Procedure (see discussion comments below before using Anders Melander's Magnifier)

  1. Double click on the Magnifier.EXE icon to start the program.  (If you're operating in 256-color mode, you will see a message in red at the bottom of the screen:   For Use with High/True Color Display Modes.   Palettes will not be correct in 256-color mode.  You may experience problems with the circular magnifying glass in 256-color mode -- see comments in Discussion below.)
  2. Press the Load button and select a file to display.

    Several graphics file formats can be read:  BMP, JPG, WMF, EMF and ICO.  If Anders Melander's TGIFImage is installed, and the program is re-compiled with a GIF conditional, then GIF files are also supported.

    The name of the last directory from which a file is loaded is stored in an IniFile.  When the program is restarted, this directory is the default.
  3. If the image is smaller than the display area, you may want to select the Stretch checkbox to enlarge the image to the full size available. 
  4. Press down with the mouse anywhere within the image to see the area enlarged with the currently selected Magnifier.  If you have a fast enough machine, press and drag to see the magnifying glass move over the image.  If your machine is too slow, the drag operation will not update quickly enough.
  5. If desired, change any of the magnifier properties:  Magnification factor, Radius, Shape, Border Color, or Cursor.
  6. If desired, move the mouse over the image without pressing the mouse key to see the (X,Y) coordinates and RGB values of a selected pixel.

Discussion
Anders Melander suggested a better approach to my original program.  I'm showing both his solution and mine since I think both can be educational.

In my program, GIF support is added during FormCreate to the OpenPictureDialog Filter with the following:

{$IFDEF GIF}
    s := OpenPictureDialog.Filter + '|GIFs (*.gif)|*.gif';
    Insert('*.gif;',s, POS('(',s)+1); // Put GIF in "All" selection
    Insert('*.gif;',s, POS('|',s)+1);
    OpenPictureDialog.Filter := s;
{$ENDIF}

Anders' program  has a better approach in its FormCreate:

OpenPictureDialog.Filter := GraphicFilter(TGraphic)

The Load button in the program is used to read various file graphics file formats.  The ButtonLoadImageClick method is used to read any of the supported file formats by calling a function LoadGraphicsFile, which is defined in the GraphicsConversionLibrary unit. 

Unfortuantely, the in-memory TBitmap does not directly support any file format except BMP.  This means that to manipulate pixels, most file formats must be first converted to a TBitmap.  In my version of LoadGraphicsFile, I used a brute force approach to perform all these conversions.   Anders' approach is much shorter using the polymorphism of  TGraphic/TPicture:

// Create TBitmap from BMP, JPG, WMF, EMF, GIF or any other
// format supported by TGraphic.

// [anme] Completely rewritten to use TPicture/TGraphic polymorphism
FUNCTION LoadGraphicsFile(CONST Filename: STRING;
                                            CONST ForcePf24bit: BOOLEAN): TBitmap;
  VAR
    Image: TPicture;
BEGIN
  Result := nil;
  if FileExists(Filename)
  then begin
    Result := TBitmap.Create;
    try
      Image := TPicture.Create;
      try
        // Load image - let TPicture worry about the image type
        Image.LoadFromFile(Filename);
        // Try converting image to bitmap
        try
          Result.Assign(Image.Graphic);
        except
          // Image didn't support conversion to TBitmap
          // Draw image on bitmap instead
          Result.Width := Image.Graphic.Width;
          Result.Height := Image.Graphic.Height;
          Result.PixelFormat := pf24bit;
          Result.Canvas.Draw(0, 0, Image.Graphic);
        end;
      finally
        Image.Free;
      end;
    except
      Result.Free;
      raise;
    end;

    // The following appears to be needed for the circular magnifier for some
    // (not all) GIF files and for pf8bit TBitmaps. Some GIF files
    // do not appear correctly without this, and pf8bit bitmaps do not
    // have "real time" updates without forcing this.  This may not be
    // desirable in other programs.

    // This is an experimental "fix" -- I wish I knew why this is necessary.
    // efg, 21 Feb 99
    IF ForcePf24bit AND (RESULT.PixelFormat <> pf24bit)
    THEN Result.PixelFormat := pf24bit

  end

END {LoadGraphicFile};

I added the ForcePf24bit parameter to Anders' routine -- this will be discussed below.

Look at the TMagnifier.ShowMagnifier method for details of how the "magnification process" works.  A brief outline appears here.

The diameter of the magnified area times the magnification factor is a constant.  For example, if the area of interest had a diameter of 30 pixels at a 2X magnification (30 *2 = 60), only 15 pixels would be selected at a 4X magnification (15 * 4 = 60).  With no magnification (1X) this would be a 60-pixel diameter area (60 * 1 = 60).  The calculation of a diameter is limited by the integer arithmetic of pixel sizes. 

An in-memory Bitmap is read as part of the OpenPictureDialog.Execute processing in the ButtonLoadImageClick method.  To avoid working with a variety of pixel formats and palettes, all the files read by LoadGraphicsFile (in the GraphicsConversionsLibrary.PAS unit) are converted to a pf24bit pixel format.  This means that correct colors are only displayed when using a high color or true color display mode. 

For the square magnifier, the original in-memory bitmap is first copied to a new ModifiedBitmap.  A small square area of the original in-memory Bitmap, around the point (X,Y) from the MouseDown or MouseMove event, is stretched onto a larger area of the ModifiedBitmap.  The ModifiedBitmap is then displayed in the ImageOnForm TImage.

Extra work is needed for the circular magnifier.  A new ModifiedBitmap is first copied from the in-memory Bitmap as described above.  A CircularMask bitmap is created with a solid black background and a filled-in white circle of appropriate size.  CopyRect with CopyMode := cmSrcAnd  converts the CircularMask to have an enlarged circular area from the original image with a black area around this circle.

An "And" of a black = 0 (for R, G, and B) area from the mask results in a black area.  An "And" of a white=255=$FF (for R, G, and B) area from the mask selects a circular area of interest from the in-memory Bitmap.    Setting  CircularMask.Transparent := TRUE allows a Draw method to overlay the circular area transparently on the ModifiedBitmap.   As with the square magnifier, the ModifiedBitmap is displayed in the ImageOnForm TImage.

When the Stretch checkbox is checked, the border line around the magnified area may be missing, or partially missing.

The "Square" or "Circle" magnifying glass will usually become a "Rectangle" or "Ellipse" when the Stretch checkbox is checked.

The circular magnifying glass has a peculiar behavior when operated in 256-color mode -- as noted above the program is only recommended for high or true color display modes.  When using the 8-bits/pixel Deer test picture, many of the pixels are white.  In all cases, the colors are not "correct" in 256-color mode since palettes are ignored.

An unexpected change was needed in Delphi 4 in ImageOnFormMouseMove to avoid a Range Check Error at runtime:

TargetColor := Bitmap.Canvas.Pixels[xActual, YActual];

IF TargetColor = -1
THEN ...

This check is needed in Delphi 4 to avoid a Range Check Error in  GetRValue, etc. These functions (GetRValue, etc.) take a DWORD argument, but DWORD has a different definition in D3 and D4.  In D3 a DWORD is an Integer, but in D4 a DWORD is a LongWord.  A value of -1 now creates a Range Check Error in D4, which is returned by Pixels above when the property is undefined like when a TBitmap is created without any assignment to Width or Height.

Overall, Anders' approach was much better than mine, but in an initial test I observed two problems.  In one case the circular magnifier didn't work correctly for certain GIFs (like the KSFlag.GIF that can be downloaded below) -- but not all GIFs.  For certain images, especially pf8bit images (like the Deer image that can be downloaded below), Anders' magnifier was sluggish.  Both of these probems were solved by the ForcePf24bit flag described above.  I'm not sure why this "solves" the problem since I won't claim to understand the "cause" of the problems.

Here's what the problem looked liked with certain GIF files:

Notes:
1.  "Force pf24bit" not checked when KSFlag.GIF was loaded
2.  "Circle" shape checked
3.  "Stretch" Checked
4.  Radius = 10 pixels
5.  Click (or press and drag) near (X,Y) = (21,20).
6.  Instead of circular magnifier, a sluggish, black rectangle is seen.
ScreenMagnifierKSFlagError.jpg (42341 bytes)

The "Force pf24bit" option solved the problem:

Notes:
1.  "Force pf24bit" was checked when KSFlag.GIF was loaded
2.  "Circle" shape checked
3.  "Stretch" Checked
4.  Radius = 10 pixels
5.  Click (or press and drag) near (X,Y) = (21,20).
6.  The stretched circular magnifier is very snappy.
ScreenMagnifierKSFlagOK.jpg (54095 bytes)

Similar Project 
Magnify by Fei Hongbin

Conclusions
Creating the circular magnifier is a good exercise in using a bitmap mask to copy a selected area of a bitmap and then drawing it on another bitmap using transparency.


Related

Non-Linear Magnification Home Page
www.cs.indiana.edu/hyplan/tkeahey/research/nlm/nlm.html

Dr. Chris Rorden's Kylix magnifier example
www.psychology.nottingham.ac.uk/staff/cr1/kylix.html 


Keywords
CopyMode, cmSrcCopy, cmSrcAnd, Bitmap Mask, CopyRect, Transparent, Draw, Ellipse, Rectangle, Invalidate, GetPixelFormatString, IsPaletteDevice, LoadGraphicsFile, Magnification Factor, BMP, JPG, WMF, EMF, GIF, WmEraseBkgnd, TIniFile, MulDiv, OpenPictureDialog, TPicture/TGraphic polymorphism

Download
Delphi 3/4 Source and EXE (229 KB):  Magnifier.ZIP
Delphi 3/4 Source Code with Anders Melanders' better solution (9 KB):  AndersMelanderMagnifier.ZIP
Mandrill test image:  Mandrill.ZIP (pf24bit bitmap)
KS Flag test image:  KSFlag.GIF
Deer test image:  Deer.ZIP (pf8bit bitmap)

Compiles without any changes in D4.


Updated 10 Jun 2003


since 2 Jan 1999