Getting a Specific Image Property Using The GDI+ API

I thought I’d look at another approach to retrieving information about images.  Previously, I demonstrated how one can get file properties (including images) by using plain VBA or PowerShell:

 

Today, however, I thought I’d demonstrate using the GDI Plus API.  This will be Part 1 in a series of posts on the GDI Plus API.  It is a very complex and versatile API, so I am breaking it up into digestible pieces of information.

The GDI Plus API

The GDI Plus API can do much, much more than retrieving information about an image!  It can for instance, edit an image (watermark anyone?!), you can even use it to alter applications, dialogs, …  This is why it is more complex to use, because it is very complex!

I won’t bore you with tons of talk, here’s the code!

Simply create a new Standard Module and Copy/Paste the following

Option Explicit

'API Declarations, ENUMS, TYPES, Global Variables, ...
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------
'GDI - General
Private Declare Function GdiplusStartup Lib "gdiplus" (ByRef token As Long, ByRef lpInput As GDIPlusStartupInput, Optional ByRef lpOutput As Any) As Status
Private Declare Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Status
Private Declare Function GdipCreateBitmapFromFile Lib "gdiplus" (ByVal FileName As Long, ByRef Bitmap As Long) As Status
Private Declare Function GdipDisposeImage Lib "gdiplus" (ByVal image As Long) As Status
'GDI - Image / Properties
Private Declare Function GdipGetPropertyItemSize Lib "gdiplus" (ByVal image As Long, ByVal propId As Long, ByRef Size As Long) As Status
Private Declare Function GdipGetPropertyItem Lib "gdiplus" (ByVal image As Long, ByVal propId As Long, ByVal PropSize As Long, ByRef buffer As Any) As Status

'Helper API Declarations
Private Declare Function lstrlenW Lib "kernel32" (lpString As Any) As Long
Private Declare Function lstrcpyW Lib "kernel32" (lpString1 As Any, lpString2 As Any) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Const GdiPlusVersion  As Long = 1

Private Type GDIPlusStartupInput
    GdiPlusVersion            As Long
    DebugEventCallback        As Long
    SuppressBackgroundThread  As Long
    SuppressExternalCodecs    As Long
End Type

Private Type PropertyItem
    id                        As Long    'PropertyTagId
    Length                    As Long
    Type                      As Integer 'PropertyTagType
    Value                     As Long
End Type

    'GDI+ Status Constants
Private Enum Status
    'https://docs.microsoft.com/en-us/windows/win32/api/gdiplustypes/ne-gdiplustypes-status
    OK = 0
    GenericError = 1
    InvalidParameter = 2
    OutOfMemory = 3
    ObjectBusy = 4
    InsufficientBuffer = 5
    NotImplemented = 6
    Win32Error = 7
    WrongState = 8
    Aborted = 9
    FileNotFound = 10
    ValueOverflow = 11
    AccessDenied = 12
    UnknownImageFormat = 13
    FontFamilyNotFound = 14
    FontStyleNotFound = 15
    NotTrueTypeFont = 16
    UnsupportedGdiplusVersion = 17
    GdiplusNotInitialized = 18
    PropertyNotFound = 19
    PropertyNotSupported = 20
    ProfileNotFound = 21
End Enum

Private Enum PropertyTagType
    'https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-constant-image-property-tag-type-constants
    TypeByte = 1
    TypeASCII = 2
    TypeShort = 3
    TypeLong = 4
    TypeRational = 5
    TypeUndefined = 7
    TypeSLong = 9
    TypeSRational = 10
End Enum

