# 
 | 
# The Python Imaging Library 
 | 
# $Id$ 
 | 
# 
 | 
# Adobe PSD 2.5/3.0 file handling 
 | 
# 
 | 
# History: 
 | 
# 1995-09-01 fl   Created 
 | 
# 1997-01-03 fl   Read most PSD images 
 | 
# 1997-01-18 fl   Fixed P and CMYK support 
 | 
# 2001-10-21 fl   Added seek/tell support (for layers) 
 | 
# 
 | 
# Copyright (c) 1997-2001 by Secret Labs AB. 
 | 
# Copyright (c) 1995-2001 by Fredrik Lundh 
 | 
# 
 | 
# See the README file for information on usage and redistribution. 
 | 
# 
 | 
  
 | 
__version__ = "0.4" 
 | 
  
 | 
import Image, ImageFile, ImagePalette 
 | 
  
 | 
MODES = { 
 | 
    # (photoshop mode, bits) -> (pil mode, required channels) 
 | 
    (0, 1): ("1", 1), 
 | 
    (0, 8): ("L", 1), 
 | 
    (1, 8): ("L", 1), 
 | 
    (2, 8): ("P", 1), 
 | 
    (3, 8): ("RGB", 3), 
 | 
    (4, 8): ("CMYK", 4), 
 | 
    (7, 8): ("L", 1), # FIXME: multilayer 
 | 
    (8, 8): ("L", 1), # duotone 
 | 
    (9, 8): ("LAB", 3) 
 | 
} 
 | 
  
 | 
# 
 | 
# helpers 
 | 
  
 | 
def i16(c): 
 | 
    return ord(c[1]) + (ord(c[0])<<8) 
 | 
  
 | 
def i32(c): 
 | 
    return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24) 
 | 
  
 | 
# --------------------------------------------------------------------. 
 | 
# read PSD images 
 | 
  
 | 
def _accept(prefix): 
 | 
    return prefix[:4] == "8BPS" 
 | 
  
 | 
## 
 | 
# Image plugin for Photoshop images. 
 | 
  
 | 
class PsdImageFile(ImageFile.ImageFile): 
 | 
  
 | 
    format = "PSD" 
 | 
    format_description = "Adobe Photoshop" 
 | 
  
 | 
    def _open(self): 
 | 
  
 | 
        read = self.fp.read 
 | 
  
 | 
        # 
 | 
        # header 
 | 
  
 | 
        s = read(26) 
 | 
        if s[:4] != "8BPS" or i16(s[4:]) != 1: 
 | 
            raise SyntaxError, "not a PSD file" 
 | 
  
 | 
        psd_bits = i16(s[22:]) 
 | 
        psd_channels = i16(s[12:]) 
 | 
        psd_mode = i16(s[24:]) 
 | 
  
 | 
        mode, channels = MODES[(psd_mode, psd_bits)] 
 | 
  
 | 
        if channels > psd_channels: 
 | 
            raise IOError, "not enough channels" 
 | 
  
 | 
        self.mode = mode 
 | 
        self.size = i32(s[18:]), i32(s[14:]) 
 | 
  
 | 
        # 
 | 
        # color mode data 
 | 
  
 | 
        size = i32(read(4)) 
 | 
        if size: 
 | 
            data = read(size) 
 | 
            if mode == "P" and size == 768: 
 | 
                self.palette = ImagePalette.raw("RGB;L", data) 
 | 
  
 | 
        # 
 | 
        # image resources 
 | 
  
 | 
        self.resources = [] 
 | 
  
 | 
        size = i32(read(4)) 
 | 
        if size: 
 | 
            # load resources 
 | 
            end = self.fp.tell() + size 
 | 
            while self.fp.tell() < end: 
 | 
                signature = read(4) 
 | 
                id = i16(read(2)) 
 | 
                name = read(ord(read(1))) 
 | 
                if not (len(name) & 1): 
 | 
                    read(1) # padding 
 | 
                data = read(i32(read(4))) 
 | 
                if (len(data) & 1): 
 | 
                    read(1) # padding 
 | 
                self.resources.append((id, name, data)) 
 | 
                if id == 1039: # ICC profile 
 | 
                    self.info["icc_profile"] = data 
 | 
  
 | 
        # 
 | 
        # layer and mask information 
 | 
  
 | 
        self.layers = [] 
 | 
  
 | 
        size = i32(read(4)) 
 | 
        if size: 
 | 
            end = self.fp.tell() + size 
 | 
            size = i32(read(4)) 
 | 
            if size: 
 | 
                self.layers = _layerinfo(self.fp) 
 | 
            self.fp.seek(end) 
 | 
  
 | 
        # 
 | 
        # image descriptor 
 | 
  
 | 
        self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) 
 | 
  
 | 
        # keep the file open 
 | 
        self._fp = self.fp 
 | 
        self.frame = 0 
 | 
  
 | 
    def seek(self, layer): 
 | 
        # seek to given layer (1..max) 
 | 
        if layer == self.frame: 
 | 
            return 
 | 
        try: 
 | 
            if layer <= 0: 
 | 
                raise IndexError 
 | 
            name, mode, bbox, tile = self.layers[layer-1] 
 | 
            self.mode = mode 
 | 
            self.tile = tile 
 | 
            self.frame = layer 
 | 
            self.fp = self._fp 
 | 
            return name, bbox 
 | 
        except IndexError: 
 | 
            raise EOFError, "no such layer" 
 | 
  
 | 
    def tell(self): 
 | 
        # return layer number (0=image, 1..max=layers) 
 | 
        return self.frame 
 | 
  
 | 
    def load_prepare(self): 
 | 
        # create image memory if necessary 
 | 
        if not self.im or\ 
 | 
           self.im.mode != self.mode or self.im.size != self.size: 
 | 
            self.im = Image.core.fill(self.mode, self.size, 0) 
 | 
        # create palette (optional) 
 | 
        if self.mode == "P": 
 | 
            Image.Image.load(self) 
 | 
  
 | 
