with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# Adapted from https://github.com/aclements/commuter/blob/master/web/png2ico
+# and https://code.google.com/p/reply-manager/source/browse/build/png2ico.py
__version__ = "$Revision: 1.0 $"
+# ref: http://msdn.microsoft.com/en-us/library/ms997538
+class ICONDIR(ctypes.LittleEndianStructure):
+ _fields_ = [("idReserved", ctypes.c_ushort),
+ ("idType", ctypes.c_ushort),
+ ("idCount", ctypes.c_ushort)]
+class ICONDIRENTRY(ctypes.LittleEndianStructure):
+ _fields_ = [("bWidth", ctypes.c_byte),
+ ("bHeight", ctypes.c_byte),
+ ("bColorCount", ctypes.c_byte),
+ ("bReserved", ctypes.c_byte),
+ ("wPlanes", ctypes.c_ushort),
+ ("wBitCount", ctypes.c_ushort),
+ ("dwBytesInRes", ctypes.c_ulong),
+ ("dwImageOffset", ctypes.c_ulong)]
"""PNG2ICO - Create a multisize ICO from a PNG
- First creates all required sizes of supplied image in memory then builds ICO file structure using struct and writes to specified output file."""
+ First creates all required sizes of supplied image in memory then
+ builds ICO file structure using struct and writes to specified
def __init__(self, image_files, out_file, sizes=[256,48,32,16]):
self.log = logging.getLogger(__name__)
def _resize(self, infile, size):
- """resize image to size x size.
- returns file like object."""
+ """Resize image to size x size.
+ Returns file like object."""
self.log.debug("Resizing {file_} to {size} x {size}".format(file_=infile, size=size))
- #outfile = "{0}_{1}.png".format(os.path.splitext(infile)[0], size)
+ #outfile = "{0}_{1}.png".format(os.path.split(infile)[1], size)
- im = Image.open(infile)
- im.thumbnail((size, size), Image.ANTIALIAS)
- im.save(outfile, "PNG")
- outfile.seek(0) # Rewind the BytesIO object to the start ready for reading.
- outfile.name = "{0}_{1}.png".format(os.path.split(infile)[1], size)
+ im = Image.open(infile).convert("RGBA") # open and convert to RGBA
+ im.thumbnail((size, size), Image.ANTIALIAS) # Create thumbnail of designated size
+ im.save(outfile, "PNG") # Save thumbnail to BytesIO instance
+ outfile.seek(0) # Rewind the BytesIO object to the start ready for reading.
+ outfile.name = "{0}_{1}.png".format(os.path.split(infile)[1], size) # Give the BytesIO object a name
raise Exception("Cannot resize '{0}' to {1}x{1} {2}".format(infile, size, e))
+ #return open(outfile, "rb")
- def _create_ico(self, out_file, image_files): """create ico file from list of image file like objects.
- this builds the ico file using struct based on the specification"""
- # Adapted from https://github.com/aclements/commuter/blob/master/web/png2ico
+ def _create_ico(self, out_file, image_files):
+ """Create ico file from list of image file like objects.
+ This builds the ico file using struct based on the specification."""
self.log.debug("Loading image files...")
for image_file in image_files:
+ # Check image_file is a PNG
if not (data.startswith('\x89PNG\x0d\x0a\x1a\x0a') and
data[12:].startswith('IHDR') and
raise Exception('not a valid png file: {0}'.format(image_file.name))
self.log.debug("{0} is a PNG image".format(image_file.name))
- w, h, bpp = struct.unpack('!IIB', data[16:16+9])
+ w, h, bpp = struct.unpack('!IIB', data[16:16+9]) # Extract width, height, and bits per pixel
+ if w > 256 or h > 256: # Check maximum icon size is not exceeded
raise Exception("{0} exceeds 256x256 size limit!".format(image_file.name))
self.log.debug("{0} is within 256x256 size limit.".format(image_file.name))
images.append((data, w, h, bpp))
- self.log.debug("Building icon data...")
- hdata = struct.pack('<HHH', 0, 1, len(images))
- pos = len(hdata) + len(images) * 16
+ self.log.debug("Building icon...")
- for data, w, h, bpp in images:
- hdata += struct.pack('<BBBBHHII', w, h, 0, 0, 1, bpp, len(data), pos)
+ # Set up the icon header
+ icondir.idType = 1 # Icons are type 1
+ icondir.idCount = len(images)
+ header_section.append(icondir)
+ icondir_size = ctypes.sizeof(icondir)
+ iconentry_size = len(images) * ctypes.sizeof(ICONDIRENTRY())
+ offset = icondir_size + iconentry_size # calculate offset after entire header section
+ for data, width, height, bpp in images:
+ iconentry = ICONDIRENTRY()
+ iconentry.bWidth = width
+ iconentry.bHeight = height
+ iconentry.bColorCount = 0 # Truecolor images have color count set to 0
+ iconentry.bReserved = 0 # Reserved, must be 0
+ iconentry.wPlanes = 1 # PNGs have 1 color plane
+ iconentry.wBitCount = bpp # bits per pixel from image_file
+ iconentry.dwBytesInRes = len(data) # Length of the image data
+ # The data will be right after the icondir and iconentry
+ iconentry.dwImageOffset = offset # Offset of header and data so far
+ offset += len(data) # Calculate new offset
+ header_section.append(iconentry)
+ data_section.append(data)
self.log.debug("Writing icon...")
with open(out_file, "wb") as f:
+ # Write header sections followed by data sections
+ for section in header_section + data_section:
images.append(self._resize(self.image_files[0], size))
self.log.info("Creating ICO file with {0} images...".format(len(images)))
self._create_ico(self.out_file, images)
if __name__ == "__main__":
PNG2ICO - Create a multisize ICO from a PNG
- Copyright (C) 2015 Oliver Morton (Sec-1 Ltd) This program comes with ABSOLUTELY NO WARRANTY.
+ Copyright (C) 2015 Oliver Morton (Sec-1 Ltd)
+ This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See GPLv2 License.
parser.error("sizes must be between {0} and {1}".format(max, min))
setattr(namespace, self.dest, values)
- parser = argparse.ArgumentParser(description="Create a multisize ICO from a PNG",
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser = argparse.ArgumentParser(description="Create a multisize ICO from a PNG")
parser.add_argument("-V", "--version", help="Print version banner", action="store_true")
parser.add_argument("-v", "--verbose", help="Debug logging", action="store_true")
parser.add_argument("-i", "--input_file", help="Input PNG file", nargs="+", required=True)