cPNGwriter.cls
上传用户:jnjasmy
上传日期:2015-01-04
资源大小:637k
文件大小:85k
源码类别:

GDI/图象编程

开发平台:

Visual Basic

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4.   Persistable = 0  'NotPersistable
  5.   DataBindingBehavior = 0  'vbNone
  6.   DataSourceBehavior  = 0  'vbNone
  7.   MTSTransactionMode  = 0  'NotAnMTSObject
  8. END
  9. Attribute VB_Name = "cPNGwriter"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = True
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14. Option Explicit
  15. ' This class is a partial version of a full-blown PNG creation class.
  16. ' It creates a PNG with many options, but not nearly all PNG options avaialble
  17. ' and is specifically modified to creating PNGs from pre-multiplied 32bpp DIBs.
  18. ' There still may be references to Interlacing. Interlacing options do not
  19. ' exist in this class and any such references are results of extracting the
  20. ' routines from the full-version PNG creation class.
  21. ' Note that GDI+ does not offer any PNG options when creating PNGs, this class
  22. ' exposes several options and can be modified to support all PNG options.
  23. ' CUSTOM TAILORED FOR PRE-MULTIPLIED 32bpp DIBS. Routines not portable for normal DIBs.
  24. ' Required is a version of the zLIB DLL which can be found at www.zlib.net.
  25. ' zLIB comes in at least two varieties: C calling convention (_cdecl) and
  26. ' VB/PASCAL calling convention (_stdcall). This routine can use either of those
  27. ' conventions, but the zLIB file must be named one of the two following,
  28. ' not case sensitive, both are original filenames:  zLib.dll or zLib1.dll
  29. ' Key highlights:
  30. ' 1. PNGs can be created without GDI+ as long as zlib or zlib1 is present
  31. ' 2. Using bit reduction algorithms, a 32bpp DIB can be converted to one of the
  32. '       the following:  8 bpp paletted, 24 bpp or 32 bpp PNGs; supporting full alpha
  33. ' 3. The PNG compression filtering mechanism in this routine is user-selected.
  34. '       Filters assist in reorganizing byte information to make it compress better
  35. '       Speed vs Size tradeoffs: filter type None is fastest while type Paeth is smallest (generally)
  36. '       See notes in FilterImage routine, set filter in c32bppDIB.PngPropertySet routine
  37. ' 4. Over a dozen options available when creating PNGs, see Me.AddProperty
  38. ' 5. This class almost always creates smaller PNG files than GDI+ when default filtering is used
  39. ' 6. PNGs can be saved to file or saved to an array
  40. ' array mapping structures
  41. Private Type SafeArrayBound
  42.     cElements As Long               ' number of array items
  43.     lLbound As Long                 ' the LBound of the array
  44. End Type
  45. Private Type SafeArray
  46.     cDims As Integer                ' numer of dimensions (1) for this UDT
  47.     fFeatures As Integer            ' not used
  48.     cbElements As Long              ' byte size of each element (byte=1,Int=2,Long=4)
  49.     cLocks As Long                  ' not used
  50.     pvData As Long                  ' pointer to memory space containing array
  51.     rgSABound(0 To 1) As SafeArrayBound
  52. End Type
  53. Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
  54. Private Declare Sub FillMemory Lib "kernel32.dll" Alias "RtlFillMemory" (ByRef Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
  55. ' change to msvbvm50.dll for VB5 projects:
  56. Private Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (Ptr() As Any) As Long
  57. ' zLIB calls, needed to compress/decompress png data
  58. ' ///////////// ZLIB.DLL REQUIREMENT \\\\\\
  59. ' validated via ValidateDLLExists function
  60. Private Declare Function Zcrc32 Lib "zlib.dll" Alias "crc32" (ByVal crc As Long, ByRef buf As Long, ByVal Length As Long) As Long
  61. Private Declare Function Zcompress Lib "zlib.dll" Alias "compress" (ByRef Dest As Long, ByRef destLen As Long, ByRef Source As Long, ByVal sourceLen As Long) As Long
  62. Private Declare Function Zcompress2 Lib "zlib.dll" Alias "compress2" (ByRef Dest As Long, ByRef destLen As Long, ByRef Source As Long, ByVal sourceLen As Long, ByVal Level As Long) As Long
  63. Private Declare Function Zcrc321 Lib "zlib1.dll" Alias "crc32" (ByVal crc As Long, ByRef buf As Any, ByVal Length As Long) As Long
  64. Private Declare Function Zcompress1 Lib "zlib1.dll" Alias "compress" (ByRef Dest As Long, ByRef destLen As Long, ByRef Source As Long, ByVal sourceLen As Long) As Long
  65. Private Declare Function Zcompress21 Lib "zlib1.dll" Alias "compress2" (ByRef Dest As Long, ByRef destLen As Long, ByRef Source As Long, ByVal sourceLen As Long, ByVal Level As Long) As Long
  66. Private Const zlibMaxCompression = 9
  67. Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, lpOverlapped As Any) As Long
  68. Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
  69. Private Const INVALID_HANDLE_VALUE = -1
  70. Private Enum eColorTypes    ' internal use only
  71.     clrGrayScale = 0
  72.     clrTrueColor = 2
  73.     clrPalette = 3
  74.     clrGrayAlpha = 4
  75.     clrTrueAlpha = 6
  76. End Enum
  77. '----------------------------------------------------------------------------
  78. ' following are optional PNG properties only
  79. Private m_Filter As eFilterMethods
  80. Private m_bKGD As Long          ' default PNG background color if a view opts to render against solid bkg
  81. Private m_Captions() As String  ' see c32bppDIB.ePngProperties
  82. Private m_PNGprops As Long      ' indicates which, if any, m_Captions are used
  83. '----------------------------------------------------------------------------
  84. Private cCfunction As cCDECL        ' class to allow using C calling convention
  85. Private m_ZLIBver As Long           ' which version of zLIB?
  86. Private m_Palette() As Byte         ' PNG palette if image can be palettized
  87. Private m_transPal() As Byte        ' alpha values for PNG palettes as needed
  88. Private m_Uncompressed() As Byte    ' initialized, contains uncompressed DIB bytes in 8,24,32 bit formats
  89. Private m_Stream() As Byte          ' never initialized, overlay to host 32bpp DIB
  90. Private m_Trans As Long             ' flag indicating whether or not transparency is used in DIB
  91. Private m_ColorType As eColorTypes  ' the color type the PNG will be created in
  92. Friend Function SavePNGex(cHost As c32bppDIB, FileName As String, outStream() As Byte) As Boolean
  93.     If cHost.Handle = 0& Then Exit Function
  94.     
  95.     Dim tSA As SafeArray    ' overlay onto our DIB as needed
  96.     Dim bSuccess As Boolean
  97.     Dim fileNum As Integer
  98.     Dim hFile As Long
  99.     Dim bSkipBKGD As Boolean
  100.     
  101.     ' if we don't have Zlib, we can't continue with this class
  102.     If zValidateZLIBversion = False Then Exit Function
  103.     
  104.     If Not FileName = vbNullString Then
  105.         hFile = iparseGetFileHandle(FileName, False)
  106.         If (hFile = INVALID_HANDLE_VALUE) Then Exit Function
  107.     End If
  108.     
  109.     With tSA                ' overlay DIB
  110.         .cbElements = 1
  111.         .cDims = 2
  112.         .pvData = cHost.BitsPointer
  113.         .rgSABound(0).cElements = cHost.Height
  114.         .rgSABound(1).cElements = cHost.scanWidth
  115.     End With
  116.     CopyMemory ByVal VarPtrArray(m_Stream), VarPtr(tSA), 4&
  117.     
  118.     ' optimizations to reduce bit depth and reduce palette data
  119.     
  120.      On Error GoTo ExitRoutine
  121.     ' Can image be palettized (smallest PNG size)?
  122.     m_Trans = -1&
  123.     If PalettizeImage(cHost.Alpha) = False Then
  124.         ' if not, can we reduce to 24bpp from 32bpp?
  125.         OptimizeTrueColor cHost.Alpha
  126.     End If
  127.     '  The above functions converted 32bpp DIB to necessary format for PNG creation
  128.     ' The conversion is in the m_uncompress array. Remove overlay now
  129.     CopyMemory ByVal VarPtrArray(m_Stream), 0&, 4&
  130.     tSA.cDims = 0
  131.     
  132. ''   CREATE THE PNG using following rules
  133. ''   ------------------------------------
  134. ''    Critical chunks (must appear in this order):
  135. ''
  136. ''               Name  Multiple Ok?  Ordering constraints
  137. ''               IHDR    No          Must be first
  138. ''               PLTE    No          Before first IDAT (chunk is optional)
  139. ''               IDAT    Yes         Multiple IDATs must be consecutive
  140. ''               IEND    No          Must be last
  141. ''
  142. ''    Ancillary chunks (among other ancilliary chunks, order is not dictated):
  143. ''
  144. ''               Name  Multiple OK?  Ordering constraints relative to Critical chunks
  145. ''               cHRM    No          Before PLTE and IDAT
  146. ''               gAMA    No          Before PLTE and IDAT
  147. ''               iCCP    No          Before PLTE and IDAT
  148. ''               sBIT    No          Before PLTE and IDAT
  149. ''               sRGB    No          Before PLTE and IDAT
  150. ''               bKGD    No          After PLTE; before IDAT
  151. ''               hIST    No          After PLTE; before IDAT
  152. ''               tRNS    No          After PLTE; before IDAT
  153. ''               pHYs    No          Before IDAT
  154. ''               sPLT    Yes         Before IDAT
  155. ''               tIME    No          None
  156. ''               iTXt    Yes         None
  157. ''               tEXt    Yes         None
  158. ''               zTXt    Yes         None
  159.     
  160.     ' Write the PNG header
  161.     If Write_IHDR(hFile, outStream, cHost, False) = False Then GoTo ExitRoutine
  162.     If Write_tEXt(hFile, outStream, True) = False Then GoTo ExitRoutine ' write the Author & Title if needed
  163.     If Write_PLTE(hFile, outStream, bSkipBKGD) = False Then GoTo ExitRoutine ' write the palette
  164.     If Not bSkipBKGD Then   ' < may be set when bkgd color is not part of palette (Color Type 3 only)
  165.         If Write_bKGD(hFile, outStream) = False Then GoTo ExitRoutine ' write bkgd color
  166.     End If
  167.     If Write_tRNS(hFile, outStream) = False Then GoTo ExitRoutine ' write transparency info
  168.     ' Here we are going to filter & compress the DIB data & then write the IDAT chunk
  169.     If FilterImage(hFile, outStream, cHost, m_Filter) = False Then GoTo ExitRoutine ' write data
  170.     If Write_tIMe(hFile, outStream) = False Then GoTo ExitRoutine ' write last modified timestamp
  171.     If Write_tEXt(hFile, outStream, False) = False Then GoTo ExitRoutine ' write other text (i.e., description, etc)
  172.     If Write_zTXt(hFile, outStream) = True Then ' write any miscellaneous text
  173.         ' Add the IEND termination to the PNG
  174.         bSuccess = Write_IEND(hFile, outStream)   ' write the end flag
  175.     End If
  176. ExitRoutine:
  177.     ' clean up as needed
  178.     If Not tSA.cDims = 0 Then CopyMemory ByVal VarPtrArray(m_Stream), 0&, 4&
  179.     If Not hFile = 0& Then CloseHandle hFile
  180.     
  181.     Erase m_transPal()
  182.     Erase m_Palette()
  183.     Erase m_Uncompressed()
  184.     Set cCfunction = Nothing
  185.     
  186.     If Err Then Err.Clear
  187.     On Error Resume Next
  188.     If bSuccess = False Then
  189.         If hFile = 0& Then Erase outStream() Else iparseDeleteFile FileName
  190.     Else
  191.         SavePNGex = bSuccess
  192.     End If
  193.     If Err Then Err.Clear
  194. End Function
  195. Private Function PropertyIndex(PropertyID As ePngProperties, Optional LargeBlockCaption As String) As Long
  196.     ' Helper function. Returns the m_Captions() array index for the passed PropertyID
  197.     Dim X As Long, CaptionID As Long
  198.     
  199.     If PropertyID = txtLargeBlockText Then
  200.         For CaptionID = 11 To UBound(m_Captions)
  201.             X = InStr(m_Captions(CaptionID), Chr$(0))
  202.             If StrComp(Left$(m_Captions(CaptionID), X - 1&), LargeBlockCaption) = 0& Then Exit For
  203.         Next
  204.         If CaptionID > UBound(m_Captions) Then CaptionID = -1&
  205.     Else
  206.         X = PropertyID
  207.         Do Until X = 1&
  208.             X = X  2&
  209.             CaptionID = CaptionID + 1&
  210.         Loop
  211.     End If
  212.     PropertyIndex = CaptionID
  213. End Function
  214. Private Function OptimizeTrueColor(ByVal isAlpha As Boolean) As Boolean
  215.     ' Function attempts to reduce 32bpp DIB to 24bpp DIB.
  216.     ' Reduction to Palette already tried before this routine was called
  217.     ' Reduction can occur when:
  218.     '   1. No transparency is used
  219.     '   2. Only one color is fully transparent (if alpha between 1 & 254 then no reduction)
  220.     
  221.     Dim X As Long, Y As Long
  222.     Dim bAbort As Boolean, tOffset As Long
  223.     Dim palAlpha(0 To 255) As Byte, palCount As Long
  224.     Dim scanWidth As Long, Color As Long
  225.     
  226.     m_Trans = -1&       ' flag indicating no simple transparency. ColorType 6 implies transparency
  227.     If isAlpha Then
  228.         ' we will test if only full transparency is used, and only one color uses transparency.
  229.         ' We don't need to determine which color is transparent, because with pre-multiplied
  230.         ' DIBs, the color is always black: 0,0,0. But if another color is transparent, can't reduce
  231.         
  232.         m_ColorType = clrTrueAlpha  ' default color type for this DIB
  233.         For Y = 0& To UBound(m_Stream, 2)
  234.             For X = 3& To UBound(m_Stream, 1) Step 4&
  235.                 ' look at alpha values, if any are semi-transparent, abort
  236.                 If m_Stream(X, Y) = 0 Then ' full transparency
  237.                     ' If color is not black, we abort
  238.                     If Not (m_Stream(X - 3, Y) = 0) Then
  239.                         bAbort = True: Exit For
  240.                     ElseIf Not (m_Stream(X - 2, Y) = 0) Then
  241.                         bAbort = True: Exit For
  242.                     ElseIf Not (m_Stream(X - 1, Y) = 0) Then
  243.                         bAbort = True: Exit For
  244.                     End If
  245.                 
  246.                 ElseIf Not m_Stream(X, Y) = 255 Then   ' partial transparency, abort
  247.                     bAbort = True: Exit For
  248.                 End If
  249.             Next
  250.             If bAbort Then Exit For
  251.         Next
  252.         If Not bAbort Then ' reduction to 24bpp can be done?
  253.             ' now here's the catch. Black is always transparent in premultiplied DIBs, but if
  254.             ' non-transparent black is used anywhere else in the image, then we can't leave
  255.             ' black as the transparent color; we'll need to change it. This routine will run
  256.             ' quickly. We will not make the effort to check every possible color in the 16 million
  257.             ' color range, rather, we will be looking at just 1024 colors: 256 Reds, 256 Greens,
  258.             ' & 256 Blues. If we find one we can use, bingo, else write as 32bpp
  259.             For Color = 0& To 2&  ' Color = B, G, R
  260.             
  261.                 tOffset = 3& - Color ' location of the alpha byte relative to "Color"
  262.                 palCount = 0&       ' number of "Color" shades used; from 1 to 256
  263.                 
  264.                 For Y = 0& To UBound(m_Stream, 2)
  265.                     For X = Color To UBound(m_Stream, 1) Step 4&
  266.                     
  267.                         If Not m_Stream(X + tOffset, Y) = 0 Then    ' is this our transparent color?
  268.                             If palAlpha(m_Stream(X, Y)) = 0 Then    ' no, but has it been counted?
  269.                                 palCount = palCount + 1&            ' up the count & abort if we maxed out
  270.                                 If palCount = 256& Then
  271.                                     bAbort = True
  272.                                     Exit For
  273.                                 End If
  274.                                 palAlpha(m_Stream(X, Y)) = 1        ' flag it
  275.                             End If
  276.                         End If
  277.                     Next
  278.                     
  279.                     If bAbort Then Exit For ' all 256 shades of "Color" used
  280.                     
  281.                 Next
  282.                 
  283.                 If palCount < 256& Then ' did we find a color we can use?
  284.                     For X = 0& To 255&  ' lets find out which it is
  285.                         If palAlpha(X) = 0 Then
  286.                             ' since the X-shade of the R, G, or B isn't used in the image,
  287.                             ' we can safely state that RGB(X,X,X) is also not in the image
  288.                             m_Trans = X Or X * &H100& Or X * &H10000
  289.                             Exit For
  290.                         End If
  291.                     Next
  292.                     m_ColorType = clrTrueColor  ' reduce to 24bpp vs 32bpp
  293.                     Exit For
  294.                 End If
  295.                 
  296.                 Erase palAlpha()    ' reset to zeros
  297.                 bAbort = False      ' reset
  298.                 
  299.             Next
  300.         End If
  301.     Else
  302.         m_ColorType = clrTrueColor      ' no transparency, reduction to 24bpp
  303.     End If
  304.     
  305.     ' Use separate loops vs adding an IF statement for every pixel to test for color type
  306.     If m_ColorType = clrTrueAlpha Then  ' 32bpp (ColorType 6)
  307.         scanWidth = UBound(m_Stream, 1) + 1&
  308.         ReDim m_Uncompressed(0 To scanWidth * (UBound(m_Stream, 2) + 1&) - 1&)
  309.         For Y = 0& To UBound(m_Stream, 2)
  310.             tOffset = Y * scanWidth
  311.             For X = 0& To UBound(m_Stream, 1) Step 4&
  312.                 ' simultaneously remove pre-multiplication
  313.                 Select Case m_Stream(X + 3&, Y)
  314.                 Case 255
  315.                     m_Uncompressed(tOffset) = m_Stream(X + 2&, Y)
  316.                     m_Uncompressed(tOffset + 1&) = m_Stream(X + 1&, Y)
  317.                     m_Uncompressed(tOffset + 2&) = m_Stream(X, Y)
  318.                     m_Uncompressed(tOffset + 3&) = 255
  319.                 Case 0 ' do nothing
  320.                 Case Else
  321.                     Color = m_Stream(X + 3&, Y)
  322.                     m_Uncompressed(tOffset) = (255& * m_Stream(X + 2&, Y)  Color)
  323.                     m_Uncompressed(tOffset + 1&) = (255& * m_Stream(X + 1&, Y)  Color)
  324.                     m_Uncompressed(tOffset + 2&) = (255& * m_Stream(X, Y)  Color)
  325.                     m_Uncompressed(tOffset + 3&) = Color
  326.                 End Select
  327.                 tOffset = tOffset + 4&
  328.             Next
  329.         Next
  330.     Else            ' 24bpp (Color Type 2) with or without simple transparency
  331.         scanWidth = iparseByteAlignOnWord(24, UBound(m_Stream, 1)  4 + 1&)
  332.         ' convert BGR to RGB, the Filter function expects 1D arrays
  333.         ReDim m_Uncompressed(0 To scanWidth * (UBound(m_Stream, 2) + 1&) - 1&)
  334.         For Y = 0& To UBound(m_Stream, 2)
  335.             tOffset = Y * scanWidth
  336.             For X = 0& To UBound(m_Stream, 1) Step 4&
  337.                 ' simultaneously remove pre-multiplication. Don't carry over any alpha values
  338.                 Select Case m_Stream(X + 3&, Y)
  339.                 Case 255
  340.                     m_Uncompressed(tOffset) = m_Stream(X + 2&, Y)
  341.                     m_Uncompressed(tOffset + 1&) = m_Stream(X + 1&, Y)
  342.                     m_Uncompressed(tOffset + 2&) = m_Stream(X, Y)
  343.                 Case 0 ' uses simple transparency (1 color is transparent)
  344.                     CopyMemory m_Uncompressed(tOffset), m_Trans, 3&
  345.                 Case Else
  346.                     Color = m_Stream(X + 3&, Y)
  347.                     m_Uncompressed(tOffset) = (255& * m_Stream(X + 2&, Y)  Color)
  348.                     m_Uncompressed(tOffset + 1&) = (255& * m_Stream(X + 1&, Y)  Color)
  349.                     m_Uncompressed(tOffset + 2&) = (255& * m_Stream(X, Y)  Color)
  350.                 End Select
  351.                 tOffset = tOffset + 3&
  352.             Next
  353.         Next
  354.     End If
  355. End Function
  356. Private Function PalettizeImage(isAlpha As Boolean) As Boolean
  357.     ' Function determines if image can be palettized vs 24/32 bpp true color
  358.     ' Once determined it can be paletted, it will optimize to include the following:
  359.     ' 1. Convert to PNG grayscale palette if possible, saves at least 768 bytes vs color palette
  360.     ' 2. Rearrange palette to reduce alpha/palette entries, saves up to 200+ bytes if alpha is used
  361.     ' 3. Converts per-color grayscale to a modified color palette, reducing size at least 50%
  362.     ' This modified version does not reduce to 1,2,or 4 bits per pixel
  363.     '   -- Any paletted image is 256 colors, but only needed palette entries are cached in PNG
  364.     
  365.     Dim X As Long, Y As Long, scanWidth As Long
  366.     Dim palCount As Long, Index As Long
  367.     Dim Color As Long, newColor As Boolean
  368.     Dim palXRef() As Byte, palAlpha() As Byte
  369.     Dim tSortIndex() As Long, tPalette() As Long
  370.     
  371.     On Error GoTo ExitRoutine
  372.     
  373.     ' count unique colors (maximum of 256 if we are to palettize)
  374.     ' Note that alphas are included in the tSortIndex. This is because any color
  375.     ' using more than one alpha value would require separate palette entries:
  376.     ' Example: Red @ Alpha 255 & Red @ Alpha 128 requires two palette entries
  377.     ReDim m_transPal(1 To 256)          ' array to hold alpha values only
  378.     ReDim tSortIndex(1 To 256)          ' sort indexes
  379.     ReDim tPalette(1 To 256) As Long    ' palette
  380.     For Y = 0& To UBound(m_Stream, 2)
  381.         For X = 0& To UBound(m_Stream, 1) Step 4&
  382.         
  383.             CopyMemory Color, m_Stream(X, Y), 4&
  384.             Index = FindColor(tSortIndex, Color, palCount, newColor)   ' use binary search routine
  385.             If newColor = True Then
  386.                 If palCount = 256& Then Exit Function       ' exceeded palette entries limit
  387.                 palCount = palCount + 1&                    ' increment entry count & shift palette to maintain asc sort
  388.                 If Index < palCount Then
  389.                     CopyMemory tSortIndex(Index + 1&), tSortIndex(Index), (palCount - Index) * 4&
  390.                     CopyMemory tPalette(Index + 1&), tPalette(Index), (palCount - Index) * 4&
  391.                 End If
  392.                 tSortIndex(Index) = Color                    ' add new color to the palette
  393.                 CopyMemory tPalette(Index), Color, 3&
  394.             End If
  395.         
  396.         Next
  397.     Next
  398.     
  399.     ' if we got here, then image can be palettized, but to which of the following?
  400.     ' 1. Palette - no transparency? like non-transparent GIFs (isAlpha=False)
  401.     ' 2. Palette - simple transparency? like transparent GIFs (grayscale handled differently in PNGs)
  402.     ' 3. Palette - per-color index transparency?
  403.     
  404.     Y = 0&
  405.     If isAlpha Then
  406.         ' separate alpha from color and count how many non-opaque alpha values
  407.         For X = 1& To palCount
  408.             If (tSortIndex(X) And &H7FFFFFFF) = tSortIndex(X) Then ' high bit not set
  409.                 m_transPal(X) = tSortIndex(X)  &H1000000
  410.             Else                                                 ' high bit is set
  411.                 m_transPal(X) = ((tSortIndex(X) And &H7FFFFFFF)  &H1000000) Or &H80
  412.             End If
  413.             If Not m_transPal(X) = 255 Then
  414.                 Y = Y + 1&   ' count different levels of transparency
  415.                 Index = X    ' track last palette entry with alpha value <> 255
  416.             End If
  417.         Next
  418.     Else
  419.         FillMemory m_transPal(1), 256&, 255 ' all alphas are opaque
  420.     End If
  421.     
  422.     Select Case Y
  423.     Case 0&
  424.     ' 1. Palette - no transparency? like non-transparent GIFs (isAlpha=False)
  425.         m_Trans = -1& ' no transparency
  426.     Case 1&
  427.     ' 2. Palette - simple transparency? like transparent GIFs
  428.         m_Trans = Index ' flag & may be changed later in this routine
  429.     Case Else
  430.     ' 3. Palette - per-color transparency?
  431.         m_Trans = 0& ' > -1 means we have transparency at some level
  432.         ' alphas are kept in the m_transPal() array
  433.     End Select
  434.     
  435. '     Now for the last optimization attempt: check for grayscale but only for non per-color
  436. '     alpha images. Why restrict grayscale to non per-color alpha when PNG can support grayscale
  437. '     per-color alpha? Here's why: per-color grayscale alpha is ColorType 4. ColorType 4
  438. '     always requires 16 bits per pixel (bpp), regardless of grayscale bit depth, but
  439. '     ColorType 3 requires 8 bpp (max) + 768 palette bytes (max) + 256 bytes (max) for alpha info:
  440. '       ColorType 4, 256x256 image: 256*256*2=131072 bytes for color information (grayscale has no palette in PNGs)
  441. '       ColorType 3 (8bpp), 256x256 image: 256*256*1+768+256=66560 bytes for color information
  442. '           note: ColorType 4 is always 16bpp, but ColorType 3 can be 1,2,4,8 bpp
  443. '           and palette/alpha arrays can be reduced too
  444.     
  445.     m_ColorType = clrPalette    ' Color Type 3 (color palette)
  446.     If Not m_Trans = 0& Then
  447.         ' check each palette entry to see if grayscale or not. When not, abort loop
  448.         For Index = 1& To palCount
  449.             If Not (tPalette(Index) And &HFF) = ((tPalette(Index)  &H100&) And &HFF) Then ' compare B to G
  450.                 Exit For
  451.             ElseIf Not (tPalette(Index) And &HFF) = ((tPalette(Index)  &H10000) And &HFF) Then ' compare B to R
  452.                 Exit For
  453.             End If
  454.         Next
  455.         
  456.         If Index > palCount Then    ' need to tweak transparency possibly
  457.         
  458.             m_ColorType = clrGrayScale ' Color Type 0
  459.             
  460.             If isAlpha = True Then
  461.                 ' we only got here because just 1 color was transparent & with a pre-multiplied DIB
  462.                 ' that color is always black. But if non-transparent black was used elsewhere in the
  463.                 ' grayscale then we need to change the transparency. Non-transparent black is very
  464.                 ' common in grayscales
  465.                 ReDim palXRef(1 To 256) ' track which grayscales are used
  466.                 For X = 1& To palCount
  467.                     If tPalette(X) = 0& Then             ' this is black
  468.                         If Not m_transPal(X) = 0& Then   ' and not our transparent black
  469.                             palXRef(1) = 1     ' mark black as used
  470.                         End If
  471.                     Else
  472.                         palXRef(Index + 1&) = 1 ' non-black, mark as used
  473.                     End If
  474.                 Next
  475.                 If palXRef(1) = 1 Then
  476.                     ' non-transparent black is used in the grayscale, so we must change our
  477.                     ' tranparent black - Locate a grayscale not in use
  478.                     For Index = 2& To palCount
  479.                         If palXRef(Index) = 0 Then
  480.                             ' bingo, we'll use this one
  481.                             m_Trans = Index - 1&
  482.                             Exit For
  483.                         End If
  484.                     Next
  485.                 Else    ' black was not in the image, we can use black as transparency
  486.                     m_Trans = 0&
  487.                 End If
  488.                 Erase palXRef
  489.             End If
  490.         End If
  491.     End If
  492.     
  493.     scanWidth = UBound(m_Stream, 1)  4& + 1&         ' width of image
  494.     X = (UBound(m_Stream, 2) + 1) * scanWidth - 1&   ' calculate size of total image bytes
  495.     ReDim m_Uncompressed(0 To X)    ' the Filter function expects 1D arrays
  496.     
  497.     If m_ColorType = clrGrayScale Then
  498.         ' grayscale is easy enough, transfer 32bpp info to 8bpp info
  499.         ' Remember, PNG grayscale color types do not use palettes.
  500.         ' Grayscale palettes are PNG decoders responsibility
  501.         Erase m_transPal()
  502.         For Y = 0& To UBound(m_Stream, 2)
  503.             Index = Y * scanWidth
  504.             For X = 0& To UBound(m_Stream, 1) Step 4&
  505.                 If m_Stream(X + 3&, Y) = 0& Then     ' transparency index needed
  506.                     m_Uncompressed(Index) = m_Trans ' use modified transparency index as necessary
  507.                 Else
  508.                     m_Uncompressed(Index) = m_Stream(X, Y)  ' use grayscale index
  509.                 End If
  510.                 Index = Index + 1&
  511.             Next
  512.         Next
  513.         
  514.     Else
  515.         ' for color palettes, we want to re-order entries when per-color alpha is used.
  516.         ' Why the hassle? Shrink PNG a bit more. When color palettes have transparency,
  517.         ' you must have a 1 byte Alpha value for each palette entry. But, that 1 byte
  518.         ' alpha value, when = 255, is optional and implied. Therefore, if we move all
  519.         ' palette entries with transparency to top of array, then all those 255s at the
  520.         ' bottom of the array don't need to be cached in the PNG; not being there, PNG
  521.         ' decoders must assume value is 255. We can save anywhere up to 200+ bytes
  522.         ' depending on the image.
  523.         ReDim palXRef(0 To 1, 0 To palCount - 1)
  524.         
  525.         If m_Trans = -1& Then   ' no transparencies and not grayscale
  526.             Erase m_transPal
  527.             For X = 0& To palCount - 1& ' all entries are opaque, no cross-referencing needed
  528.                 palXRef(0, X) = X
  529.                 palXRef(1, X) = X
  530.             Next
  531.         Else                    ' per-color alpha being used
  532.             ' since we are re-ordering, we also need to build a cross-reference so
  533.             ' we can reference palette locations, old to new and vice versa
  534.             Y = 0&: X = palCount - 1&  ' starting points for top & bottom of array
  535.             For Index = 0& To palCount - 1&
  536.                 If m_transPal(Index + 1&) = 255 Then
  537.                     palXRef(1, X) = Index ' keep full opaque entries at bottom of array
  538.                     palXRef(0, Index) = X ' double link reference
  539.                     X = X - 1&
  540.                 Else
  541.                     palXRef(0, Index) = Y ' move non-opaque entries near top of array
  542.                     palXRef(1, Y) = Index ' double link reference
  543.                     Y = Y + 1&
  544.                 End If
  545.             Next
  546.         End If
  547.         
  548.         ' now we build our 8 bpp paletted image, referencing the re-sorted palette entires
  549.         For Y = 0& To UBound(m_Stream, 2)
  550.             Index = Y * scanWidth
  551.             For X = 0& To UBound(m_Stream, 1) Step 4&
  552.                 ' get 32bit color from DIB
  553.                 CopyMemory Color, m_Stream(X, Y), 4&
  554.                 ' locate it in our temp palette using binary search algorithm
  555.                 Color = FindColor(tSortIndex, Color, palCount, False)
  556.                 ' now cache its re-sorted reference
  557.                 m_Uncompressed(Index) = palXRef(0, Color - 1&)
  558.                 Index = Index + 1&
  559.             Next
  560.         Next
  561.         
  562.         ' good, now we need to build the palette the PNG will use,
  563.         ' but we will be using 3 byte values, not 4 byte values & colors need to be RGB vs BGR
  564.         ReDim m_Palette(1 To palCount * 3& + 4&) ' extra 4 bytes are used during Write_PLTE
  565.         For X = 1& To palCount
  566.             ' calculate new index for this palette entry
  567.             Index = palXRef(0, X - 1&) * 3& + 5&   ' offset that extra 4 bytes too
  568.             ' simultaneously remove pre-multiplication
  569.             Select Case m_transPal(X)
  570.             Case 255    ' full opaque
  571.                 m_Palette(Index) = (tPalette(X)  &H10000) And &HFF&
  572.                 m_Palette(Index + 1&) = (tPalette(X)  &H100&) And &HFF&
  573.                 m_Palette(Index + 2&) = tPalette(X) And &HFF&
  574.             Case 0: ' do nothing, color is always 0,0,0
  575.             Case Else
  576.                 m_Palette(Index) = (((tPalette(X)  &H10000) And &HFF&) * m_transPal(X)  255)
  577.                 m_Palette(Index + 1&) = (((tPalette(X)  &H100&) And &HFF&) * m_transPal(X)  255)
  578.                 m_Palette(Index + 2&) = ((tPalette(X) And &HFF&) * m_transPal(X)  255)
  579.             End Select
  580.         Next
  581.         Erase tPalette()
  582.         
  583.         If Not m_Trans = -1& Then
  584.             ' now we are going to double check how many non-opaque palette entries we have
  585.             For X = 0& To palCount - 1&
  586.                 If m_transPal(palXRef(1, X) + 1&) = 255 Then
  587.                     palCount = X    ' we saved 256-X bytes at least
  588.                     Exit For
  589.                 End If
  590.             Next
  591.             ReDim palAlpha(1 To palCount + 4&)   ' extra 4 bytes used in Write_tRNS
  592.             ' rewrite the m_transPal array, only caching non-opaque palette entries
  593.             For X = 0& To palCount - 1&
  594.                 palAlpha(X + 5&) = m_transPal(palXRef(1, X) + 1&)
  595.             Next
  596.             m_transPal = palAlpha
  597.         
  598.         End If
  599.     End If
  600.     
  601.     PalettizeImage = True
  602. ExitRoutine:
  603. End Function
  604. Private Function FindColor(ByRef PaletteItems() As Long, ByVal Color As Long, ByVal Count As Long, ByRef isNew As Boolean) As Long
  605.     ' MODIFIED BINARY SEARCH ALGORITHM -- Divide and conquer.
  606.     ' Binary search algorithms are about the fastest on the planet, but
  607.     ' its biggest disadvantage is that the array must already be sorted.
  608.     ' Ex: binary search can find a value among 1 million values in less than 20 iterations
  609.     
  610.     ' [in] PaletteItems(). Long Array to search within. Array must be 1-bound
  611.     ' [in] Color. A value to search for. Order is always ascending
  612.     ' [in] Count. Number of items in PaletteItems() to compare against
  613.     ' [out] isNew. If Color not found, isNew is True else False
  614.     ' [out] Return value: The Index where Color was found or where the new Color should be inserted
  615.     Dim UB As Long, LB As Long
  616.     Dim newIndex As Long
  617.     
  618.     If Count = 0& Then
  619.         FindColor = 1&
  620.         isNew = True
  621.         Exit Function
  622.     End If
  623.     
  624.     UB = Count
  625.     LB = 1&
  626.     
  627.     Do Until LB > UB
  628.         newIndex = LB + ((UB - LB)  2&)
  629.         If PaletteItems(newIndex) = Color Then
  630.             Exit Do
  631.         ElseIf PaletteItems(newIndex) > Color Then ' new color is lower in sort order
  632.             UB = newIndex - 1&
  633.         Else ' new color is higher in sort order
  634.             LB = newIndex + 1&
  635.         End If
  636.     Loop
  637.     If LB > UB Then  ' color was not found
  638.             
  639.         If Color > PaletteItems(newIndex) Then newIndex = newIndex + 1&
  640.         isNew = True
  641.         
  642.     Else
  643.         isNew = False
  644.     End If
  645.     
  646.     FindColor = newIndex
  647. End Function
  648. Private Function ByteAlignOnByte(ByVal totalWidth As Long, ByVal btsPerPixel As Byte) As Long
  649. ' // LaVolpe, Dec 1 thru 10
  650.     ' returns number of bytes required to display n pixels at p color depth (byte aligned)
  651.     ByteAlignOnByte = (totalWidth * btsPerPixel + 7&)  8&
  652. End Function
  653. Private Function Write_IHDR(fileNum As Long, Stream() As Byte, Host As c32bppDIB, isInterlaced As Boolean) As Boolean
  654.     Const png_Signature1 As Long = 1196314761
  655.     Const png_Signature2 As Long = 169478669
  656.     Const chnk_IHDR As Long = &H52444849 'Image header
  657.     
  658.     On Error GoTo eh
  659.     Dim pngData(0 To 16) As Byte ' 13 byte header + 4 byte chunk name
  660.     Dim gpLong As Long           ' general purpose variable
  661.     Dim rwLen As Long
  662.     
  663.     ' build header
  664.     CopyMemory pngData(0), chnk_IHDR, 4&    ' chunk name
  665.     gpLong = iparseReverseLong(Host.Width)   ' png width
  666.     CopyMemory pngData(4), gpLong, 4&
  667.     gpLong = iparseReverseLong(Host.Height)  ' png height
  668.     CopyMemory pngData(8), gpLong, 4&
  669.     
  670.     ' bit depth, 16bit (PNG 16 bytes per R,G,B element or 48 bytes per pixel)
  671.     ' not supported via this class
  672.     pngData(12) = 8 ' only 1,2,4,48 bpp are different, 8,24,32 bpp is 8
  673.     
  674.     pngData(13) = m_ColorType
  675.     ' pngData(14) & (15) will always be zero (compression/filter methods)
  676.     ' next byte is 1 if interlaced
  677.     pngData(16) = Abs(isInterlaced)
  678.     If fileNum = 0& Then ' writing to array vs file
  679.         ReDim Stream(0 To 32) ' png signature, header len, header, crc value (33 bytes)
  680.         CopyMemory Stream(0), png_Signature1, 4&
  681.         CopyMemory Stream(4), png_Signature2, 4&
  682.         gpLong = iparseReverseLong(13&) ' len of header
  683.         CopyMemory Stream(8), gpLong, 4&
  684.         CopyMemory Stream(12), pngData(0), 17&
  685.         gpLong = zCreateCRC(VarPtr(pngData(0)), 17&)
  686.         CopyMemory Stream(29), gpLong, 4&
  687.         Write_IHDR = True
  688.     Else
  689.     
  690.         WriteFile fileNum, png_Signature1, 4&, rwLen, ByVal 0&
  691.         If rwLen = 4& Then
  692.             WriteFile fileNum, png_Signature2, rwLen, rwLen, ByVal 0&
  693.             If rwLen = 4& Then
  694.                 WriteFile fileNum, iparseReverseLong(13&), rwLen, rwLen, ByVal 0&
  695.                 If rwLen = 4& Then
  696.                     WriteFile fileNum, pngData(0), 17&, rwLen, ByVal 0&
  697.                     If rwLen = 17& Then
  698.                         WriteFile fileNum, zCreateCRC(VarPtr(pngData(0)), rwLen), 4&, rwLen, ByVal 0&
  699.                         Write_IHDR = (rwLen = 4&)
  700.                     End If
  701.                 End If
  702.             End If
  703.         End If
  704.     End If
  705. eh:
  706.     If Err Then Err.Clear
  707. End Function
  708. Private Function Write_PLTE(fileNum As Long, Stream() As Byte, Invalid_bKGD As Boolean) As Boolean
  709.     ' Note: the palette is preprocessed before it arrives here: BGR>RGB
  710.     On Error GoTo eh
  711.     
  712.     If m_ColorType = clrPalette Then ' paletted images only
  713.     
  714.         Const chnk_PLTE As Long = &H45544C50 'Palette
  715.         
  716.         Dim gpLong As Long          ' general purpose variable
  717.         Dim Index As Long
  718.         Dim rwLen As Long
  719.         
  720.         ' when paletted, the bKGD chunk comes after the palette, but for palettes the
  721.         ' bkgd chunk must be one of the palette entries, therefore, we will attempt to
  722.         ' find the color in the palette, add it to the palette if possible, or skip
  723.         ' the optional chunk if color is not in the palette
  724.         If (m_PNGprops And ePngProperties.colorDefaultBkg) = ePngProperties.colorDefaultBkg Then
  725.             Dim bkg(0 To 2) As Byte
  726.             CopyMemory bkg(0), m_bKGD, 3&
  727.             For Index = 5& To UBound(m_Palette) Step 3&
  728.                 If bkg(0) = m_Palette(Index) Then
  729.                     If bkg(1) = m_Palette(Index + 1&) Then
  730.                         If bkg(2) = m_Palette(Index + 2&) Then Exit For
  731.                     End If
  732.                 End If
  733.             Next
  734.             If Index < UBound(m_Palette) Then   ' found it, ref the index
  735.                 m_bKGD = (Index - 5&)  3&
  736.             ElseIf UBound(m_Palette) < 772& Then ' we can add it, let's do that
  737.                 ' ^^ 772 is 256*3+4
  738.                 ReDim Preserve m_Palette(1 To UBound(m_Palette) + 3&)
  739.                 m_bKGD = (UBound(m_Palette) - 5&)  3&
  740.                 CopyMemory m_Palette(UBound(m_Palette) - 2&), bkg(0), 3&
  741.             Else
  742.                 Invalid_bKGD = True ' do not write the bkgd chunk
  743.             End If
  744.         End If
  745.             
  746.         CopyMemory m_Palette(1), chnk_PLTE, 4&
  747.         gpLong = UBound(m_Palette)
  748.         
  749.         If fileNum = 0& Then 'writing to array vs file
  750.             Index = UBound(Stream) + 1&
  751.             ReDim Preserve Stream(0 To Index + gpLong + 7&)
  752.             rwLen = iparseReverseLong(gpLong - 4&)
  753.             CopyMemory Stream(Index), rwLen, 4&                 ' size of chunk
  754.             CopyMemory Stream(Index + 4&), m_Palette(1), gpLong ' palette
  755.             rwLen = zCreateCRC(VarPtr(m_Palette(1)), gpLong)
  756.             CopyMemory Stream(Index + gpLong + 4&), rwLen, 4&   ' crc
  757.             Write_PLTE = True
  758.         Else
  759.             WriteFile fileNum, iparseReverseLong(gpLong - 4&), 4&, rwLen, ByVal 0&
  760.             If rwLen = 4& Then
  761.                 WriteFile fileNum, m_Palette(1), gpLong, rwLen, ByVal 0&
  762.                 If rwLen = gpLong Then
  763.                     WriteFile fileNum, zCreateCRC(VarPtr(m_Palette(1)), gpLong), 4&, rwLen, ByVal 0&
  764.                     Write_PLTE = (rwLen = 4&)
  765.                 End If
  766.             End If
  767.         End If
  768.         Erase m_Palette()   ' no longer needed
  769.     Else
  770.         Write_PLTE = True
  771.     End If
  772. eh:
  773.     If Err Then Err.Clear
  774. End Function
  775. Private Function Write_tEXt(fileNum As Long, Stream() As Byte, bTitleAuthorOnly As Boolean) As Boolean
  776.     ' Function writes uncompressed standard Keywords & text to the PNG
  777.     
  778.     ' Note. Per PNG specs, some text should be written near top of the file while others
  779.     ' should be written near the end. There is no requirement for text to appear in
  780.     ' any specific location. The logic for writing some near the top is for search
  781.     ' engines only. It would be faster to find that text if nearer the top.
  782.     ' Therefore, this routine is called twice, once near the top of the PNG and
  783.     ' and again just before the IEND chunk is written
  784.     
  785.     Const chnk_tEXt As Long = &H74584574 'Text - uncompressed
  786.     
  787.     On Error GoTo ExitRoutine
  788.     
  789.     Dim pngData() As Byte   ' data to be written to PNG file
  790.     Dim txtData() As Byte
  791.     Dim gpLong As Long
  792.     Dim lenKeyword As Long
  793.     Dim lenText As Long
  794.     
  795.     Dim Index As Long
  796.     Dim CaptionID As Long
  797.     Dim tProps As Long
  798.     Dim lastCaption As Long
  799.     Dim keyWord As String
  800.     Dim rwLen As Long
  801.     
  802.     If bTitleAuthorOnly Then    ' called after writing IHDR
  803.         CaptionID = ePngProperties.txtTitle
  804.         lastCaption = ePngProperties.txtDescription
  805.     Else                        ' called before writing IEND
  806.         CaptionID = ePngProperties.txtDescription
  807.         lastCaption = ePngProperties.txtLargeBlockText
  808.     End If
  809.     tProps = m_PNGprops
  810.     Do Until CaptionID = lastCaption
  811.         If (tProps And CaptionID) = CaptionID Then
  812.             tProps = tProps And Not CaptionID
  813.             Select Case CaptionID
  814.             Case txtTitle: Index = 0
  815.                 keyWord = "Title" & Chr$(0)
  816.             Case txtAuthor: Index = 1&
  817.                 keyWord = "Author" & Chr$(0)
  818.             Case txtComment: Index = 9&
  819.                 keyWord = "Comment" & Chr$(0)
  820.             Case txtCopyright: Index = 3&
  821.                 keyWord = "Copyright" & Chr$(0)
  822.             Case txtCreationTime: Index = 4&
  823.                 keyWord = "Creation Time" & Chr$(0)
  824.             Case txtDescription: Index = 2&
  825.                 keyWord = "Description" & Chr$(0)
  826.             Case txtDisclaimer: Index = 6&
  827.                 keyWord = "Disclaimer" & Chr$(0)
  828.             Case txtSoftware: Index = 5&
  829.                 keyWord = "Software" & Chr$(0)
  830.             Case txtSource: Index = 8&
  831.                 keyWord = "Source" & Chr$(0)
  832.             Case txtWarning: Index = 7&
  833.                 keyWord = "Warning" & Chr$(0)
  834.             End Select
  835.                 
  836.             ' tXTt chunk format::
  837.             'Keyword 1-79 bytes (character string)
  838.             'Null separator 1 byte (null character)
  839.             'Text string 0 or more bytes (character string)
  840.             
  841.             lenKeyword = Len(keyWord)
  842.             txtData() = StrConv(keyWord, vbFromUnicode)
  843.             If Len(m_Captions(Index)) > 0& Then
  844.                 lenText = Len(m_Captions(Index))
  845.                 ReDim pngData(1 To lenKeyword + lenText + 4&)
  846.                 CopyMemory pngData(5), txtData(0), lenKeyword
  847.                 txtData() = StrConv(m_Captions(Index), vbFromUnicode)
  848.                 CopyMemory pngData(5& + lenKeyword), txtData(0), lenText
  849.                 
  850.             Else ' handle zero-length chunks.
  851.                 ' Note: I would prefer to just skip these, but maybe you might
  852.                 ' decide to use one as a flag for something else?
  853.                 ReDim pngData(1 To lenKeyword + 4&)
  854.                 CopyMemory pngData(5), txtData(0), lenKeyword
  855.             End If
  856.             CopyMemory pngData(1), chnk_tEXt, 4&
  857.             gpLong = lenKeyword + lenText + 4&
  858.             
  859.             If fileNum = 0& Then ' writing to stream
  860.                 Index = UBound(Stream) + 1&
  861.                 ReDim Preserve Stream(0 To Index + gpLong + 7&)
  862.                 rwLen = iparseReverseLong(gpLong - 4&)
  863.                 CopyMemory Stream(Index), rwLen, 4&
  864.                 CopyMemory Stream(Index + 4), pngData(1), gpLong
  865.                 rwLen = zCreateCRC(VarPtr(pngData(1)), gpLong)
  866.                 CopyMemory Stream(Index + 4& + gpLong), rwLen, 4&
  867.                 Write_tEXt = True
  868.             Else
  869.                 WriteFile fileNum, iparseReverseLong(gpLong - 4&), 4&, rwLen, ByVal 0&
  870.                 If rwLen = 4& Then
  871.                     WriteFile fileNum, pngData(1), gpLong, rwLen, ByVal 0&
  872.                     If rwLen = gpLong Then
  873.                         WriteFile fileNum, zCreateCRC(VarPtr(pngData(1)), gpLong), 4&, rwLen, ByVal 0&
  874.                         Write_tEXt = (rwLen = 4&)
  875.                     End If
  876.                 End If
  877.             End If
  878.         End If
  879.         CaptionID = CaptionID * 2&
  880.     Loop
  881.     
  882. ExitRoutine:
  883.     If Err Then
  884.         Err.Clear
  885.     Else
  886.         If lenKeyword = 0& Then Write_tEXt = True
  887.     End If
  888. End Function
  889. Private Function Write_tIMe(fileNum As Long, Stream() As Byte) As Boolean
  890.     ' Note: the time stamp should be Universal Time, not local area
  891.     
  892.     If (m_PNGprops And ePngProperties.dateTimeModified) = ePngProperties.dateTimeModified Then
  893.         Const chnk_tIME As Long = &H454D4974 'Timestamp
  894.         
  895.         On Error GoTo eh
  896.         Dim pngData(0 To 10) As Byte ' 7 byte date/time + 4 byte chunk name
  897.         Dim gpLong As Long
  898.         Dim gpInt As Integer
  899.         Dim dtStamp As Date
  900.         Dim rwLen As Long
  901.         
  902.         dtStamp = CDate(m_Captions(10))
  903.         
  904.         CopyMemory pngData(0), chnk_tIME, 4&
  905.             gpInt = Year(dtStamp)
  906.         CopyMemory pngData(5), gpInt, 2&
  907.         pngData(4) = pngData(6)             ' swap endian of integer
  908.             gpInt = Month(dtStamp)
  909.         CopyMemory pngData(6), gpInt, 1&
  910.             gpInt = Day(dtStamp)
  911.         CopyMemory pngData(7), gpInt, 1&
  912.             gpInt = Hour(dtStamp)
  913.         CopyMemory pngData(8), gpInt, 1&
  914.             gpInt = Minute(dtStamp)
  915.         CopyMemory pngData(9), gpInt, 1&
  916.             gpInt = Second(dtStamp)
  917.         CopyMemory pngData(10), gpInt, 1&
  918.         
  919.         If fileNum = 0& Then ' writing to stream
  920.             gpLong = UBound(Stream) + 1&
  921.             ReDim Preserve Stream(0 To gpLong + 18&)
  922.             rwLen = iparseReverseLong(7)
  923.             CopyMemory Stream(gpLong), rwLen, 4&
  924.             CopyMemory Stream(gpLong + 4&), pngData(0), 11&
  925.             rwLen = zCreateCRC(VarPtr(pngData(0)), 11&)
  926.             CopyMemory Stream(gpLong + 15&), rwLen, 4&
  927.             Write_tIMe = True
  928.         Else
  929.             WriteFile fileNum, iparseReverseLong(7), 4&, rwLen, ByVal 0&
  930.             If rwLen = 4& Then
  931.                 WriteFile fileNum, pngData(0), 11&, rwLen, ByVal 0&
  932.                 If rwLen = 11& Then
  933.                     WriteFile fileNum, zCreateCRC(VarPtr(pngData(0)), rwLen), 4&, rwLen, ByVal 0&
  934.                     Write_tIMe = (rwLen = 4&)
  935.                 End If
  936.             End If
  937.         End If
  938.     Else
  939.         Write_tIMe = True
  940.     End If
  941. eh:
  942.     If Err Then Err.Clear
  943. End Function
  944. Private Function Write_tRNS(fileNum As Long, Stream() As Byte) As Boolean
  945.     ' For paletted/grayscale images, tRNS is the palette index, otherwise RGB value
  946.     On Error GoTo eh
  947.         
  948.     If m_Trans = -1& Then
  949.         Write_tRNS = True
  950.     
  951.     Else ' transparency not used
  952.     
  953.         Const chnk_tRNS As Long = &H534E5274 'Simple Transparency & palette transparency
  954.         Dim Index As Long
  955.         Dim gpLong As Long
  956.         Dim rwLen As Long
  957.     
  958.         Select Case m_ColorType
  959.             
  960.             Case clrPalette ' Paletted (palette count * 3 + 4 byte chunk name)
  961.                 ' nothing to do; done during PalettizeImage
  962.             
  963.             Case clrGrayScale ' grayscale
  964.                 ReDim m_transPal(1 To 6)   ' 2 bytes + 4 byte chunk name
  965.                 m_transPal(6) = m_Trans
  966.                 ' Note: m_transPal(5) used with 48bit per pixel images (not supported)
  967.             
  968.             Case clrTrueColor ' we have simple transparency for true color
  969.                 ReDim m_transPal(1 To 10)   ' 6 bytes + 4 byte chunk name
  970.                 m_transPal(6) = m_Trans And &HFF
  971.                 m_transPal(8) = (m_Trans  &H100&) And &HFF
  972.                 m_transPal(10) = (m_Trans  &H10000) And &HFF
  973.                 ' Note: m_transPal(5,7,9) used with 48bit per pixel images (not supported)
  974.         
  975.             Case Else
  976.                 ' Color Types 4 & 6 are prohibited from having a tRNS chunk
  977.                 Write_tRNS = True
  978.                 Exit Function
  979.         End Select
  980.         
  981.         CopyMemory m_transPal(1), chnk_tRNS, 4&
  982.         gpLong = UBound(m_transPal)
  983.     
  984.         ' write the chunk
  985.         If fileNum = 0& Then ' writing to array vs file
  986.             Index = UBound(Stream) + 1&
  987.             ReDim Preserve Stream(0 To Index + gpLong + 7&)
  988.             rwLen = iparseReverseLong(gpLong - 4&)
  989.             CopyMemory Stream(Index), rwLen, 4&                 ' chunk size
  990.             CopyMemory Stream(Index + 4&), m_transPal(1), gpLong ' palette
  991.             gpLong = zCreateCRC(VarPtr(m_transPal(1)), gpLong)
  992.             CopyMemory Stream(Index + UBound(m_transPal) + 4&), gpLong, 4&       ' crc value
  993.             Write_tRNS = True
  994.         Else
  995.             WriteFile fileNum, iparseReverseLong(gpLong - 4&), 4&, rwLen, ByVal 0&
  996.             If rwLen = 4& Then
  997.                 WriteFile fileNum, m_transPal(1), gpLong, rwLen, ByVal 0&
  998.                 If rwLen = gpLong Then
  999.                     WriteFile fileNum, zCreateCRC(VarPtr(m_transPal(1)), gpLong), 4&, rwLen, ByVal 0&
  1000.                     Write_tRNS = (rwLen = 4&)
  1001.                 End If
  1002.             End If
  1003.         End If
  1004.         Erase m_transPal()
  1005.     End If
  1006. eh:
  1007.     If Err Then Err.Clear
  1008. End Function
  1009. Private Function Write_zTXt(fileNum As Long, Stream() As Byte) As Boolean
  1010.     ' Function writes non-reserved keyword compressed/uncompressed text to the PNG
  1011.     
  1012.     If (m_PNGprops And ePngProperties.txtLargeBlockText) = ePngProperties.txtLargeBlockText Then
  1013.         On Error GoTo eh
  1014.         Const chnk_tEXt As Long = &H74584574 'Text - uncompressed
  1015.         Const chnk_zTXt As Long = &H7458547A 'Text - compressed
  1016.         
  1017.         Dim txtData() As Byte   ' comments/text in bytes
  1018.         Dim pngData() As Byte   ' data to be written to PNG file
  1019.         Dim sText As String
  1020.         Dim gpLong As Long
  1021.         Dim Index As Long
  1022.         Dim rwLen As Long
  1023.         Dim lenKeyword As Long
  1024.         Dim lenText As Long
  1025.         Dim bWritten As Boolean
  1026.     
  1027.         For Index = 11& To UBound(m_Captions)
  1028.             ' convert keyword to bytes
  1029.             lenKeyword = InStr(m_Captions(Index), Chr$(0))
  1030.             lenText = Len(m_Captions(Index)) - lenKeyword
  1031.             txtData() = StrConv(m_Captions(Index), vbFromUnicode)
  1032.             
  1033.             ' per PNG specs....
  1034.             ' It is recommended that text items less than 1K (1024 bytes)
  1035.             ' in size should be output using uncompressed text chunks
  1036.             If lenText > 1024& Then
  1037.             
  1038.                 ' IMPORTANT: This portion of the routine is not equipped to write
  1039.                 ' zero-length text block. That is only handled above where the
  1040.                 ' .Text length is < 1025... DO NOT modify that IF statement to
  1041.                 ' allow zero-length chunks to fall thru to this portion of IF
  1042.                 
  1043.                 ' zTXt chunk format::
  1044.                 'Keyword 1-79 bytes (character string)
  1045.                 'Null separator 1 byte (null character)
  1046.                 'Compression method 1 byte
  1047.                 'Compressed text datastream n bytes
  1048.                 
  1049.                 ' Note that the compression byte of zero needs to be included too,
  1050.                 ' but we don't add it to the txtData conversion above cause zero
  1051.                 ' would be converted to 48 -- Asc("0").
  1052.                 
  1053.                 gpLong = lenText * 0.01 + 12& + lenText
  1054.                 '^^ Text won't always compress smaller; it should, but may not
  1055.                 ' That is why it is recommended to allow 1024 bytes as uncompressed
  1056.                 ReDim pngData(1 To gpLong + (lenKeyword + 5&))
  1057.                 ' ^^ include 4 bytes for chunk name + keyword length + 1 byte compression method
  1058.                     
  1059.                 If zDeflate(VarPtr(pngData(6& + lenKeyword)), gpLong, VarPtr(txtData(lenKeyword)), lenText) = True Then
  1060.                     ' ^^ store compression after chunk name, after keyword and after compression method
  1061.                     ' ^^ begin compression on 1st byte of the text, not the caption or compression method
  1062.                     
  1063.                     CopyMemory pngData(1), chnk_zTXt, 4&
  1064.                     CopyMemory pngData(5), txtData(0), lenKeyword
  1065.                     gpLong = gpLong + lenKeyword + 5&
  1066.                     If fileNum = 0& Then    ' writing to array
  1067.                         Index = UBound(Stream) + 1&
  1068.                         ReDim Preserve Stream(0 To Index + gpLong + 7&)
  1069.                         rwLen = iparseReverseLong(gpLong - 4&)
  1070.                         CopyMemory Stream(Index), rwLen, 4&
  1071.                         CopyMemory Stream(Index + 4&), pngData(1), gpLong
  1072.                         rwLen = zCreateCRC(VarPtr(pngData(1)), gpLong)
  1073.                         CopyMemory Stream(Index + gpLong + 4&), rwLen, 4&
  1074.                         Write_zTXt = True
  1075.                     Else
  1076.                         WriteFile fileNum, iparseReverseLong(gpLong - 4&), 4&, rwLen, ByVal 0&
  1077.                         If rwLen = 4& Then
  1078.                             WriteFile fileNum, pngData(1), gpLong, rwLen, ByVal 0&
  1079.                             If rwLen = gpLong Then
  1080.                                 WriteFile fileNum, zCreateCRC(VarPtr(pngData(1)), gpLong), 4&, rwLen, ByVal 0&
  1081.                                 Write_zTXt = (rwLen = 4&)
  1082.                             End If
  1083.                         End If
  1084.                     End If
  1085.                     bWritten = True
  1086.                 Else    ' failed to compress. Which means our buffer was too small
  1087.                         ' Therefore we will add it as uncompressed instead of
  1088.                         ' making the buffer even bigger
  1089.                 End If
  1090.             End If
  1091.             
  1092.             If Not bWritten Then 'either len<1025 or compression failed
  1093.                 ' tXTt chunk format::
  1094.                 'Keyword 1-79 bytes (character string)
  1095.                 'Null separator 1 byte (null character)
  1096.                 'Text string 0 or more bytes (character string)
  1097.                 gpLong = lenText + lenKeyword + 4&  ' size of chunk
  1098.                 ReDim pngData(1 To gpLong)
  1099.                 CopyMemory pngData(1), chnk_tEXt, 4&
  1100.                 CopyMemory pngData(5), txtData(0), lenKeyword
  1101.                 
  1102.                 If Not lenText = 0& Then ' zero-length text; not prohibited by PNG specs
  1103.                     CopyMemory pngData(5 + lenKeyword), txtData(lenKeyword), lenText
  1104.                 End If
  1105.                 
  1106.                 If fileNum = 0& Then ' writing to array
  1107.                     Index = UBound(Stream) + 1&
  1108.                     ReDim Preserve Stream(0 To Index + gpLong + 7&)
  1109.                     rwLen = iparseReverseLong(gpLong - 4&)
  1110.                     CopyMemory Stream(Index), rwLen, 4&
  1111.                     CopyMemory Stream(Index + 4&), pngData(1), gpLong
  1112.                     rwLen = zCreateCRC(VarPtr(pngData(1)), gpLong)
  1113.                     CopyMemory Stream(Index + gpLong + 4&), rwLen, 4&
  1114.                     Write_zTXt = True
  1115.                 Else
  1116.                     WriteFile fileNum, iparseReverseLong(gpLong - 4&), 4&, rwLen, ByVal 0&
  1117.                     If rwLen = 4& Then
  1118.                         WriteFile fileNum, pngData(1), gpLong, rwLen, ByVal 0&
  1119.                         If rwLen = gpLong Then
  1120.                             WriteFile fileNum, zCreateCRC(VarPtr(pngData(1)), gpLong), 4&, rwLen, ByVal 0&
  1121.                             Write_zTXt = (rwLen = 4&)
  1122.                         End If
  1123.                     End If
  1124.                 End If
  1125.             End If
  1126.         Next
  1127.     Else
  1128.         Write_zTXt = True
  1129.     End If
  1130.     
  1131. eh:
  1132.     If Err Then Err.Clear
  1133. End Function
  1134. Private Function Write_bKGD(fileNum As Long, Stream() As Byte) As Boolean
  1135.     ' For paletted/grayscale images, this is the palette index, otherwise RGB value
  1136.     On Error GoTo eh
  1137.     Const chnk_bKGD As Long = &H44474B62 'Window Background Color
  1138.     
  1139.     If (m_PNGprops And ePngProperties.colorDefaultBkg) = ePngProperties.colorDefaultBkg Then
  1140.     
  1141.         Dim pngData() As Byte
  1142.         Dim gpLong As Long
  1143.         Dim rwLen As Long
  1144.         Dim Index As Long
  1145.             
  1146.         ' Per PNG specs, bKGD chunk must come before IDAT and after PLTE
  1147.         Select Case m_ColorType
  1148.         Case clrPalette  ' 1 byte + 4 byte chunk name
  1149.             ReDim pngData(0 To 4)
  1150.             pngData(4) = CByte(m_bKGD)
  1151.         
  1152.         Case clrGrayScale, clrGrayAlpha   ' grayscales, 2 bytes + 4 byte chunk name
  1153.             ReDim pngData(0 To 6)
  1154.             pngData(5) = (m_bKGD And &HFF)
  1155.             ' pngData(4) used with 48bit per pixel images (not supported)
  1156.             
  1157.         Case Else ' true color, RGB format
  1158.             ReDim pngData(0 To 9)   ' 6 bytes + 4 byte chunk name
  1159.             pngData(5) = m_bKGD And &HFF
  1160.             pngData(7) = (m_bKGD  &H100&) And &HFF
  1161.             pngData(9) = (m_bKGD  &H10000) And &HFF
  1162.             ' Note: pngData(4,6,8) used with 48bit per pixel images (not supported)
  1163.         End Select
  1164.         
  1165.         CopyMemory pngData(0), chnk_bKGD, 4&
  1166.         gpLong = UBound(pngData) + 1&
  1167.         
  1168.         If fileNum = 0& Then 'writing to array
  1169.             Index = UBound(Stream) + 1&
  1170.             ReDim Preserve Stream(0 To Index + gpLong + 7&)
  1171.             rwLen = iparseReverseLong(gpLong - 4&)
  1172.             CopyMemory Stream(Index), rwLen, 4&
  1173.             CopyMemory Stream(Index + 4&), pngData(0), gpLong
  1174.             rwLen = zCreateCRC(VarPtr(pngData(0)), gpLong)
  1175.             CopyMemory Stream(Index + gpLong + 4&), rwLen, 4&
  1176.             Write_bKGD = True
  1177.         Else
  1178.             WriteFile fileNum, iparseReverseLong(gpLong - 4&), 4&, rwLen, ByVal 0&
  1179.             If rwLen = 4& Then
  1180.                 WriteFile fileNum, pngData(0), gpLong, rwLen, ByVal 0&
  1181.                 If rwLen = gpLong Then
  1182.                     WriteFile fileNum, zCreateCRC(VarPtr(pngData(0)), gpLong), 4&, rwLen, ByVal 0&
  1183.                     Write_bKGD = (rwLen = 4&)
  1184.                 End If
  1185.             End If
  1186.         End If
  1187.     Else
  1188.         Write_bKGD = True
  1189.     End If
  1190. eh:
  1191.     If Err Then Err.Clear
  1192.         
  1193. End Function
  1194. Private Function Write_IDAT(fileNum As Long, outStream() As Byte, imgData() As Byte, filterLen As Long) As Boolean
  1195.     ' Function writes the IDAT chunk(s). If more than one, they must be back to back
  1196.     ' Note: IDATs can be written in multiple chunks; if so, chunks must be consecutive
  1197.     Const chnk_IDAT As Long = &H54414449 'Image data
  1198.     
  1199.     On Error GoTo eh
  1200.     Dim gpLong As Long, Index As Long
  1201.     Dim rwLen As Long
  1202.     
  1203.     CopyMemory imgData(0), chnk_IDAT, 4&
  1204.     If fileNum = 0& Then ' writing to array vs file
  1205.         Index = UBound(outStream) + 1&
  1206.         ReDim Preserve outStream(0 To Index + filterLen + 11&)
  1207.         gpLong = iparseReverseLong(filterLen)
  1208.         CopyMemory outStream(Index), gpLong, 4&  ' add chunk size
  1209.         CopyMemory outStream(Index + 4&), imgData(0), filterLen + 4& ' add compressed data
  1210.         gpLong = zCreateCRC(VarPtr(imgData(0)), filterLen + 4&)
  1211.         CopyMemory outStream(Index + 8& + filterLen), gpLong, 4& ' add crc value
  1212.         Write_IDAT = True
  1213.     Else
  1214.         
  1215.         WriteFile fileNum, iparseReverseLong(filterLen), 4&, rwLen, ByVal 0&
  1216.         If rwLen = 4& Then
  1217.             WriteFile fileNum, imgData(0), filterLen + 4&, rwLen, ByVal 0&
  1218.             If rwLen = filterLen + 4& Then
  1219.                 WriteFile fileNum, zCreateCRC(VarPtr(imgData(0)), rwLen), 4&, rwLen, ByVal 0&
  1220.                 Write_IDAT = (rwLen = 4&)
  1221.             End If
  1222.         End If
  1223.     End If
  1224. eh:
  1225.     If Err Then Err.Clear
  1226. End Function
  1227. Private Function Write_IEND(fileNum As Long, Stream() As Byte) As Boolean
  1228.     Const chnk_IEND As Long = &H444E4549 'End of Image
  1229.     
  1230.     On Error GoTo eh
  1231.     Dim Index As Long
  1232.     Dim gpLong As Long
  1233.     Dim rwLen As Long
  1234.     
  1235.     If fileNum = 0 Then ' writing to array vs file
  1236.     
  1237.         Index = UBound(Stream) + 1&
  1238.         ReDim Preserve Stream(0 To Index + 11&)
  1239.         CopyMemory Stream(Index), 0&, 4&
  1240.         CopyMemory Stream(Index + 4), chnk_IEND, 4&   ' chunk name, chunk length is zero
  1241.         gpLong = zCreateCRC(VarPtr(chnk_IEND), 4&)
  1242.         CopyMemory Stream(Index + 8&), gpLong, 4&    ' crc value
  1243.         Write_IEND = True
  1244.     Else
  1245.     
  1246.         WriteFile fileNum, rwLen, 4&, rwLen, ByVal 0&
  1247.         If rwLen = 4& Then
  1248.             WriteFile fileNum, chnk_IEND, 4&, rwLen, ByVal 0&
  1249.             If rwLen = 4& Then
  1250.                 WriteFile fileNum, zCreateCRC(VarPtr(chnk_IEND), 4&), 4&, rwLen, ByVal 0&
  1251.                 Write_IEND = (rwLen = 4&)
  1252.             End If
  1253.         End If
  1254.     End If
  1255. eh:
  1256.     If Err Then Err.Clear
  1257. End Function
  1258. Private Sub EncodeFilter_None(pngData() As Byte, _
  1259.                         ByVal RowNr As Long, dibRowNr As Long, _
  1260.                         ByVal scanLineDIB As Long, scanLinePNG As Long, _
  1261.                         stepVal As Byte, AdptValue As Long)
  1262.     ' this routine is only called when adapative filter method is used
  1263.     
  1264.     Dim X As Long
  1265.     Dim startByte As Long, locDIB As Long
  1266.     Dim lTest As Long
  1267.     
  1268.     If scanLineDIB > -1 Then ' processing interlaced image
  1269.         ' for interlaced, m_Uncompressed will be a top-down calculated array
  1270.         ' and the scanLineDIB parameter is an offset into the interlaced array
  1271.         startByte = scanLineDIB + 1
  1272.         locDIB = startByte
  1273.     Else
  1274.         ' for non-interlaced, m_Uncompressed will be a bottom-up DIB
  1275.         locDIB = dibRowNr * -scanLineDIB
  1276.         startByte = RowNr * scanLinePNG + RowNr + 1
  1277.    End If
  1278.     
  1279.     For X = locDIB To locDIB + scanLinePNG - 1
  1280.         lTest = lTest + m_Uncompressed(X)
  1281.         If lTest > AdptValue Then Exit Sub
  1282.     Next
  1283.     
  1284.     If lTest = 0 Then lTest = 1
  1285.     AdptValue = lTest
  1286.     pngData(startByte - 1) = 0
  1287. End Sub
  1288. Private Sub EncodeFilter_Up(pngData() As Byte, _
  1289.                         ByVal RowNr As Long, dibRowNr As Long, _
  1290.                         ByVal scanLineDIB As Long, scanLinePNG As Long, _
  1291.                         stepVal As Byte, AdptValue As Long)
  1292. ' this is Filter Type 2
  1293. 'http://www.w3.org/TR/PNG/#9-table91
  1294.     
  1295.     Dim ppTop As Integer
  1296.     Dim X As Long
  1297.     Dim startByte As Long, locDIB As Long
  1298.     Dim lTest As Long, prevRow As Long
  1299.     
  1300.     If scanLineDIB > -1 Then ' processing interlaced image
  1301.         ' for interlaced, m_Uncompressed will be a top-down calculated array
  1302.         ' and the scanLineDIB parameter is an offset into the interlaced array
  1303.         startByte = scanLineDIB + 1
  1304.         scanLineDIB = scanLinePNG + 1
  1305.         locDIB = startByte
  1306.     Else
  1307.         ' for non-interlaced, m_Uncompressed will be a bottom-up DIB
  1308.         locDIB = dibRowNr * -scanLineDIB
  1309.         startByte = RowNr * scanLinePNG + RowNr + 1
  1310.     End If
  1311.         
  1312.     If AdptValue Then
  1313.         
  1314.         If RowNr Then
  1315.             For X = locDIB To locDIB + scanLinePNG - 1
  1316.                 lTest = lTest + Abs(0 + m_Uncompressed(X) - m_Uncompressed(X - scanLineDIB))
  1317.                 If lTest > AdptValue Then Exit Sub
  1318.             Next
  1319.             
  1320.             If lTest = 0 Then lTest = 1
  1321.             AdptValue = lTest
  1322.             pngData(startByte - 1) = 2
  1323.         End If
  1324.         
  1325.     Else
  1326.         For X = 0 To scanLinePNG - 1
  1327.             If RowNr Then ppTop = m_Uncompressed(locDIB + X - scanLineDIB)
  1328.             ' VB workaround for C++ unsigned math
  1329.             If ppTop > m_Uncompressed(locDIB + X) Then
  1330.                 pngData(startByte + X) = 256 - ppTop + m_Uncompressed(locDIB + X)
  1331.             Else
  1332.                 pngData(startByte + X) = m_Uncompressed(locDIB + X) - ppTop
  1333.             End If
  1334.         Next
  1335.         pngData(startByte - 1) = 2
  1336.     End If
  1337. End Sub
  1338. Private Sub EncodeFilter_Sub(pngData() As Byte, _
  1339.                         ByVal RowNr As Long, dibRowNr As Long, _
  1340.                         ByVal scanLineDIB As Long, scanLinePNG As Long, _
  1341.                         stepVal As Byte, AdptValue As Long)
  1342. ' This is Filter Type 1
  1343. 'http://www.w3.org/TR/PNG/#9-table91
  1344.     Dim X As Long
  1345.     Dim startByte As Long, locDIB As Long
  1346.     Dim lTest As Long
  1347.     
  1348.     If scanLineDIB > -1 Then ' processing interlaced image
  1349.         ' for interlaced, m_Uncompressed will be a top-down calculated array
  1350.         ' and the scanLineDIB parameter is an offset into the interlaced array
  1351.         startByte = scanLineDIB + 1
  1352.         scanLineDIB = scanLinePNG + 1
  1353.         locDIB = startByte
  1354.     Else
  1355.         ' for non-interlaced, m_Uncompressed will be a bottom-up DIB
  1356.         locDIB = dibRowNr * -scanLineDIB
  1357.         startByte = RowNr * scanLinePNG + RowNr + 1
  1358.     End If
  1359.         
  1360.     If AdptValue Then
  1361.     
  1362.         ' 1st n bytes for 1st pixel are unfiltered
  1363.         For X = locDIB To stepVal + locDIB - 1
  1364.             lTest = lTest + m_Uncompressed(X)
  1365.         Next
  1366.         
  1367.         For X = locDIB + stepVal To scanLinePNG - 1
  1368.             lTest = lTest + Abs(0 + m_Uncompressed(X) - m_Uncompressed(X - stepVal))
  1369.             If lTest > AdptValue Then Exit Sub
  1370.         Next
  1371.         
  1372.         If lTest = 0 Then lTest = 1
  1373.         AdptValue = lTest
  1374.         
  1375.     Else
  1376.         ' 1st n bytes for 1st pixel are unfiltered
  1377.         CopyMemory pngData(startByte), m_Uncompressed(locDIB), stepVal
  1378.         
  1379.         For X = stepVal To scanLinePNG - 1
  1380.             ' VB workaround for C++ unsigned math
  1381.             If m_Uncompressed(locDIB + X - stepVal) > m_Uncompressed(locDIB + X) Then
  1382.                 pngData(startByte + X) = 256 - m_Uncompressed(locDIB + X - stepVal) + m_Uncompressed(locDIB + X)
  1383.             Else
  1384.                 pngData(startByte + X) = m_Uncompressed(locDIB + X) - m_Uncompressed(locDIB + X - stepVal)
  1385.             End If
  1386.         Next
  1387.     End If
  1388.     pngData(startByte - 1) = 1
  1389. End Sub
  1390. Private Sub EncodeFilter_Avg(pngData() As Byte, _
  1391.                         ByVal RowNr As Long, dibRowNr As Long, _
  1392.                         ByVal scanLineDIB As Long, scanLinePNG As Long, _
  1393.                         stepVal As Byte, AdptValue As Long)
  1394. ' This is Filter Type 3
  1395. 'http://www.w3.org/TR/PNG/#9-table91
  1396.     Dim ppLeft As Integer, ppTop As Integer
  1397.     Dim X As Long, pReturn As Integer
  1398.     Dim locDIB As Long, startByte As Long
  1399.     Dim lTest As Long
  1400.     
  1401.     If scanLineDIB > -1 Then ' processing interlaced image
  1402.         ' for interlaced, m_Uncompressed will be a top-down calculated array
  1403.         ' and the scanLineDIB parameter is an offset into the interlaced array
  1404.         startByte = scanLineDIB + 1
  1405.         scanLineDIB = scanLinePNG + 1
  1406.         locDIB = startByte
  1407.     Else
  1408.         ' for non-interlaced, m_Uncompressed will be a bottom-up DIB
  1409.         locDIB = dibRowNr * -scanLineDIB
  1410.         startByte = RowNr * scanLinePNG + RowNr + 1
  1411.     End If
  1412.     
  1413.     If AdptValue Then
  1414.     
  1415.         If RowNr Then
  1416.         
  1417.             For X = locDIB To locDIB + scanLinePNG - 1
  1418.                 ppTop = m_Uncompressed(X - scanLineDIB)
  1419.                 If X >= locDIB + stepVal Then ppLeft = m_Uncompressed(X - stepVal)
  1420.                 lTest = lTest + Abs((m_Uncompressed(X) - (ppLeft + ppTop)  2))
  1421.                 If lTest > AdptValue Then Exit Sub
  1422.             Next
  1423.             If lTest = 0 Then lTest = 1
  1424.             AdptValue = lTest
  1425.             pngData(startByte - 1) = 3
  1426.             
  1427.         End If
  1428.         
  1429.     Else
  1430.     
  1431.         For X = 0 To scanLinePNG - 1
  1432.         
  1433.             If RowNr Then ppTop = m_Uncompressed(locDIB - scanLineDIB + X)
  1434.             If X >= stepVal Then ppLeft = m_Uncompressed(locDIB - stepVal + X)
  1435.             
  1436.             pReturn = (ppLeft + ppTop)  2
  1437.             ' VB workaround for C++ unsigned math
  1438.             If pReturn > m_Uncompressed(locDIB + X) Then
  1439.                 pngData(X + startByte) = 256 - pReturn + m_Uncompressed(X + locDIB)
  1440.             Else
  1441.                 pngData(X + startByte) = m_Uncompressed(X + locDIB) - pReturn
  1442.             End If
  1443.         
  1444.         Next
  1445.         pngData(startByte - 1) = 3
  1446.     
  1447.     End If
  1448.     
  1449. End Sub
  1450. Private Sub EncodeFilter_Paeth(pngData() As Byte, _
  1451.                         ByVal RowNr As Long, dibRowNr As Long, _
  1452.                         ByVal scanLineDIB As Long, scanLinePNG As Long, _
  1453.                         stepVal As Byte, AdptValue As Long)
  1454. ' This is Filter Type 4
  1455. 'http://www.w3.org/TR/PNG/#9-table91
  1456.     Dim ppLeft As Integer, ppTop As Integer, ppTopLeft As Integer
  1457.     Dim X As Long, pReturn As Integer
  1458.     Dim startByte As Long, locDIB As Long
  1459.     Dim lTest As Long, prevRow As Long
  1460.     
  1461.     If scanLineDIB > -1 Then ' processing interlaced image
  1462.         ' for interlaced, m_Uncompressed will be a top-down calculated array
  1463.         ' and the scanLineDIB parameter is an offset into the interlaced array
  1464.         startByte = scanLineDIB + 1
  1465.         scanLineDIB = scanLinePNG + 1
  1466.         locDIB = startByte
  1467.     Else
  1468.         ' for non-interlaced, m_Uncompressed will be a bottom-up DIB
  1469.         locDIB = dibRowNr * -scanLineDIB
  1470.         startByte = RowNr * scanLinePNG + RowNr + 1
  1471.     End If
  1472.         
  1473.     If AdptValue Then
  1474.     
  1475.         If RowNr Then
  1476.             
  1477.             For X = locDIB To locDIB + scanLinePNG - 1
  1478.                 If X >= stepVal + locDIB Then ' we are not on the 1st pixel
  1479.                     ppLeft = m_Uncompressed(X - stepVal)
  1480.                     ppTopLeft = m_Uncompressed(X - scanLineDIB - stepVal)
  1481.                 End If
  1482.                 ppTop = m_Uncompressed(X - scanLineDIB)
  1483.                 ' get the Paeth closest neighbor
  1484.                 lTest = lTest + Abs((m_Uncompressed(X) - PaethPredictor(ppLeft, ppTop, ppTopLeft)))
  1485.                 If lTest > AdptValue Then Exit Sub
  1486.             Next
  1487.             
  1488.             If lTest = 0 Then lTest = 1
  1489.             AdptValue = lTest
  1490.             pngData(startByte - 1) = 4
  1491.         End If
  1492.     Else
  1493.     
  1494.         For X = 0 To scanLinePNG - 1
  1495.             
  1496.             If X >= stepVal Then ' we are not on the 1st pixel
  1497.                 ppLeft = m_Uncompressed(locDIB + X - stepVal)
  1498.                 If RowNr Then
  1499.                     prevRow = locDIB + X - scanLineDIB
  1500.                     ppTop = m_Uncompressed(prevRow)
  1501.                     ppTopLeft = m_Uncompressed(prevRow - stepVal)
  1502.                 End If
  1503.             Else
  1504.                 If RowNr Then ppTop = m_Uncompressed(locDIB + X - scanLineDIB)
  1505.             End If
  1506.             ' get the Paeth closest neighbor
  1507.             pReturn = PaethPredictor(ppLeft, ppTop, ppTopLeft)
  1508.             
  1509.             ' VB workaround for C++ unsigned math
  1510.             If pReturn > m_Uncompressed(locDIB + X) Then
  1511.                 pngData(startByte + X) = 256 - pReturn + m_Uncompressed(locDIB + X)
  1512.             Else
  1513.                 pngData(startByte + X) = m_Uncompressed(locDIB + X) - pReturn
  1514.             End If
  1515.             
  1516.         Next
  1517.         pngData(startByte - 1) = 4
  1518.     End If
  1519. End Sub
  1520. Private Function PaethPredictor(Left As Integer, Above As Integer, UpperLeft As Integer) As Integer
  1521. ' http://www.w3.org/TR/PNG/#9-table91
  1522. ' algorithm is used for both encoding & decoding the png paeth filter
  1523. ' Based off of the formula created by Alan W. Paeth & provided fully in url above
  1524.     Dim pa As Integer, pb As Integer, pC As Integer, p As Integer
  1525.     p = Left + Above - UpperLeft
  1526.     pa = Abs(p - Left)
  1527.     pb = Abs(p - Above)
  1528.     pC = Abs(p - UpperLeft)
  1529.     
  1530.     ' tie breaker
  1531.     ' The order in which the comparisons are performed is critical and shall not be altered
  1532.     If (pa <= pb) And (pa <= pC) Then
  1533.         PaethPredictor = Left
  1534.     ElseIf pb <= pC Then
  1535.         PaethPredictor = Above
  1536.     Else
  1537.         PaethPredictor = UpperLeft
  1538.     End If
  1539. End Function
  1540. Private Function FilterImage(fileNum As Long, Stream() As Byte, Host As c32bppDIB, ByVal FilterMethod As eFilterMethods) As Boolean
  1541.     ' Routine will Filter the image in one of the 5 types of authorized PNG Filters.
  1542.     ' The Adaptive filter approach will select a best-guess filter to use for each
  1543.     ' scan line of the image. Otherwise, the same filter is applied to every scan line.
  1544.     
  1545.     ' Note about filters. Binary data compresses very poorly. Filters are a way to
  1546.     '   rewrite the binary data so that it will compress better. That is its only purpose.
  1547.     Dim scanWidth_DIB As Long, scanWidth_PNG As Long    ' scanwidths of 2 images
  1548.     Dim compressedData() As Byte          ' filtered PNG data
  1549.     Dim filteredData() As Byte           ' unfiltered PNG data
  1550.     Dim gpLong As Long              ' general purpose Long value
  1551.     Dim arrayPtr As Long, pIndex As Long    ' array/loop variables
  1552.     Dim bytePP As Byte              ' DIB/PNG bytes per pixel
  1553.     
  1554.     If FilterMethod < filterDefault Or FilterMethod > filterAdaptive Then FilterMethod = filterDefault
  1555.     Select Case m_ColorType
  1556.     Case clrGrayScale, clrPalette
  1557.         scanWidth_DIB = Host.Width
  1558.         scanWidth_PNG = scanWidth_DIB
  1559.         ' paletted images. Almost always, filter type zero is best (no filters)
  1560.         If FilterMethod = filterDefault Then FilterMethod = filterNone
  1561.         bytePP = 1
  1562.         
  1563.     Case Else ' true color, true color w/alpha (grayscale w/Alpha is converted to clrPalette in PalettizeImage)
  1564.         ' the best, non-adapative method for 24/32 bit is usually Paeth
  1565.         If FilterMethod = filterDefault Then FilterMethod = filterPaeth
  1566.         If m_ColorType = clrTrueAlpha Then
  1567.             ' get scan width for PNG file: byte aligned
  1568.             scanWidth_DIB = Host.scanWidth
  1569.             scanWidth_PNG = scanWidth_DIB
  1570.             bytePP = 4
  1571.         Else
  1572.             scanWidth_DIB = iparseByteAlignOnWord(24, Host.Width)
  1573.             scanWidth_PNG = ByteAlignOnByte(Host.Width, 24)
  1574.             bytePP = 3
  1575.         End If
  1576.     End Select
  1577.     
  1578.     ' Size raw data to be compressed and include 1 filter byte per line of image
  1579.     ReDim filteredData(0 To scanWidth_PNG * Host.Height + Host.Height - 1)
  1580.     If Err Then
  1581.         ' about the only possible error would be not enough memory to process the image file
  1582.         Err.Clear
  1583.         Exit Function
  1584.     End If
  1585.     
  1586.     
  1587.         For pIndex = 0 To Host.Height - 1
  1588.             
  1589.             arrayPtr = pIndex * scanWidth_PNG + pIndex   ' position of scanline
  1590.             
  1591.             If FilterMethod = filterAdaptive Then
  1592.                 ' adaptive filtering
  1593.                 ' although this can sequeeze an extra couple kb out of the png, I am finding
  1594.                 ' that using Paeth appears to be either better or very close to adaptive filtering
  1595.                 ' in most cases. Paeth is slowest of the top 5 filters (0-4). But adaptive filtering
  1596.                 ' is significantly slower than Paeth. The deciding factor for adapative
  1597.                 ' outdoing the others is the number of colors in the image. The more colors,
  1598.                 ' the better chances adaptive has of being smaller size. The least amount
  1599.                 ' of colors, the better chances Paeth has of being smaller.
  1600.                 
  1601.                 ' More testing needed though. I wouldn't imagine the PNG specs would recommend
  1602.                 ' adaptive filtering unless it had some huge advantage over Paeth. What
  1603.                 ' I am avoiding at all costs is a brute force routine to definitively
  1604.                 ' find the best scanline filter method. That brute force can literally
  1605.                 ' take minutes on full size 24/32bpp images.
  1606.                 
  1607.                 filteredData(arrayPtr) = 0
  1608.                 gpLong = scanWidth_PNG * 254&  ' max value
  1609.                 
  1610.                 ' listed in order of quickest
  1611.                 EncodeFilter_None filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, gpLong
  1612.                 EncodeFilter_Sub filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, gpLong
  1613.                 EncodeFilter_Up filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, gpLong
  1614.                 EncodeFilter_Avg filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, gpLong
  1615.                 EncodeFilter_Paeth filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, gpLong
  1616.                 ' ^^ the -scanWidth_DIB is a flag indicating we are not filtering interlaced PNG
  1617.             Else
  1618.                 filteredData(arrayPtr) = FilterMethod - 1
  1619.             End If
  1620.                     
  1621.             Select Case filteredData(arrayPtr) + 1 ' cache filter method into PNG data
  1622.             Case filterNone
  1623.                 gpLong = (Host.Height - pIndex - 1) * scanWidth_DIB ' get current row relative to upside down DIB
  1624.                 CopyMemory filteredData(arrayPtr + 1), m_Uncompressed(gpLong), scanWidth_PNG
  1625.             Case filterAdjLeft
  1626.                 EncodeFilter_Sub filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, 0
  1627.             Case filterAdjTop
  1628.                 EncodeFilter_Up filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, 0
  1629.             Case filterAdjAvg
  1630.                 EncodeFilter_Avg filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, 0
  1631.             Case filterPaeth
  1632.                 EncodeFilter_Paeth filteredData, pIndex, Host.Height - pIndex - 1, -scanWidth_DIB, scanWidth_PNG, bytePP, 0
  1633.             End Select
  1634.             ' ^^ the -scanWidth_DIB is a flag indicating we are not filtering interlaced PNG
  1635.         
  1636.         Next
  1637.     
  1638.     Erase m_Uncompressed    ' no longer needed
  1639.     If Err Then
  1640.         Err.Clear
  1641.     Else
  1642.         gpLong = UBound(filteredData) + 1
  1643.         gpLong = gpLong * 0.01 + 12 + gpLong ' < sizing algorithm for compressed array per zLIB specs
  1644.         ReDim compressedData(0 To gpLong + 3) ' include 4 bytes for Write_IDAT
  1645.         If zDeflate(VarPtr(compressedData(4)), gpLong, VarPtr(filteredData(0)), UBound(filteredData) + 1) = True Then
  1646.             FilterImage = Write_IDAT(fileNum, Stream(), compressedData(), gpLong)
  1647.         End If
  1648.     
  1649.     End If
  1650. End Function
  1651. Private Function FormatText(txt2format As String) As String
  1652.     
  1653.     ' Function ensures text added to a PNG file meets PNG specs
  1654.     Dim Index As Integer
  1655.     Dim rtnString As String
  1656.     Const maxLength As Long = 32700&
  1657.     ' not a PNG restriction, but self-imposed. Keep text to Integer length
  1658.     
  1659.     ' per PNG specs, text and captions are limited to Latin1 character set and the line feed
  1660.     ' Latin1 character set is Chr$(32) & above.
  1661.     ' http://www.w3.org/TR/PNG/iso_8859-1.txt
  1662.     
  1663.     If txt2format = vbNullString Then
  1664.         rtnString = txt2format
  1665.     Else
  1666.         rtnString = Left$(txt2format, maxLength)
  1667.         
  1668.         ' per PNG specs, the only character allowed lower than a SPACE is the line feed character
  1669.         ' therefore we will replace vbCrLf with the line feed chr$(10)/vbLf
  1670.         rtnString = Replace$(rtnString, vbCrLf, vbLf)
  1671.         rtnString = Replace$(rtnString, vbCr, vbLf)
  1672.         
  1673.         For Index = 1 To Len(rtnString)
  1674.             Select Case Asc(Mid$(rtnString, Index, 1))
  1675.             Case 9, 10   ' lf is valid. Tab, depending on sources, is ok too
  1676.             Case Is < 32 ' all these are not allowed
  1677.                 rtnString = vbNullString
  1678.                 Exit For
  1679.             Case Else       ' otherwise, all other characters are allowed
  1680.             End Select
  1681.         Next
  1682.     End If
  1683.     
  1684.     FormatText = rtnString
  1685. End Function
  1686. Private Function FormatCaption(ByRef Caption As String) As Long
  1687.     ' per PNG specs, text and captions are limited to Latin1 character set and the line feed
  1688.     ' Latin1 character set is Chr$(32) & above.
  1689.     ' http://www.w3.org/TR/PNG/iso_8859-1.txt
  1690.     
  1691.     ' Return values
  1692.     ' If caption is a reserved caption,
  1693.     '       then FormatCaption=PropertyID of reserved caption
  1694.     '   else FormatCaption=-1 to indicate not reserved
  1695.     ' If caption is disqualified, then Caption is returned as vbNullString
  1696.     Dim Index As Long, CaptionID As Long
  1697.     
  1698.     If Not Caption = vbNullString Then
  1699.         
  1700.         ' we are formatting/validating a keyword/caption for LargeBlockText
  1701.             
  1702.         ' when adding txtLargeBlock, a keyword is required, but must meet specs
  1703.         Caption = Trim$(Left$(Caption, 79)) ' absolute requirement
  1704.         Do Until InStr(Caption, "  ") = 0   ' absolute requirement
  1705.             Caption = Replace$(Caption, "  ", " ") ' remove all double spaces
  1706.         Loop
  1707.         
  1708.         'only character codes 32-126 and 161-255 are allowed
  1709.         For Index = 1 To Len(Caption)
  1710.             Select Case Asc(Mid$(Caption, Index, 1))
  1711.             Case 160: Mid$(Caption, Index, 1) = Chr$(32) ' suggested: convert hard space to soft space
  1712.             Case 32 To 126  ' valid
  1713.             Case 161 To 255  ' valid
  1714.             Case Else       ' otherwise, all other characters are NOT allowed, invalidating caption
  1715.                 Caption = vbNullString
  1716.                 Exit For
  1717.             End Select
  1718.         Next
  1719.         
  1720.         ' now the last check, cannot use a reserved keyword
  1721.         If Not Caption = vbNullString Then CaptionID = isKeyWord(Caption)
  1722.     
  1723.     End If
  1724.     FormatCaption = CaptionID
  1725. End Function
  1726. Private Function isKeyWord(inCaption As String) As Long
  1727.     ' compares passed caption to PNG reserved keywords
  1728.     Dim Index As Long, keyWord As String, keyID As Long
  1729.         For Index = 1 To 11
  1730.             Select Case Index
  1731.             Case 1: keyWord = "Title": keyID = txtTitle
  1732.             Case 2: keyWord = "Author": keyID = txtAuthor
  1733.             Case 3: keyWord = "Description": keyID = txtDescription
  1734.             Case 4: keyWord = "Copyright": keyID = txtCopyright
  1735.             Case 5: keyWord = "Creation Time": keyID = txtCreationTime
  1736.             Case 6: keyWord = "Software": keyID = txtSoftware
  1737.             Case 7: keyWord = "Disclaimer": keyID = txtDisclaimer
  1738.             Case 8: keyWord = "Warning": keyID = txtWarning
  1739.             Case 9: keyWord = "Source'": keyID = txtSource
  1740.             Case 10: keyWord = "Comment": keyID = txtComment
  1741.             Case 11: keyID = -1: Exit For
  1742.             End Select
  1743.             If StrComp(keyWord, inCaption, vbTextCompare) = 0 Then Exit For
  1744.         Next
  1745.     isKeyWord = keyID   ' return value of -1 indicates caption is not reserved
  1746.     
  1747. End Function
  1748. Private Function IsValidProperty(PropertyID As ePngProperties) As Boolean
  1749.     Select Case PropertyID
  1750.     Case txtAuthor: IsValidProperty = True
  1751.     Case txtComment: IsValidProperty = True
  1752.     Case txtCopyright: IsValidProperty = True
  1753.     Case txtCreationTime: IsValidProperty = True
  1754.     Case txtDescription: IsValidProperty = True
  1755.     Case txtDisclaimer: IsValidProperty = True
  1756.     Case txtLargeBlockText: IsValidProperty = True
  1757.     Case txtSoftware: IsValidProperty = True
  1758.     Case txtSource: IsValidProperty = True
  1759.     Case txtTitle: IsValidProperty = True
  1760.     Case txtWarning: IsValidProperty = True
  1761.     Case colorDefaultBkg: IsValidProperty = True
  1762.     Case filterType: IsValidProperty = True
  1763.     Case dateTimeModified: IsValidProperty = True
  1764.     End Select
  1765.     
  1766. End Function
  1767. ' =======================================
  1768. ' FOLLOWING 3 FUNCTIONS ARE ZLIB RELATED
  1769. ' =======================================
  1770. Private Function zValidateZLIBversion() As Boolean
  1771.     ' Test for zlib availability & compatibility
  1772.     ' see modParsers.iparseValidateZLib for details
  1773.     
  1774.     Dim b_cdecl As Boolean, bCompress2 As Boolean, DllName As String
  1775.     
  1776.     If iparseValidateZLIB(DllName, m_ZLIBver, b_cdecl, bCompress2) = True Then
  1777.         If b_cdecl = True Then
  1778.             Set cCfunction = New cCDECL
  1779.             cCfunction.DllLoad DllName
  1780.         End If
  1781.         If bCompress2 Then m_ZLIBver = m_ZLIBver Or 32 ' flag indicating can use better compression
  1782.         zValidateZLIBversion = True
  1783.     End If
  1784.             
  1785.     
  1786. End Function
  1787. Private Function zCreateCRC(crcSrcRef As Long, srcLength As Long) As Long
  1788.     ' function returns zLIB's CRC value for passed crcTestRef value.
  1789.     Dim lReturn As Long
  1790.     If cCfunction Is Nothing Then
  1791.         If (m_ZLIBver And 1&) = 1& Then
  1792.             lReturn = Zcrc32(0&, ByVal crcSrcRef, srcLength)
  1793.         ElseIf (m_ZLIBver And 2&) = 2& Then
  1794.             lReturn = Zcrc321(0&, ByVal crcSrcRef, srcLength)
  1795.         End If
  1796.     Else
  1797.         lReturn = cCfunction.CallFunc("crc32", 0&, crcSrcRef, srcLength)
  1798.     End If
  1799.     If Not lReturn = 0& Then zCreateCRC = iparseReverseLong(lReturn)
  1800.     
  1801. End Function
  1802. Private Function zDeflate(destRef As Long, destSize As Long, srcRef As Long, srcSizeRef As Long) As Boolean
  1803.     ' function compresses/deflates passed srcRef into passed destRef and modifies the destSizeRef to indicate byte count of destRef
  1804.     
  1805.     ' earliest versions of DLL do not have Compress2 which newer versions have.
  1806.     ' Newer versions allow a compression parameter to allow deeper compression.
  1807.     ' When compress is called in newer DLL it just reroutes to the compress2 method
  1808.     
  1809.     Dim lReturn As Long
  1810.     If cCfunction Is Nothing Then
  1811.         If m_ZLIBver = 34& Then ' 34 = 2 or 32                  ' double checked 3/1/2007
  1812.             ' use compress2 function
  1813.             zDeflate = (Zcompress21(ByVal destRef, destSize, ByVal srcRef, srcSizeRef, zlibMaxCompression) = 0&)
  1814.         ElseIf m_ZLIBver = 33& Then ' 33 = 1 or 32              ' double checked 3/1/2007
  1815.             ' use compress2 function
  1816.             zDeflate = (Zcompress2(ByVal destRef, destSize, ByVal srcRef, srcSizeRef, zlibMaxCompression) = 0&)
  1817.         ElseIf (m_ZLIBver And 1) = 1& Then                      ' double checked 3/1/2007
  1818.             ' use compress function
  1819.             zDeflate = (Zcompress(ByVal destRef, destSize, ByVal srcRef, srcSizeRef) = 0&)
  1820.         ElseIf m_ZLIBver = 2& Then                              ' double checked 3/1/2007
  1821.             ' use compress function
  1822.             zDeflate = (Zcompress1(ByVal destRef, destSize, ByVal srcRef, srcSizeRef) = 0&)
  1823.         End If
  1824.     Else
  1825.         If (m_ZLIBver And 32&) = 32& Then                       ' double checked 3/1/2007
  1826.             ' use compress2 function
  1827.             zDeflate = (cCfunction.CallFunc("compress2", destRef, VarPtr(destSize), srcRef, srcSizeRef, zlibMaxCompression) = 0&)
  1828.         Else                                                    ' double checked 3/1/2007
  1829.             ' use compress function
  1830.             zDeflate = (cCfunction.CallFunc("compress", destRef, VarPtr(destSize), srcRef, srcSizeRef) = 0&)
  1831.         End If
  1832.     End If
  1833. End Function