保存 PNG 图像后元数据中的额外 characters/bytes

Extra characters/bytes in metadata after saving a PNG image

我正在尝试创建要存储在 PNG 图像文件中的元数据,具体取决于名为 sc_status 的参数值。

代码如下:

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)

当我查看 PNG 块中存储的内容时,我希望字节序列为 [72, 81, 0][76, 81, 0][85, 81, 0],对应于字符串 "LQ""HQ""UQ" 加上自动添加到 PNG 块末尾的 vbNullChar

但出于我忽略的原因,我有时会有更长的字节序列,例如[72, 81, 28, 8, 1, 0] 这给出了 - 在使用 System.Text.Encoding.UTF8.GetString() 之后 - 字符串:

HQ & ChrW(28) & vbBack & ChrW(1) & vbNullChar

或有时[72, 81, 22, 8, 1, 0],或有时[72, 81, 19, 8, 1, 0],或有时[72, 81, 23, 8, 1, 0]

我不明白为什么在 img_src.Save() 过程中有时会在元数据中添加额外的字节。
我究竟做错了什么?非常欢迎任何帮助!

PropertyTagImageDescription 被定义为空终止的 ASCII 字符串 (PropertyTagTypeASCII)。
如文档中所述,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 the Len property to the length of the string including the null terminator. For example, the string "Hello" would have a length of 6.

一些细节:

  • PropertyItem.Value属性将数据存储为字节数组。即使文档引用 ASCII 字符串,存储 UTF-8 编码字符串的字节,检索调用 Encoding.UTF8.GetBytes(),也不是 forbidden 虽然。只需终止字符串并设置正确的存储字节数:如前所述 Value 仅存储字节。
  • PropertyItem.Len属性必须设置为字符串的长度,也就是字节的长度,也就是Value 属性.

► 没有理由将类型定义为 Object,如:

Dim qualityParam As Object
Dim encoderParams As Object = New Imaging.EncoderParameters(1)

声明这些类型。

示例实现,使用 UTF-8 编码字符串:

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))