'Image Property Tag Constants
Public Enum PropertyTagId
    'https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-constant-property-tags-in-alphabetical-order
    'https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-constant-property-tags-in-numerical-order
    '   0x0... => &H...
    '   https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-constant-property-item-descriptions
    GpsVer = 0    '&H0&
    GpsLatitudeRef = 1    '&H1&
    GpsLatitude = 2    '&H2&
    GpsLongitudeRef = 3    '&H3&
    GpsLongitude = 4    '&H4&
    GpsAltitudeRef = 5    '&H5&
    GpsAltitude = 6    '&H6&
    GpsGpsTime = 7    '&H7&
    GpsGpsSatellites = 8    '&H8&
    GpsGpsStatus = 9    '&H9&
    GpsGpsMeasureMode = 10    '&HA&
    GpsGpsDop = 11    '&HB&
    GpsSpeedRef = 12    '&HC&
    GpsSpeed = 13    '&HD&
    GpsTrackRef = 14    '&HE&
    GpsTrack = 15    '&HF&
    GpsImgDirRef = 16    '&H10&
    GpsImgDir = 17    '&H11&
    GpsMapDatum = 18    '&H12&
    GpsDestLatRef = 19    '&H13&
    GpsDestLat = 20    '&H14&
    GpsDestLongRef = 21    '&H15&
    GpsDestLong = 22    '&H16
    GpsDestBearRef = 23    '&H17&
    GpsDestBear = 24    '&H18&
    GpsDestDistRef = 25    '&H19&
    GpsDestDist = 26    '&H1A&
    NewSubfileType = 254    '&HFE&
    SubfileType = 255    '&HFF&
    ImageWidth = 256    '&H100&
    ImageHeight = 257    '&H101&
    BitsPerSample = 258    '&H102&
    Compression = 259    '&H103&
    PhotometricInterp = 262    '&H106&
    ThreshHolding = 263    '&H107&
    CellWidth = 264    '&H108&
    CellHeight = 265    '&H109&
    FillOrder = 266    '&H10A&
    DocumentName = 269    '&H10D&
    ImageDescription = 270    '&H10E&
    EquipMake = 271    '&H10F&
    EquipModel = 272    '&H110&
    StripOffsets = 273    '&H111&
    Orientation = 274    '&H112&
    SamplesPerPixel = 277    '&H115&
    RowsPerStrip = 278    '&H116&
    StripBytesCount = 279    '&H117&
    MinSampleValue = 280    '&H118&
    MaxSampleValue = 281    '&H119&
    XResolution = 282    '&H11A&
    YResolution = 283    '&H11B&
    PlanarConfig = 284    '&H11C&
    PageName = 285    '&H11D&
    XPosition = 286    '&H11E&
    YPosition = 287    '&H11F&
    FreeOffset = 288    '&H120&
    FreeByteCounts = 289    '&H121&
    GrayResponseUnit = 290    '&H122&
    GrayResponseCurve = 291    '&H123&
    T4Option = 292    '&H124&
    T6Option = 293    '&H125&
    ResolutionUnit = 296    '&H128&
    PageNumber = 297    '&H129&
    TransferFunction = 301    '&H12D&
    SoftwareUsed = 305    '&H131&
    DateTime = 306    '&H132&
    Artist = 315    '&H13B&
    HostComputer = 316    '&H13C&
    Predictor = 317    '&H13D&
    WhitePoint = 318    '&H13E&
    PrimaryChromaticities = 319    '&H13F&
    ColorMap = 320    '&H140&
    HalftoneHints = 321    '&H141&
    TileWidth = 322    '&H142&
    TileLength = 323    '&H143&
    TileOffset = 324    '&H144&
    TileByteCounts = 325    '&H145&
    InkSet = 332    '&H14C&
    InkNames = 333    '&H14D&
    NumberOfInks = 334    '&H14E&
    DotRange = 336    '&H150&
    TargetPrinter = 337    '&H151&
    ExtraSamples = 338    '&H152&
    SampleFormat = 339    '&H153&
    TransferRange = 342    '&H156&
    JPEGProc = 512    '&H200&
    JPEGInterFormat = 513    '&H201&
    JPEGInterLength = 514    '&H202&
    JPEGRestartInterval = 515    '&H203&
    JPEGLosslessPredictors = 517    '&H205&
    JPEGPointTransforms = 518    '&H206&
    JPEGQTables = 519    '&H207&
    JPEGDCTables = 520    '&H208&
    JPEGACTables = 521    '&H209&
    YCbCrCoefficients = 529    '&H211&
    YCbCrSubsampling = 530    '&H212&
    YCbCrPositioning = 531    '&H213&
    REFBlackWhite = 532    '&H214&
    Gamma = 769    '&H301&
    ICCProfileDescriptor = 770    '&H302&
    SRGBRenderingIntent = 771    '&H303&
    ImageTitle = 800    '&H320&
    ResolutionXUnit = 20481    '&H5001&
    ResolutionYUnit = 20482    '&H5002&
    ResolutionXLengthUnit = 20483    '&H5003&
    ResolutionYLengthUnit = 20484    '&H5004&
    PrintFlags = 20485    '&H5005&
    PrintFlagsVersion = 20486    '&H5006&
    PrintFlagsCrop = 20487    '&H5007&
    PrintFlagsBleedWidth = 20488    '&H5008&
    PrintFlagsBleedWidthScale = 20489    '&H5009&
    HalftoneLPI = 20490    '&H500A&
    HalftoneLPIUnit = 20491    '&H500B&
    HalftoneDegree = 20492    '&H500C&
    HalftoneShape = 20493    '&H500D&
    HalftoneMisc = 20494    '&H500E&
    HalftoneScreen = 20495    '&H500F&
    JPEGQuality = 20496    '&H5010&
    GridSize = 20497    '&H5011&
    ThumbnailFormat = 20498    '&H5012&
    ThumbnailWidth = 20499    '&H5013&
    ThumbnailHeight = 20500    '&H5014&
    ThumbnailColorDepth = 20501    '&H5015&
    ThumbnailPlanes = 20502    '&H5016&
    ThumbnailRawBytes = 20503    '&H5017&
    ThumbnailSize = 20504    '&H5018&
    ThumbnailCompressedSize = 20505    '&H5019&
    ColorTransferFunction = 20506    '&H501A&
    ThumbnailData = 20507    '&H501B&
    ThumbnailImageWidth = 20512    '&H5020&
    ThumbnailImageHeight = 20513    '&H5021&
    ThumbnailBitsPerSample = 20514    '&H5022&
    ThumbnailCompression = 20515    '&H5023&
    ThumbnailPhotometricInterp = 20516    '&H5024&
    ThumbnailImageDescription = 20517    '&H5025&
    ThumbnailEquipMake = 20518    '&H5026&
    ThumbnailEquipModel = 20519    '&H5027&
    ThumbnailStripOffsets = 20520    '&H5028&
    ThumbnailOrientation = 20521    '&H5029&
    ThumbnailSamplesPerPixel = 20522    '&H502A&
    ThumbnailRowsPerStrip = 20523    '&H502B&
    ThumbnailStripBytesCount = 20524    '&H502C&
    ThumbnailResolutionX = 20525    '&H502D&
    ThumbnailResolutionY = 20526    '&H502E&
    ThumbnailPlanarConfig = 20527    '&H502F&
    ThumbnailResolutionUnit = 20528    '&H5030&
    ThumbnailTransferFunction = 20529    '&H5031&
    ThumbnailSoftwareUsed = 20530    '&H5032&
    ThumbnailDateTime = 20531    '&H5033&
    ThumbnailArtist = 20532    '&H5034&
    ThumbnailWhitePoint = 20533    '&H5035&
    ThumbnailPrimaryChromaticities = 20534    '&H5036&
    ThumbnailYCbCrCoefficients = 20535    '&H5037&
    ThumbnailYCbCrSubsampling = 20536    '&H5038&
    ThumbnailYCbCrPositioning = 20537    '&H5039&
    ThumbnailRefBlackWhite = 20538    '&H503A&
    ThumbnailCopyRight = 20539    '&H503B&
    LuminanceTable = 20624    '&H5090&
    ChrominanceTable = 20625    '&H5091&
    FrameDelay = 20736    '&H5100&
    LoopCount = 20737    '&H5101&
    GlobalPalette = 20738    '&H5102&
    IndexBackground = 20739    '&H5103&
    IndexTransparent = 20740    '&H5104&
    PixelUnit = 20752    '&H5110&
    PixelPerUnitX = 20753    '&H5111&
    PixelPerUnitY = 20754    '&H5112&
    PaletteHistogram = 20755    '&H5113&
    Copyright = 33432    '&H8298&
    ExifExposureTime = 33434    '&H829A&
    ExifFNumber = 33437    '&H829D&
    ExifIFD = 34665    '&H8769&
    ICCProfile = 34675    '&H8773&
    ExifExposureProg = 34850    '&H8822&
    ExifSpectralSense = 34852    '&H8824&
    GpsIFD = 34853    '&H8825&
    ExifISOSpeed = 34855    '&H8827&
    ExifOECF = 34856    '&H8828&
    ExifVer = 36864    '&H9000&
    ExifDTOrig = 36867    '&H9003&
    ExifDTDigitized = 36868    '&H9004&
    ExifCompConfig = 37121    '&H9101&
    ExifCompBPP = 37122    '&H9102&
    ExifShutterSpeed = 37377    '&H9201&
    ExifAperture = 37378    '&H9202&
    ExifBrightness = 37379    '&H9203&
    ExifExposureBias = 37380    '&H9204&
    ExifMaxAperture = 37381    '&H9205&
    ExifSubjectDist = 37382    '&H9206&
    ExifMeteringMode = 37383    '&H9207&
    ExifLightSource = 37384    '&H9208&
    ExifFlash = 37385    '&H9209&
    ExifFocalLength = 37386    '&H920A&
    ExifMakerNote = 37500    '&H927C&
    ExifUserComment = 37510    '&H9286&
    ExifDTSubsec = 37520    '&H9290&
    ExifDTOrigSS = 37521    '&H9291&
    ExifDTDigSS = 37522    '&H9292&
    ExifFPXVer = 40960    '&HA000&
    ExifColorSpace = 40961    '&HA001&
    ExifPixXDim = 40962    '&HA002&
    ExifPixYDim = 40963    '&HA003&
    ExifRelatedWav = 40964    '&HA004&
    ExifInterop = 40965    '&HA005&
    ExifFlashEnergy = 41483    '&HA20B&
    ExifSpatialFR = 41484    '&HA20C&
    ExifFocalXRes = 41486    '&HA20E&
    ExifFocalYRes = 41487    '&HA20F&
    ExifFocalResUnit = 41488    '&HA210&
    ExifSubjectLoc = 41492    '&HA214&
    ExifExposureIndex = 41493    '&HA215&
    ExifSensingMethod = 41495    '&HA217&
    ExifFileSource = 41728    '&HA300&
    ExifSceneType = 41729    '&HA301&
    ExifCfaPattern = 41730    '&HA302&
