Creating/Setting an Image Property Using The GDI+ API

This is my 5th article exploring how we can use the GDI+ API(s) within VBA to work with image files and their properties.  Below are my previous articles:

Today, we will explore how we can create, or set, a property!

The Code

The concept here is pretty straightforward (the code not so much!) we simply need to use the GdipSetPropertyItem function to, you guessed it, set the property with a value.  Then issue here is that we have to pass the type of property, convert the value into the proper format and finally build a PropertyItem that we pass to the function.  Thus, we need to create a few helper functions.

Enough talk, here’s the code.

Option Explicit

'API Declarations, ENUMS, TYPES, Global Variables, ...
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------
Private Const GdiPlusVersion  As Long = 1

Private Const ImageCodecBMP = "{557CF400-1A04-11D3-9A73-0000F81EF32E}"
Private Const ImageCodecGIF = "{557CF402-1A04-11D3-9A73-0000F81EF32E}"
Private Const ImageCodecJPG = "{557CF401-1A04-11D3-9A73-0000F81EF32E}"
Private Const ImageCodecPNG = "{557CF406-1A04-11D3-9A73-0000F81EF32E}"
Private Const ImageCodecTIF = "{557CF405-1A04-11D3-9A73-0000F81EF32E}"
Private Const EncoderQuality = "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}"
Private Const EncoderCompression = "{E09D739D-CCD4-44EE-8EBA-3FBF8BE4FC58}"
Private Const TiffCompressionNone = 6
Private Const EncoderParameterValueTypeLong = 4

Private Type GUID
   Data1 As Long
   Data2 As Integer
   Data3 As Integer
   Data4(0 To 7) As Byte
End Type

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

Private Type EncoderParameter
    GUID            As GUID
    NumberOfValues  As Long
    type            As Long
    Value           As Long
End Type

Private Type EncoderParameters
    Count           As Long
    Parameter(15)   As EncoderParameter
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

'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

Public 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

'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 GdipGetAllPropertyItems Lib "gdiplus" (ByVal Image As Long, ByVal totalBufferSize As Long, ByVal numProperties As Long, ByRef allItems As PropertyItem) As Status
Private Declare Function GdipGetPropertySize Lib "gdiplus" (ByVal Image As Long, ByRef totalBufferSize As Long, ByRef numProperties As Long) As Status
Private Declare Function GdipSaveImageToFile Lib "gdiplus" (ByVal Image As Long, ByVal FileName As Long, clsidEncoder As GUID, encoderParams As Any) As Status
Private Declare Function GdipSetPropertyItem Lib "gdiplus" (ByVal Image As Long, ByRef item As Long) As Status

'Helper API Declarations
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function CLSIDFromString Lib "ole32" (ByVal str As Long, id As GUID) As Long

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

