I am trying to create metadata to be stored in a PNG Image file, depending on the value of a parameter called sc_status
.
The code is as follows:
Dim qualityParam As Object
Dim encoderParams As Object = New Imaging.EncoderParameters(1)
Dim ImgCodec As Imaging.ImageCodecInfo
ImgCodec = GetEncoderInfo("image/png")
qualityParam = New Imaging.EncoderParameter(Imaging.Encoder.ColorDepth, 32L)
encoderParams.Param(0) = qualityParam
'---
' img_src Image is created here
' file_name String is created here
'---
' Creating the PropertyItem
Dim propit As Imaging.PropertyItem = CType(System.Runtime.Serialization.FormatterServices.GetUninitializedObject(GetType(Imaging.PropertyItem)), Imaging.PropertyItem)
propit.Id = 270 '0x010E = Image description
propit.Type = 2
If sc_status = 3 Then
propit.Value = System.Text.Encoding.UTF8.GetBytes("HQ")
ElseIf sc_status = 5 Then
propit.Value = System.Text.Encoding.UTF8.GetBytes("LQ")
Else
propit.Value = System.Text.Encoding.UTF8.GetBytes("UQ")
End If
' Storing the PropertyItem
img_src.SetPropertyItem(propit)
' Saving png
img_src.Save(file_name, ImgCodec, encoderParams)
When I have a look at what is stored in the PNG chunks, I expect to have in byte sequence [72, 81, 0]
or [76, 81, 0]
or [85, 81, 0]
, corresponding to the string "LQ"
,"HQ"
,"UQ"
plus the vbNullChar
which is automatically added at the end of the PNG chunk.
But for a reason I ignore, I sometimes have a longer byte sequence, e.g. [72, 81, 28, 8, 1, 0]
which gives - after using System.Text.Encoding.UTF8.GetString()
- the string:
HQ & ChrW(28) & vbBack & ChrW(1) & vbNullChar
or sometimes [72, 81, 22, 8, 1, 0]
, or sometimes [72, 81, 19, 8, 1, 0]
or sometimes [72, 81, 23, 8, 1, 0]
.
I don't understand why sometimes extra bytes are added in the metadata during the img_src.Save()
procedure.
What am I doing wrong? Any help is very welcome!
PropertyTagImageDescription is defined as a null-terminated ASCII string (PropertyTagTypeASCII
).
As described in the Documentation, PropertyItem.Type PropertyTagTypeASCII:
Specifies that
Value
is a null-terminated ASCII string. If you set the type data member to ASCII type, you should set theLen
property to the length of the string including the null terminator. For example, the string"Hello"
would have a length of 6.
A few details:
PropertyItem.Value
property stores data as a byte array. Even though the Docs refer to an ASCII string, storing the bytes of a UTF-8 encoded strings, retrieved calling Encoding.UTF8.GetBytes()
, is no forbidden though. Just terminate the string and set the correct number of bytes stored: as mentioned Value
store just bytes.PropertyItem.Len
property must be set to the length of the string, so to the length of the byte, thus the length of the Value
property.► There's no reason to define the Types as Object
, as in:
Dim qualityParam As Object
Dim encoderParams As Object = New Imaging.EncoderParameters(1)
Declare these Types for what they are.
Sample implementation, using UTF-8 encoded strings:
Imports System.Drawing.Imaging
Imports System.Text
'0x010E = Image description
Dim imageDescriptionPropItem = &H10E
' 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.Png.Guid)
If ImgCodec Is Nothing Then
Throw New FormatException("Invalid format")
End If
encoderParams.Param(0) = New EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 32L)
Dim imagePath = [Image Source Path]
Dim imageDestinationPath = [Image Destination Path]
Dim imageSource = Image.FromStream(
New MemoryStream(File.ReadAllBytes(imageSourcePath)), False, False)
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"
Case 5
description = "LQ"
Case 100
' Test string, in Russian :)
description = "Тестовая строка"
Case Else
description = "UQ"
End Select
' Length of the string including the terminator: mandatory
propItem.Value = Encoding.UTF8.GetBytes(description & ChrW(0))
propItem.Len = propItem.Value.Length
imageSource.SetPropertyItem(propItem)
imageSource.Save(imageDestinationPath, ImgCodec, encoderParams)
' Load it back, to check what's what
Dim imageEncoded = Image.FromStream(New MemoryStream(File.ReadAllBytes(imageDestinationPath)))
Dim propItemSaved = imageEncoded.GetPropertyItem(imageDescriptionPropItem)
Dim descr = Encoding.UTF8.GetString(propItemSaved.Value).TrimEnd(ChrW(0))