End Enum

Dim lGDIpToken                 As Long
Dim bGDIpInitialized           As Boolean
Dim lBitmap                   As Long
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------


Private Function GDIErrorToString(ByVal lGDIError As Status) As String
    Select Case lGDIError
        Case GenericError
            GDIErrorToString = "Generic Error."
        Case InvalidParameter
            GDIErrorToString = "Invalid Parameter."
        Case OutOfMemory
            GDIErrorToString = "Out Of Memory."
        Case ObjectBusy
            GDIErrorToString = "Object Busy."
        Case InsufficientBuffer
            GDIErrorToString = "Insufficient Buffer."
        Case NotImplemented
            GDIErrorToString = "Not Implemented."
        Case Win32Error
            GDIErrorToString = "Win32 Error."
        Case WrongState
            GDIErrorToString = "Wrong State."
        Case Aborted
            GDIErrorToString = "Aborted."
        Case FileNotFound
            GDIErrorToString = "File Not Found."
        Case ValueOverflow
            GDIErrorToString = "Value Overflow."
        Case AccessDenied
            GDIErrorToString = "Access Denied."
        Case UnknownImageFormat
            GDIErrorToString = "Unknown Image Format."
        Case FontFamilyNotFound
            GDIErrorToString = "FontFamily Not Found."
        Case FontStyleNotFound
            GDIErrorToString = "FontStyle Not Found."
        Case NotTrueTypeFont
            GDIErrorToString = "Not TrueType Font."
        Case UnsupportedGdiplusVersion
            GDIErrorToString = "Unsupported Gdiplus Version."
        Case GdiplusNotInitialized
            GDIErrorToString = "Gdiplus Not Initialized."
        Case PropertyNotFound
            GDIErrorToString = "Property Not Found."
        Case PropertyNotSupported
            GDIErrorToString = "Property Not Supported."
        Case Else
            GDIErrorToString = "Unknown Error."
    End Select
