Pillow/PIL/ImImagePlugin.py

356 lines
10 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
# $Id$
#
# IFUNC IM file handling for PIL
#
# history:
# 1995-09-01 fl Created.
# 1997-01-03 fl Save palette images
# 1997-01-08 fl Added sequence support
# 1997-01-23 fl Added P and RGB save support
# 1997-05-31 fl Read floating point images
# 1997-06-22 fl Save floating point images
# 1997-08-27 fl Read and save 1-bit images
# 1998-06-25 fl Added support for RGB+LUT images
# 1998-07-02 fl Added support for YCC images
# 1998-07-15 fl Renamed offset attribute to avoid name clash
# 1998-12-29 fl Added I;16 support
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
# 2003-09-26 fl Added LA/PA support
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2001 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.7"
import re
2013-03-07 20:20:28 +04:00
from PIL import Image, ImageFile, ImagePalette
2014-08-26 17:47:10 +04:00
from PIL._binary import i8
2010-07-31 06:52:47 +04:00
# --------------------------------------------------------------------
# Standard tags
COMMENT = "Comment"
DATE = "Date"
EQUIPMENT = "Digitalization equipment"
FRAMES = "File size (no of images)"
LUT = "Lut"
NAME = "Name"
SCALE = "Scale (x,y)"
SIZE = "Image size (x*y)"
MODE = "Image type"
2014-08-26 17:47:10 +04:00
TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
SCALE: 0, SIZE: 0, MODE: 0}
2010-07-31 06:52:47 +04:00
OPEN = {
# ifunc93/p3cfunc formats
"0 1 image": ("1", "1"),
"L 1 image": ("1", "1"),
"Greyscale image": ("L", "L"),
"Grayscale image": ("L", "L"),
"RGB image": ("RGB", "RGB;L"),
"RLB image": ("RGB", "RLB"),
"RYB image": ("RGB", "RLB"),
"B1 image": ("1", "1"),
"B2 image": ("P", "P;2"),
"B4 image": ("P", "P;4"),
"X 24 image": ("RGB", "RGB"),
"L 32 S image": ("I", "I;32"),
"L 32 F image": ("F", "F;32"),
# old p3cfunc formats
"RGB3 image": ("RGB", "RGB;T"),
"RYB3 image": ("RGB", "RYB;T"),
# extensions
"LA image": ("LA", "LA;L"),
"RGBA image": ("RGBA", "RGBA;L"),
"RGBX image": ("RGBX", "RGBX;L"),
"CMYK image": ("CMYK", "CMYK;L"),
"YCC image": ("YCbCr", "YCbCr;L"),
}
# ifunc95 extensions
for i in ["8", "8S", "16", "16S", "32", "32F"]:
OPEN["L %s image" % i] = ("F", "F;%s" % i)
OPEN["L*%s image" % i] = ("F", "F;%s" % i)
for i in ["16", "16L", "16B"]:
OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i)
OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i)
for i in ["32S"]:
OPEN["L %s image" % i] = ("I", "I;%s" % i)
OPEN["L*%s image" % i] = ("I", "I;%s" % i)
for i in range(2, 33):
OPEN["L*%s image" % i] = ("F", "F;%s" % i)
# --------------------------------------------------------------------
# Read IM directory
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
2010-07-31 06:52:47 +04:00
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
def number(s):
try:
return int(s)
except ValueError:
return float(s)
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
##
# Image plugin for the IFUNC IM file format.
class ImImageFile(ImageFile.ImageFile):
format = "IM"
format_description = "IFUNC Image Memory"
def _open(self):
# Quick rejection: if there's not an LF among the first
# 100 bytes, this is (probably) not a text header.
2014-08-26 17:47:10 +04:00
if b"\n" not in self.fp.read(100):
raise SyntaxError("not an IM file")
2010-07-31 06:52:47 +04:00
self.fp.seek(0)
n = 0
# Default values
self.info[MODE] = "L"
self.info[SIZE] = (512, 512)
self.info[FRAMES] = 1
self.rawmode = "L"
while True:
2010-07-31 06:52:47 +04:00
s = self.fp.read(1)
# Some versions of IFUNC uses \n\r instead of \r\n...
if s == b"\r":
2010-07-31 06:52:47 +04:00
continue
if not s or s == b'\0' or s == b'\x1A':
2010-07-31 06:52:47 +04:00
break
# FIXME: this may read whole file if not a text file
s = s + self.fp.readline()
if len(s) > 100:
raise SyntaxError("not an IM file")
2010-07-31 06:52:47 +04:00
if s[-2:] == b'\r\n':
2010-07-31 06:52:47 +04:00
s = s[:-2]
elif s[-1:] == b'\n':
2010-07-31 06:52:47 +04:00
s = s[:-1]
try:
m = split.match(s)
except re.error as v:
raise SyntaxError("not an IM file")
2010-07-31 06:52:47 +04:00
if m:
2014-08-26 17:47:10 +04:00
k, v = m.group(1, 2)
2010-07-31 06:52:47 +04:00
2014-08-28 15:44:19 +04:00
# Don't know if this is the correct encoding,
# but a decent guess (I guess)
k = k.decode('latin-1', 'replace')
v = v.decode('latin-1', 'replace')
2010-07-31 06:52:47 +04:00
# Convert value as appropriate
if k in [FRAMES, SCALE, SIZE]:
v = v.replace("*", ",")
v = tuple(map(number, v.split(",")))
2010-07-31 06:52:47 +04:00
if len(v) == 1:
v = v[0]
elif k == MODE and v in OPEN:
2010-07-31 06:52:47 +04:00
v, self.rawmode = OPEN[v]
# Add to dictionary. Note that COMMENT tags are
# combined into a list of strings.
if k == COMMENT:
if k in self.info:
2010-07-31 06:52:47 +04:00
self.info[k].append(v)
else:
self.info[k] = [v]
else:
self.info[k] = v
if k in TAGS:
n += 1
2010-07-31 06:52:47 +04:00
else:
2014-08-26 17:47:10 +04:00
raise SyntaxError("Syntax error in IM header: " +
s.decode('ascii', 'replace'))
2010-07-31 06:52:47 +04:00
if not n:
raise SyntaxError("Not an IM file")
2010-07-31 06:52:47 +04:00
# Basic attributes
self.size = self.info[SIZE]
self.mode = self.info[MODE]
# Skip forward to start of image data
while s and s[0:1] != b'\x1A':
2010-07-31 06:52:47 +04:00
s = self.fp.read(1)
if not s:
raise SyntaxError("File truncated")
2010-07-31 06:52:47 +04:00
if LUT in self.info:
2010-07-31 06:52:47 +04:00
# convert lookup table to palette or lut attribute
palette = self.fp.read(768)
2014-08-26 17:47:10 +04:00
greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette
2010-07-31 06:52:47 +04:00
for i in range(256):
if palette[i] == palette[i+256] == palette[i+512]:
if i8(palette[i]) != i:
2010-07-31 06:52:47 +04:00
linear = 0
else:
greyscale = 0
if self.mode == "L" or self.mode == "LA":
if greyscale:
if not linear:
self.lut = [i8(c) for c in palette[:256]]
2010-07-31 06:52:47 +04:00
else:
if self.mode == "L":
self.mode = self.rawmode = "P"
elif self.mode == "LA":
self.mode = self.rawmode = "PA"
self.palette = ImagePalette.raw("RGB;L", palette)
elif self.mode == "RGB":
if not greyscale or not linear:
self.lut = [i8(c) for c in palette]
2010-07-31 06:52:47 +04:00
self.frame = 0
self.__offset = offs = self.fp.tell()
2014-08-26 17:47:10 +04:00
self.__fp = self.fp # FIXME: hack
2010-07-31 06:52:47 +04:00
if self.rawmode[:2] == "F;":
# ifunc95 formats
try:
# use bit decoder (if necessary)
bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]:
2014-08-26 17:47:10 +04:00
self.tile = [("bit", (0, 0)+self.size, offs,
2010-07-31 06:52:47 +04:00
(bits, 8, 3, 0, -1))]
return
except ValueError:
pass
if self.rawmode in ["RGB;T", "RYB;T"]:
# Old LabEye/3PC files. Would be very surprised if anyone
# ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1]
2014-08-26 17:47:10 +04:00
self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
2010-07-31 06:52:47 +04:00
else:
# LabEye/IFUNC files
2014-08-26 17:47:10 +04:00
self.tile = [("raw", (0, 0)+self.size, offs,
(self.rawmode, 0, -1))]
2010-07-31 06:52:47 +04:00
@property
def n_frames(self):
return self.info[FRAMES]
2015-06-30 06:25:00 +03:00
@property
def is_animated(self):
return self.info[FRAMES] > 1
2010-07-31 06:52:47 +04:00
def seek(self, frame):
if frame < 0 or frame >= self.info[FRAMES]:
raise EOFError("seek outside sequence")
2010-07-31 06:52:47 +04:00
if self.frame == frame:
return
self.frame = frame
if self.mode == "1":
bits = 1
else:
bits = 8 * len(self.mode)
size = ((self.size[0] * bits + 7) // 8) * self.size[1]
2010-07-31 06:52:47 +04:00
offs = self.__offset + frame * size
self.fp = self.__fp
2014-08-26 17:47:10 +04:00
self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
2010-07-31 06:52:47 +04:00
def tell(self):
return self.frame
#
# --------------------------------------------------------------------
# Save IM files
SAVE = {
# mode: (im type, raw mode)
"1": ("0 1", "1"),
"L": ("Greyscale", "L"),
"LA": ("LA", "LA;L"),
"P": ("Greyscale", "P"),
"PA": ("LA", "PA;L"),
"I": ("L 32S", "I;32S"),
"I;16": ("L 16", "I;16"),
"I;16L": ("L 16L", "I;16L"),
"I;16B": ("L 16B", "I;16B"),
"F": ("L 32F", "F;32F"),
"RGB": ("RGB", "RGB;L"),
"RGBA": ("RGBA", "RGBA;L"),
"RGBX": ("RGBX", "RGBX;L"),
"CMYK": ("CMYK", "CMYK;L"),
"YCbCr": ("YCC", "YCbCr;L")
}
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
def _save(im, fp, filename, check=0):
try:
2015-04-02 11:45:24 +03:00
image_type, rawmode = SAVE[im.mode]
2010-07-31 06:52:47 +04:00
except KeyError:
raise ValueError("Cannot save %s images as IM" % im.mode)
2010-07-31 06:52:47 +04:00
try:
frames = im.encoderinfo["frames"]
except KeyError:
frames = 1
if check:
return check
2015-04-02 11:45:24 +03:00
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
2010-07-31 06:52:47 +04:00
if filename:
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii'))
2010-07-31 06:52:47 +04:00
if im.mode == "P":
fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511-fp.tell()) + b"\032")
2010-07-31 06:52:47 +04:00
if im.mode == "P":
2014-08-26 17:47:10 +04:00
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
2010-07-31 06:52:47 +04:00
#
# --------------------------------------------------------------------
# Registry
Image.register_open(ImImageFile.format, ImImageFile)
Image.register_save(ImImageFile.format, _save)
2010-07-31 06:52:47 +04:00
Image.register_extension(ImImageFile.format, ".im")