Color reduction is implemented in a multistep algorithm. The first step gathers all colors and counts occurrence numbers of each color. The next step reduces the colors gathered by removing colors occurring the least. Finally the color values in the original image are replaced by the remaining colors and stored in the destination image.
The color values are gathered in a so called OctTree, whose implementation makes it easy to reduce the total number of colors and also to match newly read color values to already entered color values. Each node in the tree has up to eight children - hence the name - addressed with the color values of the color to enter.
The index of the child of an OctTree node is built by the bits of the color value
corresponding to the level of the child node. Example : The child node is at
level 3 and the color to enter is RGB=0xff00ff. The red bit is 1,
the green bit is 0 and the blue bit is 1. To build the number, the formula :
redBit * 4 + redBit * 2 + greenBit is used, giving 5 as the index
value for the color on level 3.
Distinct colors are represented by leaf nodes in the OctTree, that is by the nodes without children. A new color value is inserted on level 8 provided no leaf node exists on the graph from the root tree to the new leaf node on level 8.
When the number of leaf node becomes higher than the allowed number of colors, the tree is reduced. That is leaf nodes are merged and removed and the parent becomes the new leaf node thus also reducing the tree height. The node to be reduced is selected based on a weighting algorithm, the node with the last weight is selected. The weighting element simply is the number of occurrences of the color.
Merging child color nodes also involves weighted color weights based on the number of occurrences. That is if a color value 0xff0000 occurring 12 times and a color value 0x00ff00 occurring 2 times are mixed, the weighted result is the color value 0xda2400 (instead of 0xffff00 in the unweighted case). The weight of the new color is the sum of the weights of the merged colors, that is 14 in this case.
To ease selection of the reduction candidate, the leaf nodes are also kept in lists, one list of leaf nodes for each tree depth level. When a node and its sibblings are reduced and merged into their parent node, the former are removed from their leaf list and the latter is entered into the leaf list of its level. Example : two sibbling nodes on level 8 are merged into their parent, which is on level 7. The sibbling nodes are removed from the level 8 list and the parent, which becomes a leaf on level 7, is entered into the level 7 list.
Reduction always first checks for candidate nodes in the highest level non-empty list. That is as long as there are nodes on level 7, no nodes on level 6 will be selected a reduction candidate.
To conserve memory the image is processed by pixel row, so the basic pixel
memory requirements are 2 * 4 * 4 * width, that two arrays (2) or
ints (4) containing 4 elements per pixel in the row. Additionally for each node
in the OctTree, the memory usage is 81 bytes. The tree structure depends on the
color structure of the image. To further reduce memory consumption and
processing time, the OctTree is reduced each time, the number of distinct
colors (leaf nodes) extends beyond the maximum colors desired.
For example, to reduce 24bit PNG image (393 x 501 pixels) with originally over 48000 distinct colors, the memory used is around 20 KB. One might compare this to an early implementation, where the complete soruce and destination images are hold in a memory array. For the same image this old implementation used 1.7 MB of memory !
Nodes reduced away may be reused by new color values read from the image. To enable this reuse, the node implementaion keeps a list of recycled nodes, which are used before new nodes are created. The number of nodes to be created and allocated can thus be dramatically reduced. For the same image as above the number of nodes allocated comes down from 1268 nodes, when not reusing, to 155 nodes when reusing. Besides reducing processing time due to saving memory allocation garbage collector work, this reduces the memory footprint by around 90 KB.