End Function

Private Function GetPropertyValue(ByVal PropertyItemLength As Long, ByVal PropertyItemValue As Long, ByVal lPropertyType As PropertyTagType) As String
    Dim byteRetValue          As Byte
    Dim lRetValue             As Long
    Dim iRetValue             As Integer
    Dim bytProperty()         As Byte
    Dim sProperty             As String
    Dim DataLength            As Long
    Dim Numerator             As Long
    Dim Denominator           As Long
    Dim sTemp                 As String
    Dim i                     As Long

    If PropertyItemLength = 0 Then
        GetPropertyValue = ""
        Exit Function
    End If

    Select Case lPropertyType
        Case PropertyTagType.TypeByte
            DataLength = 1
            ReDim bytProperty(PropertyItemLength - 1)
            Call CopyMemory(bytProperty(0), ByVal PropertyItemValue, PropertyItemLength)
            Call CopyMemory(byteRetValue, bytProperty(0), DataLength)
            Erase bytProperty
            GetPropertyValue = byteRetValue
        Case PropertyTagType.TypeASCII
            sProperty = Space$(lstrlenW(ByVal PropertyItemValue))
            Call lstrcpyW(ByVal StrPtr(sProperty), ByVal PropertyItemValue)
            GetPropertyValue = Trim$(Left$(StrConv(sProperty, vbUnicode), PropertyItemLength - 1))
        Case PropertyTagType.TypeShort
            DataLength = 2
            ReDim bytProperty(PropertyItemLength - 1)
            Call CopyMemory(bytProperty(0), ByVal PropertyItemValue, PropertyItemLength)
            Call CopyMemory(iRetValue, bytProperty(0), DataLength)
            Erase bytProperty
            GetPropertyValue = iRetValue
        Case PropertyTagType.TypeLong, PropertyTagType.TypeSLong
            DataLength = 4
            ReDim bytProperty(PropertyItemLength - 1)
            Call CopyMemory(bytProperty(0), ByVal PropertyItemValue, PropertyItemLength)
            Call CopyMemory(lRetValue, bytProperty(0), DataLength)
            Erase bytProperty
            GetPropertyValue = lRetValue
        Case PropertyTagType.TypeRational, PropertyTagType.TypeSRational
            DataLength = 8
            ReDim bytProperty(PropertyItemLength - 1)
            Call CopyMemory(bytProperty(0), ByVal PropertyItemValue, PropertyItemLength)
            Call CopyMemory(Numerator, bytProperty(0), DataLength / 2)
            Call CopyMemory(Denominator, bytProperty(0 + (DataLength / 2)), DataLength / 2)
            Erase bytProperty
            GetPropertyValue = CStr(Numerator) & "/" & CStr(Denominator)
            GetPropertyValue = Eval(GetPropertyValue)
        Case PropertyTagType.TypeUndefined
            ReDim bytProperty(PropertyItemLength - 1)
            Call CopyMemory(bytProperty(0), ByVal PropertyItemValue, PropertyItemLength)
            For i = 1 To PropertyItemLength - 1
                If i > 1 Then sProperty = sProperty & Chr$(32)
                sTemp = Hex$(bytProperty(i - 1))
                If Len(sTemp) = 1 Then sTemp = "0" & sTemp
                sProperty = sProperty & sTemp
            Next i
            Erase bytProperty
            GetPropertyValue = sProperty
    End Select
