diff options
Diffstat (limited to 'PIL/IcnsImagePlugin.py')
-rw-r--r-- | PIL/IcnsImagePlugin.py | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py new file mode 100644 index 0000000..a85695d --- /dev/null +++ b/PIL/IcnsImagePlugin.py @@ -0,0 +1,211 @@ +# +# The Python Imaging Library. +# $Id: ImImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $ +# +# Mac OS X icns file decoder, based on icns.py by Bob Ippolito. +# +# history: +# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies. +# +# Copyright (c) 2004 by Bob Ippolito. +# Copyright (c) 2004 by Secret Labs. +# Copyright (c) 2004 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +import Image, ImageFile +import string, struct + +HEADERSIZE = 8 + +def nextheader(fobj): + return struct.unpack('>4sI', fobj.read(HEADERSIZE)) + +def read_32t(fobj, (start, length), (width, height)): + # The 128x128 icon seems to have an extra header for some reason. + fobj.seek(start) + sig = fobj.read(4) + if sig != '\x00\x00\x00\x00': + raise SyntaxError, 'Unknown signature, expecting 0x00000000' + return read_32(fobj, (start + 4, length - 4), (width, height)) + +def read_32(fobj, (start, length), size): + """ + Read a 32bit RGB icon resource. Seems to be either uncompressed or + an RLE packbits-like scheme. + """ + fobj.seek(start) + sizesq = size[0] * size[1] + if length == sizesq * 3: + # uncompressed ("RGBRGBGB") + indata = fobj.read(length) + im = Image.frombuffer("RGB", size, indata, "raw", "RGB", 0, 1) + else: + # decode image + im = Image.new("RGB", size, None) + for band_ix in range(3): + data = [] + bytesleft = sizesq + while bytesleft > 0: + byte = fobj.read(1) + if not byte: + break + byte = ord(byte) + if byte & 0x80: + blocksize = byte - 125 + byte = fobj.read(1) + for i in range(blocksize): + data.append(byte) + else: + blocksize = byte + 1 + data.append(fobj.read(blocksize)) + bytesleft = bytesleft - blocksize + if bytesleft <= 0: + break + if bytesleft != 0: + raise SyntaxError( + "Error reading %r channel [%r]" % (channel, bytesleft) + ) + band = Image.frombuffer( + "L", size, string.join(data, ""), "raw", "L", 0, 1 + ) + im.im.putband(band.im, band_ix) + return {"RGB": im} + +def read_mk(fobj, (start, length), size): + # Alpha masks seem to be uncompressed + fobj.seek(start) + band = Image.frombuffer( + "L", size, fobj.read(size[0]*size[1]), "raw", "L", 0, 1 + ) + return {"A": band} + +class IcnsFile: + + SIZES = { + (128, 128): [ + ('it32', read_32t), + ('t8mk', read_mk), + ], + (48, 48): [ + ('ih32', read_32), + ('h8mk', read_mk), + ], + (32, 32): [ + ('il32', read_32), + ('l8mk', read_mk), + ], + (16, 16): [ + ('is32', read_32), + ('s8mk', read_mk), + ], + } + + def __init__(self, fobj): + """ + fobj is a file-like object as an icns resource + """ + # signature : (start, length) + self.dct = dct = {} + self.fobj = fobj + sig, filesize = nextheader(fobj) + if sig != 'icns': + raise SyntaxError, 'not an icns file' + i = HEADERSIZE + while i < filesize: + sig, blocksize = nextheader(fobj) + i = i + HEADERSIZE + blocksize = blocksize - HEADERSIZE + dct[sig] = (i, blocksize) + fobj.seek(blocksize, 1) + i = i + blocksize + + def itersizes(self): + sizes = [] + for size, fmts in self.SIZES.items(): + for (fmt, reader) in fmts: + if self.dct.has_key(fmt): + sizes.append(size) + break + return sizes + + def bestsize(self): + sizes = self.itersizes() + if not sizes: + raise SyntaxError, "No 32bit icon resources found" + return max(sizes) + + def dataforsize(self, size): + """ + Get an icon resource as {channel: array}. Note that + the arrays are bottom-up like windows bitmaps and will likely + need to be flipped or transposed in some way. + """ + dct = {} + for code, reader in self.SIZES[size]: + desc = self.dct.get(code) + if desc is not None: + dct.update(reader(self.fobj, desc, size)) + return dct + + def getimage(self, size=None): + if size is None: + size = self.bestsize() + channels = self.dataforsize(size) + im = channels.get("RGB").copy() + try: + im.putalpha(channels["A"]) + except KeyError: + pass + return im + +## +# Image plugin for Mac OS icons. + +class IcnsImageFile(ImageFile.ImageFile): + """ + PIL read-only image support for Mac OS .icns files. + Chooses the best resolution, but will possibly load + a different size image if you mutate the size attribute + before calling 'load'. + + The info dictionary has a key 'sizes' that is a list + of sizes that the icns file has. + """ + + format = "ICNS" + format_description = "Mac OS icns resource" + + def _open(self): + self.icns = IcnsFile(self.fp) + self.mode = 'RGBA' + self.size = self.icns.bestsize() + self.info['sizes'] = self.icns.itersizes() + # Just use this to see if it's loaded or not yet. + self.tile = ('',) + + def load(self): + Image.Image.load(self) + if not self.tile: + return + self.load_prepare() + # This is likely NOT the best way to do it, but whatever. + im = self.icns.getimage(self.size) + self.im = im.im + self.mode = im.mode + self.size = im.size + self.fp = None + self.icns = None + self.tile = () + self.load_end() + + +Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == 'icns') +Image.register_extension("ICNS", '.icns') + +if __name__ == '__main__': + import os, sys + im = Image.open(open(sys.argv[1], "rb")) + im.save("out.png") + os.startfile("out.png") |