Attachment 'Win32IconImagePlugin.py'

Download

   1 """
   2 Win32IconImagePlugin  
   3 Alternate PIL plugin for dealing with Microsoft .ico files.
   4 
   5 http://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
   6 """
   7 
   8 #!/usr/bin/env python
   9 # -*- coding: utf-8 -*-
  10 #
  11 # Copyright 2008 Bryan Davis <casadebender+pil@gmail.com>
  12 #
  13 # Licensed under the Apache License, Version 2.0 (the "License");
  14 # you may not use this file except in compliance with the License.
  15 # You may obtain a copy of the License at 
  16 #     http://www.apache.org/licenses/LICENSE-2.0
  17 #
  18 # Unless required by applicable law or agreed to in writing, software 
  19 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
  20 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
  21 # License for the specific language governing permissions and limitations 
  22 # under the License.
  23 #
  24 # $Id$
  25 """Alternate PIL plugin for dealing with Microsoft .ico files. Handles XOR
  26 transparency masks, XP style 8bit alpha channels and Vista style PNG image 
  27 parts.
  28 
  29 >>> import PIL.Image
  30 >>> import Win32IconImagePlugin
  31 >>> ico = PIL.Image.open("down.ico")
  32 >>> print ico.info['sizes']
  33 set([(16, 16), (48, 48), (256, 256), (32, 32)])
  34 >>> ico.size = (16, 16)
  35 >>> ico.show()
  36 
  37 This implementation builds on several samples that I found around the net.
  38 Karsten Hiddemann posted a hint on Image-SIG_ that got me started on this.
  39 Some time later I found a `django snippet`_ by *dc* that I borrowed the
  40 ``struct.unpack`` syntax from. I also spent a lot of time looking at the
  41 IcoImagePlugin, BmpImagePlugin, PngImagePlugin and other files from PIL.
  42 
  43 Icon format references:
  44   * http://en.wikipedia.org/wiki/ICO_(file_format)
  45   * http://msdn.microsoft.com/en-us/library/ms997538.aspx
  46 
  47 Example icon to test with `down.ico`_
  48 
  49 .. _Image-SIG http://mail.python.org/pipermail/image-sig/2008-May/004986.html
  50 .. _django snippet http://www.djangosnippets.org/snippets/1287/
  51 .. _down.ico http://www.axialis.com/tutorials/iw/down.ico
  52 """
  53 
  54 import logging
  55 import struct
  56 import PIL.Image
  57 import PIL.ImageChops
  58 import PIL.ImageFile
  59 import PIL.BmpImagePlugin
  60 import PIL.PngImagePlugin
  61 
  62 
  63 _MAGIC = '\0\0\1\0'
  64 log = logging.getLogger(__name__)
  65 
  66 
  67 class Win32IcoFile (object):
  68   """
  69   Decoder for Microsoft .ico files.
  70   """
  71 
  72   def __init__ (self, buf):
  73     """
  74     Args:
  75       buf: file-like object containing ico file data
  76     """
  77     self.buf = buf
  78     self.entry = []
  79 
  80     header = struct.unpack('<3H', buf.read(6))
  81     if (0, 1) != header[:2]:
  82       raise SyntaxError, 'not an ico file'
  83 
  84     self.nb_items = header[2]
  85 
  86     dir_fields = ('width', 'height', 'nb_color', 'reserved', 'planes', 'bpp',
  87         'size', 'offset')
  88     for i in xrange(self.nb_items):
  89       directory = list(struct.unpack('<4B2H2I', buf.read(16)))
  90       for j in xrange(3):
  91         if not directory[j]:
  92           directory[j] = 256
  93       icon_header = dict(zip(dir_fields, directory))
  94       icon_header['color_depth'] = (
  95           icon_header['bpp'] or 
  96           (icon_header['nb_color'] == 16 and 4))
  97       icon_header['dim'] = (icon_header['width'], icon_header['height'])
  98       self.entry.append(icon_header)
  99     #end for (read headers)
 100 
 101     # order by size and color depth
 102     self.entry.sort(lambda x, y: \
 103         cmp(x['width'], y['width']) or cmp(x['color_depth'], y['color_depth']))
 104     self.entry.reverse()
 105   #end __init__
 106 
 107 
 108   def sizes (self):
 109     """
 110     Get a list of all available icon sizes and color depths.
 111     """
 112     return set((h['width'], h['height']) for h in self.entry)
 113   #end sizes
 114 
 115 
 116   def get_image (self, size, bpp=False):
 117     """
 118     Get an image from the icon
 119 
 120     Args:
 121       size: tuple of (width, height)
 122       bpp: color depth
 123     """
 124     idx = 0
 125     for i in range(self.nb_items):
 126       h = self.entry[i]
 127       if size == h['dim'] and (bpp == False or bpp == h['color_depth']):
 128         return self.frame(i)
 129 
 130     return self.frame(0)
 131   #end get_image
 132 
 133 
 134   def frame (self, idx):
 135     """
 136     Get the icon from frame idx
 137 
 138     Args:
 139       idx: Frame index
 140 
 141     Returns:
 142       PIL.Image
 143     """
 144     header = self.entry[idx]
 145     self.buf.seek(header['offset'])
 146     data = self.buf.read(8)
 147     self.buf.seek(header['offset'])
 148     if data[:8] == PIL.PngImagePlugin._MAGIC:
 149       # png frame
 150       im = PIL.PngImagePlugin.PngImageFile(self.buf)
 151 
 152     else:
 153       # XOR + AND mask bmp frame
 154       im = PIL.BmpImagePlugin.DibImageFile(self.buf)
 155       log.debug("Loaded image: %s %s %s %s", im.format, im.mode, im.size,
 156           im.info)
 157 
 158       # change tile dimension to only encompass XOR image
 159       im.size = im.size[0], im.size[1] / 2
 160       d, e, o, a = im.tile[0]
 161       im.tile[0] = d, (0,0) + im.size, o, a
 162 
 163       # figure out where AND mask image starts
 164       mode = a[0]
 165       bpp = 8
 166       for k in PIL.BmpImagePlugin.BIT2MODE.keys():
 167         if mode == PIL.BmpImagePlugin.BIT2MODE[k][1]:
 168           bpp = k
 169           break
 170       #end for
 171       log.debug("o:%s, w:%s, h:%s, bpp:%s", o, im.size[0], im.size[1], bpp)
 172       and_mask_offset = int( o + (im.size[0] * im.size[1] * (bpp / 8.0)) )
 173 
 174       if 32 == bpp:
 175         # 32-bit color depth icon image allows semitransparent areas
 176         # PIL's DIB format ignores transparency bits, recover them
 177         # The DIB is packed in BGRX byte order where X is the alpha channel
 178 
 179         # Back up to start of bmp data
 180         self.buf.seek(o)
 181         # extract every 4th byte (eg. 3,7,11,15,...)
 182         alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
 183 
 184         # convert to an 8bpp grayscale image
 185         mask = PIL.Image.frombuffer(
 186             'L',            # 8bpp
 187             im.size,        # (w, h)
 188             alpha_bytes,    # source chars
 189             'raw',          # raw decoder
 190             ('L', 0, -1)    # 8bpp inverted, unpadded, reversed
 191         )
 192 
 193         # apply mask image as alpha channel
 194         im = im.convert('RGBA')
 195         im.putalpha(mask)
 196         log.debug("image mode: %s", im.mode)
 197 
 198       else:
 199         # get AND image from end of bitmap
 200         w = im.size[0]
 201         if (w % 32) > 0:
 202           # bitmap row data is aligned to word boundaries
 203           w += 32 - (im.size[0] % 32)
 204         # the total mask data is padded row size * height / bits per char
 205         total_bytes = long((w * im.size[1]) / 8)
 206         log.debug("tot=%d, off=%d, w=%d, size=%d", 
 207             len(data), and_mask_offset, w, total_bytes)
 208 
 209         self.buf.seek(and_mask_offset)
 210         maskData = self.buf.read(total_bytes)
 211 
 212         # convert raw data to image
 213         mask = PIL.Image.frombuffer(
 214             '1',            # 1 bpp
 215             im.size,        # (w, h)
 216             maskData,       # source chars
 217             'raw',          # raw decoder
 218             ('1;I', int(w/8), -1)  # 1bpp inverted, padded, reversed
 219         )
 220 
 221         # now we have two images, im is XOR image and mask is AND image
 222         # set mask as alpha channel
 223         im = im.convert('RGBA')
 224         im.putalpha(mask)
 225         log.debug("image mode: %s", im.mode)
 226       #end if !'RGBA'
 227     #end if (png)/else(bmp)
 228 
 229     return im
 230   #end frame
 231 
 232 
 233   def __repr__ (self):
 234     s = 'Microsoft Icon: %d images (max %dx%d %dbpp)' % (
 235         len(self.entry), self.entry[0]['width'], self.entry[0]['height'], 
 236         self.entry[0]['bpp'])
 237     return s
 238   #end __repr__
 239 #end Win32IcoFile
 240 
 241 
 242 class Win32IconImageFile (PIL.ImageFile.ImageFile):
 243   """
 244   PIL read-only image support for Microsoft .ico files.
 245 
 246   By default the largest resolution image in the file will be loaded. This can 
 247   be changed by altering the 'size' attribute before calling 'load'.
 248 
 249   The info dictionary has a key 'sizes' that is a list of the sizes available 
 250   in the icon file.
 251 
 252   Handles classic, XP and Vista icon formats.
 253   """
 254 
 255   format = 'ICO'
 256   format_description = 'Microsoft icon'
 257 
 258   def _open (self):
 259     self.ico = Win32IcoFile(self.fp)
 260     self.info['sizes'] = self.ico.sizes()
 261     self.size = self.ico.entry[0]['dim']
 262     self.load()
 263   #end _open
 264 
 265   def load (self):
 266     im = self.ico.get_image(self.size)
 267     # if tile is PNG, it won't really be loaded yet
 268     im.load()
 269     self.im = im.im
 270     self.mode = im.mode
 271     self.size = im.size
 272   #end load
 273 #end class Win32IconImageFile
 274 
 275 
 276 def _accept (prefix):
 277   """
 278   Quick file test helper for Image.open()
 279   """
 280   return prefix[:4] == _MAGIC
 281 #end _accept
 282 
 283 
 284 # register our decoder with PIL
 285 PIL.Image.register_open(Win32IconImageFile.format, Win32IconImageFile, _accept)
 286 PIL.Image.register_extension(Win32IconImageFile.format, ".ico")

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2011-04-02 19:08:14, 23.2 KB) [[attachment:BitmapManip.py]]
  • [get | view] (2011-03-27 19:21:24, 1.6 KB) [[attachment:DesktopScreenShotPIL.py]]
  • [get | view] (2012-12-25 18:23:31, 8.7 KB) [[attachment:ImgConv.py]]
  • [get | view] (2011-06-06 16:22:42, 4.6 KB) [[attachment:ScreenShotWX.py]]
  • [get | view] (2011-06-06 16:22:51, 3.9 KB) [[attachment:ScreenShotWX_Demo.py]]
  • [get | view] (2011-05-16 01:34:32, 8.5 KB) [[attachment:Win32IconImagePlugin.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.