Skip to content

Commit

Permalink
PNG export: finalize support for "transparent color" detection...
Browse files Browse the repository at this point in the history
...when "automatic" output settings are used.  (Manual output settings will require additional changes tbd.)

With this, I believe PD now boasts the most comprehensive automatic PNG feature detector on the planet.  This was a large (and ugly) project and the code likely needs to be refactored to reduce some extremely large functions, but at least everything is working well.

Why all this effort?  PNGs support multiple mechanisms for storing transparency data.  Some color depths support embedding full alpha channels (e.g. "Grayscale + Alpha" and "TrueColor + Alpha" color modes).  Other color depths support flagging a single color or luminance value as transparent (e.g. "Grayscale" and "TrueColor" modes can store transparency data this way, allowing for GIF-like transparency in an 8- or 24-bit image).  While it's tedious to account for, using this feature can trim down PNG sizes on compatible images.  Note also that still other color modes allow you to assign per-index transparency values (e.g. "Palette" color mode allows this, so you can use up to 256 combinations of RGBA data, where "a" can be "any value" - pngquant relies on this to produce its images).

PD now performs comprehensive analysis on an image before exporting it, with the goal of determining which combination of features will produce the most compact PNG file.  If it can get away with using a palette or a transparent color or a special color mode like grayscale, it will, in any combination.

There are a ton of edge-cases to cover, including esoteric ones like "if an image uses all 16.7 million possible colors, *and* it has transparency data, don't use a transparent color flag because no colors are available for flagging" - but I'm pretty sure PD covers those cases completely.  The edge-cases are particularly obnoxious for grayscale modes, as 1/2/4/8-bit all have different requirements when trying to figure out if any unused shades are available for use as transparent color flags... but even monochrome images with alpha data should now be handled "ideally", with grayscale+alpha mode only being activated if absolutely necessary due to alpha channel complexity or a lack of unused gray values.

This commit also includes a large reorganization of PNG-specific export code which previously resided in the ExportPNG function.  The old organization required a ton of extra parameters to be passed between that function and the pdPNG class, and now that we no longer require FreeImage for PNG import/export, it made sense to migrate that functionality directly into pdPNG.  (This also allows me to remove a bunch of PNG-specific code from PD's existing "autodetect color mode/depth" export function, and optimize some pathways for PNG needs specifically.)
  • Loading branch information
tannerhelland committed Apr 4, 2019
1 parent 96ea6e6 commit 10c78b3
Show file tree
Hide file tree
Showing 9 changed files with 1,053 additions and 294 deletions.
105 changes: 105 additions & 0 deletions Classes/pdColorCount.cls
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,40 @@ Private Function AllocateNode(ByVal startValue As Long, Optional ByVal useSmallC
m_NumOfEntries = m_NumOfEntries + 1
End Function

'After counting colors, you can use this function to see if a specified color exists in the color tree.
Friend Function DoesColorExist(ByVal srcR As Long, ByVal srcG As Long, ByVal srcB As Long) As Boolean

DoesColorExist = False

If (Me.GetUniqueRGBACount > 0) Then

If (m_Colors(0).ceChildIndices(srcR) <> 0) Then

'R exists in the tree...
Dim gIndex As Long
gIndex = m_Colors(0).ceChildIndices(srcR)
If (gIndex <> 0) And (srcG <= UBound(m_Colors(gIndex).ceChildIndices)) Then

'G exists in the tree...
Dim bIndex As Long
bIndex = m_Colors(gIndex).ceChildIndices(srcG)
If (bIndex <> 0) And (srcB <= UBound(m_Colors(bIndex).ceChildIndices)) Then

'B exists in the tree...
Dim aIndex As Long
aIndex = m_Colors(bIndex).ceChildIndices(srcB)
DoesColorExist = (aIndex <> 0)

End If

End If

End If

End If

End Function

'After counting colors, you can use this function to retrieve a matching palette, e.g. a list of every
' color in the tree. (While this works on any size color tree, this function is primarily designed
' for use on images with 256 colors or less.)
Expand Down Expand Up @@ -382,6 +416,77 @@ Friend Function GetUniqueRGBACount() As Long
End If
End Function

'After counting colors, you can use this function to find an unused color in the current tree.
' (This is extremely helpful during PNG export, to try and find a good tRNS candidate.)
Friend Function GetUnusedColor(ByRef dstRed As Long, ByRef dstGreen As Long, ByRef dstBlue As Long) As Boolean

If (Me.GetUniqueRGBACount > 0) Then

GetUnusedColor = True

Dim r As Long, g As Long, b As Long
Dim rIndex As Long, gIndex As Long, bIndex As Long, aIndex As Long

'Iterate encountered r values
For r = 0 To 255

'This red value is unused in the image. Return any arbitrary green/blue value and exit.
If (m_Colors(0).ceChildIndices(r) = 0) Then
dstRed = r
dstGreen = 0
dstBlue = 0
Exit Function

'This red value is used, but maybe we can find a green/blue pair it isn't
' paired with.
Else

gIndex = m_Colors(0).ceChildIndices(r)
For g = 0 To UBound(m_Colors(gIndex).ceChildIndices)

'This green value is unused in the image. Return any arbitrary blue value and exit.
If (m_Colors(gIndex).ceChildIndices(g) = 0) Then
dstRed = r
dstGreen = g
dstBlue = 0
Exit Function

'This green value is used, but maybe we can find a blue value it isn't
' paired with.
Else

bIndex = m_Colors(gIndex).ceChildIndices(g)
For b = 0 To UBound(m_Colors(bIndex).ceChildIndices)

'This blue value is unused in the image. Return it and exit.
If (m_Colors(bIndex).ceChildIndices(b) = 0) Then
dstRed = r
dstGreen = g
dstBlue = b
Exit Function
End If

Next b

End If

Next g

End If

Next r

'If by some miracle we reach this point, it means every single RGB triple is used in this image
' (so the image must have at least 16.7 million pixels - interesting?). Return false.
GetUnusedColor = False

'A count hasn't been performed yet, so no color data exists
Else
GetUnusedColor = False
End If

End Function

Friend Sub SetAlphaTracking(ByVal newState As Boolean)
m_TrackRGBA = newState
End Sub
Expand Down
3 changes: 1 addition & 2 deletions Classes/pdDIB.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1589,8 +1589,7 @@ Private Sub PrepInternalSafeArray1D(ByRef dstSafeArray As SafeArray1D)
End With
End Sub

'Pre-composite an image with an alpha-channel against a background color. Until PhotoDemon is capable of rendering transparent
' images itself, this is necessary to give transparent images a white background.
'Pre-composite an image with an alpha-channel against a background color.
Friend Sub CompositeBackgroundColor(Optional ByVal newR As Byte = 255, Optional ByVal newG As Byte = 255, Optional ByVal newB As Byte = 255)

'This is only useful for images with alpha channels. Exit if no alpha channel is present.
Expand Down
Loading

0 comments on commit 10c78b3

Please sign in to comment.