[Image-SIG] Accessing Photoshop layers in PIL?

Fredrik Lundh fredrik@pythonware.com
Sat, 20 Oct 2001 17:43:02 +0200


This is a multi-part message in MIME format.

------=_NextPart_000_006D_01C1598E.AACE7610
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

Hamish Lawson wrote:

> Is it possible to access the individual layers of a Photoshop image in
> PIL? By 'layers' I mean in Photoshop's sense - a stack of images that
> overlay each other like acetates to produce the composite image -
> rather than RGB or CYMK channels.

PIL 1.1.2 only loads the first channel.  I've attached a
rewritten PsdImagePlugin which allows you to use the
seek/tell methods to select which channel to load:

    im = Image.open("spam.psd")

    im.seek(2) # get third channel
    layer3 = im.copy()

I've tested this code on exactly one sample; let me know
if it works for your images.

</F>

------=_NextPart_000_006D_01C1598E.AACE7610
Content-Type: application/octet-stream;
	name="PsdImagePlugin.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="PsdImagePlugin.py"

#
# The Python Imaging Library
# $Id: //modules/pil/PIL/PsdImagePlugin.py#3 $
#
# 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-20 fl   Added seek/tell support (for channels)
#
# 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 string
import Image, ImageFile, ImagePalette

MODES = {
    0: "1",
    1: "L",
    2: "P",
    3: "RGB",
    4: "CMYK",
    7: "L",
    8: "L",
    9: "LAB"
}

#
# 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"

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"

        bits, channels = i16(s[22:]), i16(s[12:])

        # FIXME: check number of bits

        self.mode = MODES[i16(s[24:])]

        self.size = i32(s[18:]), i32(s[14:])

        #
        # color mode data

        size = i32(read(4))
        if size:
            data = read(size)
            if self.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))

        #
        # layer and mask information

        size = i32(read(4))
        if size:
            self.fp.seek(size, 1) # ignored, for now

        #
        # image descriptor

        self.channels = []

        compression = i16(read(2))

        if compression == 0:
            #
            # raw compression
            offset = self.fp.tell()
            for channel in range(channels):
                tile = []
                for layer in self.mode:
                    if self.mode == "CMYK":
                        layer = layer + ";I"
                    tile.append(("raw", (0,0)+self.size, offset, layer))
                    offset = offset + self.size[0]*self.size[1]
                self.channels.append(tile)

        elif compression == 1:
            #
            # packbits compression
            i = 0
            bytecount = read(channels * self.size[1] * 2)
            offset = self.fp.tell()
            for channel in range(channels):
                tile = []
                for layer in self.mode:
                    if self.mode == "CMYK":
                        layer = layer + ";I"
                    tile.append(
                        ("packbits", (0,0)+self.size, offset, layer)
                        )
                    for y in range(self.size[1]):
                        offset = offset + i16(bytecount[i:i+2])
                        i = i + 2
                self.channels.append(tile)

        # keep the file open
        self.fp2 = self.fp

        self.seek(0)

    def seek(self, channel):
        try:
            self.fp = self.fp2
            self.tile = self.channels[channel]
            self.frame = channel
        except:
            raise EOFError, "no such channel"

    def tell(self, channel):
        return self.frame

# --------------------------------------------------------------------
# registry

Image.register_open("PSD", PsdImageFile, _accept)

Image.register_extension("PSD", ".psd")

------=_NextPart_000_006D_01C1598E.AACE7610--