Results 1 to 6 of 6

Thread: Custom GDI+ PNG Writer v2.0

  1. #1

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Custom GDI+ PNG Writer v2.0

    Completely revamped, comments below reflect the new version. Backup your previous version if you still want it.

    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 or to a byte array. I have added lots of comments throughout the class

    Caveats:

    1. The class does not provide compression for chunks that can include compressed data. Those chunks are IDAT (pixel data), iCCP (ICM profile), zTxt (compressed text only), iTXt (UTF8 text where compression is optional).

    2. All but a few standard PNG chunks are now coded as separate functions, which makes it easier for those not totally familiar with the PNG layout to add chunks during PNG creation. There is also a generic function for adding any chunk one would want. The chunks not directly coded for are:

    IDAT: pixel data. GDI+ creates these
    sBIT: significant bits and pertains specifically to how the pixel data is interpreted
    bKGD: suggested background color for rendering transparency on. This is specific to the bit depth of the written image
    hIST: palette histogram, again specific to the written pixel data
    tRNS: color to be transparent within the image. This is specific to the bit depth of the written image
    sPLT: suggested palette. This is specific to the bit depth of the written image
    pHYs: typically used to describe non-square pixel dimensions

    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 keySoftware, "Custom PNGWriter Class", BeforeIEND
            
            ' example of removing the gAMA, cHRM & sRGB chunks
            c.WritePngToFile hImage, "D:\Users\LaVolpe\Desktop\Test.PNG", CHUNK_gAMA, CHUNK_sRGB, CHUNK_cHRM
    
            Set c = Nothing
            GdipDisposeImage hImage
        Else
            MsgBox "Failed to load that image"
        End If
    End Sub
    Should you want a short routine to review the chunks that exist in any valid PNG file, you can use the following.
    Code:
    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 Function
    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
    Code:
        Call pvReadPngChunks("C:\Test Images\LaVolpe.PNG")
    Edited: Link to PNG format specifications

    See also:
    GDI+ Workaround: JPG > Zero-Length APP Markers
    GDI+ Workaround: TIFF > JPEG-compressed images
    GDI+ Workaround: BMP > Alpha Channels + JPG/PNG Encoded
    GDI+ Workaround: Icons & Cursors

    New version uploaded
    Attached Files Attached Files
    Last edited by LaVolpe; Dec 12th, 2015 at 05:00 PM. Reason: replaced version
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  2. #2

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Custom GDI+ PNG Writer v1.1

    Here's a bit of additional info that can be reviewed as needed if you are adding your own chunks to the PNG. The information is available on any site that describes the PNG specifications.

    I. Chunks you may consider adding/replacing.
    Note: To replace a chunk, you call AddChunk to add the chunk and also include the chunk as one to delete during the WritePngToFile call.

    gAMA: 4 byte Long calculated as desired gamma * 100000
    -- AddChunk "gAMA", lngGAMA

    cHRM: requires 8 4-byte values, i.e., Dim lngCHRM(0 To 7) As Long
    Array item represents White Point, Red,Green,Blue X,Y curves * 100000
    -- AddChunk "cHRM", lngCHRM()

    sRGB: 1 byte renedering intent...
    0=Perceptual,1=Relative Colormetric, 2=Saturation, 3=Absolute Colormetric
    -- AddChunk "sRGB', bytSRGB

    tEXt: string data in format of: keyword + null char + text
    Keyword can be 1-80 characters & some are predefined & recommended, i.e., Author, Title, etc
    -- AddChunk "tEXt", "Title" & vbNullChar & "Skyline of New York"

    tIME: is date/time when image last modified. A 7-byte format, so use byte array:
    4-byte year, 1 byte month (1-12), 1 byte day (1-31), 1 byte hour (0-23), 1 byte minute (0-59)
    and a 1 byte second (0-60). Note that 60 is a leap second.
    Universal time should be used as there is no chunk field for time zones.
    -- AddChunk "tIME", bytTIME()

    pHYs: 5 byte value that indicates the desired pixel size/aspect ratio
    -- AddChunk "pHYs", bytPHYS()

    bKGD: Suggested background color to render transparency against
    You would not normally add this chunk. It requires you to know what PNG-color type is used.
    The chunk may require: 1, 2, or 6 bytes
    -- AddChunk "bKGD", bytCOLOR
    -- AddChunk "bKGD", intCOLOR
    -- AddChunk "bKGD", bytCOLOR() ' 6 byte array

    iTXt: unicode in UTF-8 encoding. Compression is optional
    This chunk's format is a bit lengthy & can be composed in a byte array
    -- AddChunk "iTXt", bytTEXT()

    II. Chunks that require compression

    IDAT is the pixel data and GDI+ writes this for us. These can be numerable and if replaced, all must be replaced.

    iCCP is for color profiles. This chunk requires compression.
    Normally you would not add such a chunk to a PNG you have created/modified.
    -- AddChunk "iCCP", bytICPP()

    zTXt: compressed string data. You will need to compress this via zLIB.dll or other means.
    -- AddChunk "zTXt", bytTEXT()

    III. Chunks that should not be added or replaced. These should not be written by you since they pertain to the color type & pixel format GDI+ wrote...

    PLTE: palette used for pixel indexes
    tRNS: color/mask for simple transparency
    sBIT: significant bits used for pixel data
    sPLT: suggested palette
    hIST: palette histrogram
    Last edited by LaVolpe; Dec 12th, 2015 at 05:04 PM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  3. #3

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Custom GDI+ PNG Writer v2.0

    I've update the class to be more robust but also easier to use for those not familiar with the PNG layout.

    Note to self: Didn't take into consideration a scenario where user attempts to add a chunk that already exists but did not identify the existing chunk for removal. This would allow the chunk to be written more than once. All but just a few chunks can be written just once. Patch needed and will apply this weekend. Fix would be to scan the chunks to be added and if they are one of the standard types that can be written just once, ensure they are also added to the ones to be removed from the GDI+ generated PNG. Custom/Private chunk types are not written by GDI+, so this scenario should not apply when wanting to add those types of chunks.

    As a result, all the AddChunk_[xxx] methods would then be considered as: AddReplaceChunk_[xxx], though I won't rename them.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  4. #4
    Hyperactive Member
    Join Date
    Sep 2014
    Posts
    373

    Re: Custom GDI+ PNG Writer v2.0

    LaVolpe,

    Good stuff. I know how much you have put in.

    I have some similar stuff, listing all chunk tags of a PNG file, then allowing people to import, export or delete a chunk tag, etc. The listng is then refreshed, as well as the thumbnail - if user has screwed it up, the thumbnail won't be right. The validation of the insertion position alone, for example, tRNS before PLTE if it exists, is not as simple as it looks. Therefore I appreciate how much you have done for your this kind of code.

    Yes it is so strange that on Save As PNG, GDIplus likes to add sRGB, gAMA and pHYs (nowadays iCCP is common instead of sRGB). gAMA is terrible, the best write-up about gAMA which I've come across is "A sad story of PNG gamma correction" by Henri Sivonen (text updated 2010-03-31, originally written on 2003-04-21) at https://hsivonen.fi/png-gamma/.

    Alas, it seems that nowadays many people don't enjoy the fun of craftsmanship; if they want something, they just ask for someone else's code, or just deploy an external DLL, seldom roll their own sleeves first.

    It is understandable that not everyone is willing or capable of doing a code like this one, I highly recommand coders here to digest the code, and ask questions, if they want to equip themselves with the fundamentals of PNG.

    Brenker

  5. #5
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,092

    Re: Custom GDI+ PNG Writer v2.0

    I wish LaVolpe had a github account and I would gladly send PRs to his multitude of projects -- and they are a lot :)) I really enjoy reading his code as we both share common style (been borrowing from same sources I guess:-))

    Anyway, what is the "official" way nowadays to implement If Not (Not inArray()) = 0& Then without resorting to Debug.Assert App.hInstance?

    cheers,
    </wqw>

  6. #6
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: Custom GDI+ PNG Writer v2.0

    Quote Originally Posted by wqweto View Post
    I wish LaVolpe had a github account and I would gladly send PRs to his multitude of projects -- and they are a lot ) I really enjoy reading his code as we both share common style (been borrowing from same sources I guess:-))

    Anyway, what is the "official" way nowadays to implement If Not (Not inArray()) = 0& Then without resorting to Debug.Assert App.hInstance?

    cheers,
    </wqw>
    my typelib...

    Code:
    If AryPtr(inArray()) = vbNullPtr Then
    Also I agree, LaVolpe's work on github would be nice.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width