mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-09-22 20:09:04 +03:00
d50445ff30
Similar to the recent adoption of Black. isort is a Python utility to sort imports alphabetically and automatically separate into sections. By using isort, contributors can quickly and automatically conform to the projects style without thinking. Just let the tool do it. Uses the configuration recommended by the Black to avoid conflicts of style. Rewrite TestImageQt.test_deprecated to no rely on import order.
375 lines
10 KiB
Python
375 lines
10 KiB
Python
#
|
|
# 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.
|
|
#
|
|
|
|
|
|
import re
|
|
|
|
from . import Image, ImageFile, ImagePalette
|
|
from ._binary import i8
|
|
|
|
# __version__ is deprecated and will be removed in a future version. Use
|
|
# PIL.__version__ instead.
|
|
__version__ = "0.7"
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
# 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"
|
|
|
|
TAGS = {
|
|
COMMENT: 0,
|
|
DATE: 0,
|
|
EQUIPMENT: 0,
|
|
FRAMES: 0,
|
|
LUT: 0,
|
|
NAME: 0,
|
|
SCALE: 0,
|
|
SIZE: 0,
|
|
MODE: 0,
|
|
}
|
|
|
|
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"),
|
|
"PA image": ("LA", "PA;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]*$")
|
|
|
|
|
|
def number(s):
|
|
try:
|
|
return int(s)
|
|
except ValueError:
|
|
return float(s)
|
|
|
|
|
|
##
|
|
# Image plugin for the IFUNC IM file format.
|
|
|
|
|
|
class ImImageFile(ImageFile.ImageFile):
|
|
|
|
format = "IM"
|
|
format_description = "IFUNC Image Memory"
|
|
_close_exclusive_fp_after_loading = False
|
|
|
|
def _open(self):
|
|
|
|
# Quick rejection: if there's not an LF among the first
|
|
# 100 bytes, this is (probably) not a text header.
|
|
|
|
if b"\n" not in self.fp.read(100):
|
|
raise SyntaxError("not an IM file")
|
|
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:
|
|
|
|
s = self.fp.read(1)
|
|
|
|
# Some versions of IFUNC uses \n\r instead of \r\n...
|
|
if s == b"\r":
|
|
continue
|
|
|
|
if not s or s == b"\0" or s == b"\x1A":
|
|
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")
|
|
|
|
if s[-2:] == b"\r\n":
|
|
s = s[:-2]
|
|
elif s[-1:] == b"\n":
|
|
s = s[:-1]
|
|
|
|
try:
|
|
m = split.match(s)
|
|
except re.error:
|
|
raise SyntaxError("not an IM file")
|
|
|
|
if m:
|
|
|
|
k, v = m.group(1, 2)
|
|
|
|
# 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")
|
|
|
|
# Convert value as appropriate
|
|
if k in [FRAMES, SCALE, SIZE]:
|
|
v = v.replace("*", ",")
|
|
v = tuple(map(number, v.split(",")))
|
|
if len(v) == 1:
|
|
v = v[0]
|
|
elif k == MODE and v in OPEN:
|
|
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:
|
|
self.info[k].append(v)
|
|
else:
|
|
self.info[k] = [v]
|
|
else:
|
|
self.info[k] = v
|
|
|
|
if k in TAGS:
|
|
n += 1
|
|
|
|
else:
|
|
|
|
raise SyntaxError(
|
|
"Syntax error in IM header: " + s.decode("ascii", "replace")
|
|
)
|
|
|
|
if not n:
|
|
raise SyntaxError("Not an IM file")
|
|
|
|
# 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":
|
|
s = self.fp.read(1)
|
|
if not s:
|
|
raise SyntaxError("File truncated")
|
|
|
|
if LUT in self.info:
|
|
# convert lookup table to palette or lut attribute
|
|
palette = self.fp.read(768)
|
|
greyscale = 1 # greyscale palette
|
|
linear = 1 # linear greyscale palette
|
|
for i in range(256):
|
|
if palette[i] == palette[i + 256] == palette[i + 512]:
|
|
if i8(palette[i]) != i:
|
|
linear = 0
|
|
else:
|
|
greyscale = 0
|
|
if self.mode in ["L", "LA", "P", "PA"]:
|
|
if greyscale:
|
|
if not linear:
|
|
self.lut = [i8(c) for c in palette[:256]]
|
|
else:
|
|
if self.mode in ["L", "P"]:
|
|
self.mode = self.rawmode = "P"
|
|
elif self.mode in ["LA", "PA"]:
|
|
self.mode = "PA"
|
|
self.rawmode = "PA;L"
|
|
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]
|
|
|
|
self.frame = 0
|
|
|
|
self.__offset = offs = self.fp.tell()
|
|
|
|
self.__fp = self.fp # FIXME: hack
|
|
|
|
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]:
|
|
self.tile = [("bit", (0, 0) + self.size, offs, (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]
|
|
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)),
|
|
]
|
|
else:
|
|
# LabEye/IFUNC files
|
|
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
|
|
|
|
@property
|
|
def n_frames(self):
|
|
return self.info[FRAMES]
|
|
|
|
@property
|
|
def is_animated(self):
|
|
return self.info[FRAMES] > 1
|
|
|
|
def seek(self, frame):
|
|
if not self._seek_check(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]
|
|
offs = self.__offset + frame * size
|
|
|
|
self.fp = self.__fp
|
|
|
|
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
|
|
|
|
def tell(self):
|
|
return self.frame
|
|
|
|
def _close__fp(self):
|
|
try:
|
|
if self.__fp != self.fp:
|
|
self.__fp.close()
|
|
except AttributeError:
|
|
pass
|
|
finally:
|
|
self.__fp = None
|
|
|
|
|
|
#
|
|
# --------------------------------------------------------------------
|
|
# 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"),
|
|
}
|
|
|
|
|
|
def _save(im, fp, filename):
|
|
|
|
try:
|
|
image_type, rawmode = SAVE[im.mode]
|
|
except KeyError:
|
|
raise ValueError("Cannot save %s images as IM" % im.mode)
|
|
|
|
frames = im.encoderinfo.get("frames", 1)
|
|
|
|
fp.write(("Image type: %s image\r\n" % image_type).encode("ascii"))
|
|
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"))
|
|
if im.mode in ["P", "PA"]:
|
|
fp.write(b"Lut: 1\r\n")
|
|
fp.write(b"\000" * (511 - fp.tell()) + b"\032")
|
|
if im.mode in ["P", "PA"]:
|
|
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
|
|
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
|
|
|
|
|
|
#
|
|
# --------------------------------------------------------------------
|
|
# Registry
|
|
|
|
|
|
Image.register_open(ImImageFile.format, ImImageFile)
|
|
Image.register_save(ImImageFile.format, _save)
|
|
|
|
Image.register_extension(ImImageFile.format, ".im")
|