Update 2 (11/11/2017)
Contradicting what I've said before, you don't need to include the 4bpp and 8bpp frames within your icons.
Seems like, on modern icons (Windows Vista and newer), you need to include only 32bpp frames.
I've opened lots of icons from internet, and from Microsoft Windows (10) itself to get to this ultimate conclusion.
I'm sorry for that. Well, live and learn.
Plus, I'd like to mention that I've made some changes to the code and moved it to GITHUB. There you will get the most recent code, releases and etc. I've made many changes since the last update on Codeplex.
Update
Now, after some months of development, I've finished the first release of a more complete tool for icon editing featuring a more powerful IconBitmapEnconder
and a complete new IconBitmapDecoder
. This tool is called Icon Pro. The source code and the first release are available on CodePlex on this link:
Introduction
Windows Presentation Foundation was really a great revolution in application development for Windows, allowing us to create applications with high-end UIs.
However, not everything is perfect. Writing code for WPF, I realized that was missing in API an Encoder for icon files (Because WIC don't have a icon encoder, and WPF Image Encoders (and Decoders too) are wrappers to native WIC API), which would be the IconBitmapEncoder
.
Not satisfied with this, I decided to try to create an encoder for icons to cover for the deficiency. After several days of work, I managed to develop an IconBitmapEncoder
that generates icon files with high quality.
By high-quality, I mean that this encoder supports 32 Bit icons, and also the size 256x256 in png format.
Background
Basically, an icon file consists of the following parts:
However, that is not all!
An icon file must contain multiple sizes of the same image (Example: 16x16, 32x32 ...) and each image should have three different pixel formats: Indexed 4 (16 colors [4bits]), Indexed 8 (256 colors [8bits]), and RGB (A – Alpha Channel is optional) (True Colors [32bits]). Look at a visual representation of an icon:
All images in an icon file must be in Bitmap (BMP) format without the file header (First 14 Bytes), except that 256x256 image should be stored in PNG format (RGBA 32 bits).
BMP images must include a doubled height because they do not have an AND mask. For more information about AND Mask, see BMP file format (Wikipedia).
Don't ask me why, but to work right, the frames in icon file should be ordered from smallest to largest. For this reason, I did the implementation of the SortAscending
method in class IconBitmapFramesCollection
. Another thing I should emphasize is that the images must be square and must have at least 16x16 pixels and no more than 256x256 pixels.
To develop this encoder, the following articles were of great importance. Give them a read for more background information:
Using the Code
Below is a sample code for creating a high-quality icon from a single 256x256 PNG image, using my IconBitmapEncoder
class, this code is also available in the solution for download (In C# version too):
Public Sub SaveIcon(Source As BitmapSource, _
Stream As IO.Stream, Quality As Integer)
Dim encoder As New IconBitmapEncoder
Dim bmp16 As BitmapSource = IconBitmapEncoder.GetResized(Source, 16)
Dim bmp24 As BitmapSource = IconBitmapEncoder.GetResized(Source, 24)
Dim bmp32 As BitmapSource = IconBitmapEncoder.GetResized(Source, 32)
Dim bmp48 As BitmapSource = IconBitmapEncoder.GetResized(Source, 48)
Dim bmp64 As BitmapSource = IconBitmapEncoder.GetResized(Source, 64)
Dim bmp72 As BitmapSource = IconBitmapEncoder.GetResized(Source, 72)
Dim bmp96 As BitmapSource = IconBitmapEncoder.GetResized(Source, 96)
Dim bmp128 As BitmapSource = IconBitmapEncoder.GetResized(Source, 128)
Dim bmp256 As BitmapSource = IconBitmapEncoder.GetResized(Source, 256)
If Quality >= 90 Then
encoder.Frames.Add(BitmapFrame.Create(bmp256))
End If
If Quality >= 80 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp128)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp128)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp128)))
End If
If Quality >= 70 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp96)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp96)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp96)))
End If
If Quality >= 60 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp72)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp72)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp72)))
End If
If Quality >= 50 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp64)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp64)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp64)))
End If
If Quality >= 40 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp48)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp48)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp48)))
End If
If Quality >= 30 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp32)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp32)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp32)))
End If
If Quality >= 20 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp24)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp24)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp24)))
End If
If Quality >= 0 Then
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get24plus8BitImage(bmp16)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get4BitImage(bmp16)))
encoder.Frames.Add(BitmapFrame.Create_
(IconBitmapEncoder.Get8BitImage(bmp16)))
End If
encoder.Save(Stream)
Stream.Close()
End Sub
Source
is the 256x256 PNG 32bit Image; Stream
is the out
stream, and Quality
is a value in 0-100 range.
So, we need to add one BitmapFrame
for each image size, and for each image pixel format, to the encoder Frames collection.
Points of Interest
BitmapImage
pixel conversion from 32 bits to 8bits and 4bits BitmapImage
scaling - Use of
BinaryWriter
to handle the little-endian byte order writing - List items sorting, ascending and descending
Notes
- The Encoder meets all requirements for creating icons supported on Windows XP and newer. However, I only had the chance to test it on Windows 7 SP1. So if you have any problems with the icons generated on other Windows versions, please let me know and I will try to fix it.
- The demo and the API were written in Visual Studio 2012 SP2, .NET Framework 3.5 SP1.
History
- Version 1.0.0.0 – 25/11/2013
- Version 1.1.0.0 – 21/12/2013
- Solution with the source code (and a fully functional demo) uploaded
- Now the code is available both as VB.NET and C#
- Version 1.1.0.1 – 22/12/2013
- Added some more information
- Removed
IconBitmapEncoder
class source code from article page