End Function

'---------------------------------------------------------------------------------------
' Procedure : GetImageProperty
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Return the value of the specified property for a given image file
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
'             (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sFile     : Fully qualified path and filename of the image file to get info about
' lProp     : Property to retrieve the value of
'
' Usage:
' ~~~~~~
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ImageHeight) => 3648
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ImageWidth) => 2736
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", EquipModel) => ELE-L04
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", DateTime) => 2021:05:08 17:01:56
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ExifISOSpeed) => 250
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ExifShutterSpeed) => 29.8973
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ExifAperture) => 1.69
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", XResolution) => 72
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", YResolution) => 72
'? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", SoftwareUsed) => ELE-L04 11.0.0.146(C792E5R1P3)
'
' Revision History:
' Rev       Date(yyyy-mm-dd)        Description
' **************************************************************************************
' 1         2022-01-08              Initial Blog Release
'---------------------------------------------------------------------------------------
Public Function GetImageProperty(sFile As String, ByVal lProp As PropertyTagId)
    On Error GoTo Error_Handler
    Dim GDIpStartupInput      As GDIPlusStartupInput
    Dim GDIStatus             As Status
    Dim PI                    As PropertyItem
    Dim propItemData()        As Byte
    Dim sOutput               As String
    Dim lPropItemSize         As Long
    Dim vLegibleValue         As Variant
    Const bDebug              As Boolean = False    'Change to True for Development purposes/debugging

    'Start GDI
    '-------------------------------------------------------------------------------------
    If bGDIpInitialized = False Then
        GDIpStartupInput.GdiPlusVersion = 1
        GDIStatus = GdiplusStartup(lGDIpToken, GDIpStartupInput, ByVal 0)
        If GDIStatus <> Status.OK Then
            MsgBox "Unable to start the GDI+ API" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
            GoTo Error_Handler_Exit
        Else
            bGDIpInitialized = True
        End If
    End If

    'Load our Image to work with
    '-------------------------------------------------------------------------------------
    'In case we already have something in memory let's dispose of it properly
    If lBitmap <> 0 Then
        GDIStatus = GdipDisposeImage(lBitmap)
        If GDIStatus <> Status.OK Then
            MsgBox "Unable to dispose of the current image in memory" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
            GoTo Error_Handler_Exit
        End If
    End If
    'Now let's proceed with loading the actual image we want to work with
    GDIStatus = GdipCreateBitmapFromFile(StrPtr(sFile), lBitmap)
    If GDIStatus <> Status.OK Then
        MsgBox "Unable to load the specified image" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
        GoTo Error_Handler_Exit
    End If

    'Work with the image
    '-------------------------------------------------------------------------------------
    If lBitmap Then
        GDIStatus = GdipGetPropertyItemSize(lBitmap, lProp, lPropItemSize)
        If GDIStatus <> Status.OK Then
            '            If GDIStatus = Status.PropertyNotFound Then
            '                GetImageProperty = "Not Defined"
            '            Else
            MsgBox "Unable to get the specified property size" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
            '            End If
            GoTo Error_Handler_Exit
        End If

        If (lPropItemSize > 0) Then
            ReDim propItemData(lPropItemSize - 1)
            GDIStatus = GdipGetPropertyItem(lBitmap, lProp, lPropItemSize, propItemData(0))
            If GDIStatus <> Status.OK Then
                MsgBox "Unable to get the specified property" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
                GoTo Error_Handler_Exit
            End If
            Call CopyMemory(PI, propItemData(0), LenB(PI))
            Erase propItemData

            GetImageProperty = GetPropertyValue(PI.Length, PI.Value, PI.Type)
        End If
    End If

