# 
 | 
# The Python Imaging Library. 
 | 
# $Id$ 
 | 
# 
 | 
# 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 channel [%r left]" % 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") 
 |