def _layerinfo(file): 
 | 
    # read layerinfo block 
 | 
    layers = [] 
 | 
    read = file.read 
 | 
  
 | 
    for i in range(abs(i16(read(2)))): 
 | 
  
 | 
        # bounding box 
 | 
        y0 = i32(read(4)); x0 = i32(read(4)) 
 | 
        y1 = i32(read(4)); x1 = i32(read(4)) 
 | 
  
 | 
        # image info 
 | 
        info = [] 
 | 
        mode = [] 
 | 
        for i in range(i16(read(2))): 
 | 
            type = i16(read(2)) 
 | 
            if type == 65535: 
 | 
                m = "A" 
 | 
            else: 
 | 
                m = "RGB"[type] 
 | 
            mode.append(m) 
 | 
            size = i32(read(4)) 
 | 
            info.append((m, size)) 
 | 
  
 | 
        # figure out the image mode 
 | 
        mode.sort() 
 | 
        if mode == ["R"]: 
 | 
            mode = "L" 
 | 
        elif mode == ["B", "G", "R"]: 
 | 
            mode = "RGB" 
 | 
        elif mode == ["A", "B", "G", "R"]: 
 | 
            mode = "RGBA" 
 | 
        else: 
 | 
            mode = None # unknown 
 | 
  
 | 
        # skip over blend flags and extra information 
 | 
        filler = read(12) 
 | 
        name = "" 
 | 
        size = i32(read(4)) 
 | 
        combined = 0 
 | 
        if size: 
 | 
            length = i32(read(4)) 
 | 
            if length: 
 | 
                mask_y = i32(read(4)); mask_x = i32(read(4)) 
 | 
                mask_h = i32(read(4)) - mask_y; mask_w = i32(read(4)) - mask_x 
 | 
                file.seek(length - 16, 1) 
 | 
            combined += length + 4 
 | 
  
 | 
            length = i32(read(4)) 
 | 
            if length: 
 | 
                file.seek(length, 1) 
 | 
            combined += length + 4 
 | 
  
 | 
            length = ord(read(1)) 
 | 
            if length: 
 | 
                name = read(length) 
 | 
            combined += length + 1 
 | 
  
 | 
        file.seek(size - combined, 1) 
 | 
        layers.append((name, mode, (x0, y0, x1, y1))) 
 | 
  
 | 
    # get tiles 
 | 
    i = 0 
 | 
    for name, mode, bbox in layers: 
 | 
        tile = [] 
 | 
        for m in mode: 
 | 
            t = _maketile(file, m, bbox, 1) 
 | 
            if t: 
 | 
                tile.extend(t) 
 | 
        layers[i] = name, mode, bbox, tile 
 | 
        i = i + 1 
 | 
  
 | 
    return layers 
 | 
  
 | 
def _maketile(file, mode, bbox, channels): 
 | 
  
 | 
    tile = None 
 | 
    read = file.read 
 | 
  
 | 
    compression = i16(read(2)) 
 | 
  
 | 
    xsize = bbox[2] - bbox[0] 
 | 
    ysize = bbox[3] - bbox[1] 
 | 
  
 | 
    offset = file.tell() 
 | 
  
 | 
    if compression == 0: 
 | 
        # 
 | 
        # raw compression 
 | 
        tile = [] 
 | 
        for channel in range(channels): 
 | 
            layer = mode[channel] 
 | 
            if mode == "CMYK": 
 | 
                layer = layer + ";I" 
 | 
            tile.append(("raw", bbox, offset, layer)) 
 | 
            offset = offset + xsize*ysize 
 | 
  
 | 
    elif compression == 1: 
 | 
        # 
 | 
        # packbits compression 
 | 
        i = 0 
 | 
        tile = [] 
 | 
        bytecount = read(channels * ysize * 2) 
 | 
        offset = file.tell() 
 | 
        for channel in range(channels): 
 | 
            layer = mode[channel] 
 | 
            if mode == "CMYK": 
 | 
                layer = layer + ";I" 
 | 
            tile.append( 
 | 
                ("packbits", bbox, offset, layer) 
 | 
                ) 
 | 
            for y in range(ysize): 
 | 
                offset = offset + i16(bytecount[i:i+2]) 
 | 
                i = i + 2 
 | 
  
 | 
    file.seek(offset) 
 | 
  
 | 
    if offset & 1: 
 | 
        read(1) # padding 
 | 
  
 | 
    return tile 
 | 
  
 | 
# -------------------------------------------------------------------- 
 | 
# registry 
 | 
  
 | 
Image.register_open("PSD", PsdImageFile, _accept) 
 | 
  
 | 
Image.register_extension("PSD", ".psd") 
 |