Error_Handler_Exit:
    On Error Resume Next
    'Shutdown GDI
    '-------------------------------------------------------------------------------------
    If bGDIpInitialized = True Then
        If lBitmap <> 0 Then
            GDIStatus = GdipDisposeImage(lBitmap)
            If GDIStatus <> Status.OK Then
                MsgBox "Unable to dispose of the processed image" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
                Exit Function
            Else
                lBitmap = 0
            End If
        End If
        GDIStatus = GdiplusShutdown(lGDIpToken)
        If GDIStatus <> Status.OK Then
            MsgBox "Unable to shutdown the GDI+ API" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
            Exit Function
        Else
            bGDIpInitialized = False
        End If
    End If
    Exit Function

Error_Handler:
    MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: GetImageProperty" & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occured!"
    Resume Error_Handler_Exit
End Function

and as indicated in the comments, you need only call the GetImageProperty() function specifying the file to query and the property you are after to get the value back!

? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ImageHeight)
    Returns => 3648
? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", EquipModel)
    Returns => ELE-L04
? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", DateTime)
    Returns => 2021:05:08 17:01:56
? GetImageProperty("C:\Temp\IMG_20210508_170154.jpg", ExifISOSpeed)
    Returns => 250

So complex code, yes, but simple to use, a one liner!, at the end of the day.

A Final Remark

I’m sure some of you are looking at this code and saying to yourselves that this is very complex when compared to the VBA or PowerShell approaches, so why use it at all?  This is a very valid statement!  BUT, when I release my future articles on GDI you will start to see why it is worth the extra coding headaches as it opens other doors that no other approach does.  So be patient and you will be rewarded.

A Few Resources on the Subject