Function GetPropertyType(lPropertyTagId As PropertyTagId) As PropertyTagType
'https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-constant-property-item-descriptions
    Select Case lPropertyTagId
        Case PropertyTagId.GpsVer: GetPropertyType = TypeByte
        Case PropertyTagId.GpsLatitudeRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsLatitude: GetPropertyType = TypeRational
        Case PropertyTagId.GpsLongitudeRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsLongitude: GetPropertyType = TypeRational
        Case PropertyTagId.GpsAltitudeRef: GetPropertyType = TypeByte
        Case PropertyTagId.GpsAltitude: GetPropertyType = TypeRational
        Case PropertyTagId.GpsGpsTime: GetPropertyType = TypeRational
        Case PropertyTagId.GpsGpsSatellites: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsGpsStatus: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsGpsMeasureMode: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsGpsDop: GetPropertyType = TypeRational
        Case PropertyTagId.GpsSpeedRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsSpeed: GetPropertyType = TypeRational
        Case PropertyTagId.GpsTrackRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsTrack: GetPropertyType = TypeRational
        Case PropertyTagId.GpsImgDirRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsImgDir: GetPropertyType = TypeRational
        Case PropertyTagId.GpsMapDatum: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsDestLatRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsDestLat: GetPropertyType = TypeRational
        Case PropertyTagId.GpsDestLongRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsDestLong: GetPropertyType = TypeRational
        Case PropertyTagId.GpsDestBearRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsDestBear: GetPropertyType = TypeRational
        Case PropertyTagId.GpsDestDistRef: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsDestDist: GetPropertyType = TypeRational
        Case PropertyTagId.NewSubfileType: GetPropertyType = TypeLong
        Case PropertyTagId.SubfileType: GetPropertyType = TypeShort
        Case PropertyTagId.ImageWidth: GetPropertyType = TypeLong 'Short or Long?
        Case PropertyTagId.ImageHeight: GetPropertyType = TypeLong
        Case PropertyTagId.BitsPerSample: GetPropertyType = TypeShort
        Case PropertyTagId.Compression: GetPropertyType = TypeShort
        Case PropertyTagId.PhotometricInterp: GetPropertyType = TypeShort
        Case PropertyTagId.ThreshHolding: GetPropertyType = TypeShort
        Case PropertyTagId.CellWidth: GetPropertyType = TypeShort
        Case PropertyTagId.CellHeight: GetPropertyType = TypeShort
        Case PropertyTagId.FillOrder: GetPropertyType = TypeShort
        Case PropertyTagId.DocumentName: GetPropertyType = TypeASCII
        Case PropertyTagId.ImageDescription: GetPropertyType = TypeASCII
        Case PropertyTagId.EquipMake: GetPropertyType = TypeASCII
        Case PropertyTagId.EquipModel: GetPropertyType = TypeASCII
        Case PropertyTagId.StripOffsets: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.Orientation: GetPropertyType = TypeShort
        Case PropertyTagId.SamplesPerPixel: GetPropertyType = TypeShort
        Case PropertyTagId.RowsPerStrip: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.StripBytesCount: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.MinSampleValue: GetPropertyType = TypeShort
        Case PropertyTagId.MaxSampleValue: GetPropertyType = TypeShort
        Case PropertyTagId.XResolution: GetPropertyType = TypeRational
        Case PropertyTagId.YResolution: GetPropertyType = TypeRational
        Case PropertyTagId.PlanarConfig: GetPropertyType = TypeShort
        Case PropertyTagId.PageName: GetPropertyType = TypeASCII
        Case PropertyTagId.XPosition: GetPropertyType = TypeRational
        Case PropertyTagId.YPosition: GetPropertyType = TypeRational
        Case PropertyTagId.FreeOffset: GetPropertyType = TypeLong
        Case PropertyTagId.FreeByteCounts: GetPropertyType = TypeLong
        Case PropertyTagId.GrayResponseUnit: GetPropertyType = TypeShort
        Case PropertyTagId.GrayResponseCurve: GetPropertyType = TypeShort
        Case PropertyTagId.T4Option: GetPropertyType = TypeLong
        Case PropertyTagId.T6Option: GetPropertyType = TypeLong
        Case PropertyTagId.ResolutionUnit: GetPropertyType = TypeShort
        Case PropertyTagId.PageNumber: GetPropertyType = TypeShort
        Case PropertyTagId.TransferFunction: GetPropertyType = TypeShort
        Case PropertyTagId.SoftwareUsed: GetPropertyType = TypeASCII
        Case PropertyTagId.DateTime: GetPropertyType = TypeASCII
        Case PropertyTagId.Artist: GetPropertyType = TypeASCII
        Case PropertyTagId.HostComputer: GetPropertyType = TypeASCII
        Case PropertyTagId.Predictor: GetPropertyType = TypeShort
        Case PropertyTagId.WhitePoint: GetPropertyType = TypeRational
        Case PropertyTagId.PrimaryChromaticities: GetPropertyType = TypeRational
        Case PropertyTagId.ColorMap: GetPropertyType = TypeShort
        Case PropertyTagId.HalftoneHints: GetPropertyType = TypeShort
        Case PropertyTagId.TileWidth: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.TileLength: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.TileOffset: GetPropertyType = TypeLong
        Case PropertyTagId.TileByteCounts: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.InkSet: GetPropertyType = TypeShort
        Case PropertyTagId.InkNames: GetPropertyType = TypeASCII
        Case PropertyTagId.NumberOfInks: GetPropertyType = TypeShort
        Case PropertyTagId.DotRange: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.TargetPrinter: GetPropertyType = TypeASCII
        Case PropertyTagId.ExtraSamples: GetPropertyType = TypeShort
        Case PropertyTagId.SampleFormat: GetPropertyType = TypeShort
        Case PropertyTagId.TransferRange: GetPropertyType = TypeShort
        Case PropertyTagId.JPEGProc: GetPropertyType = TypeShort
        Case PropertyTagId.JPEGInterFormat: GetPropertyType = TypeLong
        Case PropertyTagId.JPEGInterLength: GetPropertyType = TypeLong
        Case PropertyTagId.JPEGRestartInterval: GetPropertyType = TypeShort
        Case PropertyTagId.JPEGLosslessPredictors: GetPropertyType = TypeShort
        Case PropertyTagId.JPEGPointTransforms: GetPropertyType = TypeShort
        Case PropertyTagId.JPEGQTables: GetPropertyType = TypeLong
        Case PropertyTagId.JPEGDCTables: GetPropertyType = TypeLong
        Case PropertyTagId.JPEGACTables: GetPropertyType = TypeLong
        Case PropertyTagId.YCbCrCoefficients: GetPropertyType = TypeRational
        Case PropertyTagId.YCbCrSubsampling: GetPropertyType = TypeShort
        Case PropertyTagId.YCbCrPositioning: GetPropertyType = TypeShort
        Case PropertyTagId.REFBlackWhite: GetPropertyType = TypeRational
        Case PropertyTagId.Gamma: GetPropertyType = TypeRational
        Case PropertyTagId.ICCProfileDescriptor: GetPropertyType = TypeASCII
        Case PropertyTagId.SRGBRenderingIntent: GetPropertyType = TypeByte
        Case PropertyTagId.ImageTitle: GetPropertyType = TypeASCII
        Case PropertyTagId.ResolutionXUnit: GetPropertyType = TypeShort
        Case PropertyTagId.ResolutionYUnit: GetPropertyType = TypeShort
        Case PropertyTagId.ResolutionXLengthUnit: GetPropertyType = TypeShort
        Case PropertyTagId.ResolutionYLengthUnit: GetPropertyType = TypeShort
        Case PropertyTagId.PrintFlags: GetPropertyType = TypeASCII
        Case PropertyTagId.PrintFlagsVersion: GetPropertyType = TypeShort
        Case PropertyTagId.PrintFlagsCrop: GetPropertyType = TypeByte
        Case PropertyTagId.PrintFlagsBleedWidth: GetPropertyType = TypeLong
        Case PropertyTagId.PrintFlagsBleedWidthScale: GetPropertyType = TypeShort
        Case PropertyTagId.HalftoneLPI: GetPropertyType = TypeRational
        Case PropertyTagId.HalftoneLPIUnit: GetPropertyType = TypeShort
        Case PropertyTagId.HalftoneDegree: GetPropertyType = TypeRational
        Case PropertyTagId.HalftoneShape: GetPropertyType = TypeShort
        Case PropertyTagId.HalftoneMisc: GetPropertyType = TypeLong
        Case PropertyTagId.HalftoneScreen: GetPropertyType = TypeByte
        Case PropertyTagId.JPEGQuality: GetPropertyType = TypeShort
        Case PropertyTagId.GridSize: GetPropertyType = TypeUndefined
        Case PropertyTagId.ThumbnailFormat: GetPropertyType = TypeLong
        Case PropertyTagId.ThumbnailWidth: GetPropertyType = TypeLong
        Case PropertyTagId.ThumbnailHeight: GetPropertyType = TypeLong
        Case PropertyTagId.ThumbnailColorDepth: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailPlanes: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailRawBytes: GetPropertyType = TypeLong
        Case PropertyTagId.ThumbnailSize: GetPropertyType = TypeLong
        Case PropertyTagId.ThumbnailCompressedSize: GetPropertyType = TypeLong
        Case PropertyTagId.ColorTransferFunction: GetPropertyType = TypeUndefined
        Case PropertyTagId.ThumbnailData: GetPropertyType = TypeByte
        Case PropertyTagId.ThumbnailImageWidth: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ThumbnailImageHeight: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ThumbnailBitsPerSample: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailCompression: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailPhotometricInterp: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailImageDescription: GetPropertyType = TypeASCII
        Case PropertyTagId.ThumbnailEquipMake: GetPropertyType = TypeASCII
        Case PropertyTagId.ThumbnailEquipModel: GetPropertyType = TypeASCII
        Case PropertyTagId.ThumbnailStripOffsets: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ThumbnailOrientation: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailSamplesPerPixel: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailRowsPerStrip: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ThumbnailStripBytesCount: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ThumbnailResolutionX: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailResolutionY: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailPlanarConfig: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailResolutionUnit: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailTransferFunction: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailSoftwareUsed: GetPropertyType = TypeASCII
        Case PropertyTagId.ThumbnailDateTime: GetPropertyType = TypeASCII
        Case PropertyTagId.ThumbnailArtist: GetPropertyType = TypeASCII
        Case PropertyTagId.ThumbnailWhitePoint: GetPropertyType = TypeRational
        Case PropertyTagId.ThumbnailPrimaryChromaticities: GetPropertyType = TypeRational
        Case PropertyTagId.ThumbnailYCbCrCoefficients: GetPropertyType = TypeRational
        Case PropertyTagId.ThumbnailYCbCrSubsampling: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailYCbCrPositioning: GetPropertyType = TypeShort
        Case PropertyTagId.ThumbnailRefBlackWhite: GetPropertyType = TypeRational
        Case PropertyTagId.ThumbnailCopyRight: GetPropertyType = TypeASCII
        Case PropertyTagId.LuminanceTable: GetPropertyType = TypeShort
        Case PropertyTagId.ChrominanceTable: GetPropertyType = TypeShort
        Case PropertyTagId.FrameDelay: GetPropertyType = TypeLong
        Case PropertyTagId.LoopCount: GetPropertyType = TypeShort
        Case PropertyTagId.GlobalPalette: GetPropertyType = TypeByte
        Case PropertyTagId.IndexBackground: GetPropertyType = TypeByte
        Case PropertyTagId.IndexTransparent: GetPropertyType = TypeByte
        Case PropertyTagId.PixelUnit: GetPropertyType = TypeByte
        Case PropertyTagId.PixelPerUnitX: GetPropertyType = TypeLong
        Case PropertyTagId.PixelPerUnitY: GetPropertyType = TypeLong
        Case PropertyTagId.PaletteHistogram: GetPropertyType = TypeByte
        Case PropertyTagId.Copyright: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifExposureTime: GetPropertyType = TypeRational
        Case PropertyTagId.ExifFNumber: GetPropertyType = TypeRational
        Case PropertyTagId.ExifIFD: GetPropertyType = TypeLong
        Case PropertyTagId.ICCProfile: GetPropertyType = TypeByte
        Case PropertyTagId.ExifExposureProg: GetPropertyType = TypeShort
        Case PropertyTagId.ExifSpectralSense: GetPropertyType = TypeASCII
        Case PropertyTagId.GpsIFD: GetPropertyType = TypeLong
        Case PropertyTagId.ExifISOSpeed: GetPropertyType = TypeShort
        Case PropertyTagId.ExifOECF: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifVer: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifDTOrig: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifDTDigitized: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifCompConfig: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifCompBPP: GetPropertyType = TypeRational
        Case PropertyTagId.ExifShutterSpeed: GetPropertyType = TypeSRational
        Case PropertyTagId.ExifAperture: GetPropertyType = TypeRational
        Case PropertyTagId.ExifBrightness: GetPropertyType = TypeSRational
        Case PropertyTagId.ExifExposureBias: GetPropertyType = TypeSRational
        Case PropertyTagId.ExifMaxAperture: GetPropertyType = GetPropertyType
        Case PropertyTagId.ExifSubjectDist: GetPropertyType = TypeRational
        Case PropertyTagId.ExifMeteringMode: GetPropertyType = TypeShort
        Case PropertyTagId.ExifLightSource: GetPropertyType = TypeShort
        Case PropertyTagId.ExifFlash: GetPropertyType = TypeShort
        Case PropertyTagId.ExifFocalLength: GetPropertyType = TypeRational
        Case PropertyTagId.ExifMakerNote: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifUserComment: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifDTSubsec: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifDTOrigSS: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifDTDigSS: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifFPXVer: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifColorSpace: GetPropertyType = TypeShort
        Case PropertyTagId.ExifPixXDim: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ExifPixYDim: GetPropertyType = TypeLong 'Short or Long
        Case PropertyTagId.ExifRelatedWav: GetPropertyType = TypeASCII
        Case PropertyTagId.ExifInterop: GetPropertyType = TypeLong
        Case PropertyTagId.ExifFlashEnergy: GetPropertyType = TypeRational
        Case PropertyTagId.ExifSpatialFR: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifFocalXRes: GetPropertyType = TypeRational
        Case PropertyTagId.ExifFocalYRes: GetPropertyType = TypeRational
        Case PropertyTagId.ExifFocalResUnit: GetPropertyType = TypeShort
        Case PropertyTagId.ExifSubjectLoc: GetPropertyType = TypeShort
        Case PropertyTagId.ExifExposureIndex: GetPropertyType = TypeRational
        Case PropertyTagId.ExifSensingMethod: GetPropertyType = TypeShort
        Case PropertyTagId.ExifFileSource: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifSceneType: GetPropertyType = TypeUndefined
        Case PropertyTagId.ExifCfaPattern: GetPropertyType = TypeUndefined
        Case Else: GetPropertyType = TypeUndefined
    End Select
