If you use GDI+ to write PNGs, one of the shortcomings is that many chunks/properties/tags are not written even if they exist in the PNG before saving it. Another issue many have is that GDI+ automatically adds a gAMA chunk whether you want it or not, whether you feel it is the correct value or not.
This class is meant as a stop-gap for adding chunks and removing chunks after the image is saved by GDI+ but before it is written to disk.
Caveats:
1. I did not add support for chunks that require data to be compressed. This is completely doable but requires significantly more code and availability of zLIB.dll (c_Decl or stdCall conventions) or homemade compression routines. Not many chunks require compression, other than the pixel data which GDI+ does, though not as well as we would like at times. Among standard chunks, only the zTXt & iCCP chunks requires compression & it is optional with the iTXt chunk.
:: iTXt is for unicode and the AddChunk method expects any passed strings to be used in the tEXt chunk (ASCII). So, if wanting to add iTXt to the PNG, simply organize it in a byte array and pass the array. Makes more sense anyway, a string isn't ideal for composing the iTXt chunk.
2. I could have cleaned up the AddChunk method, but it does what it needs to do.
3. There is no hardcoding of what order any added chunks should be placed within the PNG file. I did add comments for the most common chunks, but there are many around. Bottom line is that you must ensure you know where the chunks should fall within the PNG format. This is available via documentation, whether on the PNG site, or via custom chunk documentation for chunks you may be using, i.e., APNG formatting. Chunks are written within the PNG sections you chose during the AddChunk call, and in order of 1st come, 1st written.
The class is a text file uploaded here. Simply rename it as .cls once downloaded. Here's a really short example of usage...
- Assumption is GDI+ is loaded and running before you call any class methods.
Should you want a short routine to review the chunks that exist in any valid PNG file, you can use the following.
And a short example using the above code follows. Note. For simplicity, I used VB's File I/O functions in above code. You may want to use APIs for unicode support
Edited: Link to PNG format specifications
Replaced version. Failed to write in correct section if the PLTE chunk was missing. Simple fix. Patched & re-uploaded. I'll add the ability to save PNG to array also. When I do, I'll remember to set the return value for the functions.
This class is meant as a stop-gap for adding chunks and removing chunks after the image is saved by GDI+ but before it is written to disk.
Caveats:
1. I did not add support for chunks that require data to be compressed. This is completely doable but requires significantly more code and availability of zLIB.dll (c_Decl or stdCall conventions) or homemade compression routines. Not many chunks require compression, other than the pixel data which GDI+ does, though not as well as we would like at times. Among standard chunks, only the zTXt & iCCP chunks requires compression & it is optional with the iTXt chunk.
:: iTXt is for unicode and the AddChunk method expects any passed strings to be used in the tEXt chunk (ASCII). So, if wanting to add iTXt to the PNG, simply organize it in a byte array and pass the array. Makes more sense anyway, a string isn't ideal for composing the iTXt chunk.
2. I could have cleaned up the AddChunk method, but it does what it needs to do.
3. There is no hardcoding of what order any added chunks should be placed within the PNG file. I did add comments for the most common chunks, but there are many around. Bottom line is that you must ensure you know where the chunks should fall within the PNG format. This is available via documentation, whether on the PNG site, or via custom chunk documentation for chunks you may be using, i.e., APNG formatting. Chunks are written within the PNG sections you chose during the AddChunk call, and in order of 1st come, 1st written.
The class is a text file uploaded here. Simply rename it as .cls once downloaded. Here's a really short example of usage...
- Assumption is GDI+ is loaded and running before you call any class methods.
Code:
Private Sub Command1_Click()
Dim hImage As Long, c As IPngWriter
GdipLoadImageFromFile StrPtr("C:\Test Images\LaVolpe.png"), hImage
If hImage Then
Set c = New IPngWriter
' example of adding a tEXt chunk
c.AddChunk "tEXt", "Software" & vbNullChar & "Custom PNGWriter Class", BeforeIEND
' example of removing the gAMA and sRGB chunks
c.WritePngToFile hImage, "D:\Users\LaVolpe\Desktop\Test.PNG", "gAMA", "sRGB"
Set c = Nothing
GdipDisposeImage hImage
Else
MsgBox "Failed to load that image"
End If
End SubCode:
Private Sub pvReadPngChunks(FileName As String)
Dim fnr As Integer, lName As Long, lSize As Long
Dim sName As String * 4&
Dim lPtr As Long, lMax As Long
Dim lPrevName As Long, bFailed As Boolean
On Error Resume Next
fnr = FreeFile()
Open FileName For Binary Access Read As #fnr
If Err Then
MsgBox Err.Description, vbExclamation + vbOKOnly, "Error"
Exit Sub
End If
On Error GoTo 0
lMax = LOF(fnr)
If lMax < 46& Then
bFailed = True: GoTo ExitRoutine
Else
Get #fnr, 1, lName
If lName <> 1196314761 Then bFailed = True: GoTo ExitRoutine
Get #fnr, , lName
If lName <> 169478669 Then bFailed = True: GoTo ExitRoutine
Debug.Print "Processing "; FileName;
lPtr = 9
Do Until lPtr + 8& > lMax
Get #fnr, lPtr, lSize: lSize = pvReverseLong(lSize)
Get #fnr, , lName
Mid$(sName, 4, 1) = Chr$(((lName And &HFF000000) \ &H1000000) And &HFF)
Mid$(sName, 3, 1) = Chr$((lName And &HFF0000) \ &H10000)
Mid$(sName, 2, 1) = Chr$((lName And &HFF00&) \ &H100)
Mid$(sName, 1, 1) = Chr$(lName And &HFF)
If lName = lPrevName Then
Debug.Print ","; sName; "("; CStr(lSize); ")";
Else
lPrevName = lName
Debug.Print vbCrLf; sName; "("; CStr(lSize); ")";
End If
lPtr = lPtr + 12& + lSize
Loop
End If
Debug.Print vbCrLf; "Done..."
ExitRoutine:
Close #fnr
If bFailed Then MsgBox "Failed to process that file. Sure it was a PNG?", vbQuestion + vbOKOnly
End Sub
Private Function pvReverseLong(ByVal inLong As Long) As Long
' fast function to reverse a long value from big endian to little endian
' PNG files contain reversed longs, as do ID3 v3,4 tags
pvReverseLong = _
(((inLong And &HFF000000) \ &H1000000) And &HFF&) Or _
((inLong And &HFF0000) \ &H100&) Or _
((inLong And &HFF00&) * &H100&) Or _
((inLong And &H7F&) * &H1000000)
If (inLong And &H80&) Then pvReverseLong = pvReverseLong Or &H80000000
End FunctionCode:
Call pvReadPngChunks("C:\Test Images\LaVolpe.PNG")Replaced version. Failed to write in correct section if the PLTE chunk was missing. Simple fix. Patched & re-uploaded. I'll add the ability to save PNG to array also. When I do, I'll remember to set the return value for the functions.






