Search code examples
vb.netgraphicsmetadatajpeggdi+

Extra null Char between characters in metadata encoding a JPG image


This thread is a continuation of Extra characters/bytes in metadata after saving a PNG image

But I now consider JPEGfiles, not PNG files.

I am trying to write metadata depending on the value of a parameter called 'sc_status'. I adapted Jimi's proposal that works very fine with PNG files:

Imports System.Drawing.Imaging
Imports System.Text

'0x9286 = User Comments
Dim imageDescriptionPropItem = &H9286
' Property Type 2: null-terminated string
Dim PropertyTagTypeASCII As short = 2

Dim encoderParams As New EncoderParameters(1)
Dim ImgCodec = ImageCodecInfo.GetImageEncoders().
               FirstOrDefault(Function(enc) enc.FormatID = ImageFormat.Jpeg.Guid)
If ImgCodec Is Nothing Then
    Throw New FormatException("Invalid format")
End If
encoderParams.Param(0) = New EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 95L)

Dim imagePath = [Image Source Path]
Dim imageDestinationPath = [Image Destination Path]
Dim imageSourcePath = Image.FromStream(New MemoryStream(File.ReadAllBytes(imageSourcePath)))

Dim propItem As PropertyItem = DirectCast(FormatterServices.GetUninitializedObject(
    GetType(PropertyItem)), PropertyItem)

propItem.Id = imageDescriptionPropItem 
propItem.Type = PropertyTagTypeASCII

Dim description = String.Empty
Select Case sc_status
    Case 3
        description = "HQ" & ChrW(0)
    Case 5
        description = "LQ" & ChrW(0)
    Case Else
        description = "UQ" & ChrW(0)
End Select

' Length of the string including the terminator: mandatory
propItem.Value = Encoding.UTF8.GetBytes(description)
propItem.Len = propItem.Value.Length

imageSource.SetPropertyItem(propItem)
imageSource.Save(imageDestinationPath, ImgCodec, encoderParams)

The metadata check is performed with:

Dim imageEncoded = Image.FromStream(New MemoryStream(File.ReadAllBytes(imageDestinationPath)))
Dim propItemSaved = imageEncoded.GetPropertyItem(imageDescriptionPropItem)
Dim descr = Encoding.UTF8.GetString(propItemNew.Value).TrimEnd(ChrW(0))

I expect to have in byte sequence [72, 81, 0] or [76, 81, 0] or [85, 81, 0]. But I get [72, 0, 81, 0, 0] or [76, 0, 81, 0, 0] or [85, 0, 81, 0, 0]. So There is an extra 0 byte between each character, which provides the following strings: "L" & vbNullChar & "Q" & vbNullChar and so on.

Why are those zeroes inserted and how to get rid of this? Why does it work fine with PNG images (using Image Description prop item &H10E) and not JPEG images (using User Comment prop item &H9286)?

Many thanks in advance for your support.


Solution

  • You're setting the wrong PropertyItem.Type.
    The PropertyTagExifUserComment (0x9286) defines its Type as PropertyTagTypeUndefined = 7, while you're setting it to PropertyTagTypeASCII = 2, as the Type related to the PropertyTagImageDescription PropertyItem. These Types don't store the data in the same way:

    The character code used in the PropertyTagExifUserComment tag is identified based on an ID code in a fixed 8-byte area at the start of the tag data area. The unused portion of the area is padded with null characters (0). ID codes are assigned by means of registration. Because the type is not ASCII, it is not necessary to use a NULL terminator.

    There's no need to set the null-terminator, but if you do, that's not a problem either.

    Unfortunately, the Docs are not straightforward in relation to the PropertyItem.Type values. These are defined in gdiplusimaging.h. Use the PropertyItem.Type descriptions as reference.

    Let's define an enumerator for these PropertyItems Types, to simplify its use:

    Public Enum PropertyItemType As Short
        PropertyTagTypeByte = 1
        PropertyTagTypeASCII = 2
        PropertyTagTypeShort = 3
        PropertyTagTypeLong = 4
        PropertyTagTypeRational = 5
        PropertyTagTypeUndefined = 7
        PropertyTagTypeSLONG = 9
        PropertyTagTypeSRational = 10
    End Enum
    

    Thus the code changes in:

    Dim propTagExifUserComment = &H9286
    
    propItem.Id = propTagExifUserComment
    propItem.Type = PropertyItemType.PropertyTagTypeUndefined
    ' [...]
    Dim propItemSaved = imageSaved.GetPropertyItem(propTagExifUserComment)
    

    Expect this, the code remains the same.