End Function

'---------------------------------------------------------------------------------------
' Procedure : SetImageProperty
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Set the image property with the specified value
' 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
' lPropertyTagId    : Property to the set the value of
' vPropertyValue    : Value to set for the property.  In the case of Rational / Nominator value
' vPropertyValue2   : Rational only / Denominator value
'
' Usage:
' ~~~~~~
' ? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", EquipModel , "Huawei")
'   Returns -> True/False
' ? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", ImageWidth , 179306496)
'   Returns -> True/False
' ? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", ExifISOSpeed , 250)
'   Returns -> True/False
' ? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", ImageDescription , "View from the house")
'   Returns -> True/False
' ? SetImageProperty("C:\Temp\IMG01 - Copy.jpg",ExifFNumber , 180, 100)
'   Returns -> True/False
'
' Revision History:
' Rev       Date(yyyy-mm-dd)        Description
' **************************************************************************************
' 1         2022-01-09              Initial Blog Release
'---------------------------------------------------------------------------------------
Public Function SetImageProperty(ByVal sFile As String, ByVal lPropertyTagId As PropertyTagId, ByVal vPropertyValue As Variant, Optional ByVal vPropertyValue2 As Variant) As Boolean
    On Error GoTo Error_Handler
    Dim GDIpStartupInput      As GDIPlusStartupInput
    Dim GDIStatus             As Status
    Dim PI                    As PropertyItem
    Dim byteValue             As Byte
    Dim sValue                As String
    Dim lValue                As Long
    Dim iValue                As Integer
    Dim lPropertyType         As PropertyTagType
    Dim tEncoder              As GUID
    Dim tParams               As EncoderParameters
    Dim sTmp                  As String
    Dim sExt                  As String

    '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
    '-------------------------------------------------------------------------------------
    'Get Property Type
    lPropertyType = GetPropertyType(lPropertyTagId)

    PI.id = lPropertyTagId
    PI.type = lPropertyType

    Select Case lPropertyType
        Case PropertyTagType.TypeByte    '1
            byteValue = vPropertyValue

            PI.Length = LenB(byteValue)
            PI.Value = VarPtr(byteValue)
        Case PropertyTagType.TypeASCII    '2
            sValue = vPropertyValue & vbNullChar

            PI.Length = Len(sValue)
            PI.Value = StrPtr(StrConv(sValue, vbFromUnicode))
        Case PropertyTagType.TypeShort    '3
            iValue = vPropertyValue

            PI.Length = LenB(iValue)
            PI.Value = VarPtr(iValue)
        Case PropertyTagType.TypeLong, PropertyTagType.TypeSLong    '4, 9
            Select Case lPropertyType
                Case PropertyTagType.TypeLong
                    lValue = Abs(vPropertyValue)
                Case PropertyTagType.TypeSLong
                    lValue = vPropertyValue
            End Select

            PI.Length = LenB(lValue)
            PI.Value = VarPtr(lValue)
        Case PropertyTagType.TypeRational, PropertyTagType.TypeSRational    '5, 10
            Dim dValue        As Double
            Dim bytProperty() As Byte
            Dim DataSize      As Long
            Dim lNumerator    As Long
            Dim lDenominator  As Long

            Select Case lPropertyType
                Case PropertyTagType.TypeRational
                    lNumerator = Abs(vPropertyValue)
                    lDenominator = Abs(vPropertyValue2)
                Case PropertyTagType.TypeSRational
                    lNumerator = vPropertyValue
                    lDenominator = vPropertyValue2
            End Select

            DataSize = LenB(dValue)
            ReDim bytProperty(DataSize - 1)
            Call CopyMemory(bytProperty(0), lNumerator, DataSize / 2)
            Call CopyMemory(bytProperty(0 + (DataSize / 2)), lDenominator, DataSize / 2)
            Call CopyMemory(dValue, bytProperty(0), DataSize)
            Erase bytProperty

            PI.Length = DataSize    '8
            PI.Value = VarPtr(dValue)
        Case Else    'PropertyTagType.TypeUndefined    '7
            Exit Function
    End Select

    GDIStatus = GdipSetPropertyItem(lBitmap, ByVal VarPtr(PI))
    If GDIStatus <> Status.OK Then
        MsgBox "Unable to set the specified property" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
        GoTo Error_Handler_Exit
    Else
        'Save the changes
        '   Cannot save over the file itself, so create a tmp file, release the current one then copy the image over
        sExt = Mid(sFile, InStrRev(sFile, ".") + 1)
        Select Case sExt
            Case "bmp", "dib"
                CLSIDFromString StrPtr(ImageCodecBMP), tEncoder
            Case "gif"
                CLSIDFromString StrPtr(ImageCodecGIF), tEncoder
            Case "jpg", "jpeg", "jpe", "jfif"
                CLSIDFromString StrPtr(ImageCodecJPG), tEncoder

                With tParams
                    .Count = 1
                    .Parameter(0).NumberOfValues = 1
                    .Parameter(0).type = EncoderParameterValueTypeLong
                    .Parameter(0).Value = VarPtr(100)    '100% Quality
                    CLSIDFromString StrPtr(EncoderQuality), .Parameter(0).GUID
                End With
            Case "png"
                CLSIDFromString StrPtr(ImageCodecPNG), tEncoder
            Case "tif", "tiff"
                CLSIDFromString StrPtr(ImageCodecTIF), tEncoder

                With tParams
                    .Count = 1
                    .Parameter(0).NumberOfValues = 1
                    .Parameter(0).type = EncoderParameterValueTypeLong
                    .Parameter(0).Value = VarPtr(TiffCompressionNone)
                    CLSIDFromString StrPtr(EncoderCompression), .Parameter(0).GUID
                End With
            Case Else
                Exit Function
        End Select

        sTmp = Environ("Temp") & "\TempSave." & sExt
        GDIStatus = GdipSaveImageToFile(lBitmap, StrPtr(sTmp), tEncoder, ByVal tParams)
        If GDIStatus <> Status.OK Then
            MsgBox "Unable to save the image" & vbCrLf & vbCrLf & GDIErrorToString(GDIStatus), vbCritical Or vbOKOnly, "Operation Aborted"
            Exit Function
        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
                'Overwrite the file with our Temp File and Cleanup
                If sTmp <> "" Then
                    Call FileCopy(sTmp, sFile)
                    If Len(Dir(sFile)) > 0 Then
                        SetImageProperty = True
                        Kill sTmp
                    End If
                End If
            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

Now, with a single line of code we can set a property! For example, as indicated in the function header, we can do things like:

? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", EquipModel , "Huawei")
? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", ExifISOSpeed , 250)
? SetImageProperty("C:\Temp\IMG01 - Copy.jpg", ImageDescription , "View from the house")
? SetImageProperty("C:\Temp\IMG01 - Copy.jpg",ExifFNumber , 180, 100)

and so on.

For the sake of full disclosure, I have not tested that it works with every tag. I have tested with a bunch that I am interested in being able to work with:

  • ImageWidth
  • ImageHeight
  • ImageDescription
  • EquipMake
  • EquipModel
  • DateTime
  • ImageTitle
  • ThumbnailCopyRight

Stay tuned, I have one more article on the subject of GDI+.

A Few Resources on the Subject