When displaying images in Xbase++, you might encounter image files that appear rotated or flipped. This often happens with JPEG pictures taken with mobile phones whose cameras tend to save pictures with a constant aspect ratio, regardless of the device's orientation at the time a picture is taken. Instead, the intended orientation is recorded in the image's EXIF metadata as a cue for image renderers.

This post demonstrates how to read the orientation metadata embedded in an image, and how this information may be used for applying appropriate graphics transforms for correcting the image using Xbase++’s graphics subsystem.

Examining (EXIF) image metadata​

The method :getProperty() of the XbpBitmap class allows reading information from various sets of image metadata, such as the GPS location at which a photo was taken ("System.GPS") or the image dimensions ("System.Image"). An overview of property sets which can be accessed via :getProperty() can be found in the Windows SDK: photo metadata property sets.

In this case, we're interested in a property related to the camera which was used to take the picture. This can be found in the "Photo" property set.

1745338177648.png


1745338264966.png


The details page on this specific property ("System.Photo.Orientation") has a link at the bottom which points to a page detailing the property's enumerated values, which we can then use to determine the intended orientation, for example, by declaring them as #defines as in the code below. Excerpt:

1745338654326.png


Rotating or flipping an image using a graphics transform​

The function below inspects the "System.Photo.Orientation" property of an image, and constructs a suitable transformation matrix if the image wasn't saved in its intended orientation. This matrix is then used to create a rotated or flipped image which can be used in the application.

Xbase++:
/*
 * Check if the orientation differs from the desired one, and manipulate the
 * image as needed. Return is the corrected image or the original one,
 * if the orientation is not manipulated.
 */
FUNCTION CorrectImageOrientation( oBitmap )
   LOCAL nProperty
   LOCAL nXSize, nYSize
   LOCAL oPS
   LOCAL oBmpTmp
   LOCAL aMat := GraInitMatrix()

#define PHOTO_ORIENTATION_NORMAL          1
#define PHOTO_ORIENTATION_FLIPHORIZONTAL  2
#define PHOTO_ORIENTATION_ROTATE180       3
#define PHOTO_ORIENTATION_FLIPVERTICAL    4
#define PHOTO_ORIENTATION_TRANSPOSE       5
#define PHOTO_ORIENTATION_ROTATE270       6
#define PHOTO_ORIENTATION_TRANSVERSE      7
#define PHOTO_ORIENTATION_ROTATE90        8

   nProperty := oBitmap:getProperty( "System.Photo.Orientation" )

   IF nProperty $ {PHOTO_ORIENTATION_NORMAL, ;     // (leave unchanged)
                   PHOTO_ORIENTATION_TRANSPOSE, ;  // (unsupported)
                   PHOTO_ORIENTATION_TRANSVERSE}   // (unsupported)
      RETURN oBitmap
   ENDIF

   // Create a temporary bitmap large enough for storing the corrected
   // image. Use a transformation matrix to rotate or flip the image when
   // drawing from the original to the destination bitmap. Note that a
   // negative scale factor flips the image along the corresponding axis.
   nXSize := oBitmap:XSize
   nYSize := oBitmap:YSize

   oBmpTmp   := XbpBitmap():new():create()
   oPS       := XbpPresSpace():new():create()
   oBmpTmp:presSpace( oPS )

   DO CASE
      CASE nProperty == PHOTO_ORIENTATION_FLIPHORIZONTAL
         oBmpTmp:make( nXSize, nYSize )
         GraScale( oPS, aMat, {1,-1}, {nXSize/2,nYSize/2} )

      CASE nProperty == PHOTO_ORIENTATION_FLIPVERTICAL
         oBmpTmp:make( nXSize, nYSize )
         GraScale( oPS, aMat, {-1,1}, {nXSize/2,nYSize/2} )

      CASE nProperty == PHOTO_ORIENTATION_ROTATE180
         oBmpTmp:make( nXSize, nYSize )
         GraRotate( oPS, aMat, 180, {nXSize/2,nYSize/2} )

      CASE nProperty == PHOTO_ORIENTATION_ROTATE270
         oBmpTmp:make( nYSize, nXSize )
         GraRotate( oPS, aMat, 270, {nYSize/2,nXSize/2} )
         GraTranslate( oPS, aMat, (nXSize-nYSize)/2, (nXSize-nYSize)/2, GRA_TRANSFORM_ADD )

      CASE nProperty == PHOTO_ORIENTATION_ROTATE90
         oBmpTmp:make( nYSize, nXSize )
         GraRotate( oPS, aMat, 90, {nYSize/2,nXSize/2} )
         GraTranslate( oPS, aMat, -(nXSize-nYSize)/2, -(nXSize-nYSize)/2, GRA_TRANSFORM_ADD )

      OTHERWISE
        oBmpTmp:destroy()
        oBmpTmp := NIL
   ENDCASE

   IF oBmpTmp == NIL
      RETURN oBitmap
   ENDIF

   oPS:setGraTransform( aMat )
   oBitmap:draw( oPS, {0,0},,, GRA_BLT_BBO_IGNORE )

   oPS:destroy()
   oBitmap:destroy()
RETURN oBmpTmp

The above function ensures a bitmap is oriented as intended. Otherwise, it creates a corrected image. This approach keeps your display logic clean while ensuring your images are shown exactly as the photographer intended.

Related Documentation​

For further information check out the related Xbase++ documentation:

Function GraRotate(): Calculates a rotational transformation for a matrix.
Function GraScale(): Calculates scaling transformation for a matrix.
Method XbpBitmap():getProperty(): Retrieves a property from an image.
Class XbpPresSpace(): Class function of the XbpPresSpace class.