' ' Code C# récupéré depuis http://msdn2.microsoft.com/en-us/library/aa479306.aspx puis converti en VB. ' '''' ' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ' ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO ' THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A ' PARTICULAR PURPOSE. ' This is sample code and is freely distributable. '''' Imports System Imports System.Drawing Imports System.Drawing.Imaging Imports System.Runtime.InteropServices Namespace ImageManipulation Public MustInherit Class Quantizer ''' ''' Construct the quantizer ''' ''' If true, the quantization only needs to loop through the source pixels once ''' ''' If you construct this class with a true value for singlePass, then the code will, when quantizing your image, ''' only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' ''' and then 'QuantizeImage'. ''' Public Sub New(ByVal singlePass As Boolean) _singlePass = singlePass End Sub ''' ''' Quantize an image and return the resulting output bitmap ''' ''' The image to quantize ''' A quantized version of the image Public Function Quantize(ByVal source As Image) As Bitmap ' Get the size of the source image Dim height As Integer = source.Height Dim width As Integer = source.Width ' And construct a rectangle from these dimensions Dim bounds As Rectangle = New Rectangle(0, 0, width, height) ' First off take a 32bpp copy of the image Dim copy As Bitmap = New Bitmap(width, height, PixelFormat.Format32bppArgb) ' And construct an 8bpp version Dim output As Bitmap = New Bitmap(width, height, PixelFormat.Format8bppIndexed) ' Now lock the bitmap into memory Using g As Graphics = Graphics.FromImage(copy) g.PageUnit = GraphicsUnit.Pixel ' Draw the source image onto the copy bitmap, ' which will effect a widening as appropriate. g.DrawImageUnscaled(source, bounds) End Using ' Define a pointer to the bitmap data Dim sourceData As BitmapData = Nothing Try ' Get the source image bits and lock into memory sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb) ' Call the FirstPass function if not a single pass algorithm. ' For something like an octree quantizer, this will run through ' all image pixels, build a data structure, and create a palette. If Not _singlePass Then FirstPass(sourceData, width, height) ' Then set the color palette on the output bitmap. I'm passing in the current palette ' as there's no way to construct a new, empty palette. output.Palette = Me.GetPalette(output.Palette) ' Then call the second pass which actually does the conversion SecondPass(sourceData, output, width, height, bounds) Finally ' Ensure that the bits are unlocked copy.UnlockBits(sourceData) End Try ' Last but not least, return the output bitmap Return output End Function ''' ''' Execute the first pass through the pixels in the image ''' ''' The source data ''' The width in pixels of the image ''' The height in pixels of the image Protected Overridable Sub FirstPass(ByVal sourceData As BitmapData, ByVal width As Integer, ByVal height As Integer) ' Define the source data pointers. Dim rowOffset As Integer = 0 Dim pixelOffset As Integer = 0 ' Loop through each row For row As Integer = 0 To height - 1 ' Set the source pixel to the first pixel in this row pixelOffset = 0 ' And loop through each column For col As Integer = 0 To width - 1 ' Now I have the pixel, call the FirstPassQuantize function... Dim pixel As New Color32(Marshal.ReadInt32(sourceData.Scan0, rowOffset + pixelOffset)) InitialQuantizePixel(pixel) pixelOffset += 4 ' 4 car l'offset qu'on passe à Marshal.ReadInt32 est exprimé en octets Next ' Add the stride to the source row rowOffset += sourceData.Stride Next End Sub ''' ''' Execute a second pass through the bitmap ''' ''' The source bitmap, locked into memory ''' The output bitmap ''' The width in pixels of the image ''' The height in pixels of the image ''' The bounding rectangle Protected Overridable Sub SecondPass(ByVal sourceData As BitmapData, ByVal output As Bitmap, ByVal width As Integer, ByVal height As Integer, ByVal bounds As Rectangle) Dim outputData As BitmapData = Nothing Try ' Lock the output bitmap into memory outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed) ' Define the source data pointers. Dim sourceRowOffset As Integer = 0 Dim sourcePixelOffset As Integer = 0 ' Now define the destination data pointers Dim destinationRowOffset As Integer = 0 Dim destinationPixelOffset As Integer = 0 Dim previousPixel As Integer = -1 ' And convert the first pixel, so that I have values going into the loop Dim pixelValue As Byte = QuantizePixel(New Color32(Marshal.ReadInt32(sourceData.Scan0, 0))) ' Assign the value of the first pixel Marshal.WriteByte(outputData.Scan0, pixelValue) ' Loop through each row For row As Integer = 0 To height - 1 ' Set the source pixel to the first pixel in this row sourcePixelOffset = 0 ' And set the destination pixel pointer to the first pixel in the row destinationPixelOffset = 0 ' Loop through each pixel on this scan line For col As Integer = 0 To width - 1 Dim pixel As Integer = Marshal.ReadInt32(sourceData.Scan0, sourceRowOffset + sourcePixelOffset) ' Check if this is the same as the last pixel. If so use that value ' rather than calculating it again. This is an inexpensive optimisation. If previousPixel <> pixel Then ' Quantize the pixel pixelValue = QuantizePixel(New Color32(pixel)) ' And setup the previous pointer previousPixel = pixel End If ' And set the pixel in the output Marshal.WriteByte(outputData.Scan0, destinationRowOffset + destinationPixelOffset, pixelValue) sourcePixelOffset += 4 ' 4 car l'offset qu'on passe à Marshal.ReadInt32 est exprimé en octets destinationPixelOffset += 1 Next ' Add the stride to the source row sourceRowOffset += sourceData.Stride ' And to the destination row destinationRowOffset += outputData.Stride Next Finally ' Ensure that I unlock the output bits output.UnlockBits(outputData) End Try End Sub ''' ''' Override this to process the pixel in the first pass of the algorithm ''' ''' The pixel to quantize ''' ''' This function need only be overridden if your quantize algorithm needs two passes, ''' such as an Octree quantizer. ''' Protected Overridable Sub InitialQuantizePixel(ByVal pixel As Color32) End Sub ''' ''' Override this to process the pixel in the second pass of the algorithm ''' ''' The pixel to quantize ''' The quantized value Protected MustOverride Function QuantizePixel(ByVal pixel As Color32) As Byte ''' ''' Retrieve the palette for the quantized image ''' ''' Any old palette, this is overrwritten ''' The new color palette Protected MustOverride Function GetPalette(ByVal original As ColorPalette) As ColorPalette ''' ''' Flag used to indicate whether a single pass or two passes are needed for quantization. ''' Private _singlePass As Boolean ''' ''' Struct that defines a 32 bpp colour ''' ''' ''' This struct is used to read data from a 32 bits per pixel image ''' in memory, and is ordered in this manner as this is the way that ''' the data is layed out in memory ''' _ Public Structure Color32 ''' ''' Holds the blue component of the colour ''' _ Public Blue As Byte ''' ''' Holds the green component of the colour ''' _ Public Green As Byte ''' ''' Holds the red component of the colour ''' _ Public Red As Byte ''' ''' Holds the alpha component of the colour ''' _ Public Alpha As Byte ''' ''' Permits the color32 to be treated as an int32 ''' _ Public ARGB As Integer ''' ''' Return the color for this Color32 object ''' Public ReadOnly Property Color() As Color Get Return Color.FromArgb(Alpha, Red, Green, Blue) End Get End Property Public Sub New(ByVal argb As Integer) Me.ARGB = argb End Sub End Structure End Class '''' ' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ' ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO ' THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A ' PARTICULAR PURPOSE. ' This is sample code and is freely distributable. '''' ''' ''' Quantize using an Octree ''' Public Class OctreeQuantizer Inherits Quantizer ''' ''' Construct the octree quantizer ''' ''' ''' The Octree quantizer is a two pass algorithm. The initial pass sets up the octree, ''' the second pass quantizes a color based on the nodes in the tree ''' ''' The maximum number of colors to return ''' The number of significant bits Public Sub New(ByVal maxColors As Integer, ByVal maxColorBits As Integer) MyBase.New(False) If maxColors > 255 Then Throw New ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be less than 256") If ((maxColorBits < 1) OrElse (maxColorBits > 8)) Then Throw New ArgumentOutOfRangeException("maxColorBits", maxColorBits, "This should be between 1 and 8") ' Construct the octree _octree = New Octree(maxColorBits) _maxColors = maxColors End Sub ''' ''' Process the pixel in the first pass of the algorithm ''' ''' The pixel to quantize ''' ''' This function need only be overridden if your quantize algorithm needs two passes, ''' such as an Octree quantizer. ''' Protected Overrides Sub InitialQuantizePixel(ByVal pixel As Color32) ' Add the color to the octree _octree.AddColor(pixel) End Sub ''' ''' Override this to process the pixel in the second pass of the algorithm ''' ''' The pixel to quantize ''' The quantized value Protected Overrides Function QuantizePixel(ByVal pixel As Color32) As Byte Dim paletteIndex As Byte = _maxColors ' The color at [_maxColors] is set to transparent ' Get the palette index if this non-transparent If pixel.Alpha > 0 Then paletteIndex = _octree.GetPaletteIndex(pixel) Return paletteIndex End Function ''' ''' Retrieve the palette for the quantized image ''' ''' Any old palette, this is overrwritten ''' The new color palette Protected Overrides Function GetPalette(ByVal original As ColorPalette) As ColorPalette ' First off convert the octree to _maxColors colors Dim palette As arraylist = _octree.Palletize(_maxColors - 1) ' Then convert the palette based on those colors For index As Integer = 0 To palette.Count - 1 original.Entries(index) = palette(index) Next ' Add the transparent color original.Entries(_maxColors) = Color.FromArgb(0, 0, 0, 0) Return original End Function ''' ''' Stores the tree ''' Private _octree As Octree ''' ''' Maximum allowed color depth ''' Private _maxColors As Integer ''' ''' Class which does the actual quantization ''' Private Class Octree ''' ''' Construct the octree ''' ''' The maximum number of significant bits in the image Public Sub New(ByVal maxColorBits As Integer) _maxColorBits = maxColorBits _leafCount = 0 _reducibleNodes = New OctreeNode(8) {} _root = New OctreeNode(0, _maxColorBits, Me) _previousColor = 0 _previousNode = Nothing End Sub ''' ''' Add a given color value to the octree ''' ''' Public Sub AddColor(ByVal pixel As Color32) ' Check if this request is for the same color as the last If (_previousColor = pixel.ARGB) Then ' If so, check if I have a previous node setup. This will only ocurr if the first color in the image ' happens to be black, with an alpha component of zero. If (Nothing Is _previousNode) Then _previousColor = pixel.ARGB _root.AddColor(pixel, _maxColorBits, 0, Me) Else ' Just update the previous node _previousNode.Increment(pixel) End If Else _previousColor = pixel.ARGB _root.AddColor(pixel, _maxColorBits, 0, Me) End If End Sub ''' ''' Reduce the depth of the tree ''' Public Sub Reduce() Dim index As Integer ' Find the deepest level containing at least one reducible node index = _maxColorBits - 1 While index > 0 AndAlso _reducibleNodes(index) Is Nothing index -= 1 End While ' Reduce the node most recently added to the list at level 'index' Dim node As OctreeNode = _reducibleNodes(index) _reducibleNodes(index) = node.NextReducible ' Decrement the leaf count after reducing the node _leafCount -= node.Reduce() ' And just in case I've reduced the last color to be added, and the next color to ' be added is the same, invalidate the previousNode... _previousNode = Nothing End Sub ''' ''' Get/Set the number of leaves in the tree ''' Public Property Leaves() As Integer Get Return _leafCount End Get Set(ByVal Value As Integer) _leafCount = Value End Set End Property ''' ''' Return the array of reducible nodes ''' Protected ReadOnly Property ReducibleNodes() As OctreeNode() Get Return _reducibleNodes End Get End Property ''' ''' Keep track of the previous node that was quantized ''' ''' The node last quantized Protected Sub TrackPrevious(ByVal node As OctreeNode) _previousNode = node End Sub ''' ''' Convert the nodes in the octree to a palette with a maximum of colorCount colors ''' ''' The maximum number of colors ''' An arraylist with the palettized colors Public Function Palletize(ByVal colorCount As Integer) As ArrayList While (Leaves > colorCount) Reduce() End While ' Now palettize the nodes Dim palette As ArrayList = New ArrayList(Leaves) Dim paletteIndex As Integer = 0 _root.ConstructPalette(palette, paletteIndex) ' And return the palette Return palette End Function ''' ''' Get the palette index for the passed color ''' ''' ''' Public Function GetPaletteIndex(ByVal pixel As Color32) As Integer Return _root.GetPaletteIndex(pixel, 0) End Function ''' ''' Mask used when getting the appropriate pixels for a given node ''' Private Shared mask As Integer() = New Integer(7) {&H80, &H40, &H20, &H10, &H8, &H4, &H2, &H1} ''' ''' The root of the octree ''' Private _root As OctreeNode ''' ''' Number of leaves in the tree ''' Private _leafCount As Integer ''' ''' Array of reducible nodes ''' Private _reducibleNodes As OctreeNode() ''' ''' Maximum number of significant bits in the image ''' Private _maxColorBits As Integer ''' ''' Store the last node quantized ''' Private _previousNode As OctreeNode ''' ''' Cache the previous color quantized ''' Private _previousColor As Integer ''' ''' Class which encapsulates each node in the tree ''' Protected Class OctreeNode ''' ''' Construct the node ''' ''' The level in the tree = 0 - 7 ''' The number of significant color bits in the image ''' The tree to which this node belongs Public Sub New(ByVal level As Integer, ByVal colorBits As Integer, ByVal octree As Octree) ' Construct the new node _leaf = (level = colorBits) _red = 0 _green = 0 _blue = 0 _pixelCount = 0 ' If a leaf, increment the leaf count If _leaf Then octree.Leaves += 1 _nextReducible = Nothing _children = Nothing Else ' Otherwise add this to the reducible nodes _nextReducible = octree.ReducibleNodes(level) octree.ReducibleNodes(level) = Me _children = New OctreeNode(7) {} End If End Sub ''' ''' Add a color into the tree ''' ''' The color ''' The number of significant color bits ''' The level in the tree ''' The tree to which this node belongs Public Sub AddColor(ByVal pixel As Color32, ByVal colorBits As Integer, ByVal level As Integer, ByVal octree As Octree) ' Update the color information if this is a leaf If (_leaf) Then Increment(pixel) ' Setup the previous node octree.TrackPrevious(Me) Else ' Go to the next level down in the tree Dim shift As Integer = 7 - level Dim index As Integer = ((pixel.Red And mask(level)) >> (shift - 2)) Or _ ((pixel.Green And mask(level)) >> (shift - 1)) Or _ ((pixel.Blue And mask(level)) >> (shift)) Dim child As OctreeNode = _children(index) If (Nothing Is child) Then ' Create a new child node & store in the array child = New OctreeNode(level + 1, colorBits, octree) _children(index) = child End If ' Add the color to the child node child.AddColor(pixel, colorBits, level + 1, octree) End If End Sub ''' ''' Get/Set the next reducible node ''' Public Property NextReducible() As OctreeNode Get Return _nextReducible End Get Set(ByVal Value As OctreeNode) _nextReducible = value End Set End Property ''' ''' Return the child nodes ''' Public ReadOnly Property Children() As OctreeNode() Get Return _children End Get End Property ''' ''' Reduce this node by removing all of its children ''' ''' The number of leaves removed Public Function Reduce() As Integer _red = 0 _green = 0 _blue = 0 Dim children As Integer = 0 ' Loop through all children and add their information to this node For index As Integer = 0 To 7 If (Nothing IsNot _children(index)) Then _red += _children(index)._red _green += _children(index)._green _blue += _children(index)._blue _pixelCount += _children(index)._pixelCount children += 1 _children(index) = Nothing End If Next ' Now change this to a leaf node _leaf = True ' Return the number of nodes to decrement the leaf count by Return (children - 1) End Function ''' ''' Traverse the tree, building up the color palette ''' ''' The palette ''' The current palette index Public Sub ConstructPalette(ByVal palette As ArrayList, ByRef paletteIndex As Integer) If (_leaf) Then ' Consume the next palette index _paletteIndex = paletteIndex paletteIndex += 1 ' And set the color of the palette entry palette.Add(Color.FromArgb(_red / _pixelCount, _green / _pixelCount, _blue / _pixelCount)) Else ' Loop through children looking for leaves For index As Integer = 0 To 7 If (Nothing IsNot _children(index)) Then _children(index).ConstructPalette(palette, paletteIndex) Next End If End Sub ''' ''' Return the palette index for the passed color ''' Public Function GetPaletteIndex(ByVal pixel As Color32, ByVal level As Integer) As Integer Dim paletteIndex As Integer = _paletteIndex If (Not _leaf) Then Dim shift As Integer = 7 - level Dim index As Integer = ((pixel.Red And mask(level)) >> (shift - 2)) Or _ ((pixel.Green And mask(level)) >> (shift - 1)) Or _ ((pixel.Blue And mask(level)) >> (shift)) If (Nothing IsNot _children(index)) Then paletteIndex = _children(index).GetPaletteIndex(pixel, level + 1) Else Throw New Exception("Didn't expect this!") End If End If Return paletteIndex End Function ''' ''' Increment the pixel count and add to the color information ''' Public Sub Increment(ByVal pixel As Color32) _pixelCount += 1 _red += pixel.Red _green += pixel.Green _blue += pixel.Blue End Sub ''' ''' Flag indicating that this is a leaf node ''' Private _leaf As Boolean ''' ''' Number of pixels in this node ''' Private _pixelCount As Integer ''' ''' Red component ''' Private _red As Integer ''' ''' Green Component ''' Private _green As Integer ''' ''' Blue component ''' Private _blue As Integer ''' ''' Pointers to any child nodes ''' Private _children As OctreeNode() ''' ''' Pointer to next reducible node ''' Private _nextReducible As OctreeNode ''' ''' The index of this node in the palette ''' Private _paletteIndex As Integer End Class End Class End Class End Namespace