VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "cPNGparser"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
' PNG Parser & PNG to 32bpp converter
' The PNG will be parsed using the following resources if they are available
' and in the following order.
' 1) If GDI+ is available, the entire PNG will be processed via GDI+
' 2) If zLIB.DLL or zLIB1.DLL is available, the PNG will be decompressed via zLIB
' 3) If none of the above, the PNG will be decompressed with pure VB

' No APIs are declared public. This is to prevent possibly, differently
' declared APIs, or different versions of the same API, from conflicting
' with any APIs you declared in your project. Same rule for UDTs.
' Note: I did take some liberties in several API declarations throughout

Private Declare Function ReadFile Lib "kernel32.dll" (ByVal hFile As Long, ByRef lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, ByRef lpNumberOfBytesRead As Long, ByRef lpOverlapped As Any) As Long
Private Declare Function SetFilePointer Lib "kernel32.dll" (ByVal hFile As Long, ByVal lDistanceToMove As Long, ByRef lpDistanceToMoveHigh As Long, ByVal dwMoveMethod As Long) As Long
Private Declare Function GetFileSize Lib "kernel32.dll" (ByVal hFile As Long, ByRef lpFileSizeHigh As Long) As Long
Private Const FILE_CURRENT As Long = 1

' Used to create a return DIB section
Private Declare Sub FillMemory Lib "kernel32.dll" Alias "RtlFillMemory" (ByRef Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
Private Declare Function CreateCompatibleDC Lib "gdi32.dll" (ByVal hDC As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hDC As Long) As Long
Private Declare Function SelectObject Lib "gdi32.dll" (ByVal hDC As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32.dll" (ByVal hObject As Long) As Long
Private Declare Function GetDC Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32.dll" (ByVal hwnd As Long, ByVal hDC As Long) As Long
Private Declare Function CreateDIBSection Lib "gdi32.dll" (ByVal hDC As Long, ByRef pBitmapInfo As Any, ByVal un As Long, ByRef lplpVoid As Long, ByVal Handle As Long, ByVal dw As Long) As Long
Private Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (Ptr() As Any) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Type SafeArrayBound
    cElements As Long
    lLbound As Long
End Type
Private Type SafeArray
    cDims As Integer
    fFeatures As Integer
    cbElements As Long
    cLocks As Long
    pvData As Long
    rgSABound(0 To 1) As SafeArrayBound
End Type
Private Type BITMAPINFOHEADER
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type
Private Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As Long
End Type


' Following are used only if PNG file is being manually decompressed with pure VB
Private Type CodesType
    Length() As Long
    code() As Long
End Type
Private OutPos As Long
Private Inpos As Long
Private ByteBuff As Long
Private BitNum As Long
Private BitMask() As Long
Private Pow2() As Long

Private LCodes As CodesType
Private DCodes As CodesType
Private LitLen As CodesType
Private Dist As CodesType
Private TempLit As CodesType
Private TempDist As CodesType
Private LenOrder() As Long

' Following are used if PNG will be decompressed by zLIB
' -- older version of zLIB (version 1.1.? or earlier)
Private Declare Function Zuncompress Lib "zlib.dll" Alias "uncompress" (ByRef Dest As Any, ByRef destLen As Long, ByRef Source As Any, ByVal sourceLen As Long) As Long
Private Declare Function Zcrc32 Lib "zlib.dll" Alias "crc32" (ByVal crc As Long, ByRef buf As Any, ByVal Length As Long) As Long
' -- latest version of zLIB (version 1.2.3)
Private Declare Function Zuncompress1 Lib "zlib1.dll" Alias "uncompress" (ByRef Dest As Any, ByRef destLen As Long, ByRef Source As Any, ByVal sourceLen As Long) As Long
Private Declare Function Zcrc321 Lib "zlib1.dll" Alias "crc32" (ByVal crc As Long, ByRef buf As Any, ByVal Length As Long) As Long


' following are the actual PNG image properties, exposed via class properties
' (**)Not all are translated until called from the appropriate class property
Private m_Width As Long                 ' image width
Private m_Height As Long                ' image height
Private m_BitDepth As Byte              ' image bit depth/count: 1,2,4,8,16
Private m_ColorType As Byte             ' image color type: 0,2,3,4,6
Private m_Interlacing As Byte           ' interlaced: 0,1
Private m_Palette() As Byte             ' image palette information
Private m_TransSimple() As Byte         ' image simple transparency information
Private m_TransColor As Long  ' translated simple transparency color (BGR or index value)

' matrix/lookup tables
Private pow2x8() As Long                ' a look up table for bit shifting (1,2,4 bit pixels)
Private m_MatrixDat() As Byte           ' see eMatrixType below & InitializeMatrix routine

Private Enum eColorTypes ' internal use only
    clrGrayScale = 0
    clrTrueColor = 2
    clrPalette = 3
    clrGrayAlpha = 4
    clrTrueAlpha = 6
End Enum
Private Enum eMatrixType ' internal use only
    MatrixRow = 0           ' row where each pass starts within interlace matrix
    MatrixCol = 1           ' column where each pass starts within interlace matrix
    MatrixRowAdd = 2        ' gaps between each row withiin each pass
    MatrixColAdd = 3        ' gaps between each column within each pass
    MatrixPixelHeight = 4   ' height of each pixel in a scanline (progressive display)
    MatrixPixelWidth = 5    ' width of each pixel in a scanline (progressive display)
End Enum

' PNG chunk names & their numerical equivalent (those used in this class)
' Per png specs; using alpha chars is a no-no should system not support those characters
' http://www.libpng.org/pub/png/spec/1.1/PNG-Chunks.html
Private Const chnk_IHDR As Long = &H52444849 'Image header
Private Const chnk_IDAT As Long = &H54414449 'Image data
Private Const chnk_IEND As Long = &H444E4549 'End of Image
Private Const chnk_PLTE As Long = &H45544C50 'Palette
Private Const chnk_tRNS As Long = &H534E5274 'Simple Transparency

Private Const png_Signature1 As Long = 1196314761
Private Const png_Signature2 As Long = 169478669
'^^ Complete signature is 8 bytes: 137 80 78 71 13 10 26 10

Private inStream() As Byte      ' overlay only for vbDecompress routine; nevery initialized
Private cCfunction As cCDECL    ' allows calling DLL's that export _CDECL functions, not _StdCall functions
Private m_ZLIBver As Long       ' indicates which zLIB version was found on system: 1=older, 2=newer, 0=dll not found

Private pngStream() As Byte ' overlay of bytes when using LoadStream, else individual chunk bytes when using LoadFile
Private cHost As c32bppDIB  ' owner of 32bpp destination image

Public Function LoadStream(Stream() As Byte, dibClass As c32bppDIB, _
            Optional ByVal streamOffset As Long = 0, _
            Optional ByVal streamLength As Long = 0, _
            Optional GlobalToken As Long) As Boolean

    ' PURPOSE: Determine if passed array is a PNG & if it is, then convert it to
    ' a 32bpp owned by dibClass
    
    ' Parameters.
    ' Stream() :: a byte array containing the possible PNG image
    ' dibClass :: an initialized c32bppDIB class
    ' streamOffset :: array position for 1st byte in the stream
    ' streamLength :: size of stream that contains the image
    '   - If zero, then size is UBound(inStream)-streamOffset+1

    ' IMPORTANT: the array offset & length are not checked in this class.
    '   They were checked before this class was called. If this class is to
    '   be pulled out and put in another project, ensure you include the
    '   validation shown in c32bppDIB.LoadPicture_Stream
    
    Dim tSA As SafeArray
    With tSA    ' prepare to overlay. Overlay prevents VB copying bytes into another array for processing
        .cbElements = 1     ' as byte array
        .cDims = 1          ' 1 dimensional
        .pvData = VarPtr(Stream(streamOffset))
        .rgSABound(0).cElements = streamLength
    End With
    CopyMemory ByVal VarPtrArray(pngStream), VarPtr(tSA), 4& ' establish overlay

    Set cHost = dibClass
    LoadStream = LoadPNG(0&, vbNullString, streamLength, GlobalToken)
    CopyMemory ByVal VarPtrArray(pngStream), 0&, 4& ' remove overlay
    Set cHost = Nothing
    
End Function

Public Function LoadFile(ByVal FileHandle As Long, ByVal FileName As String, dibClass As c32bppDIB, Optional GlobalToken As Long) As Boolean

    ' PURPOSE: Determine if passed file is a PNG & if it is, then convert it to
    ' a 32bpp owned by dibClass
    
    ' Parameters.
    ' FileName :: full path and file
    ' dibClass :: an initialized c32bppDIB class

    ' IMPORTANT: the existance and validity of the filename is not checked here.
    '   They were checked before this class was called. If this class is to
    '   be pulled out and put in another project, ensure you include the
    '   validation shown in c32bppDIB.LoadPicture_File

    Set cHost = dibClass
    LoadFile = LoadPNG(FileHandle, FileName, 0&, GlobalToken)
    Set cHost = Nothing

End Function

Private Function LoadPNG(FileHandle As Long, FileName As String, streamLength As Long, Optional GlobalToken As Long) As Boolean

    ' PURPOSE: Determine if passed file is a PNG & if it is, then convert it to
    ' a 32bpp owned by dibClass
    
    ' Parameters.
    ' FileName :: full path and file
    ' dibClass :: an initialized c32bppDIB class

    Dim ptrLoc As Long          ' used to ensure parsing doesn't go past EOF of corrupted file
    Dim ptrArray As Long
    Dim FileNumber As Long      ' the file handle
    Dim gpLong As Long          ' general purpose long value
    Dim readRtn As Long
    Dim lenIDAT As Long         ' running total of the png data size (compressed)
    
    Dim ChunkName As Long       ' name of the chunk
    Dim ChunkLen As Long        ' length of the chunk
    
    Dim RawPNGdata() As Byte    ' uncompressed png data
    Dim IDATdata() As Byte      ' compressed png data
    
    Dim uncmprssSize As Long    ' calculated size of uncompressed PNG data
    Dim lError As Long
    
    Dim bCRCchecks As Boolean   ' whether or not to use CRC checks on chunks
    Dim crc32value As Long      ' if CRC checks applied, the the CRC value
    
    Dim cGDIp As cGDIPlus
    
    ' reset class' only key property
    m_TransColor = -1&
    
    ' attempt to open the file with read access
    If FileName = vbNullString Then
    
        ptrLoc = 7&              ' counter to prevent overflow of array
        ptrArray = 8&            ' current position in passed array
        If IsPNG() = False Then
            Exit Function
        Else
            LoadPNG = True ' & process it using GDI+ if available
            Set cGDIp = New cGDIPlus
            If cGDIp.GDIplusLoadPNG(FileName, pngStream(), cHost, GlobalToken) = True Then
                m_Width = cHost.Width
                m_Height = cHost.Height
                Exit Function
            End If
            Set cGDIp = Nothing
        End If
        
    Else
        On Error Resume Next
        FileNumber = FileHandle
        SetFilePointer FileNumber, 0&, 0&, 0&
        
        ' validate we are looking at a png file
        streamLength = GetFileSize(FileHandle, 0&)
        If streamLength > 56& Then ' minimal (signature=8;header=13,3 rqd chunks=36 min)
            ReDim pngStream(0 To 57)
            ReadFile FileNumber, pngStream(0), 58, readRtn, ByVal 0&
            'Get FileNumber, 1, pngStream()
            If IsPNG() = True Then
                LoadPNG = True
            Else
                Exit Function
            End If
        End If
        On Error GoTo 0
        ' process using GDI+ if available
        Set cGDIp = New cGDIPlus
        If cGDIp.GDIplusLoadPNG(FileName, pngStream(), cHost, GlobalToken) = True Then
            m_Width = cHost.Width
            m_Height = cHost.Height
            LoadPNG = True
            Exit Function
        End If
        Set cGDIp = Nothing
        ptrArray = -4&
        ptrLoc = 8&                 ' next position in the file
        SetFilePointer FileNumber, ptrLoc, 0&, 0&
   End If
    
    ReDim IDATdata(0 To streamLength \ 2&)  ' array to hold compressed data; start with arbritrary length
    bCRCchecks = zValidateZLIBversion()     ' verify we can use zLIB
    
    Do ' read & pre-process the png file
    
        ' Chunks consist of 4 bytes for the length of the chunk
        '                 + n bytes for the chunk
        '                 + 4 bytes for a CRC value
        If FileNumber = 0& Then
            CopyMemory gpLong, pngStream(ptrArray), 4& ' length of the current chunk
        Else
            'Get FileNumber, , gpLong ' number of bytes for the chunk
            ReadFile FileNumber, gpLong, 4&, readRtn, ByVal 0&
        End If
        ChunkLen = iparseReverseLong(gpLong) ' longs are big endian, need little endian for Windows
        
        ' track position of pointer in the file
        ptrLoc = ptrLoc + ChunkLen + 12& ' 12 = 4byte name + 4byte CRC + 4byte chunk count
        If ptrLoc > streamLength Then
            ' corrupted file; abort
            lError = 1&
            Exit Do
        End If
        
        ' read chunk name & chunk data, read CRC separately
        If FileNumber = 0& Then
            If bCRCchecks = True Then CopyMemory crc32value, pngStream(ptrArray + ChunkLen + 8&), 4&
        Else
            ReDim pngStream(0 To ChunkLen + 3&)
            'Get FileNumber, , pngStream
            'Get FileNumber, , gpLong             ' read the CRC value (big endian)
            ReadFile FileNumber, pngStream(0), ChunkLen + 4&, readRtn, ByVal 0&
            ReadFile FileNumber, gpLong, 4&, readRtn, ByVal 0&
            If bCRCchecks = True Then crc32value = gpLong
        End If
        CopyMemory ChunkName, pngStream(ptrArray + 4&), 4& ' extract the chunk name
        
        If ChunkLen < 1& Then
            ' should never be <0; however can be zero at anytime
            If ChunkName = chnk_IEND Then Exit Do
        Else
            
            ' each of the chunk parsing routines will be in a separate function.
            ' This is so that they can easily be modified without affecting any
            ' of the other code. Additionally, it is possible that chunk types will
            ' increase as PNG continues to evolve. Except IDAT, all chunks
            ' are in their own routines
            Select Case ChunkName
            
            Case chnk_IDAT ' UCase chunk names are critical - CRC check
                ' compressed, filtered image data
                On Error Resume Next
                ' error? what error? all precautions taken in zChunk_IHDR function
                ' However; no predicting "Out of Memory" errors
                If Not crc32value = 0& Then
                    lError = Not (zCheckCRCvalue(VarPtr(pngStream(ptrArray + 4&)), ChunkLen + 4&, crc32value))
                End If
                If lError = 0& Then
                    gpLong = lenIDAT + ChunkLen ' size of array needed
                    If gpLong > UBound(IDATdata) Then ' test length & increment/buffer if needed
                        ReDim Preserve IDATdata(0 To gpLong + streamLength \ 4&)
                    End If
                    CopyMemory IDATdata(lenIDAT), pngStream(ptrArray + 8&), ChunkLen ' & append the new data
                    lenIDAT = gpLong  ' cache number of compressed bytes so far
                    If Err Then
                        lError = 1&
                        Exit Do
                    End If
                End If
                On Error GoTo 0
            
            Case chnk_PLTE ' UCase chunk names are critical - CRC check
                lError = zChunk_PLTE(ChunkLen, ptrArray + 4&, crc32value)
            
            Case chnk_tRNS ' simple transparency option
                ' CRC checked 'cause if invalid, we could generate an out of bounds
                ' error in one of the other routines that reference this array
                lError = zChunk_tRNS(ChunkLen, ptrArray + 4&, crc32value)
                
            Case chnk_IHDR ' UCase chunk names are critical - CRC check
                ' Note: the zChunk_IHDR routine also calculates uncompressed size
                lError = zChunk_IHDR(ChunkLen, ptrArray + 4&, uncmprssSize, crc32value)
                
            Case chnk_IEND ' UCase chunk names are critical - CRC check
                ' should CRC check for corrupted file; but why? we're at end of image
                Exit Do
                
            End Select
            If Not lError = 0& Then Exit Do
    
        End If
        If FileNumber = 0& Then ptrArray = ptrArray + ChunkLen + 12& ' move to next position in the array
    Loop

ExitRoutine:
    ' clean up
    If Not FileNumber = 0& Then
        'Close #FileNumber
        Erase pngStream()
    End If
    
    If lenIDAT = 0& Or Not lError = 0& Then  ' invalid png image
        If Err Then Err.Clear
    Else

        ' process the compressed data
        Call PostLoadPNG(IDATdata(), lenIDAT, uncmprssSize)
    End If

End Function

Private Function PostLoadPNG(IDATdata() As Byte, lenIDAT As Long, uncmprssSize As Long) As Boolean

    ' Purpose: Uncompress compressed bytes and send to the un-filtering routines
    Dim RawPNGdata() As Byte
    Dim bUncompressed As Boolean
    Dim lRtn As Long

    On Error Resume Next
    ' we need to uncompress our PNG file
    ReDim RawPNGdata(0 To uncmprssSize - 1&)
    
    ' if zLIB is available, let it uncompress; faster than pure VB
    If Not m_ZLIBver = 0& Then   ' tested/set in LoadPNG routine
        bUncompressed = zInflate(VarPtr(RawPNGdata(0)), VarPtr(uncmprssSize), VarPtr(IDATdata(0)), lenIDAT)
    End If

    If Not bUncompressed Then
        ' either zLib returned an error or it wasn't available, uncompress by hand
        bUncompressed = vbDecompress(RawPNGdata(), IDATdata(), uncmprssSize)
        If Err Then Err.Clear
    End If
    Erase IDATdata()
    
    If Not bUncompressed Then
        ' failed to uncompress & shouldn't happen 'cause if I calculated uncmprssSize
        ' wrong, then other calculations in this routine are wrong too
        ' See: CalcUncompressedWidth
        Exit Function
    End If

    Call InitializePalette  ' if PNG is palettized, create palette
    cHost.InitializeDIB m_Width, m_Height    ' create 32bpp DIB to hold PNG
    
    ' call function to begin converting PNG to Bitmap
    If m_Interlacing = 0& Then
        lRtn = UnfilterNI(RawPNGdata()) ' non-interlaced image
    Else
        lRtn = UnfilterInterlaced(RawPNGdata()) ' interlaced image
    End If

    ' return results
    If lRtn = 0& Then
        cHost.DestroyDIB ' failure decoding the PNG
    Else
        If m_ColorType > clrPalette Then
            cHost.Alpha = True
        ElseIf Not m_TransColor = -1& Then
            cHost.Alpha = True
        Else
            cHost.Alpha = False
        End If
        cHost.ImageType = imgPNG
        PostLoadPNG = True
    End If

End Function

Private Function CalcUncompressedWidth() As Long

    Dim uncompressedWidth As Long, iBitPP As Byte
    Dim Pass As Long, passWidth As Long, passHeight As Long

    On Error GoTo NoLoad

    InitializeMatrix    ' build the interlacing matrix; also used for non-interlaced too

    ' get the actual bits per pixel the png is using
    ' (i.e., 16bitdepth png @ ColorType 6 = 64bits per pixel)
    GetDepthInfo 0, 0, iBitPP, 0
    
    If m_Interlacing = 0& Then ' no interlacing
        ' uncompressed width will be byte aligned width + 1 for filter byte
        ' multiplied by the height
        passWidth = GetBytesPerPixel(m_Width, iBitPP)
        uncompressedWidth = passWidth * m_Height + m_Height
    Else
        ' interlaced will also be byte aligned but per scanline width
        ' Each of the 7 passes can have different widths + 1 filter byte per line
        For Pass = 1& To 7&
            ' calculate number of pixels per scan line
            passWidth = m_Width \ m_MatrixDat(Pass, MatrixColAdd) - (m_Width Mod m_MatrixDat(Pass, MatrixColAdd) > m_MatrixDat(Pass, MatrixCol))
            ' determine number of bytes needed for each scanline
            passWidth = GetBytesPerPixel(passWidth, iBitPP)
            ' calculate number of rows for this scan's pass
            passHeight = m_Height \ m_MatrixDat(Pass, MatrixRowAdd) - (m_Height Mod m_MatrixDat(Pass, MatrixRowAdd) > m_MatrixDat(Pass, MatrixRow))
            ' now get the total bytes needed for the entire pass,
            ' adding 1 filter byte for each line in the pass:  i.e., + passHeight
            uncompressedWidth = uncompressedWidth + passWidth * passHeight + passHeight
        Next
        
    End If
    
    CalcUncompressedWidth = uncompressedWidth

NoLoad:
End Function

Private Sub InitializeMatrix()
        
    ' a quick look up table for bit shifting operations
    If m_ColorType = clrGrayScale Or m_ColorType = clrPalette Then
        Dim i As Integer
        ReDim pow2x8(0 To 8)
        pow2x8(0) = 1&
        For i = 1& To 8&
            pow2x8(i) = pow2x8(i - 1&) * 2&
        Next
    End If
    ReDim m_MatrixDat(1 To 8, MatrixRow To MatrixColAdd)
    ' for rendering progressive display:
    '   - change 2D elements above: MatrixRow to MatrixPixelWidth
    '   - unrem final array element assignments below
    
    ' initialize interlacing matrix, used in the ConvertPNGtoBMP routine and
    ' also used to calculate the uncompressed size of the compressed PNG data
    
    ' Non-interlaced images are considered Pass#8, where interlaced images always
    ' contain 7 passes (1 thru 7).
    
    ' determines what row in the interlaced image, the current pass begins at
    CopyMemory m_MatrixDat(1, MatrixRow), 262144, 4&
    CopyMemory m_MatrixDat(5, MatrixRow), 65538, 4&  'Array(0, 0, 4, 0, 2, 0, 1, 0)
    ' determines what column in the interlaced image, the current pass begins at
    CopyMemory m_MatrixDat(1, MatrixCol), 33555456, 4&
    CopyMemory m_MatrixDat(5, MatrixCol), 256&, 4& 'Array(0, 4, 0, 2, 0, 1, 0, 0)
    ' determines the row interval of the current pass
    CopyMemory m_MatrixDat(1, MatrixRowAdd), 67635208, 4&
    CopyMemory m_MatrixDat(5, MatrixRowAdd), 16908804, 4& 'Array(8, 8, 8, 4, 4, 2, 2, 1)
    ' determines the column interval of the current pass
    CopyMemory m_MatrixDat(1, MatrixColAdd), 67373064, 4&
    CopyMemory m_MatrixDat(5, MatrixColAdd), 16843266, 4& 'Array(8, 8, 4, 4, 2, 2, 1, 1)
    
    ' 1st 7 elements of next 2 arrays used for pixellated interlaced images
    
    ' determines the width of each pixellated pixel for the current pass (Used only when progressive display rendering)
    'CopyMemory m_MatrixDat(1, MatrixPixelWidth), 33817608, 4&
    'CopyMemory m_MatrixDat(5, MatrixPixelWidth), 16843010, 4& 'Array(8, 4, 4, 2, 2, 1, 1, 1)
    ' determines the height of each pixellated pixel for the current pass
    'CopyMemory m_MatrixDat(1, MatrixPixelHeight), m_MatrixDat(1, MatrixColAdd), &H8 'Array(8, 8, 4, 4, 2, 2, 1, 1)

End Sub


Private Function ConvertPNGtoBMP_NonPalette(rawBytes() As Byte, ByVal scanPass As Byte, _
                    ByVal scanCY As Long, ByVal rBPRow As Long, _
                    Optional ByVal startOffset As Long = 0) As Boolean

' Routine processes only non-paletted, non-16bit PNG data

' rawBytes() are the png scanline bytes
' scanPass() is a valid number btwn 1-8 (8 indicates non-interlaced)
' scanCY is number of scanlines (btwn 1 and image height)
' rBPRow is the number of bytes per scanline of the rayBytes array (byte aligned)

    Dim rRow As Long, rColumn As Long ' current row/column of the png image
    Dim dRow As Long, dColumn As Long ' current row/column of destination bitmap
    Dim dIndex As Long ' position of dRow in the destBytes() array
    
    Dim rBytePP As Byte ' nr of bytes per pixel in png image
    Dim destPos As Long, rgbIncrR As Long, rgbIncrG As Long
    
    Dim destBytes() As Byte ' placeholders for DIB bits (DMA)
    Dim tSA As SafeArray  ' array overlays for DIB bits (DMA)
    
    On Error GoTo err_h
    
    ' use direct memory access (DMA) to reference the DIB pixel data
    With tSA
        .cDims = 2                          ' Number of dimensions
        .cbElements = 1                     ' Size of data elements
        .pvData = cHost.BitsPointer         ' Data address
        .rgSABound(0).cElements = m_Height
        .rgSABound(1).cElements = m_Width * 4& ' Nr of Elements
    End With
    CopyMemory ByVal VarPtrArray(destBytes), VarPtr(tSA), 4&

    ' determine the bits/bytes of the png and bitmap images
    GetDepthInfo 0, 0, 0, rBytePP

    ' get location of BMP scanline we are processing from PNG scanline
    If startOffset = 0& Then
        dRow = m_MatrixDat(scanPass, MatrixRow)
    Else
        dRow = startOffset
    End If

    If Not m_ColorType = clrGrayAlpha Then
        rgbIncrR = 2&
        rgbIncrG = 1&
    End If

    For rRow = startOffset To scanCY - 1& ' < in relation to rawBytes() array
        
        dIndex = (m_Height - dRow - 1&) '* dRowWidth ' < destBytes array pointer for 1st pixel in scanline
        rColumn = rRow * rBPRow + 1&  ' < rawBytes array pointer for 1st pixel in scanline
        
        dColumn = m_MatrixDat(scanPass, MatrixCol) ' column in relation to the destBytes() array
        
        Do While dColumn < m_Width
        
            destPos = dColumn * 4& + 3&
            
            Select Case m_ColorType
            
            Case clrTrueAlpha ' true color with alpha (32 bit)
                destBytes(destPos, dIndex) = rawBytes(rColumn + 3&)  ' alpha channel
                
            Case clrGrayAlpha  ' grayscale with alpha (1,2,4,8 bit)
                destBytes(destPos, dIndex) = rawBytes(rColumn + 1&)  ' alpha channel
            
            Case clrTrueColor ' true color + simple transparency (24 bit)
                destBytes(destPos, dIndex) = &HFF                    ' alpha channel
                If Not m_TransColor = -1& Then   ' transparency is used
                    If (m_TransColor And &HFF) = rawBytes(rColumn + 2&) Then
                        If ((m_TransColor \ &H100&) And &HFF) = rawBytes(rColumn + 1&) Then
                            If ((m_TransColor \ &H10000) And &HFF) = rawBytes(rColumn) Then destBytes(destPos, dIndex) = 0&
                        End If
                    End If
                End If

            End Select
            
            Select Case destBytes(destPos, dIndex)
            Case 0: ' do nothing, RGB is zero
            Case 255
                destBytes(destPos - 3&, dIndex) = rawBytes(rColumn + rgbIncrR)
                destBytes(destPos - 2&, dIndex) = rawBytes(rColumn + rgbIncrG)
                destBytes(destPos - 1&, dIndex) = rawBytes(rColumn)
            Case Else
                destBytes(destPos - 3&, dIndex) = (0& + rawBytes(rColumn + rgbIncrR)) * destBytes(destPos, dIndex) \ 255
                destBytes(destPos - 2&, dIndex) = (0& + rawBytes(rColumn + rgbIncrG)) * destBytes(destPos, dIndex) \ 255
                destBytes(destPos - 1&, dIndex) = (0& + rawBytes(rColumn)) * destBytes(destPos, dIndex) \ 255
            End Select
            
            rColumn = rColumn + rBytePP      ' else increment per source byte pp
            dColumn = dColumn + m_MatrixDat(scanPass, MatrixColAdd)
        
        Loop
        dRow = dRow + m_MatrixDat(scanPass, MatrixRowAdd)
        
    Next

    ' clean up & return result
    CopyMemory ByVal VarPtrArray(destBytes), 0&, 4&
    ConvertPNGtoBMP_NonPalette = True

    Exit Function

err_h:  ' should never get here
Err.Clear
If tSA.cDims Then CopyMemory ByVal VarPtrArray(destBytes), 0&, 4&

End Function

Private Function ConvertPNGtoBMP_Palettes(rawBytes() As Byte, ByVal scanPass As Byte, _
                    ByVal scanCY As Long, ByVal rBPRow As Long, _
                    Optional ByVal startOffset As Long = 0) As Boolean

' Routine processes only paletted, non-16bit PNG data

' rawBytes() are the png scanline bytes
' scanPass() is a valid number btwn 1-8 (8 indicates non-interlaced)
' scanCY is number of scanlines (btwn 1 and image height)
' rBPRow is the number of bytes per scanline of the rayBytes array (byte aligned)

    Dim rRow As Long, rColumn As Long ' current row/column of the png image
    Dim dRow As Long, dColumn As Long ' current row/column of destination bitmap
    Dim dIndex As Long ' position of dRow in the destBytes() array
    
    Dim rBytePP As Byte, rBitPP As Byte ' nr of bytes per pixel in png image
    
    Dim tColor(0 To 3) As Byte ' color value when copying 3 or 4 bytes to a 3 or 4 byte array
    Dim palOffset As Long
    Dim destPos As Long
    Dim pIndex As Byte ' alpha related variables
    
    Dim destBytes() As Byte ' placeholders for DIB bits (DMA)
    Dim tSA As SafeArray  ' array overlays for DIB bits (DMA)
    
    Dim maskShift As Long

    On Error GoTo err_h
    
    ' use direct memory access (DMA) to reference the DIB pixel data
    With tSA
        .cDims = 2                          ' Number of dimensions
        .cbElements = 1                     ' Size of data elements
        .pvData = cHost.BitsPointer         ' Data address
        .rgSABound(0).cElements = m_Height
        .rgSABound(1).cElements = m_Width * 4& ' Nr of Elements
    End With
    CopyMemory ByVal VarPtrArray(destBytes), VarPtr(tSA), 4&

    ' determine the bits/bytes of the png and bitmap images
    GetDepthInfo 0, 0, 0, rBytePP

    ' get location of BMP scanline we are processing from PNG scanline
    If startOffset = 0& Then
        dRow = m_MatrixDat(scanPass, MatrixRow)
    Else
        dRow = startOffset
    End If

    For rRow = startOffset To scanCY - 1& ' < in relation to rawBytes() array
        
        dIndex = (m_Height - dRow - 1&) '* dRowWidth ' < destBytes array pointer for 1st pixel in scanline
        rColumn = rRow * rBPRow + 1&  ' < rawBytes array pointer for 1st pixel in scanline
        
        dColumn = m_MatrixDat(scanPass, MatrixCol) ' column in relation to the destBytes() array
        maskShift = 8& - m_BitDepth
        
        Do While dColumn < m_Width
        
            destPos = dColumn * 4&
            
            Select Case m_ColorType
            
            Case clrPalette ' paletted with/without simple transparency in its own palette-alpha table
                ' 1,2,4,8 bit
                Call GetPaletteValue(maskShift, rawBytes(rColumn), pIndex)
                palOffset = pIndex * 3&
                If m_TransColor = -1& Then    ' no transparency used
                    destBytes(destPos + 3&, dIndex) = &HFF
                    destBytes(destPos, dIndex) = m_Palette(palOffset + 2&)
                    destBytes(destPos + 1&, dIndex) = m_Palette(palOffset + 1&)
                    destBytes(destPos + 2&, dIndex) = m_Palette(palOffset)
                Else
                    destBytes(destPos + 3&, dIndex) = m_TransSimple(pIndex)
                    Select Case m_TransSimple(pIndex)
                    Case 0
                    Case 255
                        destBytes(destPos, dIndex) = m_Palette(palOffset + 2&)
                        destBytes(destPos + 1&, dIndex) = m_Palette(palOffset + 1&)
                        destBytes(destPos + 2&, dIndex) = m_Palette(palOffset)
                    Case Else
                        destBytes(destPos, dIndex) = (0& + m_Palette(palOffset + 2&)) * m_TransSimple(pIndex) \ &HFF
                        destBytes(destPos + 1&, dIndex) = (0& + m_Palette(palOffset + 1&)) * m_TransSimple(pIndex) \ &HFF
                        destBytes(destPos + 2&, dIndex) = (0& + m_Palette(palOffset)) * m_TransSimple(pIndex) \ &HFF
                    End Select
                End If

            Case clrGrayScale ' grayscale with/without simple transparency
                ' 1,2,4,8 bit
                Call GetPaletteValue(maskShift, rawBytes(rColumn), pIndex)
                If Not m_TransColor = pIndex Then   ' else fully transparent
                    destBytes(destPos + 3&, dIndex) = &HFF
                    destBytes(destPos, dIndex) = m_Palette(3& * pIndex)
                    destBytes(destPos + 1&, dIndex) = destBytes(destPos, dIndex)
                    destBytes(destPos + 2&, dIndex) = destBytes(destPos, dIndex)
                End If

            End Select
            
            ' ensure our source byte pointer is moved along appropriately
            If m_BitDepth < 8& Then
                If maskShift = 0& Then
                    rColumn = rColumn + 1&
                    maskShift = 8& - m_BitDepth
                Else
                    maskShift = maskShift - m_BitDepth
                End If
            Else
                rColumn = rColumn + rBytePP      ' else increment per source byte pp
            End If
            dColumn = dColumn + m_MatrixDat(scanPass, MatrixColAdd)
        
        Loop
        dRow = dRow + m_MatrixDat(scanPass, MatrixRowAdd)
        
    Next

    ' clean up & return result
    CopyMemory ByVal VarPtrArray(destBytes), 0&, 4&
    ConvertPNGtoBMP_Palettes = True

    Exit Function

err_h:  ' should never get here
Err.Clear
If tSA.cDims Then CopyMemory ByVal VarPtrArray(destBytes), 0&, 4

End Function

Private Function ConvertPNGtoBMP_16Bit(rawBytes() As Byte, ByVal scanPass As Byte, _
                    ByVal scanCY As Long, ByVal rBPRow As Long, _
                    Optional ByVal startOffset As Long = 0) As Boolean

' Routine processes only 16bit PNG data

' rawBytes() are the png scanline bytes
' scanPass() is a valid number btwn 1-8 (8 indicates non-interlaced)
' scanCY is number of scanlines (btwn 1 and image height)
' rBPRow is the number of bytes per scanline of the rayBytes array (byte aligned)

    Dim rRow As Long, rColumn As Long ' current row/column of the png image
    Dim dRow As Long, dColumn As Long ' current row/column of destination bitmap
    Dim dIndex As Long ' position of dRow in the destBytes() array
    
    Dim destPos As Long, rBytePP As Byte
    Dim rgbIncrR As Long, rgbIncrG As Long
    
    Dim destBytes() As Byte ' placeholders for DIB bits (DMA)
    Dim tSA As SafeArray  ' array overlays for DIB bits (DMA)

    On Error GoTo err_h
    
    ' determine the bits/bytes of the png and bitmap images
    GetDepthInfo 0, 0, 0, rBytePP
    
    ' use direct memory access (DMA) to reference the DIB pixel data
    With tSA
        .cDims = 2                          ' Number of dimensions
        .cbElements = 1                     ' Size of data elements
        .pvData = cHost.BitsPointer         ' Data address
        .rgSABound(0).cElements = m_Height
        .rgSABound(1).cElements = m_Width * 4& ' Nr of Elements
    End With
    CopyMemory ByVal VarPtrArray(destBytes), VarPtr(tSA), 4&

    ' get location of BMP scanline we are processing from PNG scanline
    If startOffset = 0& Then
        dRow = m_MatrixDat(scanPass, MatrixRow)
    Else
        dRow = startOffset
    End If
    
    If m_ColorType = clrTrueAlpha Or m_ColorType = clrTrueColor Then
        rgbIncrR = 4&: rgbIncrG = 2&
    End If

    For rRow = startOffset To scanCY - 1& ' < in relation to rawBytes() array
        
        dIndex = (m_Height - dRow - 1&) '* dRowWidth ' < destBytes array pointer for 1st pixel in scanline
        rColumn = rRow * rBPRow + 1&  ' < rawBytes array pointer for 1st pixel in scanline
        
        dColumn = m_MatrixDat(scanPass, MatrixCol) ' column in relation to the destBytes() array
        
        Do While dColumn < m_Width
        
            destPos = dColumn * 4& + 3&
            
            Select Case m_ColorType
            
            Case clrTrueAlpha ' true color with alpha (64 bit)
                destBytes(destPos, dIndex) = rawBytes(rColumn + 6&)  ' alpha channel
            
            Case clrGrayAlpha  ' grayscale with alpha (32 bit)
                destBytes(destPos, dIndex) = rawBytes(rColumn + 2&)
            
            Case clrTrueColor ' true color with/without simple transparency (48 bit)
                
                destBytes(destPos, dIndex) = &HFF
                If Not m_TransColor = -1& Then   ' transparency is used
                    If rawBytes(rColumn + rgbIncrR) = m_TransSimple(rgbIncrR) Then
                        If rawBytes(rColumn + rgbIncrG) = m_TransSimple(rgbIncrG) Then
                            If rawBytes(rColumn) = m_TransSimple(0) Then destBytes(destPos, dIndex) = 0&
                        End If
                    End If
                End If

            Case clrGrayScale ' grayscale with or without simple transparency (16 bit)
                If m_TransColor = -1& Then
                    destBytes(destPos, dIndex) = &HFF
                ElseIf Not rawBytes(rColumn) = m_TransSimple(0) Then
                    destBytes(destPos, dIndex) = &HFF
                End If

            End Select
            
            Select Case destBytes(destPos, dIndex)
            Case 0: ' do nothing, fully transparent
            Case 255
                destBytes(destPos - 3&, dIndex) = rawBytes(rColumn + rgbIncrR)
                destBytes(destPos - 2&, dIndex) = rawBytes(rColumn + rgbIncrG)
                destBytes(destPos - 1&, dIndex) = rawBytes(rColumn)
            Case Else
                destBytes(destPos - 3&, dIndex) = (0& + rawBytes(rColumn + rgbIncrR)) * destBytes(destPos, dIndex) \ 255
                destBytes(destPos - 2&, dIndex) = (0& + rawBytes(rColumn + rgbIncrG)) * destBytes(destPos, dIndex) \ 255
                destBytes(destPos - 1&, dIndex) = (0& + rawBytes(rColumn)) * destBytes(destPos, dIndex) \ 255
            End Select
            
            ' ensure our source byte pointer is moved along appropriately
            rColumn = rColumn + rBytePP
            dColumn = dColumn + m_MatrixDat(scanPass, MatrixColAdd)
        
        Loop
        dRow = dRow + m_MatrixDat(scanPass, MatrixRowAdd)
        
    Next

    ' clean up & return result
    CopyMemory ByVal VarPtrArray(destBytes), 0&, 4&
    ConvertPNGtoBMP_16Bit = True

    Exit Function

err_h:  ' should never get here
Err.Clear
If tSA.cDims Then CopyMemory ByVal VarPtrArray(destBytes), 0&, 4&

End Function

Private Sub GetPaletteValue(ByVal PixelPos As Long, ByVal PixelValue As Byte, _
                Optional ByRef RtnIndex As Byte)

    ' // LaVolpe, Dec 1 thru 10 - added from scratch
    ' Returns a palette index and palette color from a compressed byte
    RtnIndex = (PixelValue \ pow2x8(PixelPos)) And (pow2x8(m_BitDepth) - 1)
    
End Sub

Private Function GetBytesPerPixel(totalWidth As Long, btsPerPixel As Byte) As Long
    ' // LaVolpe, Dec 1 thru 10
    ' returns number of bytes required to display n pixels at p color depth (byte aligned)
    GetBytesPerPixel = (totalWidth * btsPerPixel + 7&) \ 8&

End Function

Private Sub GetDepthInfo(destBitPP As Byte, destBytePP As Byte, _
                        rawBitsPP As Byte, rawBytesPP As Byte)
    
' returns the bits per pixel & bytes per pixel for the destination bitmap
' and also the respective values for the png image
    
' PNG > DIB bmp (per pixel) conversion chart I use throughout the routines:
'Color Type     bit depth   PNG bits/bytes per pixel  BMP bits/bytes pp (ignore alpha)
'----------     ---------   ------------------------- --------------------------------
'0 gray scale   1                   1   1                   1   1   (?  ?)
'               2                   2   1                   4   1   (?  ?)
'               4                   4   1                   4   1   (?  ?)
'               8                   8   1                   8   1   (8  1)
'               16                  16  2                   8   1   (8  1)

'2 true color   8                   24  3                   24  3   (24 3)
'               16                  48  6                   24  3   (24 3)

'3 palette      1                   1   1                   1   1   (?  ?)
'               2                   2   1                   4   1   (?  ?)
'               4                   4   1                   4   1   (?  ?)
'               8                   8   1                   8   1   (8  1)

'4 gray+alpha   8                   16  2                   32  4   (24 3)
'               16                  32  4                   32  4   (24 3)

'6 true+alpha   8                   32  4                   32  4   (24 3)
'               16                  64  8                   32  4   (24 3)

'any bit depth that uses simple transparency (trns chunk)   32  4   (n/a)
'--------------------------------------------------------------------------

    
    Select Case m_ColorType
    
    Case clrTrueAlpha ' true color w/alpha (only 8,16 bpp pngs)
        rawBytesPP = 4& * (m_BitDepth \ 8&): rawBitsPP = m_BitDepth * 4&
        
    Case clrGrayAlpha: ' grayscale w/alpha (only 8,16 bpp pngs)
        rawBytesPP = 2& * (m_BitDepth \ 8&): rawBitsPP = m_BitDepth * 2&
        
    Case clrTrueColor: ' true color (rgb triples) (8,16 bpp pngs)
        rawBytesPP = 3& * (m_BitDepth \ 8&): rawBitsPP = m_BitDepth * 3&
        
    Case clrGrayScale ' grayscale images (all bit depths)
        If m_BitDepth = 2& Then ' special case as MS bitmaps don't do 2bpp
            rawBytesPP = 1&: rawBitsPP = 2&
        ElseIf m_BitDepth > 4& Then ' (8,16 bpp pngs)
            rawBytesPP = m_BitDepth \ 8&: rawBitsPP = m_BitDepth
        Else ' (1,4 bpp pngs)
            rawBytesPP = 1: rawBitsPP = m_BitDepth
        End If
        
    Case clrPalette: ' palette entries (1,2,4,8 bpp pngs)
        rawBytesPP = 1: rawBitsPP = m_BitDepth
        
    End Select
    
    ' our DIB will always be 32bpp
    destBitPP = 32: destBytePP = 4

End Sub

Private Function PaethPredictor(ByVal Left As Integer, ByVal Above As Integer, ByVal UpperLeft As Integer) As Integer

    ' // LaVolpe, Dec 1 thru 10 - rewrote for understanding & commented/linked
    
    ' http://www.w3.org/TR/PNG/#9-table91
    ' algorithm is used for both encoding & decoding the png image's filter
    ' based off of the formula created by Alan W. Paeth & provided fully in url above

    Dim pa As Integer, pb As Integer, pC As Integer, p As Integer
    p = (Left + Above - UpperLeft)
    pa = Abs(p - Left)
    pb = Abs(p - Above)
    pC = Abs(p - UpperLeft)
    
    ' tie breaker
    ' The order in which the comparisons are performed is critical and shall not be altered
    If (pa <= pb) And (pa <= pC) Then
        PaethPredictor = Left
    ElseIf pb <= pC Then
        PaethPredictor = Above
    Else
        PaethPredictor = UpperLeft
    End If

End Function

Private Sub DecodeFilter_Avg(Filtered() As Byte, ByVal RowNr As Long, ByVal ScanLine As Long, ByVal stepVal As Integer)

    ' // LaVolpe, Dec 1 thru 10 - rewrote for understanding; nothing wrong with original
    'http://www.w3.org/TR/PNG/#9-table91
    'Filters may use the original values of the following bytes to generate the new byte value:
    '
    'x  the byte being filtered;
    'a  the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
    'b  the byte corresponding to x in the previous scanline;
    'c  the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).
    
    ' algorithm: Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
    ' Unsigned arithmetic modulo 256 is used, so that both the inputs and outputs fit into bytes

    Dim X As Long, startByte As Long
    
    startByte = RowNr * ScanLine + 1
    
    On Error GoTo eh
    ' break out for faster loops, removing IF statements/combinations
    If RowNr = 0 Then   ' 1st row; there will be no Top row to get data from
        ' if png is encoded properly, shouldn't get here
        ' now process the 2nd pixel on, to finish the scanline
        For X = startByte + stepVal To startByte + ScanLine - 2
            Filtered(X) = (0 + Filtered(X) + (Filtered(X - stepVal) \ 2)) Mod 256
        Next
        
    Else    ' 2nd or subsequent rows
        ' process the 1st n bytes (1st pixel only)
        For X = startByte To startByte + stepVal - 1
            Filtered(X) = (0 + Filtered(X) + (Filtered(X - ScanLine) \ 2)) Mod 256
        Next
        ' now process the 2nd pixel on, to finish the scanline
        For X = X To startByte + ScanLine - 2
            Filtered(X) = (0 + Filtered(X) + (0 + Filtered(X - stepVal) + Filtered(X - ScanLine)) \ 2) Mod 256
        Next
    End If
eh:
End Sub

Private Sub DecodeFilter_Paeth(Filtered() As Byte, ByVal RowNr As Long, ByVal ScanLine As Long, ByVal stepVal As Integer)

    ' // LaVolpe, Dec 1 thru 10 - rewrote for understanding; nothing wrong with original
    'http://www.w3.org/TR/PNG/#9-table91
    
    'Filters may use the original values of the following bytes to generate the new byte value:
    '
    'x  the byte being filtered;
    'a  the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
    'b  the byte corresponding to x in the previous scanline;
    'c  the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).
    
    ' algorithm: Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))
    ' Unsigned arithmetic modulo 256 is used, so that both the inputs and outputs fit into bytes

    Dim X As Long, startByte As Long

    startByte = RowNr * ScanLine + 1
    
    ' break out for faster loops, removing IF statements/combinations
    On Error GoTo eh
    
    If RowNr = 0 Then    ' 1st row; there will be no Top row to get data from
        ' if png is encoded properly, shouldn't get here
        ' now process the 2nd pixel on, to finish the scanline
        For X = startByte + stepVal To startByte + ScanLine - 2
            Filtered(X) = (0 + Filtered(X) + Filtered(X - stepVal)) Mod 256
        Next
    
    Else    ' 2nd or subsequent rows
        ' process the 1st n bytes (1st pixel only)
        For X = startByte To startByte + stepVal - 1
            Filtered(X) = (0 + Filtered(X) + Filtered(X - ScanLine)) Mod 256
        Next
        ' now process the 2nd pixel on, to finish the scanline
        For X = X To startByte + ScanLine - 2
            Filtered(X) = (0 + Filtered(X) + PaethPredictor(Filtered(X - stepVal), Filtered(X - ScanLine), Filtered(X - ScanLine - stepVal))) Mod 256
        Next

    End If
eh:
End Sub

Private Sub DecodeFilter_Sub(Filtered() As Byte, ByVal RowNr As Long, ByVal ScanLine As Long, ByVal stepVal As Integer)

    ' // LaVolpe, Dec 1 thru 10 - rewrote for understanding; nothing wrong with original
    'http://www.w3.org/TR/PNG/#9-table91
    
    'Filters may use the original values of the following bytes to generate the new byte value:
    '
    'x  the byte being filtered;
    'a  the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
    'b  the byte corresponding to x in the previous scanline;
    'c  the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).
    
    ' algorithm: Recon(x) = Filt(x) + Recon(a)
    ' Unsigned arithmetic modulo 256 is used, so that both the inputs and outputs fit into bytes

    Dim startByte As Long
    Dim n As Long, X As Long
    
    startByte = RowNr * ScanLine + 1
    
    On Error GoTo eh
    ' 1st n bytes for 1st pixel are unfiltered
    For n = startByte + stepVal To startByte + ScanLine - 2 Step stepVal
        For X = n To n + stepVal - 1
            Filtered(X) = (0 + Filtered(X) + Filtered(X - stepVal)) Mod 256
        Next
    Next
eh:
End Sub

Private Sub DecodeFilter_Up(Filtered() As Byte, ByVal RowNr As Long, ByVal ScanLine As Long, ByVal stepVal As Integer)

    ' // LaVolpe, Dec 1 thru 10 - rewrote for understanding; nothing wrong with original
    'http://www.w3.org/TR/PNG/#9-table91
    
    'Filters may use the original values of the following bytes to generate the new byte value:
    '
    'x  the byte being filtered;
    'a  the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
    'b  the byte corresponding to x in the previous scanline;
    'c  the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).
    
    ' algorithm:  Recon(x) = Filt(x) + Recon(b)
    ' Unsigned arithmetic modulo 256 is used, so that both the inputs and outputs fit into bytes
    
    Dim startByte As Long, X As Long
    
    On Error GoTo eh
    If Not RowNr = 0 Then    ' 1st row; there will be no Top row to get data from
        startByte = RowNr * ScanLine + 1
        For X = startByte To startByte + ScanLine - 2
            Filtered(X) = (0 + Filtered(X) + Filtered(X - ScanLine)) Mod 256
        Next
    End If
eh:
End Sub

Private Function UnfilterInterlaced(Filtered() As Byte) As Boolean

    ' // LaVolpe, Dec 1 thru 10 - built from scratch
    ' http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html#DR.Interlaced-data-order
    
    ' Progressive display/scan order per 8 pixel blocks (64 total pixels)
    '   1 6 4 6 2 6 4 6     ' 1st scan: 1 pixel (@col 0), row 0 [1/64 of total image]
    '   7 7 7 7 7 7 7 7     ' 2nd scan: 1 pixel (@col 4), row 0 [1/32 of image shown]
    '   5 6 5 6 5 6 5 6     ' 3rd scan: 2 pixels (@cols 0:4), row 4 [1/16 of image]
    '   7 7 7 7 7 7 7 7     ' 4th scan: 4 pixels (@cols 2:6), rows 0:4 [1/8]
    '   3 6 4 6 3 6 4 6     ' 5th scan: 8 pixels (@cols 0:2:4:6), rows 2:6 [1/4]
    '   7 7 7 7 7 7 7 7     ' 6th scan: 16 pixels (@cols 1:3:5:7), rows 0:2:4:6 [1/2]
    '   5 6 5 6 5 6 5 6     ' 7th scan: 32 pixels (@cols all), rows 1:3:5:7 [100%]
    '   7 7 7 7 7 7 7 7                 64 pixels, 15 scanlines over 7 passes
    
    ' Note : all logic in this routine is based off of the above grid.
    ' Scanline widths are only guaranteed to be same for each scanline in the same pass.
    ' Scanlines can be padded both horizontally & vertically if the image doesn't fit into
    '   a nice 8x8 grid evenly.
    ' Each scanline in interlaced image is also filtered, but they are filtered in relation
    ' to only the other scanlines in the same pass, different than non-interlaced images.
    ' Think of non-interlaced images as single-pass interlaced images.

    ' counter variables
    Dim Pass As Byte, srcRow As Long
    ' sizing/bit alignment variables
    Dim nr8wide As Long, nr8high As Long
    Dim nrBytes As Long, passPtr As Long
    Dim InterlacePass() As Byte  ' unfiltered progressive display (used 7x for 7 passes)
    ' bytes and bits per pixel values
    Dim bytesPP As Byte, BPRow As Long, bitPP As Byte
    
    ' need bit & byte information
    GetDepthInfo 0, 0, bitPP, bytesPP
    
    ' oversize array for "pass" bytes to prevent reszing array on each pass
    BPRow = GetBytesPerPixel((m_Width \ m_MatrixDat(7, MatrixColAdd)), bitPP)
    ' how many bytes are needed for the final pass; largest pass size in bytes
    nrBytes = (BPRow + 1) * (m_Height \ m_MatrixDat(7, MatrixRowAdd))
    ReDim InterlacePass(0 To nrBytes - 1&)

    ' interlaced images always come in 7 passes; although not all passes may be used
    For Pass = 1 To 7
        ' ensure bounds are valid. If image is smaller than 8x8 not all passes are valid/used
        ' Tested with images as small as 1x1
    
        ' calculate nr of pixels for this pass that will fit in width of image
        nr8wide = m_Width \ m_MatrixDat(Pass, MatrixColAdd) - (m_Width Mod m_MatrixDat(Pass, MatrixColAdd) > m_MatrixDat(Pass, MatrixCol))
        If nr8wide > 0& Then
            
            ' calcuate nr of rows for this pass that will fit in height of image
            nr8high = m_Height \ m_MatrixDat(Pass, MatrixRowAdd) - (m_Height Mod m_MatrixDat(Pass, MatrixRowAdd) > m_MatrixDat(Pass, MatrixRow))
            If nr8high > 0& Then
    
                ' calculate row bytes for the interlaced image, byte aligned
                BPRow = GetBytesPerPixel(nr8wide, bitPP) + 1&
                ' how many bytes are needed for the complete pass, less filter byte?
                nrBytes = BPRow * nr8high
                '^^ the filter routines expect the filter byte to be in its parameters, so add it
                
                ' unfilter the scanlines
                CopyMemory InterlacePass(0), Filtered(passPtr), nrBytes
                For srcRow = 0& To nr8high - 1&
                    Select Case Filtered(BPRow * srcRow + passPtr)
                    Case 0: ' no filtering
                    Case 1: ' sub filter
                        DecodeFilter_Sub InterlacePass, srcRow, BPRow, bytesPP
                    Case 2: ' up filter
                        DecodeFilter_Up InterlacePass, srcRow, BPRow, 0
                    Case 3: ' average filter
                        DecodeFilter_Avg InterlacePass, srcRow, BPRow, bytesPP
                    Case 4: ' paeth filter
                        DecodeFilter_Paeth InterlacePass, srcRow, BPRow, bytesPP
                    Case Else
                        ' If we got here, there is a different filtering mechanism at large
                        Exit Function
                    End Select
                Next
        
                ' offset the filtered array pointer to account for the 1byte filter flag per scanline
                ' This will point to the next pass's X,Y position in the Unfiltered() array
                passPtr = passPtr + nrBytes
            
                ' send unfiltered array to be transfered to the DIB
                ' color formats broken into different routines to help speed up transfering
                Select Case m_ColorType
                Case clrTrueAlpha, clrGrayAlpha, clrTrueColor
                    If m_BitDepth < 16& Then
                        If ConvertPNGtoBMP_NonPalette(InterlacePass(), Pass, nr8high, BPRow) = False Then Exit Function
                    Else
                        If ConvertPNGtoBMP_16Bit(InterlacePass(), Pass, nr8high, BPRow) = False Then Exit Function
                    End If
                Case clrPalette, clrGrayScale
                    If m_BitDepth < 16& Then
                        If ConvertPNGtoBMP_Palettes(InterlacePass(), Pass, nr8high, BPRow) = False Then Exit Function
                    Else
                        If ConvertPNGtoBMP_16Bit(InterlacePass(), Pass, nr8high, BPRow) = False Then Exit Function
                    End If
                End Select
            End If ' check for nr8high < 1
        End If ' check for nr8wide < 1
    
    Next Pass

    UnfilterInterlaced = True
    
End Function

Private Function UnfilterNI(filteredData() As Byte) As Boolean

    ' // LaVolpe, Dec 1 thru 10 - completely rewritten to remove excess large array usage
    ' http://www.w3.org/TR/PNG/#9-table91

    Dim Row As Long, BPRow As Long
    Dim lBpp As Byte, stepVal As Byte
    
    GetDepthInfo 0, 0, lBpp, stepVal
    BPRow = GetBytesPerPixel(m_Width, lBpp) + 1&
    '^^ the filtered row contains an extra byte (1st byte of each row)
    '   that identifies the filter algorithm used for that row
    
    For Row = 0& To m_Height - 1&

        Select Case filteredData(BPRow * Row)
        Case 0 'no filtering
        Case 1 'Sub
            DecodeFilter_Sub filteredData, Row, BPRow, stepVal
        Case 2 'Up
            DecodeFilter_Up filteredData, Row, BPRow, 0
        Case 3 'Average
            DecodeFilter_Avg filteredData, Row, BPRow, stepVal
        Case 4 'Paeth
            DecodeFilter_Paeth filteredData, Row, BPRow, stepVal
        Case Else
            ' invalid filter type; no action
        End Select
        
    Next Row
    
    ' color formats broken into different routines to help speed up transferring
    Select Case m_ColorType
    Case clrTrueAlpha, clrGrayAlpha, clrTrueColor
        If m_BitDepth < 16& Then
            UnfilterNI = ConvertPNGtoBMP_NonPalette(filteredData(), 8, Row, BPRow, 0)
        Else
            UnfilterNI = ConvertPNGtoBMP_16Bit(filteredData(), 8, Row, BPRow, 0)
        End If
    Case clrPalette, clrGrayScale
        If m_BitDepth < 16& Then
            UnfilterNI = ConvertPNGtoBMP_Palettes(filteredData(), 8, Row, BPRow, 0)
        Else
            UnfilterNI = ConvertPNGtoBMP_16Bit(filteredData(), 8, Row, BPRow, 0)
        End If
    End Select
    
End Function

Private Function zChunk_IHDR(bufLen As Long, streamOffset As Long, cmprSize As Long, crcValue As Long) As Long
                
    ' IHDR structure
    '    Width As Long              << cannot be negative
    '    Height As Long             << cannot be negative
    '    BitDepth As Byte           << must be 1,2,4,8,16
    '    ColorType As Byte          << must be 0,2,3,4,6
    '    Compression As Byte        << must be zero
    '    Filter As Byte             << must be zero
    '    Interlacing As Byte        << must be zero or one
    
    On Error Resume Next
    Dim lRtn As Long, lValue As Long
    
    If Not crcValue = 0& Then
        lRtn = Not (zCheckCRCvalue(VarPtr(pngStream(streamOffset)), bufLen + 4&, crcValue))
    End If
    If lRtn = 0& Then
        
        CopyMemory m_Width, pngStream(streamOffset + 4&), 4&
        m_Width = iparseReverseLong(m_Width)
        CopyMemory m_Height, pngStream(streamOffset + 8&), 4&
        m_Height = iparseReverseLong(m_Height)
        
        If m_Width < 1& Or m_Height < 1& Then
            lRtn = 1& 'Corrupted Image Header. Cannot continue.
        
        Else
            
            If Not pngStream(streamOffset + 14&) = 0 Then
                lRtn = 1& ' Invalid Compression Flag in Header. Cannot continue.
            Else
                If Not pngStream(streamOffset + 15&) = 0 Then
                    lRtn = 1& 'Invalid Filter Flag in Header. Cannot continue.
                Else
                    
                    m_BitDepth = pngStream(streamOffset + 12&)
                    Select Case m_BitDepth
                    Case 1&, 2&, 4&, 8&, 16&
                        ' it is a valid bit depth
                        m_ColorType = pngStream(streamOffset + 13&)
                        Select Case m_ColorType
                        Case 0&, 2&, 3&, 4&, 6&
                            ' it is a valid color type
                            m_Interlacing = pngStream(streamOffset + 16&)
                            If m_Interlacing > 1& Then
                                lRtn = 1& 'Invalid Interlacing Flag in Header. Cannot continue.
                            End If
                        Case Else
                            lRtn = 1& 'Invalid Color Type Flag in Header. Cannot continue.
                        End Select
                    Case Else
                        lRtn = 1& 'Invalid Bit Depth Flag in Header. Cannot continue.
                    End Select
                    
                End If  ' Filter flag
            End If  ' Compression flag
        End If  ' Dimensions
        
        If lRtn = 0& Then
            ' check for png sizes that would cause overflow errors in other calculations...
            ' This has 2 basic checks
            ' check DWord width alignment * height first are within bounds
            lValue = 32& * m_Width * m_Height     ' max number of bytes needed for DIB
            ' see if uncompress png data is too long
            If Not Err Then
                cmprSize = CalcUncompressedWidth()
            End If
            If Err Then
                Err.Clear
                lRtn = 1&
            End If
        End If
    End If

    zChunk_IHDR = lRtn

End Function

Private Function zChunk_PLTE(bufLen As Long, streamOffset As Long, crcValue As Long) As Long

    ' http://www.w3.org/TR/PNG/#11PLTE
    If m_ColorType = 0& Or m_ColorType = 4& Then Exit Function
    '^^ per specs, palettes shall not appear for those color types
    '   Since we can ignore the palette, we won't trigger a critcal error
    
    Dim lRtn As Long
    If Not crcValue = 0& Then
        lRtn = Not (zCheckCRCvalue(VarPtr(pngStream(streamOffset)), bufLen + 4&, crcValue))
    End If
    If lRtn = 0& Then
        
        ' per png specs, palette must be divisible by 3
        If bufLen Mod 3& = 0& Then
            ReDim m_Palette(0 To bufLen - 1&)
            CopyMemory m_Palette(0), pngStream(streamOffset + 4&), bufLen
        Else ' error
            lRtn = 1& 'Invalid Palette. Cannot continue.
        End If
    End If
    zChunk_PLTE = lRtn

End Function

Private Function zChunk_tRNS(bufLen As Long, streamOffset As Long, crcValue As Long) As Long
'http://www.w3.org/TR/PNG/#11tRNS
    
    If m_ColorType > clrPalette Then Exit Function
    ' Per specs, the tRNS chunk shall not be used for Color Types 4 and 6

    On Error GoTo ExitMe
    Dim UB As Long, palIndex As Byte, lRtn As Long
    
    If Not crcValue = 0& Then
        lRtn = Not (zCheckCRCvalue(VarPtr(pngStream(streamOffset)), bufLen + 4, crcValue))
    End If
    If lRtn = 0& Then
        
        ' we will ensure the passed array is dimensioned properly and also cache
        ' the simple transparency color for easier reference while processing
        
        ReDim m_TransSimple(0 To bufLen - 1&)
        CopyMemory m_TransSimple(0), pngStream(streamOffset + 4&), bufLen
    
        If m_ColorType = clrGrayScale Then ' grayscale with simple transparency
            ' least significant bits used. Tweak array to hold only those bits in byte format
            m_TransColor = m_TransSimple(1) ' color-index value not a color
            
        ElseIf m_ColorType = clrTrueColor Then ' rgb triple (true color)
            ' save as BGR to be compared against PNG samples
            m_TransColor = m_TransSimple(5) Or m_TransSimple(3) * &H100& Or m_TransSimple(1) * &H10000
            ' for 16bpp PNGs, the 0,2,4 array elements are needed also but will be tested in ConvertPngToBmp
            
        ElseIf m_ColorType = clrPalette Then ' TransSimple() is an array
            ' This array is directly related to the Palette. Each palette entry
            ' will have a related TransSimple() entry. Exception: When Palette entries
            ' are sorted (in ascending order of alpha value), then any Palette entries
            ' that have alpha values of 255 probably will not be in that related array.
            ' In these cases, we will fake it & provide the missing entries.
        
            ' to prevent out of bounds errors, ensure array is 255
            If UBound(m_TransSimple) < 255& Then ' pngs are not required to provide all
                UB = UBound(m_TransSimple)
                ReDim Preserve m_TransSimple(0 To 255)    ' prevent out ouf bounds errors
                FillMemory m_TransSimple(UB + 1&), 255& - UB, 255
            End If
            m_TransColor = 0& ' simply a flag > -1, has no other meaning
        End If
        
        If Err Then
            Err.Clear   ' an error regarding the TransSimple() array
            m_TransColor = -1& ' no transparency color
        End If
    
    End If

ExitMe:
End Function

Private Sub InitializePalette()

    ' Purpose: Create a palette for the PNG file, if needed
    ' The colors from the palette will be transfered to the 32bpp image

    If m_ColorType = clrPalette Or m_ColorType = clrGrayScale Then
    
        Dim nrEntries As Long, stepVal As Long
        Dim X As Long, Index As Long, Color As Long

        ' PNG grayscale palettes are not provided, they are assumed...
        If iparseIsArrayEmpty(VarPtrArray(m_Palette)) = 0& Then
            
            ReDim m_Palette(0 To 767)
            If m_ColorType = clrGrayScale Then
            
                nrEntries = pow2x8(m_BitDepth) - 1&       ' number grayscale palette entries
                stepVal = 255 \ (pow2x8(m_BitDepth) - 1&) ' step value for the palette
                For X = 1& To nrEntries
                    Color = X * stepVal
                    Index = X * 3&
                    m_Palette(Index) = Color
                    m_Palette(Index + 1&) = Color
                    m_Palette(Index + 2&) = Color
                Next
                
            ElseIf m_BitDepth = 1 Then              ' fix up 2 color palette
                CopyMemory m_Palette(3), vbWhite, 3&
            End If
        End If
    
    End If
    
End Sub

Private Function IsPNG() As Boolean

    ' Purpose: Determine if PNG magic number exists in 1st 8 bytes of the file/array
    ' Note: array was already validated as not empty when class LoadStream/LoadFile was called
    Dim gpLong As Long
    
    ' validate we are looking at a png file
    CopyMemory gpLong, pngStream(LBound(pngStream)), 4&
    If gpLong = png_Signature1 Then
        CopyMemory gpLong, pngStream(LBound(pngStream) + 4), 4&
        IsPNG = (gpLong = png_Signature2)
    End If

End Function



' decompression using pure VB, this is only run if system
' does not have GDI+ nor does it have zLib.dll
' source by: alfred.koppold@freenet.de
' Note: This does have a calc error I haven't been able to track down yet.
'   The error is noticable in very few PNGs (especially 1bpp pngs), but can be visually noticed

Private Function vbDecompress(outStream() As Byte, CompressedArray() As Byte, ByVal UncompressedSize As Long, Optional ZIP64 As Boolean = False) As Boolean
    
    Dim IsLastBlock As Boolean
    Dim CompType As Long
    Dim Char As Long
    Dim Nubits As Long
    Dim L1 As Long
    Dim L2 As Long
    Dim X As Long
    Dim lRtn As Long
    Dim MinLLength As Long
    Dim MaxLLength As Long
    Dim MinDLength As Long
    Dim MaxDLength As Long
    Dim IsStaticBuild As Boolean
    
    On Error GoTo Stop_Decompression
    
    Dim tSAIN As SafeArray
    With tSAIN
        .cbElements = 1
        .cDims = 1
        .pvData = VarPtr(CompressedArray(0))
        .rgSABound(0).cElements = UBound(CompressedArray) + 1&
    End With
    CopyMemory ByVal VarPtrArray(inStream), VarPtr(tSAIN), 4&
    
    'InStream = ByteArray
    Call vbInit_Decompress
    Inpos = 2&
    Do
        IsLastBlock = vbGetBits(1)    ' last compressed block?
        CompType = vbGetBits(2)       ' compression type used for block
        If CompType = 0& Then
            If Inpos + 4& > UBound(inStream) Then
                ' ensure 4 more bytes exist for L1 and L2 below
                lRtn = -1&
                Exit Do
            End If
        
            If BitNum >= 8& Then
                Inpos = Inpos - (BitNum \ 8&)
                BitNum = BitNum - ((BitNum \ 8&) * 8&)
            End If
            
            L1 = inStream(Inpos) Or (inStream(Inpos + 1&) * &H100&)
            L2 = inStream(Inpos + 2&) Or (inStream(Inpos + 3&) * &H100&)
            
            Inpos = Inpos + 4&
            If L1 - (Not (L2) And &HFFFF&) Then
                lRtn = -2&
                Exit Do
            End If
            If Inpos + L1 - 1& > UBound(inStream) Then
                lRtn = -1&
                Exit Do
            End If
            
            If OutPos + L1 > UncompressedSize Then
                lRtn = -1&
                Exit Do
            End If
            
            CopyMemory outStream(OutPos), inStream(Inpos), L1
            OutPos = OutPos + L1
            Inpos = Inpos + L1
            ByteBuff = 0&
            BitNum = 0&
            
        ElseIf CompType = 3& Then
            lRtn = -1&
            Exit Do
            
        Else
            If CompType = 1& Then
                If Not vbCreate_Static_Tree(MinLLength, MaxLLength, MinDLength, MaxDLength, IsStaticBuild) = 0& Then
                    lRtn = 9&
                    Exit Do
                End If
            Else
                If Not vbCreate_Dynamic_Tree(MinLLength, MaxLLength, MinDLength, MaxDLength) = 0& Then
                    lRtn = 9&
                    Exit Do
                End If
            End If
 
            Do
                vbNeedBits MaxLLength
                Nubits = MinLLength
                Do While Not LitLen.Length(ByteBuff And BitMask(Nubits)) = Nubits
                    Nubits = Nubits + 1&
                Loop
 
                Char = LitLen.code(ByteBuff And BitMask(Nubits))
                vbDropBits Nubits
                
                If Char < 256& Then
                    outStream(OutPos) = Char
                    OutPos = OutPos + 1&
                    
                ElseIf Char > 256& Then
                    Char = Char - 257&
                    L1 = LCodes.code(Char) + vbGetBits(LCodes.Length(Char))
                    If (L1 = 258&) And ZIP64 Then L1 = vbGetBits(16) + 3&
                    vbNeedBits MaxDLength
                    Nubits = MinDLength

                    Do While Not Dist.Length(ByteBuff And BitMask(Nubits)) = Nubits
                        Nubits = Nubits + 1&
                    Loop
 
                    Char = Dist.code(ByteBuff And BitMask(Nubits))
                    vbDropBits Nubits
                    L2 = DCodes.code(Char) + vbGetBits(DCodes.Length(Char))
    
                    For X = 1& To L1
                        If OutPos > UncompressedSize Then
                            OutPos = UncompressedSize
                            GoTo Stop_Decompression
                        End If
                         outStream(OutPos) = outStream(OutPos - L2)
                         OutPos = OutPos + 1&
                    Next X
                
                End If

            Loop While Not Char = 256& 'EOF
        End If
    Loop While Not IsLastBlock
    
Stop_Decompression:

CopyMemory ByVal VarPtrArray(inStream), 0&, 4&

Erase BitMask
Erase Pow2
Erase LCodes.code
Erase LCodes.Length
Erase DCodes.code
Erase DCodes.Length
Erase LitLen.code
Erase LitLen.Length
Erase Dist.code
Erase Dist.Length
Erase LenOrder

If Err Then
    lRtn = Err.Number
    Err.Clear
End If

vbDecompress = (lRtn = 0&)

End Function

Private Function vbCreate_Static_Tree(MinLLength As Long, MaxLLength As Long, MinDLength As Long, MaxDLength As Long, IsStaticBuild As Boolean) As Long

    Dim X As Long
    Dim Length(0 To 287) As Long

    If IsStaticBuild = False Then
        ' quick fill the tree (tile Blt)
        For X = 0& To 7&: Length(X) = 8&: Next ' 0 to 143 elements = 8
        For X = X To 143& Step 8&
            CopyMemory Length(X), Length(0), 32&
        Next
        For X = X To X + 7&: Length(X) = 9&: Next ' 144 to 255 elements = 9
        For X = X To 255& Step 8&
            CopyMemory Length(X), Length(144), 32&
        Next
        For X = X To X + 7&: Length(X) = 7&: Next ' 256 to 279 elements = 7
        For X = X To 279& Step 8&
            CopyMemory Length(X), Length(256), 32&
        Next
        For X = X To X + 3&: Length(X) = 8&: Next ' 280 to 287 elements = 8
        CopyMemory Length(X), Length(280), 16&
        
        If Not vbCreate_Codes(TempLit, Length, 287&, MaxLLength, MinLLength) = 0& Then
             vbCreate_Static_Tree = -1&
            Exit Function
        End If
        
        For X = 0& To 7&: Length(X) = 5&: Next  ' reset 0 to 32 to 5's
        For X = X To 31& Step 8&
            CopyMemory Length(X), Length(0), 32&
        Next
        vbCreate_Static_Tree = vbCreate_Codes(TempDist, Length, 31&, MaxDLength, MinDLength)
        IsStaticBuild = True
        
    Else
        MinLLength = 7&
        MaxLLength = 9&
        MinDLength = 5&
        MaxDLength = 5&
    End If

    LitLen = TempLit
    Dist = TempDist
End Function

Private Function vbCreate_Dynamic_Tree(MinLLength As Long, MaxLLength As Long, MinDLength As Long, MaxDLength As Long) As Long
    Dim Length() As Long
    Dim Bl_Tree As CodesType
    Dim MinBL As Long
    Dim MaxBL As Long
    Dim NumLen As Long
    Dim Numdis As Long
    Dim NumCod As Long
    Dim Char As Long
    Dim Nubits As Long
    Dim LN As Long
    Dim Pos As Long
    Dim X As Long

    NumLen = vbGetBits(5) + 257&
    Numdis = vbGetBits(5) + 1&
    NumCod = vbGetBits(4) + 4&

    ReDim Length(18)
    For X = 0& To NumCod - 1&
        Length(LenOrder(X)) = vbGetBits(3)
    Next

    For X = NumCod To 18&
        Length(LenOrder(X)) = 0&
    Next

    If Not vbCreate_Codes(Bl_Tree, Length, 18&, MaxBL, MinBL) = 0& Then
        vbCreate_Dynamic_Tree = -1&
        Exit Function
    End If
    
    ReDim Length(NumLen + Numdis)
    Pos = 0&
    Do While Pos < NumLen + Numdis
        vbNeedBits MaxBL
        Nubits = MinBL
        
        Do While Not Bl_Tree.Length(ByteBuff And BitMask(Nubits)) = Nubits
             Nubits = Nubits + 1&
        Loop

        Char = Bl_Tree.code(ByteBuff And BitMask(Nubits))
        vbDropBits Nubits

        If Char < 16& Then
            Length(Pos) = Char
            Pos = Pos + 1&
        Else
            If Char = 16& Then
                If Pos = 0& Then
                    vbCreate_Dynamic_Tree = -5&
                    Exit Function
                End If
                LN = Length(Pos - 1&)
                Char = 3& + vbGetBits(2)
            ElseIf Char = 17 Then
                 Char = 3& + vbGetBits(3)
                 LN = 0&
            Else
                Char = 11& + vbGetBits(7)
                LN = 0&
            End If

            If Pos + Char > NumLen + Numdis Then
                vbCreate_Dynamic_Tree = -6&
                Exit Function
            End If

            Do While Char > 0&
                Char = Char - 1&
                Length(Pos) = LN
                Pos = Pos + 1&
            Loop
        End If
    Loop

    If Not vbCreate_Codes(LitLen, Length, NumLen - 1, MaxLLength, MinLLength) = 0& Then
        vbCreate_Dynamic_Tree = -1&
        Exit Function
    End If

    For X = 0& To Numdis
        Length(X) = Length(X + NumLen)
    Next
    
    vbCreate_Dynamic_Tree = vbCreate_Codes(Dist, Length, Numdis - 1&, MaxDLength, MinDLength)

End Function

Private Function vbCreate_Codes(tree As CodesType, Lengths() As Long, NumCodes As Long, MaxBits As Long, Minbits As Long) As Long
    
    Dim Bits(16) As Long
    Dim next_code(16) As Long
    Dim code As Long
    Dim LN As Long
    Dim X As Long

    Minbits = 16&
    For X = 0& To NumCodes
        Bits(Lengths(X)) = Bits(Lengths(X)) + 1&
        If Lengths(X) > MaxBits Then MaxBits = Lengths(X)
        If Lengths(X) < Minbits And Lengths(X) > 0& Then Minbits = Lengths(X)
    Next

    LN = 1&
    For X = 1& To MaxBits
        LN = LN + LN
        LN = LN - Bits(X)
        If LN < 0& Then
            vbCreate_Codes = LN
            Exit Function
        End If
    Next

    vbCreate_Codes = LN
    ReDim tree.code(2& ^ MaxBits - 1&)
    ReDim tree.Length(2& ^ MaxBits - 1&)
    code = 0&
    Bits(0) = 0&
    
    For X = 1& To MaxBits
        code = (code + Bits(X - 1&)) * 2&
        next_code(X) = code
    Next

    For X = 0& To NumCodes
        LN = Lengths(X)
        If Not LN = 0& Then
            code = vbBit_Reverse(next_code(LN), LN)
            tree.Length(code) = LN
            tree.code(code) = X
            next_code(LN) = next_code(LN) + 1&
        End If
    Next

End Function

Private Function vbBit_Reverse(ByVal Value As Long, ByVal Numbits As Long) As Long

    Do While Numbits > 0&
        vbBit_Reverse = vbBit_Reverse * 2& + (Value And 1&)
        Numbits = Numbits - 1&
        Value = Value \ 2&
    Loop
    
End Function

Private Sub vbInit_Decompress()
    
    Dim Temp()
    Dim X As Long

    Erase LitLen.code
    Erase LitLen.Length
    Erase Dist.code
    Erase Dist.Length

    ReDim LCodes.code(31)
    ReDim LCodes.Length(31)
    ReDim DCodes.code(31)
    ReDim DCodes.Length(31)
    ReDim LenOrder(0 To 18)

    Temp() = Array(16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15)
    For X = 0 To UBound(Temp): LenOrder(X) = Temp(X): Next
     Temp() = Array(3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258)
    For X = 0 To UBound(Temp): LCodes.code(X) = Temp(X): Next
     Temp() = Array(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0)
    For X = 0 To UBound(Temp): LCodes.Length(X) = Temp(X): Next
     Temp() = Array(1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32769, 49153)
    For X = 0 To UBound(Temp): DCodes.code(X) = Temp(X): Next
     Temp() = Array(0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14)
    For X = 0 To UBound(Temp): DCodes.Length(X) = Temp(X): Next
    
    ReDim Pow2(0 To 16)
    ReDim BitMask(0 To 16)
    Pow2(0) = 1&
    For X = 1& To 16&
     Pow2(X) = Pow2(X - 1&) * 2&
     BitMask(X) = Pow2(X) - 1&
    Next
    OutPos = 0&
    Inpos = 0&
    ByteBuff = 0&
    BitNum = 0&

End Sub

Private Sub vbNeedBits(Numbits As Long)
    Do While BitNum < Numbits
        If Inpos > UBound(inStream) Then Exit Do
        ByteBuff = ByteBuff + (inStream(Inpos) * Pow2(BitNum))
        BitNum = BitNum + 8&
        Inpos = Inpos + 1&
    Loop
End Sub

Private Sub vbDropBits(Numbits As Long)
    ByteBuff = ByteBuff \ Pow2(Numbits)
    BitNum = BitNum - Numbits
End Sub

Private Function vbGetBits(Numbits As Long) As Long
    
    While BitNum < Numbits
        ByteBuff = ByteBuff + (inStream(Inpos) * Pow2(BitNum))
        BitNum = BitNum + 8&
        Inpos = Inpos + 1&
    Wend
    vbGetBits = ByteBuff And BitMask(Numbits)
    ByteBuff = ByteBuff \ Pow2(Numbits)
    BitNum = BitNum - Numbits

End Function


' =======================================
' FOLLOWING 3 FUNCTIONS ARE ZLIB RELATED
' =======================================

Private Function zValidateZLIBversion() As Boolean

    ' Test for zlib availability & compatibility
    ' see modParsers.iparseValidateZLib for details
    
    Dim b_cdecl As Boolean, DllName As String
    
    If iparseValidateZLIB(DllName, m_ZLIBver, b_cdecl, False) = True Then
        If b_cdecl = True Then
            Set cCfunction = New cCDECL
            cCfunction.DllLoad DllName
        End If
        zValidateZLIBversion = True
    End If
            
    
End Function

Private Function zCheckCRCvalue(ByVal crcTestRef As Long, ByVal valLength As Long, ByVal srcCRCvalue As Long) As Boolean

    ' function returns zLIB's CRC value for passed crcTestRef value.
    Dim lReturn As Long
    If cCfunction Is Nothing Then
        If m_ZLIBver = 1& Then
            lReturn = Zcrc32(0&, ByVal crcTestRef, valLength)
        ElseIf m_ZLIBver = 2& Then
            lReturn = Zcrc321(0&, ByVal crcTestRef, valLength)
        End If
    Else
        lReturn = cCfunction.CallFunc("crc32", 0&, crcTestRef, valLength)
    End If
    If Not lReturn = 0& Then
        zCheckCRCvalue = (srcCRCvalue = iparseReverseLong(lReturn))
    End If
    
End Function

Private Function zInflate(ByVal srcRef As Long, ByVal srcSizeRef As Long, ByVal destRef As Long, ByVal destSize As Long) As Boolean

    ' function uncompresses/inflates passed srcRef into passed destRef and modifies the destSizeRef to indicate byte count of destRef
    Dim lReturn As Long
    If cCfunction Is Nothing Then
        If m_ZLIBver = 1& Then
            zInflate = (Zuncompress(ByVal srcRef, ByVal srcSizeRef, ByVal destRef, destSize) = 0&)
        ElseIf m_ZLIBver = 2& Then
            zInflate = (Zuncompress1(ByVal srcRef, ByVal srcSizeRef, ByVal destRef, destSize) = 0&)
        End If
    Else
        zInflate = (cCfunction.CallFunc("uncompress", srcRef, srcSizeRef, destRef, destSize) = 0&)
    End If

End Function

