# 
 | 
# THIS IS WORK IN PROGRESS 
 | 
# 
 | 
# The Python Imaging Library. 
 | 
# $Id$ 
 | 
# 
 | 
# FlashPix support for PIL 
 | 
# 
 | 
# History: 
 | 
# 97-01-25 fl   Created (reads uncompressed RGB images only) 
 | 
# 
 | 
# Copyright (c) Secret Labs AB 1997. 
 | 
# Copyright (c) Fredrik Lundh 1997. 
 | 
# 
 | 
# See the README file for information on usage and redistribution. 
 | 
# 
 | 
  
 | 
  
 | 
__version__ = "0.1" 
 | 
  
 | 
  
 | 
import Image, ImageFile 
 | 
from OleFileIO import * 
 | 
  
 | 
  
 | 
# we map from colour field tuples to (mode, rawmode) descriptors 
 | 
MODES = { 
 | 
    # opacity 
 | 
    (0x00007ffe): ("A", "L"), 
 | 
    # monochrome 
 | 
    (0x00010000,): ("L", "L"), 
 | 
    (0x00018000, 0x00017ffe): ("RGBA", "LA"), 
 | 
    # photo YCC 
 | 
    (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), 
 | 
    (0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"), 
 | 
    # standard RGB (NIFRGB) 
 | 
    (0x00030000, 0x00030001, 0x00030002): ("RGB","RGB"), 
 | 
    (0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA","RGBA"), 
 | 
} 
 | 
  
 | 
# 
 | 
# -------------------------------------------------------------------- 
 | 
  
 | 
def _accept(prefix): 
 | 
    return prefix[:8] == MAGIC 
 | 
  
 | 
## 
 | 
# Image plugin for the FlashPix images. 
 | 
  
 | 
class FpxImageFile(ImageFile.ImageFile): 
 | 
  
 | 
    format = "FPX" 
 | 
    format_description = "FlashPix" 
 | 
  
 | 
    def _open(self): 
 | 
        # 
 | 
        # read the OLE directory and see if this is a likely 
 | 
        # to be a FlashPix file 
 | 
  
 | 
        try: 
 | 
            self.ole = OleFileIO(self.fp) 
 | 
        except IOError: 
 | 
            raise SyntaxError, "not an FPX file; invalid OLE file" 
 | 
  
 | 
        if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": 
 | 
            raise SyntaxError, "not an FPX file; bad root CLSID" 
 | 
  
 | 
        self._open_index(1) 
 | 
  
 | 
    def _open_index(self, index = 1): 
 | 
        # 
 | 
        # get the Image Contents Property Set 
 | 
  
 | 
        prop = self.ole.getproperties([ 
 | 
            "Data Object Store %06d" % index, 
 | 
            "\005Image Contents" 
 | 
        ]) 
 | 
  
 | 
        # size (highest resolution) 
 | 
  
 | 
        self.size = prop[0x1000002], prop[0x1000003] 
 | 
  
 | 
        size = max(self.size) 
 | 
        i = 1 
 | 
        while size > 64: 
 | 
            size = size / 2 
 | 
            i = i + 1 
 | 
        self.maxid = i - 1 
 | 
  
 | 
        # mode.  instead of using a single field for this, flashpix 
 | 
        # requires you to specify the mode for each channel in each 
 | 
        # resolution subimage, and leaves it to the decoder to make 
 | 
        # sure that they all match.  for now, we'll cheat and assume 
 | 
        # that this is always the case. 
 | 
  
 | 
        id = self.maxid << 16 
 | 
  
 | 
        s = prop[0x2000002|id] 
 | 
  
 | 
        colors = [] 
 | 
        for i in range(i32(s, 4)): 
 | 
            # note: for now, we ignore the "uncalibrated" flag 
 | 
            colors.append(i32(s, 8+i*4) & 0x7fffffff) 
 | 
  
 | 
        self.mode, self.rawmode = MODES[tuple(colors)] 
 | 
  
 | 
        # load JPEG tables, if any 
 | 
        self.jpeg = {} 
 | 
        for i in range(256): 
 | 
            id = 0x3000001|(i << 16) 
 | 
            if prop.has_key(id): 
 | 
                self.jpeg[i] = prop[id] 
 | 
  
 | 
        # print len(self.jpeg), "tables loaded" 
 | 
  
 | 
        self._open_subimage(1, self.maxid) 
 | 
  
 | 
    def _open_subimage(self, index = 1, subimage = 0): 
 | 
        # 
 | 
        # setup tile descriptors for a given subimage 
 | 
  
 | 
        stream = [ 
 | 
            "Data Object Store %06d" % index, 
 | 
            "Resolution %04d" % subimage, 
 | 
            "Subimage 0000 Header" 
 | 
        ] 
 | 
  
 | 
        fp = self.ole.openstream(stream) 
 | 
  
 | 
        # skip prefix 
 | 
        p = fp.read(28) 
 | 
  
 | 
        # header stream 
 | 
        s = fp.read(36) 
 | 
  
 | 
        size = i32(s, 4), i32(s, 8) 
 | 
        tilecount = i32(s, 12) 
 | 
        tilesize = i32(s, 16), i32(s, 20) 
 | 
        channels = i32(s, 24) 
 | 
        offset = i32(s, 28) 
 | 
        length = i32(s, 32) 
 | 
  
 | 
        # print size, self.mode, self.rawmode 
 | 
  
 | 
        if size != self.size: 
 | 
            raise IOError, "subimage mismatch" 
 | 
  
 | 
        # get tile descriptors 
 | 
        fp.seek(28 + offset) 
 | 
        s = fp.read(i32(s, 12) * length) 
 | 
  
 | 
        x = y = 0 
 | 
        xsize, ysize = size 
 | 
        xtile, ytile = tilesize 
 | 
        self.tile = [] 
 | 
  
 | 
        for i in range(0, len(s), length): 
 | 
  
 | 
            compression = i32(s, i+8) 
 | 
  
 | 
            if compression == 0: 
 | 
                self.tile.append(("raw", (x,y,x+xtile,y+ytile), 
 | 
                        i32(s, i) + 28, (self.rawmode))) 
 | 
  
 | 
            elif compression == 1: 
 | 
  
 | 
                # FIXME: the fill decoder is not implemented 
 | 
                self.tile.append(("fill", (x,y,x+xtile,y+ytile), 
 | 
                        i32(s, i) + 28, (self.rawmode, s[12:16]))) 
 | 
  
 | 
            elif compression == 2: 
 | 
  
 | 
                internal_color_conversion = ord(s[14]) 
 | 
                jpeg_tables = ord(s[15]) 
 | 
                rawmode = self.rawmode 
 | 
  
 | 
                if internal_color_conversion: 
 | 
                    # The image is stored as usual (usually YCbCr). 
 | 
                    if rawmode == "RGBA": 
 | 
                        # For "RGBA", data is stored as YCbCrA based on 
 | 
                        # negative RGB. The following trick works around 
 | 
                        # this problem : 
 | 
                        jpegmode, rawmode = "YCbCrK", "CMYK" 
 | 
                    else: 
 | 
                        jpegmode = None # let the decoder decide 
 | 
  
 | 
                else: 
 | 
                    # The image is stored as defined by rawmode 
 | 
                    jpegmode = rawmode 
 | 
  
 | 
                self.tile.append(("jpeg", (x,y,x+xtile,y+ytile), 
 | 
                        i32(s, i) + 28, (rawmode, jpegmode))) 
 | 
  
 | 
                # FIXME: jpeg tables are tile dependent; the prefix 
 | 
                # data must be placed in the tile descriptor itself! 
 | 
  
 | 
                if jpeg_tables: 
 | 
                    self.tile_prefix = self.jpeg[jpeg_tables] 
 | 
  
 | 
            else: 
 | 
                raise IOError, "unknown/invalid compression" 
 | 
  
 | 
            x = x + xtile 
 | 
            if x >= xsize: 
 | 
                x, y = 0, y + ytile 
 | 
                if y >= ysize: 
 | 
                    break # isn't really required 
 | 
  
 | 
        self.stream = stream 
 | 
        self.fp = None 
 | 
  
 | 
    def load(self): 
 | 
  
 | 
        if not self.fp: 
 | 
            self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) 
 | 
  
 | 
        ImageFile.ImageFile.load(self) 
 | 
  
 | 
# 
 | 
# -------------------------------------------------------------------- 
 | 
  
 | 
Image.register_open("FPX", FpxImageFile, _accept) 
 | 
  
 | 
Image.register_extension("FPX", ".fpx") 
 |