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.You are not allowed to attach a file to this page.