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.
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.
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:
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.
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.
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.
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:
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.