Format code with Black (#3733)

Format code with Black
This commit is contained in:
Hugo 2019-06-11 16:24:09 +03:00 committed by GitHub
commit a986fed5b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 3257 additions and 2709 deletions

View File

@ -2,6 +2,7 @@
-e . -e .
alabaster alabaster
Babel Babel
black; python_version >= '3.6'
check-manifest check-manifest
cov-core cov-core
coverage coverage

View File

@ -2,4 +2,5 @@
test=pytest test=pytest
[flake8] [flake8]
extend-ignore = E203, W503
max-line-length = 88 max-line-length = 88

View File

@ -32,14 +32,10 @@ bdf_slant = {
"O": "Oblique", "O": "Oblique",
"RI": "Reverse Italic", "RI": "Reverse Italic",
"RO": "Reverse Oblique", "RO": "Reverse Oblique",
"OT": "Other" "OT": "Other",
} }
bdf_spacing = { bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
"P": "Proportional",
"M": "Monospaced",
"C": "Cell"
}
def bdf_char(f): def bdf_char(f):
@ -50,7 +46,7 @@ def bdf_char(f):
return None return None
if s[:9] == b"STARTCHAR": if s[:9] == b"STARTCHAR":
break break
id = s[9:].strip().decode('ascii') id = s[9:].strip().decode("ascii")
# load symbol properties # load symbol properties
props = {} props = {}
@ -59,7 +55,7 @@ def bdf_char(f):
if not s or s[:6] == b"BITMAP": if not s or s[:6] == b"BITMAP":
break break
i = s.find(b" ") i = s.find(b" ")
props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii') props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
# load bitmap # load bitmap
bitmap = [] bitmap = []
@ -73,7 +69,7 @@ def bdf_char(f):
[x, y, l, d] = [int(p) for p in props["BBX"].split()] [x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(p) for p in props["DWIDTH"].split()] [dx, dy] = [int(p) for p in props["DWIDTH"].split()]
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y) bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y)
try: try:
im = Image.frombytes("1", (x, y), bitmap, "hex", "1") im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
@ -87,8 +83,8 @@ def bdf_char(f):
## ##
# Font file plugin for the X11 BDF format. # Font file plugin for the X11 BDF format.
class BdfFontFile(FontFile.FontFile):
class BdfFontFile(FontFile.FontFile):
def __init__(self, fp): def __init__(self, fp):
FontFile.FontFile.__init__(self) FontFile.FontFile.__init__(self)
@ -105,10 +101,10 @@ class BdfFontFile(FontFile.FontFile):
if not s or s[:13] == b"ENDPROPERTIES": if not s or s[:13] == b"ENDPROPERTIES":
break break
i = s.find(b" ") i = s.find(b" ")
props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii') props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
if s[:i] in [b"COMMENT", b"COPYRIGHT"]: if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
if s.find(b"LogicalFontDescription") < 0: if s.find(b"LogicalFontDescription") < 0:
comments.append(s[i+1:-1].decode('ascii')) comments.append(s[i + 1 : -1].decode("ascii"))
while True: while True:
c = bdf_char(fp) c = bdf_char(fp)

View File

@ -47,11 +47,7 @@ BLP_ALPHA_ENCODING_DXT5 = 7
def unpack_565(i): def unpack_565(i):
return ( return (((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3)
((i >> 11) & 0x1f) << 3,
((i >> 5) & 0x3f) << 2,
(i & 0x1f) << 3
)
def decode_dxt1(data, alpha=False): def decode_dxt1(data, alpha=False):
@ -119,7 +115,7 @@ def decode_dxt3(data):
for block in range(blocks): for block in range(blocks):
idx = block * 16 idx = block * 16
block = data[idx:idx + 16] block = data[idx : idx + 16]
# Decode next 16-byte block. # Decode next 16-byte block.
bits = struct.unpack_from("<8B", block) bits = struct.unpack_from("<8B", block)
color0, color1 = struct.unpack_from("<HH", block, 8) color0, color1 = struct.unpack_from("<HH", block, 8)
@ -139,7 +135,7 @@ def decode_dxt3(data):
a >>= 4 a >>= 4
else: else:
high = True high = True
a &= 0xf a &= 0xF
a *= 17 # We get a value between 0 and 15 a *= 17 # We get a value between 0 and 15
color_code = (code >> 2 * (4 * j + i)) & 0x03 color_code = (code >> 2 * (4 * j + i)) & 0x03
@ -172,14 +168,12 @@ def decode_dxt5(data):
for block in range(blocks): for block in range(blocks):
idx = block * 16 idx = block * 16
block = data[idx:idx + 16] block = data[idx : idx + 16]
# Decode next 16-byte block. # Decode next 16-byte block.
a0, a1 = struct.unpack_from("<BB", block) a0, a1 = struct.unpack_from("<BB", block)
bits = struct.unpack_from("<6B", block, 2) bits = struct.unpack_from("<6B", block, 2)
alphacode1 = ( alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
)
alphacode2 = bits[0] | (bits[1] << 8) alphacode2 = bits[0] | (bits[1] << 8)
color0, color1 = struct.unpack_from("<HH", block, 8) color0, color1 = struct.unpack_from("<HH", block, 8)
@ -242,6 +236,7 @@ class BlpImageFile(ImageFile.ImageFile):
""" """
Blizzard Mipmap Format Blizzard Mipmap Format
""" """
format = "BLP" format = "BLP"
format_description = "Blizzard Mipmap Format" format_description = "Blizzard Mipmap Format"
@ -258,9 +253,7 @@ class BlpImageFile(ImageFile.ImageFile):
else: else:
raise BLPFormatError("Bad BLP magic %r" % (self.magic)) raise BLPFormatError("Bad BLP magic %r" % (self.magic))
self.tile = [ self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
]
def _read_blp_header(self): def _read_blp_header(self):
self._blp_compression, = struct.unpack("<i", self.fp.read(4)) self._blp_compression, = struct.unpack("<i", self.fp.read(4))
@ -324,7 +317,6 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
class BLP1Decoder(_BLPBaseDecoder): class BLP1Decoder(_BLPBaseDecoder):
def _load(self): def _load(self):
if self._blp_compression == BLP_FORMAT_JPEG: if self._blp_compression == BLP_FORMAT_JPEG:
self._decode_jpeg_stream() self._decode_jpeg_stream()
@ -368,7 +360,6 @@ class BLP1Decoder(_BLPBaseDecoder):
class BLP2Decoder(_BLPBaseDecoder): class BLP2Decoder(_BLPBaseDecoder):
def _load(self): def _load(self):
palette = self._read_palette() palette = self._read_palette()
@ -393,8 +384,7 @@ class BLP2Decoder(_BLPBaseDecoder):
linesize = (self.size[0] + 3) // 4 * 8 linesize = (self.size[0] + 3) // 4 * 8
for yb in range((self.size[1] + 3) // 4): for yb in range((self.size[1] + 3) // 4):
for d in decode_dxt1( for d in decode_dxt1(
self.fd.read(linesize), self.fd.read(linesize), alpha=bool(self._blp_alpha_depth)
alpha=bool(self._blp_alpha_depth)
): ):
data += d data += d
@ -410,18 +400,14 @@ class BLP2Decoder(_BLPBaseDecoder):
for d in decode_dxt5(self.fd.read(linesize)): for d in decode_dxt5(self.fd.read(linesize)):
data += d data += d
else: else:
raise BLPFormatError("Unsupported alpha encoding %r" % ( raise BLPFormatError(
self._blp_alpha_encoding "Unsupported alpha encoding %r" % (self._blp_alpha_encoding)
)) )
else: else:
raise BLPFormatError( raise BLPFormatError("Unknown BLP encoding %r" % (self._blp_encoding))
"Unknown BLP encoding %r" % (self._blp_encoding)
)
else: else:
raise BLPFormatError( raise BLPFormatError("Unknown BLP compression %r" % (self._blp_compression))
"Unknown BLP compression %r" % (self._blp_compression)
)
self.set_as_raw(bytes(data)) self.set_as_raw(bytes(data))

View File

@ -25,8 +25,7 @@
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16le as i16, i32le as i32, \ from ._binary import i8, i16le as i16, i32le as i32, o8, o16le as o16, o32le as o32
o8, o16le as o16, o32le as o32
# __version__ is deprecated and will be removed in a future version. Use # __version__ is deprecated and will be removed in a future version. Use
# PIL.__version__ instead. # PIL.__version__ instead.
@ -66,14 +65,7 @@ class BmpImageFile(ImageFile.ImageFile):
format = "BMP" format = "BMP"
# -------------------------------------------------- BMP Compression values # -------------------------------------------------- BMP Compression values
COMPRESSIONS = { COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
'RAW': 0,
'RLE8': 1,
'RLE4': 2,
'BITFIELDS': 3,
'JPEG': 4,
'PNG': 5
}
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5 RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
def _bitmap(self, header=0, offset=0): def _bitmap(self, header=0, offset=0):
@ -83,53 +75,54 @@ class BmpImageFile(ImageFile.ImageFile):
seek(header) seek(header)
file_info = {} file_info = {}
# read bmp header size @offset 14 (this is part of the header size) # read bmp header size @offset 14 (this is part of the header size)
file_info['header_size'] = i32(read(4)) file_info["header_size"] = i32(read(4))
file_info['direction'] = -1 file_info["direction"] = -1
# -------------------- If requested, read header at a specific position # -------------------- If requested, read header at a specific position
# read the rest of the bmp header, without its size # read the rest of the bmp header, without its size
header_data = ImageFile._safe_read(self.fp, header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
file_info['header_size'] - 4)
# -------------------------------------------------- IBM OS/2 Bitmap v1 # -------------------------------------------------- IBM OS/2 Bitmap v1
# ----- This format has different offsets because of width/height types # ----- This format has different offsets because of width/height types
if file_info['header_size'] == 12: if file_info["header_size"] == 12:
file_info['width'] = i16(header_data[0:2]) file_info["width"] = i16(header_data[0:2])
file_info['height'] = i16(header_data[2:4]) file_info["height"] = i16(header_data[2:4])
file_info['planes'] = i16(header_data[4:6]) file_info["planes"] = i16(header_data[4:6])
file_info['bits'] = i16(header_data[6:8]) file_info["bits"] = i16(header_data[6:8])
file_info['compression'] = self.RAW file_info["compression"] = self.RAW
file_info['palette_padding'] = 3 file_info["palette_padding"] = 3
# --------------------------------------------- Windows Bitmap v2 to v5 # --------------------------------------------- Windows Bitmap v2 to v5
# v3, OS/2 v2, v4, v5 # v3, OS/2 v2, v4, v5
elif file_info['header_size'] in (40, 64, 108, 124): elif file_info["header_size"] in (40, 64, 108, 124):
file_info['y_flip'] = i8(header_data[7]) == 0xff file_info["y_flip"] = i8(header_data[7]) == 0xFF
file_info['direction'] = 1 if file_info['y_flip'] else -1 file_info["direction"] = 1 if file_info["y_flip"] else -1
file_info['width'] = i32(header_data[0:4]) file_info["width"] = i32(header_data[0:4])
file_info['height'] = (i32(header_data[4:8]) file_info["height"] = (
if not file_info['y_flip'] i32(header_data[4:8])
else 2**32 - i32(header_data[4:8])) if not file_info["y_flip"]
file_info['planes'] = i16(header_data[8:10]) else 2 ** 32 - i32(header_data[4:8])
file_info['bits'] = i16(header_data[10:12]) )
file_info['compression'] = i32(header_data[12:16]) file_info["planes"] = i16(header_data[8:10])
file_info["bits"] = i16(header_data[10:12])
file_info["compression"] = i32(header_data[12:16])
# byte size of pixel data # byte size of pixel data
file_info['data_size'] = i32(header_data[16:20]) file_info["data_size"] = i32(header_data[16:20])
file_info['pixels_per_meter'] = (i32(header_data[20:24]), file_info["pixels_per_meter"] = (
i32(header_data[24:28])) i32(header_data[20:24]),
file_info['colors'] = i32(header_data[28:32]) i32(header_data[24:28]),
file_info['palette_padding'] = 4 )
file_info["colors"] = i32(header_data[28:32])
file_info["palette_padding"] = 4
self.info["dpi"] = tuple( self.info["dpi"] = tuple(
int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter']) int(x / 39.3701 + 0.5) for x in file_info["pixels_per_meter"]
if file_info['compression'] == self.BITFIELDS: )
if file_info["compression"] == self.BITFIELDS:
if len(header_data) >= 52: if len(header_data) >= 52:
for idx, mask in enumerate(['r_mask', for idx, mask in enumerate(
'g_mask', ["r_mask", "g_mask", "b_mask", "a_mask"]
'b_mask', ):
'a_mask']): file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4])
file_info[mask] = i32(
header_data[36 + idx * 4:40 + idx * 4]
)
else: else:
# 40 byte headers only have the three components in the # 40 byte headers only have the three components in the
# bitfields masks, ref: # bitfields masks, ref:
@ -139,121 +132,133 @@ class BmpImageFile(ImageFile.ImageFile):
# There is a 4th component in the RGBQuad, in the alpha # There is a 4th component in the RGBQuad, in the alpha
# location, but it is listed as a reserved component, # location, but it is listed as a reserved component,
# and it is not generally an alpha channel # and it is not generally an alpha channel
file_info['a_mask'] = 0x0 file_info["a_mask"] = 0x0
for mask in ['r_mask', 'g_mask', 'b_mask']: for mask in ["r_mask", "g_mask", "b_mask"]:
file_info[mask] = i32(read(4)) file_info[mask] = i32(read(4))
file_info['rgb_mask'] = (file_info['r_mask'], file_info["rgb_mask"] = (
file_info['g_mask'], file_info["r_mask"],
file_info['b_mask']) file_info["g_mask"],
file_info['rgba_mask'] = (file_info['r_mask'], file_info["b_mask"],
file_info['g_mask'], )
file_info['b_mask'], file_info["rgba_mask"] = (
file_info['a_mask']) file_info["r_mask"],
file_info["g_mask"],
file_info["b_mask"],
file_info["a_mask"],
)
else: else:
raise IOError("Unsupported BMP header type (%d)" % raise IOError("Unsupported BMP header type (%d)" % file_info["header_size"])
file_info['header_size'])
# ------------------ Special case : header is reported 40, which # ------------------ Special case : header is reported 40, which
# ---------------------- is shorter than real size for bpp >= 16 # ---------------------- is shorter than real size for bpp >= 16
self._size = file_info['width'], file_info['height'] self._size = file_info["width"], file_info["height"]
# ------- If color count was not found in the header, compute from bits # ------- If color count was not found in the header, compute from bits
file_info["colors"] = (file_info["colors"] file_info["colors"] = (
if file_info.get("colors", 0) file_info["colors"]
else (1 << file_info["bits"])) if file_info.get("colors", 0)
else (1 << file_info["bits"])
)
# ------------------------------- Check abnormal values for DOS attacks # ------------------------------- Check abnormal values for DOS attacks
if file_info['width'] * file_info['height'] > 2**31: if file_info["width"] * file_info["height"] > 2 ** 31:
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
# ---------------------- Check bit depth for unusual unsupported values # ---------------------- Check bit depth for unusual unsupported values
self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
if self.mode is None: if self.mode is None:
raise IOError("Unsupported BMP pixel depth (%d)" raise IOError("Unsupported BMP pixel depth (%d)" % file_info["bits"])
% file_info['bits'])
# ---------------- Process BMP with Bitfields compression (not palette) # ---------------- Process BMP with Bitfields compression (not palette)
if file_info['compression'] == self.BITFIELDS: if file_info["compression"] == self.BITFIELDS:
SUPPORTED = { SUPPORTED = {
32: [(0xff0000, 0xff00, 0xff, 0x0), 32: [
(0xff0000, 0xff00, 0xff, 0xff000000), (0xFF0000, 0xFF00, 0xFF, 0x0),
(0xff, 0xff00, 0xff0000, 0xff000000), (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
(0x0, 0x0, 0x0, 0x0), (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
(0xff000000, 0xff0000, 0xff00, 0x0)], (0x0, 0x0, 0x0, 0x0),
24: [(0xff0000, 0xff00, 0xff)], (0xFF000000, 0xFF0000, 0xFF00, 0x0),
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] ],
24: [(0xFF0000, 0xFF00, 0xFF)],
16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
} }
MASK_MODES = { MASK_MODES = {
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
(32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR", (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
(32, (0xff, 0xff00, 0xff0000, 0xff000000)): "RGBA", (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
(24, (0xff0000, 0xff00, 0xff)): "BGR", (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
(16, (0x7c00, 0x3e0, 0x1f)): "BGR;15" (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
} }
if file_info['bits'] in SUPPORTED: if file_info["bits"] in SUPPORTED:
if file_info['bits'] == 32 and \ if (
file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: file_info["bits"] == 32
raw_mode = MASK_MODES[ and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
(file_info["bits"], file_info["rgba_mask"]) ):
] raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
self.mode = "RGBA" if "A" in raw_mode else self.mode self.mode = "RGBA" if "A" in raw_mode else self.mode
elif (file_info['bits'] in (24, 16) and elif (
file_info['rgb_mask'] in SUPPORTED[file_info['bits']]): file_info["bits"] in (24, 16)
raw_mode = MASK_MODES[ and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
(file_info['bits'], file_info['rgb_mask']) ):
] raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
else: else:
raise IOError("Unsupported BMP bitfields layout") raise IOError("Unsupported BMP bitfields layout")
else: else:
raise IOError("Unsupported BMP bitfields layout") raise IOError("Unsupported BMP bitfields layout")
elif file_info['compression'] == self.RAW: elif file_info["compression"] == self.RAW:
if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self.mode = "BGRA", "RGBA" raw_mode, self.mode = "BGRA", "RGBA"
else: else:
raise IOError("Unsupported BMP compression (%d)" % raise IOError("Unsupported BMP compression (%d)" % file_info["compression"])
file_info['compression'])
# --------------- Once the header is processed, process the palette/LUT # --------------- Once the header is processed, process the palette/LUT
if self.mode == "P": # Paletted for 1, 4 and 8 bit images if self.mode == "P": # Paletted for 1, 4 and 8 bit images
# ---------------------------------------------------- 1-bit images # ---------------------------------------------------- 1-bit images
if not (0 < file_info['colors'] <= 65536): if not (0 < file_info["colors"] <= 65536):
raise IOError("Unsupported BMP Palette size (%d)" % raise IOError("Unsupported BMP Palette size (%d)" % file_info["colors"])
file_info['colors'])
else: else:
padding = file_info['palette_padding'] padding = file_info["palette_padding"]
palette = read(padding * file_info['colors']) palette = read(padding * file_info["colors"])
greyscale = True greyscale = True
indices = (0, 255) if file_info['colors'] == 2 else \ indices = (
list(range(file_info['colors'])) (0, 255)
if file_info["colors"] == 2
else list(range(file_info["colors"]))
)
# ----------------- Check if greyscale and ignore palette if so # ----------------- Check if greyscale and ignore palette if so
for ind, val in enumerate(indices): for ind, val in enumerate(indices):
rgb = palette[ind*padding:ind*padding + 3] rgb = palette[ind * padding : ind * padding + 3]
if rgb != o8(val) * 3: if rgb != o8(val) * 3:
greyscale = False greyscale = False
# ------- If all colors are grey, white or black, ditch palette # ------- If all colors are grey, white or black, ditch palette
if greyscale: if greyscale:
self.mode = "1" if file_info['colors'] == 2 else "L" self.mode = "1" if file_info["colors"] == 2 else "L"
raw_mode = self.mode raw_mode = self.mode
else: else:
self.mode = "P" self.mode = "P"
self.palette = ImagePalette.raw( self.palette = ImagePalette.raw(
"BGRX" if padding == 4 else "BGR", palette) "BGRX" if padding == 4 else "BGR", palette
)
# ---------------------------- Finally set the tile data for the plugin # ---------------------------- Finally set the tile data for the plugin
self.info['compression'] = file_info['compression'] self.info["compression"] = file_info["compression"]
self.tile = [ self.tile = [
('raw', (
(0, 0, file_info['width'], file_info['height']), "raw",
offset or self.fp.tell(), (0, 0, file_info["width"], file_info["height"]),
(raw_mode, offset or self.fp.tell(),
((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), (
file_info['direction'])) raw_mode,
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
file_info["direction"],
),
)
] ]
def _open(self): def _open(self):
@ -280,6 +285,7 @@ class DibImageFile(BmpImageFile):
def _open(self): def _open(self):
self._bitmap() self._bitmap()
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write BMP file # Write BMP file
@ -311,31 +317,36 @@ def _save(im, fp, filename, bitmap_header=True):
# 1 meter == 39.3701 inches # 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi)) ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
stride = ((im.size[0]*bits+7)//8+3) & (~3) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
header = 40 # or 64 for OS/2 version 2 header = 40 # or 64 for OS/2 version 2
image = stride * im.size[1] image = stride * im.size[1]
# bitmap header # bitmap header
if bitmap_header: if bitmap_header:
offset = 14 + header + colors * 4 offset = 14 + header + colors * 4
fp.write(b"BM" + # file type (magic) fp.write(
o32(offset+image) + # file size b"BM"
o32(0) + # reserved + o32(offset + image) # file type (magic)
o32(offset)) # image data offset + o32(0) # file size
+ o32(offset) # reserved
) # image data offset
# bitmap info header # bitmap info header
fp.write(o32(header) + # info header size fp.write(
o32(im.size[0]) + # width o32(header) # info header size
o32(im.size[1]) + # height + o32(im.size[0]) # width
o16(1) + # planes + o32(im.size[1]) # height
o16(bits) + # depth + o16(1) # planes
o32(0) + # compression (0=uncompressed) + o16(bits) # depth
o32(image) + # size of bitmap + o32(0) # compression (0=uncompressed)
o32(ppm[0]) + o32(ppm[1]) + # resolution + o32(image) # size of bitmap
o32(colors) + # colors used + o32(ppm[0]) # resolution
o32(colors)) # colors important + o32(ppm[1]) # resolution
+ o32(colors) # colors used
+ o32(colors) # colors important
)
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
if im.mode == "1": if im.mode == "1":
for i in (0, 255): for i in (0, 255):
@ -346,8 +357,8 @@ def _save(im, fp, filename, bitmap_header=True):
elif im.mode == "P": elif im.mode == "P":
fp.write(im.im.getpalette("RGB", "BGRX")) fp.write(im.im.getpalette("RGB", "BGRX"))
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
(rawmode, stride, -1))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -27,6 +27,7 @@ def register_handler(handler):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC" return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"

View File

@ -22,7 +22,6 @@ import io
class ContainerIO(object): class ContainerIO(object):
def __init__(self, file, offset, length): def __init__(self, file, offset, length):
""" """
Create file object. Create file object.

View File

@ -36,6 +36,7 @@ def _accept(prefix):
## ##
# Image plugin for Windows Cursor files. # Image plugin for Windows Cursor files.
class CurImageFile(BmpImagePlugin.BmpImageFile): class CurImageFile(BmpImagePlugin.BmpImageFile):
format = "CUR" format = "CUR"
@ -65,9 +66,9 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
self._bitmap(i32(m[12:]) + offset) self._bitmap(i32(m[12:]) + offset)
# patch up the bitmap height # patch up the bitmap height
self._size = self.size[0], self.size[1]//2 self._size = self.size[0], self.size[1] // 2
d, e, o, a = self.tile[0] d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0)+self.size, o, a self.tile[0] = d, (0, 0) + self.size, o, a
return return

View File

@ -39,6 +39,7 @@ def _accept(prefix):
## ##
# Image plugin for the Intel DCX format. # Image plugin for the Intel DCX format.
class DcxImageFile(PcxImageFile): class DcxImageFile(PcxImageFile):
format = "DCX" format = "DCX"

View File

@ -61,8 +61,7 @@ DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
DDS_ALPHA = DDPF_ALPHA DDS_ALPHA = DDPF_ALPHA
DDS_PAL8 = DDPF_PALETTEINDEXED8 DDS_PAL8 = DDPF_PALETTEINDEXED8
DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
DDSD_PIXELFORMAT)
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
@ -130,8 +129,8 @@ class DdsImageFile(ImageFile.ImageFile):
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
rawmode = "" rawmode = ""
if bitcount == 32: if bitcount == 32:
rawmode += masks[0xff000000] rawmode += masks[0xFF000000]
rawmode += masks[0xff0000] + masks[0xff00] + masks[0xff] rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))] self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))]
else: else:
@ -151,24 +150,21 @@ class DdsImageFile(ImageFile.ImageFile):
# ignoring flags which pertain to volume textures and cubemaps # ignoring flags which pertain to volume textures and cubemaps
dxt10 = BytesIO(self.fp.read(20)) dxt10 = BytesIO(self.fp.read(20))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8)) dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7" self.pixel_format = "BC7"
n = 7 n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB: elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
self.pixel_format = "BC7" self.pixel_format = "BC7"
self.im_info["gamma"] = 1/2.2 self.im_info["gamma"] = 1 / 2.2
n = 7 n = 7
else: else:
raise NotImplementedError("Unimplemented DXGI format %d" % raise NotImplementedError(
(dxgi_format)) "Unimplemented DXGI format %d" % (dxgi_format)
)
else: else:
raise NotImplementedError("Unimplemented pixel format %r" % raise NotImplementedError("Unimplemented pixel format %r" % (fourcc))
(fourcc))
self.tile = [ self.tile = [("bcn", (0, 0) + self.size, data_start, (n))]
("bcn", (0, 0) + self.size, data_start, (n))
]
def load_seek(self, pos): def load_seek(self, pos):
pass pass

View File

@ -38,15 +38,17 @@ split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
gs_windows_binary = None gs_windows_binary = None
if sys.platform.startswith('win'): if sys.platform.startswith("win"):
import shutil import shutil
if hasattr(shutil, 'which'):
if hasattr(shutil, "which"):
which = shutil.which which = shutil.which
else: else:
# Python 2 # Python 2
import distutils.spawn import distutils.spawn
which = distutils.spawn.find_executable which = distutils.spawn.find_executable
for binary in ('gswin32c', 'gswin64c', 'gs'): for binary in ("gswin32c", "gswin64c", "gs"):
if which(binary) is not None: if which(binary) is not None:
gs_windows_binary = binary gs_windows_binary = binary
break break
@ -57,11 +59,12 @@ if sys.platform.startswith('win'):
def has_ghostscript(): def has_ghostscript():
if gs_windows_binary: if gs_windows_binary:
return True return True
if not sys.platform.startswith('win'): if not sys.platform.startswith("win"):
import subprocess import subprocess
try: try:
with open(os.devnull, 'wb') as devnull: with open(os.devnull, "wb") as devnull:
subprocess.check_call(['gs', '--version'], stdout=devnull) subprocess.check_call(["gs", "--version"], stdout=devnull)
return True return True
except OSError: except OSError:
# No Ghostscript # No Ghostscript
@ -82,8 +85,10 @@ def Ghostscript(tile, size, fp, scale=1):
# orig_bbox = bbox # orig_bbox = bbox
size = (size[0] * scale, size[1] * scale) size = (size[0] * scale, size[1] * scale)
# resolution is dependent on bbox and size # resolution is dependent on bbox and size
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])), res = (
float((72.0 * size[1]) / (bbox[3]-bbox[1]))) float((72.0 * size[0]) / (bbox[2] - bbox[0])),
float((72.0 * size[1]) / (bbox[3] - bbox[1])),
)
import subprocess import subprocess
import tempfile import tempfile
@ -92,7 +97,7 @@ def Ghostscript(tile, size, fp, scale=1):
os.close(out_fd) os.close(out_fd)
infile_temp = None infile_temp = None
if hasattr(fp, 'name') and os.path.exists(fp.name): if hasattr(fp, "name") and os.path.exists(fp.name):
infile = fp.name infile = fp.name
else: else:
in_fd, infile_temp = tempfile.mkstemp() in_fd, infile_temp = tempfile.mkstemp()
@ -102,7 +107,7 @@ def Ghostscript(tile, size, fp, scale=1):
# Ignore length and offset! # Ignore length and offset!
# Ghostscript can read it # Ghostscript can read it
# Copy whole file to read in Ghostscript # Copy whole file to read in Ghostscript
with open(infile_temp, 'wb') as f: with open(infile_temp, "wb") as f:
# fetch length of fp # fetch length of fp
fp.seek(0, io.SEEK_END) fp.seek(0, io.SEEK_END)
fsize = fp.tell() fsize = fp.tell()
@ -111,38 +116,42 @@ def Ghostscript(tile, size, fp, scale=1):
fp.seek(0) fp.seek(0)
lengthfile = fsize lengthfile = fsize
while lengthfile > 0: while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024)) s = fp.read(min(lengthfile, 100 * 1024))
if not s: if not s:
break break
lengthfile -= len(s) lengthfile -= len(s)
f.write(s) f.write(s)
# Build Ghostscript command # Build Ghostscript command
command = ["gs", command = [
"-q", # quiet mode "gs",
"-g%dx%d" % size, # set output geometry (pixels) "-q", # quiet mode
"-r%fx%f" % res, # set input DPI (dots per inch) "-g%dx%d" % size, # set output geometry (pixels)
"-dBATCH", # exit after processing "-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE", # don't pause between pages "-dBATCH", # exit after processing
"-dSAFER", # safe mode "-dNOPAUSE", # don't pause between pages
"-sDEVICE=ppmraw", # ppm driver "-dSAFER", # safe mode
"-sOutputFile=%s" % outfile, # output file "-sDEVICE=ppmraw", # ppm driver
# adjust for image origin "-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]), # adjust for image origin
"-f", infile, # input file "-c",
# showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) "%d %d translate" % (-bbox[0], -bbox[1]),
"-c", "showpage", "-f",
] infile, # input file
# showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
"-c",
"showpage",
]
if gs_windows_binary is not None: if gs_windows_binary is not None:
if not gs_windows_binary: if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths') raise WindowsError("Unable to locate Ghostscript on paths")
command[0] = gs_windows_binary command[0] = gs_windows_binary
# push data through Ghostscript # push data through Ghostscript
try: try:
startupinfo = None startupinfo = None
if sys.platform.startswith('win'): if sys.platform.startswith("win"):
startupinfo = subprocess.STARTUPINFO() startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.check_call(command, startupinfo=startupinfo) subprocess.check_call(command, startupinfo=startupinfo)
@ -163,6 +172,7 @@ class PSFile(object):
""" """
Wrapper for bytesio object that treats either CR or LF as end of line. Wrapper for bytesio object that treats either CR or LF as end of line.
""" """
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
self.char = None self.char = None
@ -185,12 +195,12 @@ class PSFile(object):
if self.char in b"\r\n": if self.char in b"\r\n":
self.char = None self.char = None
return s.decode('latin-1') return s.decode("latin-1")
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"%!PS" or \ return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
(len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
## ##
# Image plugin for Encapsulated Postscript. This plugin supports only # Image plugin for Encapsulated Postscript. This plugin supports only
@ -224,7 +234,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Load EPS header # Load EPS header
s_raw = fp.readline() s_raw = fp.readline()
s = s_raw.strip('\r\n') s = s_raw.strip("\r\n")
while s_raw: while s_raw:
if s: if s:
@ -246,8 +256,9 @@ class EpsImageFile(ImageFile.ImageFile):
# put floating point values there anyway. # put floating point values there anyway.
box = [int(float(i)) for i in v.split()] box = [int(float(i)) for i in v.split()]
self._size = box[2] - box[0], box[3] - box[1] self._size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0, 0) + self.size, offset, self.tile = [
(length, box))] ("eps", (0, 0) + self.size, offset, (length, box))
]
except Exception: except Exception:
pass pass
@ -262,7 +273,7 @@ class EpsImageFile(ImageFile.ImageFile):
self.info[k[:8]] = k[9:] self.info[k[:8]] = k[9:]
else: else:
self.info[k] = "" self.info[k] = ""
elif s[0] == '%': elif s[0] == "%":
# handle non-DSC Postscript comments that some # handle non-DSC Postscript comments that some
# tools mistakenly put in the Comments section # tools mistakenly put in the Comments section
pass pass
@ -270,7 +281,7 @@ class EpsImageFile(ImageFile.ImageFile):
raise IOError("bad EPS header") raise IOError("bad EPS header")
s_raw = fp.readline() s_raw = fp.readline()
s = s_raw.strip('\r\n') s = s_raw.strip("\r\n")
if s and s[:1] != "%": if s and s[:1] != "%":
break break
@ -297,7 +308,7 @@ class EpsImageFile(ImageFile.ImageFile):
self._size = int(x), int(y) self._size = int(x), int(y)
return return
s = fp.readline().strip('\r\n') s = fp.readline().strip("\r\n")
if not s: if not s:
break break
@ -344,6 +355,7 @@ class EpsImageFile(ImageFile.ImageFile):
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _save(im, fp, filename, eps=1): def _save(im, fp, filename, eps=1):
"""EPS Writer for the Python Imaging Library.""" """EPS Writer for the Python Imaging Library."""
@ -366,7 +378,7 @@ def _save(im, fp, filename, eps=1):
wrapped_fp = False wrapped_fp = False
if fp != sys.stdout: if fp != sys.stdout:
if sys.version_info.major > 2: if sys.version_info.major > 2:
fp = io.TextIOWrapper(fp, encoding='latin-1') fp = io.TextIOWrapper(fp, encoding="latin-1")
wrapped_fp = True wrapped_fp = True
try: try:
@ -381,7 +393,7 @@ def _save(im, fp, filename, eps=1):
fp.write("%%EndComments\n") fp.write("%%EndComments\n")
fp.write("%%Page: 1 1\n") fp.write("%%Page: 1 1\n")
fp.write("%%ImageData: %d %d " % im.size) fp.write("%%ImageData: %d %d " % im.size)
fp.write("%d %d 0 1 1 \"%s\"\n" % operator) fp.write('%d %d 0 1 1 "%s"\n' % operator)
# #
# image header # image header
@ -396,7 +408,7 @@ def _save(im, fp, filename, eps=1):
if hasattr(fp, "flush"): if hasattr(fp, "flush"):
fp.flush() fp.flush()
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)]) ImageFile._save(im, base_fp, [("eps", (0, 0) + im.size, 0, None)])
fp.write("\n%%%%EndBinary\n") fp.write("\n%%%%EndBinary\n")
fp.write("grestore end\n") fp.write("grestore end\n")
@ -406,6 +418,7 @@ def _save(im, fp, filename, eps=1):
if wrapped_fp: if wrapped_fp:
fp.detach() fp.detach()
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -18,11 +18,10 @@
# Maps EXIF tags to tag names. # Maps EXIF tags to tag names.
TAGS = { TAGS = {
# possibly incomplete # possibly incomplete
0x000b: "ProcessingSoftware", 0x000B: "ProcessingSoftware",
0x00fe: "NewSubfileType", 0x00FE: "NewSubfileType",
0x00ff: "SubfileType", 0x00FF: "SubfileType",
0x0100: "ImageWidth", 0x0100: "ImageWidth",
0x0101: "ImageLength", 0x0101: "ImageLength",
0x0102: "BitsPerSample", 0x0102: "BitsPerSample",
@ -31,10 +30,10 @@ TAGS = {
0x0107: "Thresholding", 0x0107: "Thresholding",
0x0108: "CellWidth", 0x0108: "CellWidth",
0x0109: "CellLength", 0x0109: "CellLength",
0x010a: "FillOrder", 0x010A: "FillOrder",
0x010d: "DocumentName", 0x010D: "DocumentName",
0x010e: "ImageDescription", 0x010E: "ImageDescription",
0x010f: "Make", 0x010F: "Make",
0x0110: "Model", 0x0110: "Model",
0x0111: "StripOffsets", 0x0111: "StripOffsets",
0x0112: "Orientation", 0x0112: "Orientation",
@ -43,10 +42,10 @@ TAGS = {
0x0117: "StripByteCounts", 0x0117: "StripByteCounts",
0x0118: "MinSampleValue", 0x0118: "MinSampleValue",
0x0119: "MaxSampleValue", 0x0119: "MaxSampleValue",
0x011a: "XResolution", 0x011A: "XResolution",
0x011b: "YResolution", 0x011B: "YResolution",
0x011c: "PlanarConfiguration", 0x011C: "PlanarConfiguration",
0x011d: "PageName", 0x011D: "PageName",
0x0120: "FreeOffsets", 0x0120: "FreeOffsets",
0x0121: "FreeByteCounts", 0x0121: "FreeByteCounts",
0x0122: "GrayResponseUnit", 0x0122: "GrayResponseUnit",
@ -55,24 +54,24 @@ TAGS = {
0x0125: "T6Options", 0x0125: "T6Options",
0x0128: "ResolutionUnit", 0x0128: "ResolutionUnit",
0x0129: "PageNumber", 0x0129: "PageNumber",
0x012d: "TransferFunction", 0x012D: "TransferFunction",
0x0131: "Software", 0x0131: "Software",
0x0132: "DateTime", 0x0132: "DateTime",
0x013b: "Artist", 0x013B: "Artist",
0x013c: "HostComputer", 0x013C: "HostComputer",
0x013d: "Predictor", 0x013D: "Predictor",
0x013e: "WhitePoint", 0x013E: "WhitePoint",
0x013f: "PrimaryChromaticities", 0x013F: "PrimaryChromaticities",
0x0140: "ColorMap", 0x0140: "ColorMap",
0x0141: "HalftoneHints", 0x0141: "HalftoneHints",
0x0142: "TileWidth", 0x0142: "TileWidth",
0x0143: "TileLength", 0x0143: "TileLength",
0x0144: "TileOffsets", 0x0144: "TileOffsets",
0x0145: "TileByteCounts", 0x0145: "TileByteCounts",
0x014a: "SubIFDs", 0x014A: "SubIFDs",
0x014c: "InkSet", 0x014C: "InkSet",
0x014d: "InkNames", 0x014D: "InkNames",
0x014e: "NumberOfInks", 0x014E: "NumberOfInks",
0x0150: "DotRange", 0x0150: "DotRange",
0x0151: "TargetPrinter", 0x0151: "TargetPrinter",
0x0152: "ExtraSamples", 0x0152: "ExtraSamples",
@ -83,9 +82,9 @@ TAGS = {
0x0157: "ClipPath", 0x0157: "ClipPath",
0x0158: "XClipPathUnits", 0x0158: "XClipPathUnits",
0x0159: "YClipPathUnits", 0x0159: "YClipPathUnits",
0x015a: "Indexed", 0x015A: "Indexed",
0x015b: "JPEGTables", 0x015B: "JPEGTables",
0x015f: "OPIProxy", 0x015F: "OPIProxy",
0x0200: "JPEGProc", 0x0200: "JPEGProc",
0x0201: "JpegIFOffset", 0x0201: "JpegIFOffset",
0x0202: "JpegIFByteCount", 0x0202: "JpegIFByteCount",
@ -99,20 +98,20 @@ TAGS = {
0x0212: "YCbCrSubSampling", 0x0212: "YCbCrSubSampling",
0x0213: "YCbCrPositioning", 0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite", 0x0214: "ReferenceBlackWhite",
0x02bc: "XMLPacket", 0x02BC: "XMLPacket",
0x1000: "RelatedImageFileFormat", 0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageWidth", 0x1001: "RelatedImageWidth",
0x1002: "RelatedImageLength", 0x1002: "RelatedImageLength",
0x4746: "Rating", 0x4746: "Rating",
0x4749: "RatingPercent", 0x4749: "RatingPercent",
0x800d: "ImageID", 0x800D: "ImageID",
0x828d: "CFARepeatPatternDim", 0x828D: "CFARepeatPatternDim",
0x828e: "CFAPattern", 0x828E: "CFAPattern",
0x828f: "BatteryLevel", 0x828F: "BatteryLevel",
0x8298: "Copyright", 0x8298: "Copyright",
0x829a: "ExposureTime", 0x829A: "ExposureTime",
0x829d: "FNumber", 0x829D: "FNumber",
0x83bb: "IPTCNAA", 0x83BB: "IPTCNAA",
0x8649: "ImageResources", 0x8649: "ImageResources",
0x8769: "ExifOffset", 0x8769: "ExifOffset",
0x8773: "InterColorProfile", 0x8773: "InterColorProfile",
@ -122,8 +121,8 @@ TAGS = {
0x8827: "ISOSpeedRatings", 0x8827: "ISOSpeedRatings",
0x8828: "OECF", 0x8828: "OECF",
0x8829: "Interlace", 0x8829: "Interlace",
0x882a: "TimeZoneOffset", 0x882A: "TimeZoneOffset",
0x882b: "SelfTimerMode", 0x882B: "SelfTimerMode",
0x9000: "ExifVersion", 0x9000: "ExifVersion",
0x9003: "DateTimeOriginal", 0x9003: "DateTimeOriginal",
0x9004: "DateTimeDigitized", 0x9004: "DateTimeDigitized",
@ -138,142 +137,142 @@ TAGS = {
0x9207: "MeteringMode", 0x9207: "MeteringMode",
0x9208: "LightSource", 0x9208: "LightSource",
0x9209: "Flash", 0x9209: "Flash",
0x920a: "FocalLength", 0x920A: "FocalLength",
0x920b: "FlashEnergy", 0x920B: "FlashEnergy",
0x920c: "SpatialFrequencyResponse", 0x920C: "SpatialFrequencyResponse",
0x920d: "Noise", 0x920D: "Noise",
0x9211: "ImageNumber", 0x9211: "ImageNumber",
0x9212: "SecurityClassification", 0x9212: "SecurityClassification",
0x9213: "ImageHistory", 0x9213: "ImageHistory",
0x9214: "SubjectLocation", 0x9214: "SubjectLocation",
0x9215: "ExposureIndex", 0x9215: "ExposureIndex",
0x9216: "TIFF/EPStandardID", 0x9216: "TIFF/EPStandardID",
0x927c: "MakerNote", 0x927C: "MakerNote",
0x9286: "UserComment", 0x9286: "UserComment",
0x9290: "SubsecTime", 0x9290: "SubsecTime",
0x9291: "SubsecTimeOriginal", 0x9291: "SubsecTimeOriginal",
0x9292: "SubsecTimeDigitized", 0x9292: "SubsecTimeDigitized",
0x9c9b: "XPTitle", 0x9C9B: "XPTitle",
0x9c9c: "XPComment", 0x9C9C: "XPComment",
0x9c9d: "XPAuthor", 0x9C9D: "XPAuthor",
0x9c9e: "XPKeywords", 0x9C9E: "XPKeywords",
0x9c9f: "XPSubject", 0x9C9F: "XPSubject",
0xa000: "FlashPixVersion", 0xA000: "FlashPixVersion",
0xa001: "ColorSpace", 0xA001: "ColorSpace",
0xa002: "ExifImageWidth", 0xA002: "ExifImageWidth",
0xa003: "ExifImageHeight", 0xA003: "ExifImageHeight",
0xa004: "RelatedSoundFile", 0xA004: "RelatedSoundFile",
0xa005: "ExifInteroperabilityOffset", 0xA005: "ExifInteroperabilityOffset",
0xa20b: "FlashEnergy", 0xA20B: "FlashEnergy",
0xa20c: "SpatialFrequencyResponse", 0xA20C: "SpatialFrequencyResponse",
0xa20e: "FocalPlaneXResolution", 0xA20E: "FocalPlaneXResolution",
0xa20f: "FocalPlaneYResolution", 0xA20F: "FocalPlaneYResolution",
0xa210: "FocalPlaneResolutionUnit", 0xA210: "FocalPlaneResolutionUnit",
0xa214: "SubjectLocation", 0xA214: "SubjectLocation",
0xa215: "ExposureIndex", 0xA215: "ExposureIndex",
0xa217: "SensingMethod", 0xA217: "SensingMethod",
0xa300: "FileSource", 0xA300: "FileSource",
0xa301: "SceneType", 0xA301: "SceneType",
0xa302: "CFAPattern", 0xA302: "CFAPattern",
0xa401: "CustomRendered", 0xA401: "CustomRendered",
0xa402: "ExposureMode", 0xA402: "ExposureMode",
0xa403: "WhiteBalance", 0xA403: "WhiteBalance",
0xa404: "DigitalZoomRatio", 0xA404: "DigitalZoomRatio",
0xa405: "FocalLengthIn35mmFilm", 0xA405: "FocalLengthIn35mmFilm",
0xa406: "SceneCaptureType", 0xA406: "SceneCaptureType",
0xa407: "GainControl", 0xA407: "GainControl",
0xa408: "Contrast", 0xA408: "Contrast",
0xa409: "Saturation", 0xA409: "Saturation",
0xa40a: "Sharpness", 0xA40A: "Sharpness",
0xa40b: "DeviceSettingDescription", 0xA40B: "DeviceSettingDescription",
0xa40c: "SubjectDistanceRange", 0xA40C: "SubjectDistanceRange",
0xa420: "ImageUniqueID", 0xA420: "ImageUniqueID",
0xa430: "CameraOwnerName", 0xA430: "CameraOwnerName",
0xa431: "BodySerialNumber", 0xA431: "BodySerialNumber",
0xa432: "LensSpecification", 0xA432: "LensSpecification",
0xa433: "LensMake", 0xA433: "LensMake",
0xa434: "LensModel", 0xA434: "LensModel",
0xa435: "LensSerialNumber", 0xA435: "LensSerialNumber",
0xa500: "Gamma", 0xA500: "Gamma",
0xc4a5: "PrintImageMatching", 0xC4A5: "PrintImageMatching",
0xc612: "DNGVersion", 0xC612: "DNGVersion",
0xc613: "DNGBackwardVersion", 0xC613: "DNGBackwardVersion",
0xc614: "UniqueCameraModel", 0xC614: "UniqueCameraModel",
0xc615: "LocalizedCameraModel", 0xC615: "LocalizedCameraModel",
0xc616: "CFAPlaneColor", 0xC616: "CFAPlaneColor",
0xc617: "CFALayout", 0xC617: "CFALayout",
0xc618: "LinearizationTable", 0xC618: "LinearizationTable",
0xc619: "BlackLevelRepeatDim", 0xC619: "BlackLevelRepeatDim",
0xc61a: "BlackLevel", 0xC61A: "BlackLevel",
0xc61b: "BlackLevelDeltaH", 0xC61B: "BlackLevelDeltaH",
0xc61c: "BlackLevelDeltaV", 0xC61C: "BlackLevelDeltaV",
0xc61d: "WhiteLevel", 0xC61D: "WhiteLevel",
0xc61e: "DefaultScale", 0xC61E: "DefaultScale",
0xc61f: "DefaultCropOrigin", 0xC61F: "DefaultCropOrigin",
0xc620: "DefaultCropSize", 0xC620: "DefaultCropSize",
0xc621: "ColorMatrix1", 0xC621: "ColorMatrix1",
0xc622: "ColorMatrix2", 0xC622: "ColorMatrix2",
0xc623: "CameraCalibration1", 0xC623: "CameraCalibration1",
0xc624: "CameraCalibration2", 0xC624: "CameraCalibration2",
0xc625: "ReductionMatrix1", 0xC625: "ReductionMatrix1",
0xc626: "ReductionMatrix2", 0xC626: "ReductionMatrix2",
0xc627: "AnalogBalance", 0xC627: "AnalogBalance",
0xc628: "AsShotNeutral", 0xC628: "AsShotNeutral",
0xc629: "AsShotWhiteXY", 0xC629: "AsShotWhiteXY",
0xc62a: "BaselineExposure", 0xC62A: "BaselineExposure",
0xc62b: "BaselineNoise", 0xC62B: "BaselineNoise",
0xc62c: "BaselineSharpness", 0xC62C: "BaselineSharpness",
0xc62d: "BayerGreenSplit", 0xC62D: "BayerGreenSplit",
0xc62e: "LinearResponseLimit", 0xC62E: "LinearResponseLimit",
0xc62f: "CameraSerialNumber", 0xC62F: "CameraSerialNumber",
0xc630: "LensInfo", 0xC630: "LensInfo",
0xc631: "ChromaBlurRadius", 0xC631: "ChromaBlurRadius",
0xc632: "AntiAliasStrength", 0xC632: "AntiAliasStrength",
0xc633: "ShadowScale", 0xC633: "ShadowScale",
0xc634: "DNGPrivateData", 0xC634: "DNGPrivateData",
0xc635: "MakerNoteSafety", 0xC635: "MakerNoteSafety",
0xc65a: "CalibrationIlluminant1", 0xC65A: "CalibrationIlluminant1",
0xc65b: "CalibrationIlluminant2", 0xC65B: "CalibrationIlluminant2",
0xc65c: "BestQualityScale", 0xC65C: "BestQualityScale",
0xc65d: "RawDataUniqueID", 0xC65D: "RawDataUniqueID",
0xc68b: "OriginalRawFileName", 0xC68B: "OriginalRawFileName",
0xc68c: "OriginalRawFileData", 0xC68C: "OriginalRawFileData",
0xc68d: "ActiveArea", 0xC68D: "ActiveArea",
0xc68e: "MaskedAreas", 0xC68E: "MaskedAreas",
0xc68f: "AsShotICCProfile", 0xC68F: "AsShotICCProfile",
0xc690: "AsShotPreProfileMatrix", 0xC690: "AsShotPreProfileMatrix",
0xc691: "CurrentICCProfile", 0xC691: "CurrentICCProfile",
0xc692: "CurrentPreProfileMatrix", 0xC692: "CurrentPreProfileMatrix",
0xc6bf: "ColorimetricReference", 0xC6BF: "ColorimetricReference",
0xc6f3: "CameraCalibrationSignature", 0xC6F3: "CameraCalibrationSignature",
0xc6f4: "ProfileCalibrationSignature", 0xC6F4: "ProfileCalibrationSignature",
0xc6f6: "AsShotProfileName", 0xC6F6: "AsShotProfileName",
0xc6f7: "NoiseReductionApplied", 0xC6F7: "NoiseReductionApplied",
0xc6f8: "ProfileName", 0xC6F8: "ProfileName",
0xc6f9: "ProfileHueSatMapDims", 0xC6F9: "ProfileHueSatMapDims",
0xc6fa: "ProfileHueSatMapData1", 0xC6FA: "ProfileHueSatMapData1",
0xc6fb: "ProfileHueSatMapData2", 0xC6FB: "ProfileHueSatMapData2",
0xc6fc: "ProfileToneCurve", 0xC6FC: "ProfileToneCurve",
0xc6fd: "ProfileEmbedPolicy", 0xC6FD: "ProfileEmbedPolicy",
0xc6fe: "ProfileCopyright", 0xC6FE: "ProfileCopyright",
0xc714: "ForwardMatrix1", 0xC714: "ForwardMatrix1",
0xc715: "ForwardMatrix2", 0xC715: "ForwardMatrix2",
0xc716: "PreviewApplicationName", 0xC716: "PreviewApplicationName",
0xc717: "PreviewApplicationVersion", 0xC717: "PreviewApplicationVersion",
0xc718: "PreviewSettingsName", 0xC718: "PreviewSettingsName",
0xc719: "PreviewSettingsDigest", 0xC719: "PreviewSettingsDigest",
0xc71a: "PreviewColorSpace", 0xC71A: "PreviewColorSpace",
0xc71b: "PreviewDateTime", 0xC71B: "PreviewDateTime",
0xc71c: "RawImageDigest", 0xC71C: "RawImageDigest",
0xc71d: "OriginalRawFileDigest", 0xC71D: "OriginalRawFileDigest",
0xc71e: "SubTileBlockSize", 0xC71E: "SubTileBlockSize",
0xc71f: "RowInterleaveFactor", 0xC71F: "RowInterleaveFactor",
0xc725: "ProfileLookTableDims", 0xC725: "ProfileLookTableDims",
0xc726: "ProfileLookTableData", 0xC726: "ProfileLookTableData",
0xc740: "OpcodeList1", 0xC740: "OpcodeList1",
0xc741: "OpcodeList2", 0xC741: "OpcodeList2",
0xc74e: "OpcodeList3", 0xC74E: "OpcodeList3",
0xc761: "NoiseProfile" 0xC761: "NoiseProfile",
} }
## ##

View File

@ -23,6 +23,7 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter

View File

@ -27,6 +27,7 @@ __version__ = "0.2"
# #
# decoder # decoder
def _accept(prefix): def _accept(prefix):
return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12] return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12]
@ -35,6 +36,7 @@ def _accept(prefix):
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b> # Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
# method to load individual frames. # method to load individual frames.
class FliImageFile(ImageFile.ImageFile): class FliImageFile(ImageFile.ImageFile):
format = "FLI" format = "FLI"
@ -46,9 +48,11 @@ class FliImageFile(ImageFile.ImageFile):
# HEAD # HEAD
s = self.fp.read(128) s = self.fp.read(128)
magic = i16(s[4:6]) magic = i16(s[4:6])
if not (magic in [0xAF11, 0xAF12] and if not (
i16(s[14:16]) in [0, 3] and # flags magic in [0xAF11, 0xAF12]
s[20:22] == b"\x00\x00"): # reserved and i16(s[14:16]) in [0, 3] # flags
and s[20:22] == b"\x00\x00" # reserved
):
raise SyntaxError("not an FLI/FLC file") raise SyntaxError("not an FLI/FLC file")
# frames # frames
@ -84,7 +88,7 @@ class FliImageFile(ImageFile.ImageFile):
elif i16(s[4:6]) == 4: elif i16(s[4:6]) == 4:
self._palette(palette, 0) self._palette(palette, 0)
palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette] palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))
# set things up to decode first frame # set things up to decode first frame
@ -106,8 +110,8 @@ class FliImageFile(ImageFile.ImageFile):
s = self.fp.read(n * 3) s = self.fp.read(n * 3)
for n in range(0, len(s), 3): for n in range(0, len(s), 3):
r = i8(s[n]) << shift r = i8(s[n]) << shift
g = i8(s[n+1]) << shift g = i8(s[n + 1]) << shift
b = i8(s[n+2]) << shift b = i8(s[n + 2]) << shift
palette[i] = (r, g, b) palette[i] = (r, g, b)
i += 1 i += 1
@ -152,7 +156,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s) framesize = i32(s)
self.decodermaxblock = framesize self.decodermaxblock = framesize
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)] self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
self.__offset += framesize self.__offset += framesize

View File

@ -33,6 +33,7 @@ def puti16(fp, values):
## ##
# Base class for raster font file handlers. # Base class for raster font file handlers.
class FontFile(object): class FontFile(object):
bitmap = None bitmap = None
@ -61,7 +62,7 @@ class FontFile(object):
w = w + (src[2] - src[0]) w = w + (src[2] - src[0])
if w > WIDTH: if w > WIDTH:
lines += 1 lines += 1
w = (src[2] - src[0]) w = src[2] - src[0]
maxwidth = max(maxwidth, w) maxwidth = max(maxwidth, w)
xsize = maxwidth xsize = maxwidth
@ -103,7 +104,7 @@ class FontFile(object):
# font metrics # font metrics
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp: with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
fp.write(b"PILfont\n") fp.write(b"PILfont\n")
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!! fp.write((";;;;;;%d;\n" % self.ysize).encode("ascii")) # HACK!!!
fp.write(b"DATA\n") fp.write(b"DATA\n")
for id in range(256): for id in range(256):
m = self.metrics[id] m = self.metrics[id]

View File

@ -29,22 +29,23 @@ __version__ = "0.1"
# we map from colour field tuples to (mode, rawmode) descriptors # we map from colour field tuples to (mode, rawmode) descriptors
MODES = { MODES = {
# opacity # opacity
(0x00007ffe): ("A", "L"), (0x00007FFE): ("A", "L"),
# monochrome # monochrome
(0x00010000,): ("L", "L"), (0x00010000,): ("L", "L"),
(0x00018000, 0x00017ffe): ("RGBA", "LA"), (0x00018000, 0x00017FFE): ("RGBA", "LA"),
# photo YCC # photo YCC
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
(0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"), (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
# standard RGB (NIFRGB) # standard RGB (NIFRGB)
(0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"), (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"), (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
} }
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _accept(prefix): def _accept(prefix):
return prefix[:8] == olefile.MAGIC return prefix[:8] == olefile.MAGIC
@ -52,6 +53,7 @@ def _accept(prefix):
## ##
# Image plugin for the FlashPix images. # Image plugin for the FlashPix images.
class FpxImageFile(ImageFile.ImageFile): class FpxImageFile(ImageFile.ImageFile):
format = "FPX" format = "FPX"
@ -76,10 +78,9 @@ class FpxImageFile(ImageFile.ImageFile):
# #
# get the Image Contents Property Set # get the Image Contents Property Set
prop = self.ole.getproperties([ prop = self.ole.getproperties(
"Data Object Store %06d" % index, ["Data Object Store %06d" % index, "\005Image Contents"]
"\005Image Contents" )
])
# size (highest resolution) # size (highest resolution)
@ -105,7 +106,7 @@ class FpxImageFile(ImageFile.ImageFile):
colors = [] colors = []
for i in range(i32(s, 4)): for i in range(i32(s, 4)):
# note: for now, we ignore the "uncalibrated" flag # note: for now, we ignore the "uncalibrated" flag
colors.append(i32(s, 8+i*4) & 0x7fffffff) colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
self.mode, self.rawmode = MODES[tuple(colors)] self.mode, self.rawmode = MODES[tuple(colors)]
@ -125,7 +126,7 @@ class FpxImageFile(ImageFile.ImageFile):
stream = [ stream = [
"Data Object Store %06d" % index, "Data Object Store %06d" % index,
"Resolution %04d" % subimage, "Resolution %04d" % subimage,
"Subimage 0000 Header" "Subimage 0000 Header",
] ]
fp = self.ole.openstream(stream) fp = self.ole.openstream(stream)
@ -157,17 +158,29 @@ class FpxImageFile(ImageFile.ImageFile):
for i in range(0, len(s), length): for i in range(0, len(s), length):
compression = i32(s, i+8) compression = i32(s, i + 8)
if compression == 0: if compression == 0:
self.tile.append(("raw", (x, y, x+xtile, y+ytile), self.tile.append(
i32(s, i) + 28, (self.rawmode))) (
"raw",
(x, y, x + xtile, y + ytile),
i32(s, i) + 28,
(self.rawmode),
)
)
elif compression == 1: elif compression == 1:
# FIXME: the fill decoder is not implemented # FIXME: the fill decoder is not implemented
self.tile.append(("fill", (x, y, x+xtile, y+ytile), self.tile.append(
i32(s, i) + 28, (self.rawmode, s[12:16]))) (
"fill",
(x, y, x + xtile, y + ytile),
i32(s, i) + 28,
(self.rawmode, s[12:16]),
)
)
elif compression == 2: elif compression == 2:
@ -189,8 +202,14 @@ class FpxImageFile(ImageFile.ImageFile):
# The image is stored as defined by rawmode # The image is stored as defined by rawmode
jpegmode = rawmode jpegmode = rawmode
self.tile.append(("jpeg", (x, y, x+xtile, y+ytile), self.tile.append(
i32(s, i) + 28, (rawmode, jpegmode))) (
"jpeg",
(x, y, x + xtile, y + ytile),
i32(s, i) + 28,
(rawmode, jpegmode),
)
)
# FIXME: jpeg tables are tile dependent; the prefix # FIXME: jpeg tables are tile dependent; the prefix
# data must be placed in the tile descriptor itself! # data must be placed in the tile descriptor itself!
@ -213,11 +232,11 @@ class FpxImageFile(ImageFile.ImageFile):
def load(self): def load(self):
if not self.fp: if not self.fp:
self.fp = self.ole.openstream(self.stream[:2] + self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
["Subimage 0000 Data"])
return ImageFile.ImageFile.load(self) return ImageFile.ImageFile.load(self)
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -87,10 +87,9 @@ class FtexImageFile(ImageFile.ImageFile):
self.mode = "RGBA" self.mode = "RGBA"
self.tile = [("bcn", (0, 0) + self.size, 0, (1))] self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
elif format == FORMAT_UNCOMPRESSED: elif format == FORMAT_UNCOMPRESSED:
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))] self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
else: else:
raise ValueError( raise ValueError("Invalid texture compression format: %r" % (format))
"Invalid texture compression format: %r" % (format))
self.fp.close() self.fp.close()
self.fp = BytesIO(data) self.fp = BytesIO(data)

View File

@ -29,13 +29,13 @@ from ._binary import i32be as i32
def _accept(prefix): def _accept(prefix):
return len(prefix) >= 8 and \ return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
## ##
# Image plugin for the GIMP brush format. # Image plugin for the GIMP brush format.
class GbrImageFile(ImageFile.ImageFile): class GbrImageFile(ImageFile.ImageFile):
format = "GBR" format = "GBR"
@ -55,24 +55,23 @@ class GbrImageFile(ImageFile.ImageFile):
if width <= 0 or height <= 0: if width <= 0 or height <= 0:
raise SyntaxError("not a GIMP brush") raise SyntaxError("not a GIMP brush")
if color_depth not in (1, 4): if color_depth not in (1, 4):
raise SyntaxError( raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth)
"Unsupported GIMP brush color depth: %s" % color_depth)
if version == 1: if version == 1:
comment_length = header_size-20 comment_length = header_size - 20
else: else:
comment_length = header_size-28 comment_length = header_size - 28
magic_number = self.fp.read(4) magic_number = self.fp.read(4)
if magic_number != b'GIMP': if magic_number != b"GIMP":
raise SyntaxError("not a GIMP brush, bad magic number") raise SyntaxError("not a GIMP brush, bad magic number")
self.info['spacing'] = i32(self.fp.read(4)) self.info["spacing"] = i32(self.fp.read(4))
comment = self.fp.read(comment_length)[:-1] comment = self.fp.read(comment_length)[:-1]
if color_depth == 1: if color_depth == 1:
self.mode = "L" self.mode = "L"
else: else:
self.mode = 'RGBA' self.mode = "RGBA"
self._size = width, height self._size = width, height
@ -88,6 +87,7 @@ class GbrImageFile(ImageFile.ImageFile):
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size)) self.frombytes(self.fp.read(self._data_size))
# #
# registry # registry

View File

@ -37,6 +37,7 @@ __version__ = "0.1"
# this plugin, you have to import the <b>GdImageFile</b> module and # this plugin, you have to import the <b>GdImageFile</b> module and
# use the <b>GdImageFile.open</b> function. # use the <b>GdImageFile.open</b> function.
class GdImageFile(ImageFile.ImageFile): class GdImageFile(ImageFile.ImageFile):
format = "GD" format = "GD"
@ -57,15 +58,17 @@ class GdImageFile(ImageFile.ImageFile):
trueColorOffset = 2 if trueColor else 0 trueColorOffset = 2 if trueColor else 0
# transparency index # transparency index
tindex = i32(s[7+trueColorOffset:7+trueColorOffset+4]) tindex = i32(s[7 + trueColorOffset : 7 + trueColorOffset + 4])
if tindex < 256: if tindex < 256:
self.info["transparency"] = tindex self.info["transparency"] = tindex
self.palette = ImagePalette.raw( self.palette = ImagePalette.raw(
"XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4]) "XBGR", s[7 + trueColorOffset + 4 : 7 + trueColorOffset + 4 + 256 * 4]
)
self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, self.tile = [
("L", 0, 1))] ("raw", (0, 0) + self.size, 7 + trueColorOffset + 4 + 256 * 4, ("L", 0, 1))
]
def open(fp, mode="r"): def open(fp, mode="r"):

View File

@ -37,6 +37,7 @@ __version__ = "0.9"
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Identify/read GIF files # Identify/read GIF files
def _accept(prefix): def _accept(prefix):
return prefix[:6] in [b"GIF87a", b"GIF89a"] return prefix[:6] in [b"GIF87a", b"GIF89a"]
@ -45,6 +46,7 @@ def _accept(prefix):
# Image plugin for GIF images. This plugin supports both GIF87 and # Image plugin for GIF images. This plugin supports both GIF87 and
# GIF89 images. # GIF89 images.
class GifImageFile(ImageFile.ImageFile): class GifImageFile(ImageFile.ImageFile):
format = "GIF" format = "GIF"
@ -78,7 +80,7 @@ class GifImageFile(ImageFile.ImageFile):
# check if palette contains colour indices # check if palette contains colour indices
p = self.fp.read(3 << bits) p = self.fp.read(3 << bits)
for i in range(0, len(p), 3): for i in range(0, len(p), 3):
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])): if not (i // 3 == i8(p[i]) == i8(p[i + 1]) == i8(p[i + 2])):
p = ImagePalette.raw("RGB", p) p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p self.global_palette = self.palette = p
break break
@ -168,6 +170,7 @@ class GifImageFile(ImageFile.ImageFile):
self.im.paste(self.dispose, self.dispose_extent) self.im.paste(self.dispose, self.dispose_extent)
from copy import copy from copy import copy
self.palette = copy(self.global_palette) self.palette = copy(self.global_palette)
info = {} info = {}
@ -242,16 +245,14 @@ class GifImageFile(ImageFile.ImageFile):
if flags & 128: if flags & 128:
bits = (flags & 7) + 1 bits = (flags & 7) + 1
self.palette =\ self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits))
ImagePalette.raw("RGB", self.fp.read(3 << bits))
# image data # image data
bits = i8(self.fp.read(1)) bits = i8(self.fp.read(1))
self.__offset = self.fp.tell() self.__offset = self.fp.tell()
self.tile = [("gif", self.tile = [
(x0, y0, x1, y1), ("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))
self.__offset, ]
(bits, interlace))]
break break
else: else:
@ -264,8 +265,7 @@ class GifImageFile(ImageFile.ImageFile):
self.dispose = None self.dispose = None
elif self.disposal_method == 2: elif self.disposal_method == 2:
# replace with background colour # replace with background colour
self.dispose = Image.core.fill("P", self.size, self.dispose = Image.core.fill("P", self.size, self.info["background"])
self.info["background"])
else: else:
# replace with previous contents # replace with previous contents
if self.im: if self.im:
@ -303,8 +303,7 @@ class GifImageFile(ImageFile.ImageFile):
# we do this by pasting the updated area onto the previous # we do this by pasting the updated area onto the previous
# frame which we then use as the current image content # frame which we then use as the current image content
updated = self._crop(self.im, self.dispose_extent) updated = self._crop(self.im, self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent, self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
updated.convert('RGBA'))
self.im = self._prev_im self.im = self._prev_im
self._prev_im = self.im.copy() self._prev_im = self.im.copy()
@ -317,15 +316,12 @@ class GifImageFile(ImageFile.ImageFile):
finally: finally:
self.__fp = None self.__fp = None
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write GIF files # Write GIF files
RAWMODE = { RAWMODE = {"1": "L", "L": "L", "P": "P"}
"1": "L",
"L": "L",
"P": "P"
}
def _normalize_mode(im, initial_call=False): def _normalize_mode(im, initial_call=False):
@ -376,19 +372,23 @@ def _normalize_palette(im, palette, info):
if isinstance(palette, (bytes, bytearray, list)): if isinstance(palette, (bytes, bytearray, list)):
source_palette = bytearray(palette[:768]) source_palette = bytearray(palette[:768])
if isinstance(palette, ImagePalette.ImagePalette): if isinstance(palette, ImagePalette.ImagePalette):
source_palette = bytearray(itertools.chain.from_iterable( source_palette = bytearray(
zip(palette.palette[:256], itertools.chain.from_iterable(
palette.palette[256:512], zip(
palette.palette[512:768]))) palette.palette[:256],
palette.palette[256:512],
palette.palette[512:768],
)
)
)
if im.mode == "P": if im.mode == "P":
if not source_palette: if not source_palette:
source_palette = im.im.getpalette("RGB")[:768] source_palette = im.im.getpalette("RGB")[:768]
else: # L-mode else: # L-mode
if not source_palette: if not source_palette:
source_palette = bytearray(i//3 for i in range(768)) source_palette = bytearray(i // 3 for i in range(768))
im.palette = ImagePalette.ImagePalette("RGB", im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
palette=source_palette)
used_palette_colors = _get_optimize(im, info) used_palette_colors = _get_optimize(im, info)
if used_palette_colors is not None: if used_palette_colors is not None:
@ -414,8 +414,7 @@ def _write_single_frame(im, fp, palette):
_write_local_header(fp, im, (0, 0), flags) _write_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im)) im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
RAWMODE[im_out.mode])])
fp.write(b"\0") # end of image data fp.write(b"\0") # end of image data
@ -427,8 +426,7 @@ def _write_multiple_frames(im, fp, palette):
im_frames = [] im_frames = []
frame_count = 0 frame_count = 0
for imSequence in itertools.chain([im], for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
im.encoderinfo.get("append_images", [])):
for im_frame in ImageSequence.Iterator(imSequence): for im_frame in ImageSequence.Iterator(imSequence):
# a copy is required here since seek can still mutate the image # a copy is required here since seek can still mutate the image
im_frame = _normalize_mode(im_frame.copy()) im_frame = _normalize_mode(im_frame.copy())
@ -439,7 +437,7 @@ def _write_multiple_frames(im, fp, palette):
encoderinfo = im.encoderinfo.copy() encoderinfo = im.encoderinfo.copy()
if isinstance(duration, (list, tuple)): if isinstance(duration, (list, tuple)):
encoderinfo['duration'] = duration[frame_count] encoderinfo["duration"] = duration[frame_count]
if isinstance(disposal, (list, tuple)): if isinstance(disposal, (list, tuple)):
encoderinfo["disposal"] = disposal[frame_count] encoderinfo["disposal"] = disposal[frame_count]
frame_count += 1 frame_count += 1
@ -447,44 +445,37 @@ def _write_multiple_frames(im, fp, palette):
if im_frames: if im_frames:
# delta frame # delta frame
previous = im_frames[-1] previous = im_frames[-1]
if _get_palette_bytes(im_frame) == \ if _get_palette_bytes(im_frame) == _get_palette_bytes(previous["im"]):
_get_palette_bytes(previous['im']): delta = ImageChops.subtract_modulo(im_frame, previous["im"])
delta = ImageChops.subtract_modulo(im_frame,
previous['im'])
else: else:
delta = ImageChops.subtract_modulo( delta = ImageChops.subtract_modulo(
im_frame.convert('RGB'), previous['im'].convert('RGB')) im_frame.convert("RGB"), previous["im"].convert("RGB")
)
bbox = delta.getbbox() bbox = delta.getbbox()
if not bbox: if not bbox:
# This frame is identical to the previous frame # This frame is identical to the previous frame
if duration: if duration:
previous['encoderinfo']['duration'] += \ previous["encoderinfo"]["duration"] += encoderinfo["duration"]
encoderinfo['duration']
continue continue
else: else:
bbox = None bbox = None
im_frames.append({ im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
'im': im_frame,
'bbox': bbox,
'encoderinfo': encoderinfo
})
if len(im_frames) > 1: if len(im_frames) > 1:
for frame_data in im_frames: for frame_data in im_frames:
im_frame = frame_data['im'] im_frame = frame_data["im"]
if not frame_data['bbox']: if not frame_data["bbox"]:
# global header # global header
for s in _get_global_header(im_frame, for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
frame_data['encoderinfo']):
fp.write(s) fp.write(s)
offset = (0, 0) offset = (0, 0)
else: else:
# compress difference # compress difference
frame_data['encoderinfo']['include_color_table'] = True frame_data["encoderinfo"]["include_color_table"] = True
im_frame = im_frame.crop(frame_data['bbox']) im_frame = im_frame.crop(frame_data["bbox"])
offset = frame_data['bbox'][:2] offset = frame_data["bbox"][:2]
_write_frame_data(fp, im_frame, offset, frame_data['encoderinfo']) _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
return True return True
@ -543,7 +534,7 @@ def _write_local_header(fp, im, offset, flags):
else: else:
duration = 0 duration = 0
disposal = int(im.encoderinfo.get('disposal', 0)) disposal = int(im.encoderinfo.get("disposal", 0))
if transparent_color_exists or duration != 0 or disposal: if transparent_color_exists or duration != 0 or disposal:
packed_flag = 1 if transparent_color_exists else 0 packed_flag = 1 if transparent_color_exists else 0
@ -551,50 +542,53 @@ def _write_local_header(fp, im, offset, flags):
if not transparent_color_exists: if not transparent_color_exists:
transparency = 0 transparency = 0
fp.write(b"!" + fp.write(
o8(249) + # extension intro b"!"
o8(4) + # length + o8(249) # extension intro
o8(packed_flag) + # packed fields + o8(4) # length
o16(duration) + # duration + o8(packed_flag) # packed fields
o8(transparency) + # transparency index + o16(duration) # duration
o8(0)) + o8(transparency) # transparency index
+ o8(0)
)
if "comment" in im.encoderinfo and \ if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]):
1 <= len(im.encoderinfo["comment"]): fp.write(b"!" + o8(254)) # extension intro
fp.write(b"!" +
o8(254)) # extension intro
for i in range(0, len(im.encoderinfo["comment"]), 255): for i in range(0, len(im.encoderinfo["comment"]), 255):
subblock = im.encoderinfo["comment"][i:i+255] subblock = im.encoderinfo["comment"][i : i + 255]
fp.write(o8(len(subblock)) + fp.write(o8(len(subblock)) + subblock)
subblock)
fp.write(o8(0)) fp.write(o8(0))
if "loop" in im.encoderinfo: if "loop" in im.encoderinfo:
number_of_loops = im.encoderinfo["loop"] number_of_loops = im.encoderinfo["loop"]
fp.write(b"!" + fp.write(
o8(255) + # extension intro b"!"
o8(11) + + o8(255) # extension intro
b"NETSCAPE2.0" + + o8(11)
o8(3) + + b"NETSCAPE2.0"
o8(1) + + o8(3)
o16(number_of_loops) + # number of loops + o8(1)
o8(0)) + o16(number_of_loops) # number of loops
include_color_table = im.encoderinfo.get('include_color_table') + o8(0)
)
include_color_table = im.encoderinfo.get("include_color_table")
if include_color_table: if include_color_table:
palette_bytes = _get_palette_bytes(im) palette_bytes = _get_palette_bytes(im)
color_table_size = _get_color_table_size(palette_bytes) color_table_size = _get_color_table_size(palette_bytes)
if color_table_size: if color_table_size:
flags = flags | 128 # local color table flag flags = flags | 128 # local color table flag
flags = flags | color_table_size flags = flags | color_table_size
fp.write(b"," + fp.write(
o16(offset[0]) + # offset b","
o16(offset[1]) + + o16(offset[0]) # offset
o16(im.size[0]) + # size + o16(offset[1])
o16(im.size[1]) + + o16(im.size[0]) # size
o8(flags)) # flags + o16(im.size[1])
+ o8(flags) # flags
)
if include_color_table and color_table_size: if include_color_table and color_table_size:
fp.write(_get_header_palette(palette_bytes)) fp.write(_get_header_palette(palette_bytes))
fp.write(o8(8)) # bits fp.write(o8(8)) # bits
def _save_netpbm(im, fp, filename): def _save_netpbm(im, fp, filename):
@ -608,21 +602,23 @@ def _save_netpbm(im, fp, filename):
import os import os
from subprocess import Popen, check_call, PIPE, CalledProcessError from subprocess import Popen, check_call, PIPE, CalledProcessError
tempfile = im._dump() tempfile = im._dump()
with open(filename, 'wb') as f: with open(filename, "wb") as f:
if im.mode != "RGB": if im.mode != "RGB":
with open(os.devnull, 'wb') as devnull: with open(os.devnull, "wb") as devnull:
check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull) check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull)
else: else:
# Pipe ppmquant output into ppmtogif # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
quant_cmd = ["ppmquant", "256", tempfile] quant_cmd = ["ppmquant", "256", tempfile]
togif_cmd = ["ppmtogif"] togif_cmd = ["ppmtogif"]
with open(os.devnull, 'wb') as devnull: with open(os.devnull, "wb") as devnull:
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, togif_proc = Popen(
stdout=f, stderr=devnull) togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=devnull
)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits # Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close() quant_proc.stdout.close()
@ -668,7 +664,7 @@ def _get_optimize(im, info):
# * If we have a 'large' image, the palette is in the noise. # * If we have a 'large' image, the palette is in the noise.
# create the new palette if not every color is used # create the new palette if not every color is used
optimise = _FORCE_OPTIMIZE or im.mode == 'L' optimise = _FORCE_OPTIMIZE or im.mode == "L"
if optimise or im.width * im.height < 512 * 512: if optimise or im.width * im.height < 512 * 512:
# check which colors are used # check which colors are used
used_palette_colors = [] used_palette_colors = []
@ -676,15 +672,18 @@ def _get_optimize(im, info):
if count: if count:
used_palette_colors.append(i) used_palette_colors.append(i)
if optimise or (len(used_palette_colors) <= 128 and if optimise or (
max(used_palette_colors) > len(used_palette_colors)): len(used_palette_colors) <= 128
and max(used_palette_colors) > len(used_palette_colors)
):
return used_palette_colors return used_palette_colors
def _get_color_table_size(palette_bytes): def _get_color_table_size(palette_bytes):
# calculate the palette size for the header # calculate the palette size for the header
import math import math
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
color_table_size = int(math.ceil(math.log(len(palette_bytes) // 3, 2))) - 1
if color_table_size < 0: if color_table_size < 0:
color_table_size = 0 color_table_size = 0
return color_table_size return color_table_size
@ -702,7 +701,7 @@ def _get_header_palette(palette_bytes):
# add the missing amount of bytes # add the missing amount of bytes
# the palette has to be 2<<n in size # the palette has to be 2<<n in size
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3 actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
if actual_target_size_diff > 0: if actual_target_size_diff > 0:
palette_bytes += o8(0) * 3 * actual_target_size_diff palette_bytes += o8(0) * 3 * actual_target_size_diff
return palette_bytes return palette_bytes
@ -727,9 +726,9 @@ def _get_global_header(im, info):
version = b"87a" version = b"87a"
for extensionKey in ["transparency", "duration", "loop", "comment"]: for extensionKey in ["transparency", "duration", "loop", "comment"]:
if info and extensionKey in info: if info and extensionKey in info:
if ((extensionKey == "duration" and info[extensionKey] == 0) or if (extensionKey == "duration" and info[extensionKey] == 0) or (
(extensionKey == "comment" and extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255)
not (1 <= len(info[extensionKey]) <= 255))): ):
continue continue
version = b"89a" version = b"89a"
break break
@ -750,18 +749,17 @@ def _get_global_header(im, info):
color_table_size = _get_color_table_size(palette_bytes) color_table_size = _get_color_table_size(palette_bytes)
return [ return [
b"GIF"+version + # signature + version b"GIF" # signature
o16(im.size[0]) + # canvas width + version # version
o16(im.size[1]), # canvas height + o16(im.size[0]) # canvas width
+ o16(im.size[1]), # canvas height
# Logical Screen Descriptor # Logical Screen Descriptor
# size of global color table + global color table flag # size of global color table + global color table flag
o8(color_table_size + 128), # packed fields o8(color_table_size + 128), # packed fields
# background + reserved/aspect # background + reserved/aspect
o8(background) + o8(0), o8(background) + o8(0),
# Global Color Table # Global Color Table
_get_header_palette(palette_bytes) _get_header_palette(palette_bytes),
] ]
@ -772,13 +770,15 @@ def _write_frame_data(fp, im_frame, offset, params):
# local image header # local image header
_write_local_header(fp, im_frame, offset, 0) _write_local_header(fp, im_frame, offset, 0)
ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0, ImageFile._save(
RAWMODE[im_frame.mode])]) im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
)
fp.write(b"\0") # end of image data fp.write(b"\0") # end of image data
finally: finally:
del im_frame.encoderinfo del im_frame.encoderinfo
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Legacy GIF utilities # Legacy GIF utilities
@ -827,6 +827,7 @@ def getdata(im, offset=(0, 0), **params):
:returns: List of Bytes containing gif encoded frame data :returns: List of Bytes containing gif encoded frame data
""" """
class Collector(object): class Collector(object):
data = [] data = []

View File

@ -72,7 +72,7 @@ class GradientFile(object):
for i in range(entries): for i in range(entries):
x = i / float(entries-1) x = i / float(entries - 1)
while x1 < x: while x1 < x:
ix += 1 ix += 1
@ -100,8 +100,8 @@ class GradientFile(object):
## ##
# File handler for GIMP's gradient format. # File handler for GIMP's gradient format.
class GimpGradientFile(GradientFile):
class GimpGradientFile(GradientFile):
def __init__(self, fp): def __init__(self, fp):
if fp.readline()[:13] != b"GIMP Gradient": if fp.readline()[:13] != b"GIMP Gradient":

View File

@ -21,13 +21,14 @@ from ._binary import o8
## ##
# File handler for GIMP's palette format. # File handler for GIMP's palette format.
class GimpPaletteFile(object): class GimpPaletteFile(object):
rawmode = "RGB" rawmode = "RGB"
def __init__(self, fp): def __init__(self, fp):
self.palette = [o8(i)*3 for i in range(256)] self.palette = [o8(i) * 3 for i in range(256)]
if fp.readline()[:12] != b"GIMP Palette": if fp.readline()[:12] != b"GIMP Palette":
raise SyntaxError("not a GIMP palette file") raise SyntaxError("not a GIMP palette file")

View File

@ -28,6 +28,7 @@ def register_handler(handler):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1 return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1

View File

@ -27,6 +27,7 @@ def register_handler(handler):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:8] == b"\x89HDF\r\n\x1a\n" return prefix[:8] == b"\x89HDF\r\n\x1a\n"

View File

@ -24,7 +24,7 @@ import struct
import sys import sys
import tempfile import tempfile
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') enable_jpeg2k = hasattr(Image.core, "jp2klib_version")
if enable_jpeg2k: if enable_jpeg2k:
from PIL import Jpeg2KImagePlugin from PIL import Jpeg2KImagePlugin
@ -32,7 +32,7 @@ HEADERSIZE = 8
def nextheader(fobj): def nextheader(fobj):
return struct.unpack('>4sI', fobj.read(HEADERSIZE)) return struct.unpack(">4sI", fobj.read(HEADERSIZE))
def read_32t(fobj, start_length, size): def read_32t(fobj, start_length, size):
@ -40,8 +40,8 @@ def read_32t(fobj, start_length, size):
(start, length) = start_length (start, length) = start_length
fobj.seek(start) fobj.seek(start)
sig = fobj.read(4) sig = fobj.read(4)
if sig != b'\x00\x00\x00\x00': if sig != b"\x00\x00\x00\x00":
raise SyntaxError('Unknown signature, expecting 0x00000000') raise SyntaxError("Unknown signature, expecting 0x00000000")
return read_32(fobj, (start + 4, length - 4), size) return read_32(fobj, (start + 4, length - 4), size)
@ -81,12 +81,8 @@ def read_32(fobj, start_length, size):
if bytesleft <= 0: if bytesleft <= 0:
break break
if bytesleft != 0: if bytesleft != 0:
raise SyntaxError( raise SyntaxError("Error reading channel [%r left]" % bytesleft)
"Error reading channel [%r left]" % bytesleft band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1)
)
band = Image.frombuffer(
"L", pixel_size, b"".join(data), "raw", "L", 0, 1
)
im.im.putband(band.im, band_ix) im.im.putband(band.im, band_ix)
return {"RGB": im} return {"RGB": im}
@ -97,9 +93,7 @@ def read_mk(fobj, start_length, size):
fobj.seek(start) fobj.seek(start)
pixel_size = (size[0] * size[2], size[1] * size[2]) pixel_size = (size[0] * size[2], size[1] * size[2])
sizesq = pixel_size[0] * pixel_size[1] sizesq = pixel_size[0] * pixel_size[1]
band = Image.frombuffer( band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1)
"L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1
)
return {"A": band} return {"A": band}
@ -107,73 +101,58 @@ def read_png_or_jpeg2000(fobj, start_length, size):
(start, length) = start_length (start, length) = start_length
fobj.seek(start) fobj.seek(start)
sig = fobj.read(12) sig = fobj.read(12)
if sig[:8] == b'\x89PNG\x0d\x0a\x1a\x0a': if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
fobj.seek(start) fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj) im = PngImagePlugin.PngImageFile(fobj)
return {"RGBA": im} return {"RGBA": im}
elif sig[:4] == b'\xff\x4f\xff\x51' \ elif (
or sig[:4] == b'\x0d\x0a\x87\x0a' \ sig[:4] == b"\xff\x4f\xff\x51"
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': or sig[:4] == b"\x0d\x0a\x87\x0a"
or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
):
if not enable_jpeg2k: if not enable_jpeg2k:
raise ValueError('Unsupported icon subimage format (rebuild PIL ' raise ValueError(
'with JPEG 2000 support to fix this)') "Unsupported icon subimage format (rebuild PIL "
"with JPEG 2000 support to fix this)"
)
# j2k, jpc or j2c # j2k, jpc or j2c
fobj.seek(start) fobj.seek(start)
jp2kstream = fobj.read(length) jp2kstream = fobj.read(length)
f = io.BytesIO(jp2kstream) f = io.BytesIO(jp2kstream)
im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
if im.mode != 'RGBA': if im.mode != "RGBA":
im = im.convert('RGBA') im = im.convert("RGBA")
return {"RGBA": im} return {"RGBA": im}
else: else:
raise ValueError('Unsupported icon subimage format') raise ValueError("Unsupported icon subimage format")
class IcnsFile(object): class IcnsFile(object):
SIZES = { SIZES = {
(512, 512, 2): [ (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)],
(b'ic10', read_png_or_jpeg2000), (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)],
], (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)],
(512, 512, 1): [ (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)],
(b'ic09', read_png_or_jpeg2000), (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)],
],
(256, 256, 2): [
(b'ic14', read_png_or_jpeg2000),
],
(256, 256, 1): [
(b'ic08', read_png_or_jpeg2000),
],
(128, 128, 2): [
(b'ic13', read_png_or_jpeg2000),
],
(128, 128, 1): [ (128, 128, 1): [
(b'ic07', read_png_or_jpeg2000), (b"ic07", read_png_or_jpeg2000),
(b'it32', read_32t), (b"it32", read_32t),
(b't8mk', read_mk), (b"t8mk", read_mk),
],
(64, 64, 1): [
(b'icp6', read_png_or_jpeg2000),
],
(32, 32, 2): [
(b'ic12', read_png_or_jpeg2000),
],
(48, 48, 1): [
(b'ih32', read_32),
(b'h8mk', read_mk),
], ],
(64, 64, 1): [(b"icp6", read_png_or_jpeg2000)],
(32, 32, 2): [(b"ic12", read_png_or_jpeg2000)],
(48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)],
(32, 32, 1): [ (32, 32, 1): [
(b'icp5', read_png_or_jpeg2000), (b"icp5", read_png_or_jpeg2000),
(b'il32', read_32), (b"il32", read_32),
(b'l8mk', read_mk), (b"l8mk", read_mk),
],
(16, 16, 2): [
(b'ic11', read_png_or_jpeg2000),
], ],
(16, 16, 2): [(b"ic11", read_png_or_jpeg2000)],
(16, 16, 1): [ (16, 16, 1): [
(b'icp4', read_png_or_jpeg2000), (b"icp4", read_png_or_jpeg2000),
(b'is32', read_32), (b"is32", read_32),
(b's8mk', read_mk), (b"s8mk", read_mk),
], ],
} }
@ -185,13 +164,13 @@ class IcnsFile(object):
self.dct = dct = {} self.dct = dct = {}
self.fobj = fobj self.fobj = fobj
sig, filesize = nextheader(fobj) sig, filesize = nextheader(fobj)
if sig != b'icns': if sig != b"icns":
raise SyntaxError('not an icns file') raise SyntaxError("not an icns file")
i = HEADERSIZE i = HEADERSIZE
while i < filesize: while i < filesize:
sig, blocksize = nextheader(fobj) sig, blocksize = nextheader(fobj)
if blocksize <= 0: if blocksize <= 0:
raise SyntaxError('invalid block header') raise SyntaxError("invalid block header")
i += HEADERSIZE i += HEADERSIZE
blocksize -= HEADERSIZE blocksize -= HEADERSIZE
dct[sig] = (i, blocksize) dct[sig] = (i, blocksize)
@ -233,7 +212,7 @@ class IcnsFile(object):
size = (size[0], size[1], 1) size = (size[0], size[1], 1)
channels = self.dataforsize(size) channels = self.dataforsize(size)
im = channels.get('RGBA', None) im = channels.get("RGBA", None)
if im: if im:
return im return im
@ -248,6 +227,7 @@ class IcnsFile(object):
## ##
# Image plugin for Mac OS icons. # Image plugin for Mac OS icons.
class IcnsImageFile(ImageFile.ImageFile): class IcnsImageFile(ImageFile.ImageFile):
""" """
PIL image support for Mac OS .icns files. PIL image support for Mac OS .icns files.
@ -264,13 +244,15 @@ class IcnsImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
self.icns = IcnsFile(self.fp) self.icns = IcnsFile(self.fp)
self.mode = 'RGBA' self.mode = "RGBA"
self.info['sizes'] = self.icns.itersizes() self.info["sizes"] = self.icns.itersizes()
self.best_size = self.icns.bestsize() self.best_size = self.icns.bestsize()
self.size = (self.best_size[0] * self.best_size[2], self.size = (
self.best_size[1] * self.best_size[2]) self.best_size[0] * self.best_size[2],
self.best_size[1] * self.best_size[2],
)
# Just use this to see if it's loaded or not yet. # Just use this to see if it's loaded or not yet.
self.tile = ('',) self.tile = ("",)
@property @property
def size(self): def size(self):
@ -279,24 +261,29 @@ class IcnsImageFile(ImageFile.ImageFile):
@size.setter @size.setter
def size(self, value): def size(self, value):
info_size = value info_size = value
if info_size not in self.info['sizes'] and len(info_size) == 2: if info_size not in self.info["sizes"] and len(info_size) == 2:
info_size = (info_size[0], info_size[1], 1) info_size = (info_size[0], info_size[1], 1)
if info_size not in self.info['sizes'] and len(info_size) == 3 and \ if (
info_size[2] == 1: info_size not in self.info["sizes"]
simple_sizes = [(size[0] * size[2], size[1] * size[2]) and len(info_size) == 3
for size in self.info['sizes']] and info_size[2] == 1
):
simple_sizes = [
(size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
]
if value in simple_sizes: if value in simple_sizes:
info_size = self.info['sizes'][simple_sizes.index(value)] info_size = self.info["sizes"][simple_sizes.index(value)]
if info_size not in self.info['sizes']: if info_size not in self.info["sizes"]:
raise ValueError( raise ValueError("This is not one of the allowed sizes of this image")
"This is not one of the allowed sizes of this image")
self._size = value self._size = value
def load(self): def load(self):
if len(self.size) == 3: if len(self.size) == 3:
self.best_size = self.size self.best_size = self.size
self.size = (self.best_size[0] * self.best_size[2], self.size = (
self.best_size[1] * self.best_size[2]) self.best_size[0] * self.best_size[2],
self.best_size[1] * self.best_size[2],
)
Image.Image.load(self) Image.Image.load(self)
if not self.tile: if not self.tile:
@ -331,31 +318,30 @@ def _save(im, fp, filename):
fp.flush() fp.flush()
# create the temporary set of pngs # create the temporary set of pngs
iconset = tempfile.mkdtemp('.iconset') iconset = tempfile.mkdtemp(".iconset")
provided_images = {im.width: im provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])}
for im in im.encoderinfo.get("append_images", [])}
last_w = None last_w = None
second_path = None second_path = None
for w in [16, 32, 128, 256, 512]: for w in [16, 32, 128, 256, 512]:
prefix = 'icon_{}x{}'.format(w, w) prefix = "icon_{}x{}".format(w, w)
first_path = os.path.join(iconset, prefix+'.png') first_path = os.path.join(iconset, prefix + ".png")
if last_w == w: if last_w == w:
shutil.copyfile(second_path, first_path) shutil.copyfile(second_path, first_path)
else: else:
im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS)) im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS))
im_w.save(first_path) im_w.save(first_path)
second_path = os.path.join(iconset, prefix+'@2x.png') second_path = os.path.join(iconset, prefix + "@2x.png")
im_w2 = provided_images.get(w*2, im.resize((w*2, w*2), Image.LANCZOS)) im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS))
im_w2.save(second_path) im_w2.save(second_path)
last_w = w*2 last_w = w * 2
# iconutil -c icns -o {} {} # iconutil -c icns -o {} {}
from subprocess import Popen, PIPE, CalledProcessError from subprocess import Popen, PIPE, CalledProcessError
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
with open(os.devnull, 'wb') as devnull: with open(os.devnull, "wb") as devnull:
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull) convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
convert_proc.stdout.close() convert_proc.stdout.close()
@ -369,29 +355,28 @@ def _save(im, fp, filename):
raise CalledProcessError(retcode, convert_cmd) raise CalledProcessError(retcode, convert_cmd)
Image.register_open(IcnsImageFile.format, IcnsImageFile, Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns")
lambda x: x[:4] == b'icns') Image.register_extension(IcnsImageFile.format, ".icns")
Image.register_extension(IcnsImageFile.format, '.icns')
if sys.platform == 'darwin': if sys.platform == "darwin":
Image.register_save(IcnsImageFile.format, _save) Image.register_save(IcnsImageFile.format, _save)
Image.register_mime(IcnsImageFile.format, "image/icns") Image.register_mime(IcnsImageFile.format, "image/icns")
if __name__ == '__main__': if __name__ == "__main__":
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Syntax: python IcnsImagePlugin.py [file]") print("Syntax: python IcnsImagePlugin.py [file]")
sys.exit() sys.exit()
imf = IcnsImageFile(open(sys.argv[1], 'rb')) imf = IcnsImageFile(open(sys.argv[1], "rb"))
for size in imf.info['sizes']: for size in imf.info["sizes"]:
imf.size = size imf.size = size
imf.load() imf.load()
im = imf.im im = imf.im
im.save('out-%s-%s-%s.png' % size) im.save("out-%s-%s-%s.png" % size)
im = Image.open(sys.argv[1]) im = Image.open(sys.argv[1])
im.save("out.png") im.save("out.png")
if sys.platform == 'windows': if sys.platform == "windows":
os.startfile("out.png") os.startfile("out.png")

View File

@ -42,16 +42,20 @@ _MAGIC = b"\0\0\1\0"
def _save(im, fp, filename): def _save(im, fp, filename):
fp.write(_MAGIC) # (2+2) fp.write(_MAGIC) # (2+2)
sizes = im.encoderinfo.get("sizes", sizes = im.encoderinfo.get(
[(16, 16), (24, 24), (32, 32), (48, 48), "sizes",
(64, 64), (128, 128), (256, 256)]) [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)],
)
width, height = im.size width, height = im.size
sizes = filter(lambda x: False if (x[0] > width or x[1] > height or sizes = filter(
x[0] > 256 or x[1] > 256) else True, lambda x: False
sizes) if (x[0] > width or x[1] > height or x[0] > 256 or x[1] > 256)
else True,
sizes,
)
sizes = list(sizes) sizes = list(sizes)
fp.write(struct.pack("<H", len(sizes))) # idCount(2) fp.write(struct.pack("<H", len(sizes))) # idCount(2)
offset = fp.tell() + len(sizes)*16 offset = fp.tell() + len(sizes) * 16
for size in sizes: for size in sizes:
width, height = size width, height = size
# 0 means 256 # 0 means 256
@ -104,49 +108,52 @@ class IcoFile(object):
s = buf.read(16) s = buf.read(16)
icon_header = { icon_header = {
'width': i8(s[0]), "width": i8(s[0]),
'height': i8(s[1]), "height": i8(s[1]),
'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp) "nb_color": i8(s[2]), # No. of colors in image (0 if >=8bpp)
'reserved': i8(s[3]), "reserved": i8(s[3]),
'planes': i16(s[4:]), "planes": i16(s[4:]),
'bpp': i16(s[6:]), "bpp": i16(s[6:]),
'size': i32(s[8:]), "size": i32(s[8:]),
'offset': i32(s[12:]) "offset": i32(s[12:]),
} }
# See Wikipedia # See Wikipedia
for j in ('width', 'height'): for j in ("width", "height"):
if not icon_header[j]: if not icon_header[j]:
icon_header[j] = 256 icon_header[j] = 256
# See Wikipedia notes about color depth. # See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes # We need this just to differ images with equal sizes
icon_header['color_depth'] = (icon_header['bpp'] or icon_header["color_depth"] = (
(icon_header['nb_color'] != 0 and icon_header["bpp"]
ceil(log(icon_header['nb_color'], or (
2))) or 256) icon_header["nb_color"] != 0
and ceil(log(icon_header["nb_color"], 2))
)
or 256
)
icon_header['dim'] = (icon_header['width'], icon_header['height']) icon_header["dim"] = (icon_header["width"], icon_header["height"])
icon_header['square'] = (icon_header['width'] * icon_header["square"] = icon_header["width"] * icon_header["height"]
icon_header['height'])
self.entry.append(icon_header) self.entry.append(icon_header)
self.entry = sorted(self.entry, key=lambda x: x['color_depth']) self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
# ICO images are usually squares # ICO images are usually squares
# self.entry = sorted(self.entry, key=lambda x: x['width']) # self.entry = sorted(self.entry, key=lambda x: x['width'])
self.entry = sorted(self.entry, key=lambda x: x['square']) self.entry = sorted(self.entry, key=lambda x: x["square"])
self.entry.reverse() self.entry.reverse()
def sizes(self): def sizes(self):
""" """
Get a list of all available icon sizes and color depths. Get a list of all available icon sizes and color depths.
""" """
return {(h['width'], h['height']) for h in self.entry} return {(h["width"], h["height"]) for h in self.entry}
def getentryindex(self, size, bpp=False): def getentryindex(self, size, bpp=False):
for (i, h) in enumerate(self.entry): for (i, h) in enumerate(self.entry):
if size == h['dim'] and (bpp is False or bpp == h['color_depth']): if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
return i return i
return 0 return 0
@ -163,9 +170,9 @@ class IcoFile(object):
header = self.entry[idx] header = self.entry[idx]
self.buf.seek(header['offset']) self.buf.seek(header["offset"])
data = self.buf.read(8) data = self.buf.read(8)
self.buf.seek(header['offset']) self.buf.seek(header["offset"])
if data[:8] == PngImagePlugin._MAGIC: if data[:8] == PngImagePlugin._MAGIC:
# png frame # png frame
@ -200,11 +207,11 @@ class IcoFile(object):
# convert to an 8bpp grayscale image # convert to an 8bpp grayscale image
mask = Image.frombuffer( mask = Image.frombuffer(
'L', # 8bpp "L", # 8bpp
im.size, # (w, h) im.size, # (w, h)
alpha_bytes, # source chars alpha_bytes, # source chars
'raw', # raw decoder "raw", # raw decoder
('L', 0, -1) # 8bpp inverted, unpadded, reversed ("L", 0, -1), # 8bpp inverted, unpadded, reversed
) )
else: else:
# get AND image from end of bitmap # get AND image from end of bitmap
@ -216,8 +223,7 @@ class IcoFile(object):
# the total mask data is # the total mask data is
# padded row size * height / bits per char # padded row size * height / bits per char
and_mask_offset = o + int(im.size[0] * im.size[1] * and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0))
(bpp / 8.0))
total_bytes = int((w * im.size[1]) / 8) total_bytes = int((w * im.size[1]) / 8)
self.buf.seek(and_mask_offset) self.buf.seek(and_mask_offset)
@ -225,17 +231,17 @@ class IcoFile(object):
# convert raw data to image # convert raw data to image
mask = Image.frombuffer( mask = Image.frombuffer(
'1', # 1 bpp "1", # 1 bpp
im.size, # (w, h) im.size, # (w, h)
mask_data, # source chars mask_data, # source chars
'raw', # raw decoder "raw", # raw decoder
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
) )
# now we have two images, im is XOR image and mask is AND image # now we have two images, im is XOR image and mask is AND image
# apply mask image as alpha channel # apply mask image as alpha channel
im = im.convert('RGBA') im = im.convert("RGBA")
im.putalpha(mask) im.putalpha(mask)
return im return im
@ -244,6 +250,7 @@ class IcoFile(object):
## ##
# Image plugin for Windows Icon files. # Image plugin for Windows Icon files.
class IcoImageFile(ImageFile.ImageFile): class IcoImageFile(ImageFile.ImageFile):
""" """
PIL read-only image support for Microsoft Windows .ico files. PIL read-only image support for Microsoft Windows .ico files.
@ -260,13 +267,14 @@ class IcoImageFile(ImageFile.ImageFile):
<casadebender@gmail.com>. <casadebender@gmail.com>.
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
""" """
format = "ICO" format = "ICO"
format_description = "Windows Icon" format_description = "Windows Icon"
def _open(self): def _open(self):
self.ico = IcoFile(self.fp) self.ico = IcoFile(self.fp)
self.info['sizes'] = self.ico.sizes() self.info["sizes"] = self.ico.sizes()
self.size = self.ico.entry[0]['dim'] self.size = self.ico.entry[0]["dim"]
self.load() self.load()
@property @property
@ -275,9 +283,8 @@ class IcoImageFile(ImageFile.ImageFile):
@size.setter @size.setter
def size(self, value): def size(self, value):
if value not in self.info['sizes']: if value not in self.info["sizes"]:
raise ValueError( raise ValueError("This is not one of the allowed sizes of this image")
"This is not one of the allowed sizes of this image")
self._size = value self._size = value
def load(self): def load(self):
@ -290,9 +297,9 @@ class IcoImageFile(ImageFile.ImageFile):
warnings.warn("Image was not the expected size") warnings.warn("Image was not the expected size")
index = self.ico.getentryindex(self.size) index = self.ico.getentryindex(self.size)
sizes = list(self.info['sizes']) sizes = list(self.info["sizes"])
sizes[index] = im.size sizes[index] = im.size
self.info['sizes'] = set(sizes) self.info["sizes"] = set(sizes)
self.size = im.size self.size = im.size
@ -300,6 +307,8 @@ class IcoImageFile(ImageFile.ImageFile):
# Flag the ImageFile.Parser so that it # Flag the ImageFile.Parser so that it
# just does all the decode at the end. # just does all the decode at the end.
pass pass
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -48,8 +48,17 @@ SCALE = "Scale (x,y)"
SIZE = "Image size (x*y)" SIZE = "Image size (x*y)"
MODE = "Image type" MODE = "Image type"
TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0, TAGS = {
SCALE: 0, SIZE: 0, MODE: 0} COMMENT: 0,
DATE: 0,
EQUIPMENT: 0,
FRAMES: 0,
LUT: 0,
NAME: 0,
SCALE: 0,
SIZE: 0,
MODE: 0,
}
OPEN = { OPEN = {
# ifunc93/p3cfunc formats # ifunc93/p3cfunc formats
@ -108,6 +117,7 @@ def number(s):
## ##
# Image plugin for the IFUNC IM file format. # Image plugin for the IFUNC IM file format.
class ImImageFile(ImageFile.ImageFile): class ImImageFile(ImageFile.ImageFile):
format = "IM" format = "IM"
@ -140,7 +150,7 @@ class ImImageFile(ImageFile.ImageFile):
if s == b"\r": if s == b"\r":
continue continue
if not s or s == b'\0' or s == b'\x1A': if not s or s == b"\0" or s == b"\x1A":
break break
# FIXME: this may read whole file if not a text file # FIXME: this may read whole file if not a text file
@ -149,9 +159,9 @@ class ImImageFile(ImageFile.ImageFile):
if len(s) > 100: if len(s) > 100:
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file")
if s[-2:] == b'\r\n': if s[-2:] == b"\r\n":
s = s[:-2] s = s[:-2]
elif s[-1:] == b'\n': elif s[-1:] == b"\n":
s = s[:-1] s = s[:-1]
try: try:
@ -165,8 +175,8 @@ class ImImageFile(ImageFile.ImageFile):
# Don't know if this is the correct encoding, # Don't know if this is the correct encoding,
# but a decent guess (I guess) # but a decent guess (I guess)
k = k.decode('latin-1', 'replace') k = k.decode("latin-1", "replace")
v = v.decode('latin-1', 'replace') v = v.decode("latin-1", "replace")
# Convert value as appropriate # Convert value as appropriate
if k in [FRAMES, SCALE, SIZE]: if k in [FRAMES, SCALE, SIZE]:
@ -192,8 +202,9 @@ class ImImageFile(ImageFile.ImageFile):
else: else:
raise SyntaxError("Syntax error in IM header: " + raise SyntaxError(
s.decode('ascii', 'replace')) "Syntax error in IM header: " + s.decode("ascii", "replace")
)
if not n: if not n:
raise SyntaxError("Not an IM file") raise SyntaxError("Not an IM file")
@ -203,7 +214,7 @@ class ImImageFile(ImageFile.ImageFile):
self.mode = self.info[MODE] self.mode = self.info[MODE]
# Skip forward to start of image data # Skip forward to start of image data
while s and s[0:1] != b'\x1A': while s and s[0:1] != b"\x1A":
s = self.fp.read(1) s = self.fp.read(1)
if not s: if not s:
raise SyntaxError("File truncated") raise SyntaxError("File truncated")
@ -214,7 +225,7 @@ class ImImageFile(ImageFile.ImageFile):
greyscale = 1 # greyscale palette greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette linear = 1 # linear greyscale palette
for i in range(256): for i in range(256):
if palette[i] == palette[i+256] == palette[i+512]: if palette[i] == palette[i + 256] == palette[i + 512]:
if i8(palette[i]) != i: if i8(palette[i]) != i:
linear = 0 linear = 0
else: else:
@ -247,8 +258,7 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary) # use bit decoder (if necessary)
bits = int(self.rawmode[2:]) bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]: if bits not in [8, 16, 32]:
self.tile = [("bit", (0, 0)+self.size, offs, self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
(bits, 8, 3, 0, -1))]
return return
except ValueError: except ValueError:
pass pass
@ -257,13 +267,14 @@ class ImImageFile(ImageFile.ImageFile):
# Old LabEye/3PC files. Would be very surprised if anyone # Old LabEye/3PC files. Would be very surprised if anyone
# ever stumbled upon such a file ;-) # ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1] size = self.size[0] * self.size[1]
self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)), self.tile = [
("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)), ("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))] ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
]
else: else:
# LabEye/IFUNC files # LabEye/IFUNC files
self.tile = [("raw", (0, 0)+self.size, offs, self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
(self.rawmode, 0, -1))]
@property @property
def n_frames(self): def n_frames(self):
@ -289,7 +300,7 @@ class ImImageFile(ImageFile.ImageFile):
self.fp = self.__fp self.fp = self.__fp
self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))] self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
def tell(self): def tell(self):
return self.frame return self.frame
@ -303,6 +314,7 @@ class ImImageFile(ImageFile.ImageFile):
finally: finally:
self.__fp = None self.__fp = None
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Save IM files # Save IM files
@ -324,7 +336,7 @@ SAVE = {
"RGBA": ("RGBA", "RGBA;L"), "RGBA": ("RGBA", "RGBA;L"),
"RGBX": ("RGBX", "RGBX;L"), "RGBX": ("RGBX", "RGBX;L"),
"CMYK": ("CMYK", "CMYK;L"), "CMYK": ("CMYK", "CMYK;L"),
"YCbCr": ("YCC", "YCbCr;L") "YCbCr": ("YCC", "YCbCr;L"),
} }
@ -337,17 +349,18 @@ def _save(im, fp, filename):
frames = im.encoderinfo.get("frames", 1) frames = im.encoderinfo.get("frames", 1)
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii')) fp.write(("Image type: %s image\r\n" % image_type).encode("ascii"))
if filename: if filename:
fp.write(("Name: %s\r\n" % filename).encode('ascii')) 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(("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')) fp.write(("File size (no of images): %d\r\n" % frames).encode("ascii"))
if im.mode in ["P", "PA"]: if im.mode in ["P", "PA"]:
fp.write(b"Lut: 1\r\n") fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511-fp.tell()) + b"\032") fp.write(b"\000" * (511 - fp.tell()) + b"\032")
if im.mode in ["P", "PA"]: if im.mode in ["P", "PA"]:
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))]) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -19,12 +19,14 @@ from __future__ import print_function
import sys import sys
from PIL import Image from PIL import Image
try: try:
from PIL import _imagingcms from PIL import _imagingcms
except ImportError as ex: except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing # Allow error import for doc purposes, but error out when accessing
# anything in core. # anything in core.
from ._util import deferred_error from ._util import deferred_error
_imagingcms = deferred_error(ex) _imagingcms = deferred_error(ex)
from PIL._util import isStringType from PIL._util import isStringType
@ -132,7 +134,7 @@ FLAGS = {
"SOFTPROOFING": 16384, # Do softproofing "SOFTPROOFING": 16384, # Do softproofing
"PRESERVEBLACK": 32768, # Black preservation "PRESERVEBLACK": 32768, # Black preservation
"NODEFAULTRESOURCEDEF": 16777216, # CRD special "NODEFAULTRESOURCEDEF": 16777216, # CRD special
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16, # Gridpoints
} }
_MAX_FLAG = 0 _MAX_FLAG = 0
@ -148,8 +150,8 @@ for flag in FLAGS.values():
## ##
# Profile. # Profile.
class ImageCmsProfile(object):
class ImageCmsProfile(object):
def __init__(self, profile): def __init__(self, profile):
""" """
:param profile: Either a string representing a filename, :param profile: Either a string representing a filename,
@ -197,22 +199,31 @@ class ImageCmsTransform(Image.ImagePointHandler):
Will return the output profile in the output.info['icc_profile']. Will return the output profile in the output.info['icc_profile'].
""" """
def __init__(self, input, output, input_mode, output_mode, def __init__(
intent=INTENT_PERCEPTUAL, proof=None, self,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): input,
output,
input_mode,
output_mode,
intent=INTENT_PERCEPTUAL,
proof=None,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC,
flags=0,
):
if proof is None: if proof is None:
self.transform = core.buildTransform( self.transform = core.buildTransform(
input.profile, output.profile, input.profile, output.profile, input_mode, output_mode, intent, flags
input_mode, output_mode,
intent,
flags
) )
else: else:
self.transform = core.buildProofTransform( self.transform = core.buildProofTransform(
input.profile, output.profile, proof.profile, input.profile,
input_mode, output_mode, output.profile,
intent, proof_intent, proof.profile,
flags input_mode,
output_mode,
intent,
proof_intent,
flags,
) )
# Note: inputMode and outputMode are for pyCMS compatibility only # Note: inputMode and outputMode are for pyCMS compatibility only
self.input_mode = self.inputMode = input_mode self.input_mode = self.inputMode = input_mode
@ -228,7 +239,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
if imOut is None: if imOut is None:
imOut = Image.new(self.output_mode, im.size, None) imOut = Image.new(self.output_mode, im.size, None)
self.transform.apply(im.im.id, imOut.im.id) self.transform.apply(im.im.id, imOut.im.id)
imOut.info['icc_profile'] = self.output_profile.tobytes() imOut.info["icc_profile"] = self.output_profile.tobytes()
return imOut return imOut
def apply_in_place(self, im): def apply_in_place(self, im):
@ -236,7 +247,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
if im.mode != self.output_mode: if im.mode != self.output_mode:
raise ValueError("mode mismatch") # wrong output mode raise ValueError("mode mismatch") # wrong output mode
self.transform.apply(im.im.id, im.im.id) self.transform.apply(im.im.id, im.im.id)
im.info['icc_profile'] = self.output_profile.tobytes() im.info["icc_profile"] = self.output_profile.tobytes()
return im return im
@ -247,6 +258,7 @@ def get_display_profile(handle=None):
if sys.platform == "win32": if sys.platform == "win32":
from PIL import ImageWin from PIL import ImageWin
if isinstance(handle, ImageWin.HDC): if isinstance(handle, ImageWin.HDC):
profile = core.get_display_profile_win32(handle, 1) profile = core.get_display_profile_win32(handle, 1)
else: else:
@ -265,16 +277,24 @@ def get_display_profile(handle=None):
# pyCMS compatible layer # pyCMS compatible layer
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
class PyCMSError(Exception): class PyCMSError(Exception):
""" (pyCMS) Exception class. """ (pyCMS) Exception class.
This is used for all errors in the pyCMS API. """ This is used for all errors in the pyCMS API. """
pass pass
def profileToProfile( def profileToProfile(
im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, im,
outputMode=None, inPlace=False, flags=0): inputProfile,
outputProfile,
renderingIntent=INTENT_PERCEPTUAL,
outputMode=None,
inPlace=False,
flags=0,
):
""" """
(pyCMS) Applies an ICC transformation to a given image, mapping from (pyCMS) Applies an ICC transformation to a given image, mapping from
inputProfile to outputProfile. inputProfile to outputProfile.
@ -333,8 +353,7 @@ def profileToProfile(
raise PyCMSError("renderingIntent must be an integer between 0 and 3") raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError( raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
"flags must be an integer between 0 and %s" + _MAX_FLAG)
try: try:
if not isinstance(inputProfile, ImageCmsProfile): if not isinstance(inputProfile, ImageCmsProfile):
@ -342,8 +361,12 @@ def profileToProfile(
if not isinstance(outputProfile, ImageCmsProfile): if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile) outputProfile = ImageCmsProfile(outputProfile)
transform = ImageCmsTransform( transform = ImageCmsTransform(
inputProfile, outputProfile, im.mode, outputMode, inputProfile,
renderingIntent, flags=flags outputProfile,
im.mode,
outputMode,
renderingIntent,
flags=flags,
) )
if inPlace: if inPlace:
transform.apply_in_place(im) transform.apply_in_place(im)
@ -379,8 +402,13 @@ def getOpenProfile(profileFilename):
def buildTransform( def buildTransform(
inputProfile, outputProfile, inMode, outMode, inputProfile,
renderingIntent=INTENT_PERCEPTUAL, flags=0): outputProfile,
inMode,
outMode,
renderingIntent=INTENT_PERCEPTUAL,
flags=0,
):
""" """
(pyCMS) Builds an ICC transform mapping from the inputProfile to the (pyCMS) Builds an ICC transform mapping from the inputProfile to the
outputProfile. Use applyTransform to apply the transform to a given outputProfile. Use applyTransform to apply the transform to a given
@ -440,8 +468,7 @@ def buildTransform(
raise PyCMSError("renderingIntent must be an integer between 0 and 3") raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError( raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
"flags must be an integer between 0 and %s" + _MAX_FLAG)
try: try:
if not isinstance(inputProfile, ImageCmsProfile): if not isinstance(inputProfile, ImageCmsProfile):
@ -449,17 +476,22 @@ def buildTransform(
if not isinstance(outputProfile, ImageCmsProfile): if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile) outputProfile = ImageCmsProfile(outputProfile)
return ImageCmsTransform( return ImageCmsTransform(
inputProfile, outputProfile, inMode, outMode, inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
renderingIntent, flags=flags) )
except (IOError, TypeError, ValueError) as v: except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def buildProofTransform( def buildProofTransform(
inputProfile, outputProfile, proofProfile, inMode, outMode, inputProfile,
renderingIntent=INTENT_PERCEPTUAL, outputProfile,
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, proofProfile,
flags=FLAGS["SOFTPROOFING"]): inMode,
outMode,
renderingIntent=INTENT_PERCEPTUAL,
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
flags=FLAGS["SOFTPROOFING"],
):
""" """
(pyCMS) Builds an ICC transform mapping from the inputProfile to the (pyCMS) Builds an ICC transform mapping from the inputProfile to the
outputProfile, but tries to simulate the result that would be outputProfile, but tries to simulate the result that would be
@ -538,8 +570,7 @@ def buildProofTransform(
raise PyCMSError("renderingIntent must be an integer between 0 and 3") raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError( raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
"flags must be an integer between 0 and %s" + _MAX_FLAG)
try: try:
if not isinstance(inputProfile, ImageCmsProfile): if not isinstance(inputProfile, ImageCmsProfile):
@ -549,8 +580,15 @@ def buildProofTransform(
if not isinstance(proofProfile, ImageCmsProfile): if not isinstance(proofProfile, ImageCmsProfile):
proofProfile = ImageCmsProfile(proofProfile) proofProfile = ImageCmsProfile(proofProfile)
return ImageCmsTransform( return ImageCmsTransform(
inputProfile, outputProfile, inMode, outMode, renderingIntent, inputProfile,
proofProfile, proofRenderingIntent, flags) outputProfile,
inMode,
outMode,
renderingIntent,
proofProfile,
proofRenderingIntent,
flags,
)
except (IOError, TypeError, ValueError) as v: except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
@ -641,15 +679,16 @@ def createProfile(colorSpace, colorTemp=-1):
if colorSpace not in ["LAB", "XYZ", "sRGB"]: if colorSpace not in ["LAB", "XYZ", "sRGB"]:
raise PyCMSError( raise PyCMSError(
"Color space not supported for on-the-fly profile creation (%s)" "Color space not supported for on-the-fly profile creation (%s)"
% colorSpace) % colorSpace
)
if colorSpace == "LAB": if colorSpace == "LAB":
try: try:
colorTemp = float(colorTemp) colorTemp = float(colorTemp)
except (TypeError, ValueError): except (TypeError, ValueError):
raise PyCMSError( raise PyCMSError(
"Color temperature must be numeric, \"%s\" not valid" 'Color temperature must be numeric, "%s" not valid' % colorTemp
% colorTemp) )
try: try:
return core.createProfile(colorSpace, colorTemp) return core.createProfile(colorSpace, colorTemp)
@ -948,7 +987,4 @@ def versions():
(pyCMS) Fetches versions. (pyCMS) Fetches versions.
""" """
return ( return (VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__)
VERSION, core.littlecms_version,
sys.version.split()[0], Image.__version__
)

View File

@ -41,95 +41,77 @@ def getrgb(color):
return rgb return rgb
# check for known string formats # check for known string formats
if re.match('#[a-f0-9]{3}$', color): if re.match("#[a-f0-9]{3}$", color):
return ( return (int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16))
int(color[1]*2, 16),
int(color[2]*2, 16),
int(color[3]*2, 16),
)
if re.match('#[a-f0-9]{4}$', color): if re.match("#[a-f0-9]{4}$", color):
return ( return (
int(color[1]*2, 16), int(color[1] * 2, 16),
int(color[2]*2, 16), int(color[2] * 2, 16),
int(color[3]*2, 16), int(color[3] * 2, 16),
int(color[4]*2, 16), int(color[4] * 2, 16),
) )
if re.match('#[a-f0-9]{6}$', color): if re.match("#[a-f0-9]{6}$", color):
return ( return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16))
int(color[1:3], 16),
int(color[3:5], 16),
int(color[5:7], 16),
)
if re.match('#[a-f0-9]{8}$', color): if re.match("#[a-f0-9]{8}$", color):
return ( return (
int(color[1:3], 16), int(color[1:3], 16),
int(color[3:5], 16), int(color[3:5], 16),
int(color[5:7], 16), int(color[5:7], 16),
int(color[7:9], 16), int(color[7:9], 16),
) )
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
if m: if m:
return ( return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
int(m.group(1)),
int(m.group(2)),
int(m.group(3))
)
m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color) m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
if m: if m:
return ( return (
int((int(m.group(1)) * 255) / 100.0 + 0.5), int((int(m.group(1)) * 255) / 100.0 + 0.5),
int((int(m.group(2)) * 255) / 100.0 + 0.5), int((int(m.group(2)) * 255) / 100.0 + 0.5),
int((int(m.group(3)) * 255) / 100.0 + 0.5) int((int(m.group(3)) * 255) / 100.0 + 0.5),
) )
m = re.match( m = re.match(
r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
color,
) )
if m: if m:
from colorsys import hls_to_rgb from colorsys import hls_to_rgb
rgb = hls_to_rgb( rgb = hls_to_rgb(
float(m.group(1)) / 360.0, float(m.group(1)) / 360.0,
float(m.group(3)) / 100.0, float(m.group(3)) / 100.0,
float(m.group(2)) / 100.0, float(m.group(2)) / 100.0,
) )
return ( return (
int(rgb[0] * 255 + 0.5), int(rgb[0] * 255 + 0.5),
int(rgb[1] * 255 + 0.5), int(rgb[1] * 255 + 0.5),
int(rgb[2] * 255 + 0.5) int(rgb[2] * 255 + 0.5),
) )
m = re.match( m = re.match(
r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
color,
) )
if m: if m:
from colorsys import hsv_to_rgb from colorsys import hsv_to_rgb
rgb = hsv_to_rgb( rgb = hsv_to_rgb(
float(m.group(1)) / 360.0, float(m.group(1)) / 360.0,
float(m.group(2)) / 100.0, float(m.group(2)) / 100.0,
float(m.group(3)) / 100.0, float(m.group(3)) / 100.0,
) )
return ( return (
int(rgb[0] * 255 + 0.5), int(rgb[0] * 255 + 0.5),
int(rgb[1] * 255 + 0.5), int(rgb[1] * 255 + 0.5),
int(rgb[2] * 255 + 0.5) int(rgb[2] * 255 + 0.5),
) )
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
color)
if m: if m:
return ( return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)))
int(m.group(1)),
int(m.group(2)),
int(m.group(3)),
int(m.group(4))
)
raise ValueError("unknown color specifier: %r" % color) raise ValueError("unknown color specifier: %r" % color)
@ -151,11 +133,11 @@ def getcolor(color, mode):
if Image.getmodebase(mode) == "L": if Image.getmodebase(mode) == "L":
r, g, b = color r, g, b = color
color = (r*299 + g*587 + b*114)//1000 color = (r * 299 + g * 587 + b * 114) // 1000
if mode[-1] == 'A': if mode[-1] == "A":
return (color, alpha) return (color, alpha)
else: else:
if mode[-1] == 'A': if mode[-1] == "A":
return color + (alpha,) return color + (alpha,)
return color return color

View File

@ -45,7 +45,6 @@ directly.
class ImageDraw(object): class ImageDraw(object):
def __init__(self, im, mode=None): def __init__(self, im, mode=None):
""" """
Create a drawing instance. Create a drawing instance.
@ -95,6 +94,7 @@ class ImageDraw(object):
if not self.font: if not self.font:
# FIXME: should add a font repository # FIXME: should add a font repository
from . import ImageFont from . import ImageFont
self.font = ImageFont.load_default() self.font = ImageFont.load_default()
return self.font return self.font
@ -156,13 +156,12 @@ class ImageDraw(object):
if ink is not None: if ink is not None:
self.draw.draw_lines(xy, ink, width) self.draw.draw_lines(xy, ink, width)
if joint == "curve" and width > 4: if joint == "curve" and width > 4:
for i in range(1, len(xy)-1): for i in range(1, len(xy) - 1):
point = xy[i] point = xy[i]
angles = [ angles = [
math.degrees(math.atan2( math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))
end[0] - start[0], start[1] - end[1] % 360
)) % 360 for start, end in ((xy[i - 1], point), (point, xy[i + 1]))
for start, end in ((xy[i-1], point), (point, xy[i+1]))
] ]
if angles[0] == angles[1]: if angles[0] == angles[1]:
# This is a straight line, so no joint is required # This is a straight line, so no joint is required
@ -171,21 +170,23 @@ class ImageDraw(object):
def coord_at_angle(coord, angle): def coord_at_angle(coord, angle):
x, y = coord x, y = coord
angle -= 90 angle -= 90
distance = width/2 - 1 distance = width / 2 - 1
return tuple([ return tuple(
p + [
(math.floor(p_d) if p_d > 0 else math.ceil(p_d)) p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
for p, p_d in for p, p_d in (
((x, distance * math.cos(math.radians(angle))), (x, distance * math.cos(math.radians(angle))),
(y, distance * math.sin(math.radians(angle)))) (y, distance * math.sin(math.radians(angle))),
]) )
flipped = ((angles[1] > angles[0] and ]
angles[1] - 180 > angles[0]) or )
(angles[1] < angles[0] and
angles[1] + 180 > angles[0])) flipped = (
angles[1] > angles[0] and angles[1] - 180 > angles[0]
) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])
coords = [ coords = [
(point[0] - width/2 + 1, point[1] - width/2 + 1), (point[0] - width / 2 + 1, point[1] - width / 2 + 1),
(point[0] + width/2 - 1, point[1] + width/2 - 1) (point[0] + width / 2 - 1, point[1] + width / 2 - 1),
] ]
if flipped: if flipped:
start, end = (angles[1] + 90, angles[0] + 90) start, end = (angles[1] + 90, angles[0] + 90)
@ -197,15 +198,15 @@ class ImageDraw(object):
# Cover potential gaps between the line and the joint # Cover potential gaps between the line and the joint
if flipped: if flipped:
gapCoords = [ gapCoords = [
coord_at_angle(point, angles[0]+90), coord_at_angle(point, angles[0] + 90),
point, point,
coord_at_angle(point, angles[1]+90) coord_at_angle(point, angles[1] + 90),
] ]
else: else:
gapCoords = [ gapCoords = [
coord_at_angle(point, angles[0]-90), coord_at_angle(point, angles[0] - 90),
point, point,
coord_at_angle(point, angles[1]-90) coord_at_angle(point, angles[1] - 90),
] ]
self.line(gapCoords, fill, width=3) self.line(gapCoords, fill, width=3)
@ -259,11 +260,9 @@ class ImageDraw(object):
return text.split(split_character) return text.split(split_character)
def text(self, xy, text, fill=None, font=None, anchor=None, def text(self, xy, text, fill=None, font=None, anchor=None, *args, **kwargs):
*args, **kwargs):
if self._multiline_check(text): if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor, return self.multiline_text(xy, text, fill, font, anchor, *args, **kwargs)
*args, **kwargs)
ink, fill = self._getink(fill) ink, fill = self._getink(fill)
if font is None: if font is None:
font = self.getfont() font = self.getfont()
@ -271,8 +270,7 @@ class ImageDraw(object):
ink = fill ink = fill
if ink is not None: if ink is not None:
try: try:
mask, offset = font.getmask2(text, self.fontmode, mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
*args, **kwargs)
xy = xy[0] + offset[0], xy[1] + offset[1] xy = xy[0] + offset[0], xy[1] + offset[1]
except AttributeError: except AttributeError:
try: try:
@ -281,18 +279,27 @@ class ImageDraw(object):
mask = font.getmask(text) mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink) self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None, def multiline_text(
spacing=4, align="left", direction=None, features=None, self,
language=None): xy,
text,
fill=None,
font=None,
anchor=None,
spacing=4,
align="left",
direction=None,
features=None,
language=None,
):
widths = [] widths = []
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing line_spacing = self.textsize("A", font=font)[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.textsize(line, font, line_width, line_height = self.textsize(
direction=direction, line, font, direction=direction, features=features, language=language
features=features, )
language=language)
widths.append(line_width) widths.append(line_width)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
left, top = xy left, top = xy
@ -302,36 +309,47 @@ class ImageDraw(object):
elif align == "center": elif align == "center":
left += (max_width - widths[idx]) / 2.0 left += (max_width - widths[idx]) / 2.0
elif align == "right": elif align == "right":
left += (max_width - widths[idx]) left += max_width - widths[idx]
else: else:
raise ValueError('align must be "left", "center" or "right"') raise ValueError('align must be "left", "center" or "right"')
self.text((left, top), line, fill, font, anchor, self.text(
direction=direction, features=features, language=language) (left, top),
line,
fill,
font,
anchor,
direction=direction,
features=features,
language=language,
)
top += line_spacing top += line_spacing
left = xy[0] left = xy[0]
def textsize(self, text, font=None, spacing=4, direction=None, def textsize(
features=None, language=None): self, text, font=None, spacing=4, direction=None, features=None, language=None
):
"""Get the size of a given string, in pixels.""" """Get the size of a given string, in pixels."""
if self._multiline_check(text): if self._multiline_check(text):
return self.multiline_textsize(text, font, spacing, return self.multiline_textsize(
direction, features, language) text, font, spacing, direction, features, language
)
if font is None: if font is None:
font = self.getfont() font = self.getfont()
return font.getsize(text, direction, features, language) return font.getsize(text, direction, features, language)
def multiline_textsize(self, text, font=None, spacing=4, direction=None, def multiline_textsize(
features=None, language=None): self, text, font=None, spacing=4, direction=None, features=None, language=None
):
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing line_spacing = self.textsize("A", font=font)[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.textsize(line, font, spacing, line_width, line_height = self.textsize(
direction, features, line, font, spacing, direction, features, language
language) )
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing return max_width, len(lines) * line_spacing - spacing
def Draw(im, mode=None): def Draw(im, mode=None):
@ -417,7 +435,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
while edge: while edge:
new_edge = set() new_edge = set()
for (x, y) in edge: # 4 adjacent method for (x, y) in edge: # 4 adjacent method
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): for (s, t) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if (s, t) in full_edge: if (s, t) in full_edge:
continue # if already processed, skip continue # if already processed, skip
try: try:
@ -442,6 +460,6 @@ def _color_diff(color1, color2):
Uses 1-norm distance to calculate difference between two values. Uses 1-norm distance to calculate difference between two values.
""" """
if isinstance(color2, tuple): if isinstance(color2, tuple):
return sum([abs(color1[i]-color2[i]) for i in range(0, len(color2))]) return sum([abs(color1[i] - color2[i]) for i in range(0, len(color2))])
else: else:
return abs(color1-color2) return abs(color1 - color2)

View File

@ -38,7 +38,6 @@ class Font(object):
class Draw(object): class Draw(object):
def __init__(self, image, size=None, color=None): def __init__(self, image, size=None, color=None):
if not hasattr(image, "im"): if not hasattr(image, "im"):
image = Image.new(image, size, color) image = Image.new(image, size, color)

View File

@ -22,7 +22,6 @@ from . import Image, ImageFilter, ImageStat
class _Enhance(object): class _Enhance(object):
def enhance(self, factor): def enhance(self, factor):
""" """
Returns an enhanced image. Returns an enhanced image.
@ -45,14 +44,14 @@ class Color(_Enhance):
factor of 0.0 gives a black and white image. A factor of 1.0 gives factor of 0.0 gives a black and white image. A factor of 1.0 gives
the original image. the original image.
""" """
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.intermediate_mode = 'L' self.intermediate_mode = "L"
if 'A' in image.getbands(): if "A" in image.getbands():
self.intermediate_mode = 'LA' self.intermediate_mode = "LA"
self.degenerate = image.convert( self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
self.intermediate_mode).convert(image.mode)
class Contrast(_Enhance): class Contrast(_Enhance):
@ -62,13 +61,14 @@ class Contrast(_Enhance):
to the contrast control on a TV set. An enhancement factor of 0.0 to the contrast control on a TV set. An enhancement factor of 0.0
gives a solid grey image. A factor of 1.0 gives the original image. gives a solid grey image. A factor of 1.0 gives the original image.
""" """
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5) mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
self.degenerate = Image.new("L", image.size, mean).convert(image.mode) self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
if 'A' in image.getbands(): if "A" in image.getbands():
self.degenerate.putalpha(image.getchannel('A')) self.degenerate.putalpha(image.getchannel("A"))
class Brightness(_Enhance): class Brightness(_Enhance):
@ -78,12 +78,13 @@ class Brightness(_Enhance):
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
original image. original image.
""" """
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.degenerate = Image.new(image.mode, image.size, 0) self.degenerate = Image.new(image.mode, image.size, 0)
if 'A' in image.getbands(): if "A" in image.getbands():
self.degenerate.putalpha(image.getchannel('A')) self.degenerate.putalpha(image.getchannel("A"))
class Sharpness(_Enhance): class Sharpness(_Enhance):
@ -93,9 +94,10 @@ class Sharpness(_Enhance):
enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the
original image, and a factor of 2.0 gives a sharpened image. original image, and a factor of 2.0 gives a sharpened image.
""" """
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.degenerate = image.filter(ImageFilter.SMOOTH) self.degenerate = image.filter(ImageFilter.SMOOTH)
if 'A' in image.getbands(): if "A" in image.getbands():
self.degenerate.putalpha(image.getchannel('A')) self.degenerate.putalpha(image.getchannel("A"))

View File

@ -35,7 +35,7 @@ import struct
MAXBLOCK = 65536 MAXBLOCK = 65536
SAFEBLOCK = 1024*1024 SAFEBLOCK = 1024 * 1024
LOAD_TRUNCATED_IMAGES = False LOAD_TRUNCATED_IMAGES = False
@ -44,7 +44,7 @@ ERRORS = {
-2: "decoding error", -2: "decoding error",
-3: "unknown error", -3: "unknown error",
-8: "bad configuration", -8: "bad configuration",
-9: "out of memory error" -9: "out of memory error",
} }
@ -62,6 +62,7 @@ def raise_ioerror(error):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helpers # Helpers
def _tilesort(t): def _tilesort(t):
# sort on offset # sort on offset
return t[2] return t[2]
@ -71,6 +72,7 @@ def _tilesort(t):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# ImageFile base class # ImageFile base class
class ImageFile(Image.Image): class ImageFile(Image.Image):
"Base class for image file format handlers." "Base class for image file format handlers."
@ -101,11 +103,13 @@ class ImageFile(Image.Image):
try: try:
self._open() self._open()
except (IndexError, # end of data except (
TypeError, # end of data (ord) IndexError, # end of data
KeyError, # unsupported mode TypeError, # end of data (ord)
EOFError, # got header but not the first frame KeyError, # unsupported mode
struct.error) as v: EOFError, # got header but not the first frame
struct.error,
) as v:
# close the file only if we have opened it this constructor # close the file only if we have opened it this constructor
if self._exclusive_fp: if self._exclusive_fp:
self.fp.close() self.fp.close()
@ -147,7 +151,7 @@ class ImageFile(Image.Image):
self.map = None self.map = None
use_mmap = self.filename and len(self.tile) == 1 use_mmap = self.filename and len(self.tile) == 1
# As of pypy 2.1.0, memory mapping was failing here. # As of pypy 2.1.0, memory mapping was failing here.
use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info') use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
readonly = 0 readonly = 0
@ -168,9 +172,12 @@ class ImageFile(Image.Image):
if use_mmap: if use_mmap:
# try memory mapping # try memory mapping
decoder_name, extents, offset, args = self.tile[0] decoder_name, extents, offset, args = self.tile[0]
if decoder_name == "raw" and len(args) >= 3 and \ if (
args[0] == self.mode and \ decoder_name == "raw"
args[0] in Image._MAPMODES: and len(args) >= 3
and args[0] == self.mode
and args[0] in Image._MAPMODES
):
try: try:
if hasattr(Image.core, "map"): if hasattr(Image.core, "map"):
# use built-in mapper WIN32 only # use built-in mapper WIN32 only
@ -178,16 +185,18 @@ class ImageFile(Image.Image):
self.map.seek(offset) self.map.seek(offset)
self.im = self.map.readimage( self.im = self.map.readimage(
self.mode, self.size, args[1], args[2] self.mode, self.size, args[1], args[2]
) )
else: else:
# use mmap, if possible # use mmap, if possible
import mmap import mmap
with open(self.filename, "r") as fp: with open(self.filename, "r") as fp:
self.map = mmap.mmap(fp.fileno(), 0, self.map = mmap.mmap(
access=mmap.ACCESS_READ) fp.fileno(), 0, access=mmap.ACCESS_READ
)
self.im = Image.core.map_buffer( self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, extents, self.map, self.size, decoder_name, extents, offset, args
offset, args) )
readonly = 1 readonly = 1
# After trashing self.im, # After trashing self.im,
# we might need to reload the palette data. # we might need to reload the palette data.
@ -209,8 +218,9 @@ class ImageFile(Image.Image):
prefix = b"" prefix = b""
for decoder_name, extents, offset, args in self.tile: for decoder_name, extents, offset, args in self.tile:
decoder = Image._getdecoder(self.mode, decoder_name, decoder = Image._getdecoder(
args, self.decoderconfig) self.mode, decoder_name, args, self.decoderconfig
)
try: try:
seek(offset) seek(offset)
decoder.setimage(self.im, extents) decoder.setimage(self.im, extents)
@ -234,9 +244,10 @@ class ImageFile(Image.Image):
break break
else: else:
self.tile = [] self.tile = []
raise IOError("image file is truncated " raise IOError(
"(%d bytes not processed)" % "image file is truncated "
len(b)) "(%d bytes not processed)" % len(b)
)
b = b + s b = b + s
n, err_code = decoder.decode(b) n, err_code = decoder.decode(b)
@ -264,8 +275,7 @@ class ImageFile(Image.Image):
def load_prepare(self): def load_prepare(self):
# create image memory if necessary # create image memory if necessary
if not self.im or\ if not self.im or self.im.mode != self.mode or self.im.size != self.size:
self.im.mode != self.mode or self.im.size != self.size:
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
# create palette (optional) # create palette (optional)
if self.mode == "P": if self.mode == "P":
@ -284,11 +294,15 @@ class ImageFile(Image.Image):
# pass # pass
def _seek_check(self, frame): def _seek_check(self, frame):
if (frame < self._min_frame or if (
frame < self._min_frame
# Only check upper limit on frames if additional seek operations # Only check upper limit on frames if additional seek operations
# are not required to do so # are not required to do so
(not (hasattr(self, "_n_frames") and self._n_frames is None) and or (
frame >= self.n_frames+self._min_frame)): not (hasattr(self, "_n_frames") and self._n_frames is None)
and frame >= self.n_frames + self._min_frame
)
):
raise EOFError("attempt to seek outside sequence") raise EOFError("attempt to seek outside sequence")
return self.tell() != frame return self.tell() != frame
@ -303,9 +317,7 @@ class StubImageFile(ImageFile):
""" """
def _open(self): def _open(self):
raise NotImplementedError( raise NotImplementedError("StubImageFile subclass must implement _open")
"StubImageFile subclass must implement _open"
)
def load(self): def load(self):
loader = self._load() loader = self._load()
@ -319,9 +331,7 @@ class StubImageFile(ImageFile):
def _load(self): def _load(self):
"""(Hook) Find actual image loader.""" """(Hook) Find actual image loader."""
raise NotImplementedError( raise NotImplementedError("StubImageFile subclass must implement _load")
"StubImageFile subclass must implement _load"
)
class Parser(object): class Parser(object):
@ -329,6 +339,7 @@ class Parser(object):
Incremental image parser. This class implements the standard Incremental image parser. This class implements the standard
feed/close consumer interface. feed/close consumer interface.
""" """
incremental = None incremental = None
image = None image = None
data = None data = None
@ -413,15 +424,13 @@ class Parser(object):
im.load_prepare() im.load_prepare()
d, e, o, a = im.tile[0] d, e, o, a = im.tile[0]
im.tile = [] im.tile = []
self.decoder = Image._getdecoder( self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig)
im.mode, d, a, im.decoderconfig
)
self.decoder.setimage(im.im, e) self.decoder.setimage(im.im, e)
# calculate decoder offset # calculate decoder offset
self.offset = o self.offset = o
if self.offset <= len(self.data): if self.offset <= len(self.data):
self.data = self.data[self.offset:] self.data = self.data[self.offset :]
self.offset = 0 self.offset = 0
self.image = im self.image = im
@ -463,6 +472,7 @@ class Parser(object):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _save(im, fp, tile, bufsize=0): def _save(im, fp, tile, bufsize=0):
"""Helper to save image based on tile list """Helper to save image based on tile list
@ -557,8 +567,7 @@ class PyCodecState(object):
self.yoff = 0 self.yoff = 0
def extents(self): def extents(self):
return (self.xoff, self.yoff, return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
self.xoff+self.xsize, self.yoff+self.ysize)
class PyDecoder(object): class PyDecoder(object):
@ -648,8 +657,10 @@ class PyDecoder(object):
if self.state.xsize <= 0 or self.state.ysize <= 0: if self.state.xsize <= 0 or self.state.ysize <= 0:
raise ValueError("Size cannot be negative") raise ValueError("Size cannot be negative")
if (self.state.xsize + self.state.xoff > self.im.size[0] or if (
self.state.ysize + self.state.yoff > self.im.size[1]): self.state.xsize + self.state.xoff > self.im.size[0]
or self.state.ysize + self.state.yoff > self.im.size[1]
):
raise ValueError("Tile cannot extend outside image") raise ValueError("Tile cannot extend outside image")
def set_as_raw(self, data, rawmode=None): def set_as_raw(self, data, rawmode=None):
@ -664,7 +675,7 @@ class PyDecoder(object):
if not rawmode: if not rawmode:
rawmode = self.mode rawmode = self.mode
d = Image._getdecoder(self.mode, 'raw', (rawmode)) d = Image._getdecoder(self.mode, "raw", (rawmode))
d.setimage(self.im, self.state.extents()) d.setimage(self.im, self.state.extents())
s = d.decode(data) s = d.decode(data)

View File

@ -57,12 +57,13 @@ class Kernel(BuiltinFilter):
:param offset: Offset. If given, this value is added to the result, :param offset: Offset. If given, this value is added to the result,
after it has been divided by the scale factor. after it has been divided by the scale factor.
""" """
name = "Kernel" name = "Kernel"
def __init__(self, size, kernel, scale=None, offset=0): def __init__(self, size, kernel, scale=None, offset=0):
if scale is None: if scale is None:
# default scale is sum of kernel # default scale is sum of kernel
scale = functools.reduce(lambda a, b: a+b, kernel) scale = functools.reduce(lambda a, b: a + b, kernel)
if size[0] * size[1] != len(kernel): if size[0] * size[1] != len(kernel):
raise ValueError("not enough coefficients in kernel") raise ValueError("not enough coefficients in kernel")
self.filterargs = size, scale, offset, kernel self.filterargs = size, scale, offset, kernel
@ -78,6 +79,7 @@ class RankFilter(Filter):
``size * size / 2`` for a median filter, ``size * size - 1`` ``size * size / 2`` for a median filter, ``size * size - 1``
for a max filter, etc. for a max filter, etc.
""" """
name = "Rank" name = "Rank"
def __init__(self, size, rank): def __init__(self, size, rank):
@ -87,7 +89,7 @@ class RankFilter(Filter):
def filter(self, image): def filter(self, image):
if image.mode == "P": if image.mode == "P":
raise ValueError("cannot filter palette images") raise ValueError("cannot filter palette images")
image = image.expand(self.size//2, self.size//2) image = image.expand(self.size // 2, self.size // 2)
return image.rankfilter(self.size, self.rank) return image.rankfilter(self.size, self.rank)
@ -98,11 +100,12 @@ class MedianFilter(RankFilter):
:param size: The kernel size, in pixels. :param size: The kernel size, in pixels.
""" """
name = "Median" name = "Median"
def __init__(self, size=3): def __init__(self, size=3):
self.size = size self.size = size
self.rank = size*size//2 self.rank = size * size // 2
class MinFilter(RankFilter): class MinFilter(RankFilter):
@ -112,6 +115,7 @@ class MinFilter(RankFilter):
:param size: The kernel size, in pixels. :param size: The kernel size, in pixels.
""" """
name = "Min" name = "Min"
def __init__(self, size=3): def __init__(self, size=3):
@ -126,11 +130,12 @@ class MaxFilter(RankFilter):
:param size: The kernel size, in pixels. :param size: The kernel size, in pixels.
""" """
name = "Max" name = "Max"
def __init__(self, size=3): def __init__(self, size=3):
self.size = size self.size = size
self.rank = size*size-1 self.rank = size * size - 1
class ModeFilter(Filter): class ModeFilter(Filter):
@ -141,6 +146,7 @@ class ModeFilter(Filter):
:param size: The kernel size, in pixels. :param size: The kernel size, in pixels.
""" """
name = "Mode" name = "Mode"
def __init__(self, size=3): def __init__(self, size=3):
@ -155,6 +161,7 @@ class GaussianBlur(MultibandFilter):
:param radius: Blur radius. :param radius: Blur radius.
""" """
name = "GaussianBlur" name = "GaussianBlur"
def __init__(self, radius=2): def __init__(self, radius=2):
@ -175,6 +182,7 @@ class BoxBlur(MultibandFilter):
returns an identical image. Radius 1 takes 1 pixel returns an identical image. Radius 1 takes 1 pixel
in each direction, i.e. 9 pixels in total. in each direction, i.e. 9 pixels in total.
""" """
name = "BoxBlur" name = "BoxBlur"
def __init__(self, radius): def __init__(self, radius):
@ -198,6 +206,7 @@ class UnsharpMask(MultibandFilter):
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
""" # noqa: E501 """ # noqa: E501
name = "UnsharpMask" name = "UnsharpMask"
def __init__(self, radius=2, percent=150, threshold=3): def __init__(self, radius=2, percent=150, threshold=3):
@ -211,96 +220,116 @@ class UnsharpMask(MultibandFilter):
class BLUR(BuiltinFilter): class BLUR(BuiltinFilter):
name = "Blur" name = "Blur"
# fmt: off
filterargs = (5, 5), 16, 0, ( filterargs = (5, 5), 16, 0, (
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 0, 0, 0, 1,
1, 1, 1, 1, 1 1, 1, 1, 1, 1,
) )
# fmt: on
class CONTOUR(BuiltinFilter): class CONTOUR(BuiltinFilter):
name = "Contour" name = "Contour"
# fmt: off
filterargs = (3, 3), 1, 255, ( filterargs = (3, 3), 1, 255, (
-1, -1, -1, -1, -1, -1,
-1, 8, -1, -1, 8, -1,
-1, -1, -1 -1, -1, -1,
) )
# fmt: on
class DETAIL(BuiltinFilter): class DETAIL(BuiltinFilter):
name = "Detail" name = "Detail"
# fmt: off
filterargs = (3, 3), 6, 0, ( filterargs = (3, 3), 6, 0, (
0, -1, 0, 0, -1, 0,
-1, 10, -1, -1, 10, -1,
0, -1, 0 0, -1, 0,
) )
# fmt: on
class EDGE_ENHANCE(BuiltinFilter): class EDGE_ENHANCE(BuiltinFilter):
name = "Edge-enhance" name = "Edge-enhance"
# fmt: off
filterargs = (3, 3), 2, 0, ( filterargs = (3, 3), 2, 0, (
-1, -1, -1, -1, -1, -1,
-1, 10, -1, -1, 10, -1,
-1, -1, -1 -1, -1, -1,
) )
# fmt: on
class EDGE_ENHANCE_MORE(BuiltinFilter): class EDGE_ENHANCE_MORE(BuiltinFilter):
name = "Edge-enhance More" name = "Edge-enhance More"
# fmt: off
filterargs = (3, 3), 1, 0, ( filterargs = (3, 3), 1, 0, (
-1, -1, -1, -1, -1, -1,
-1, 9, -1, -1, 9, -1,
-1, -1, -1 -1, -1, -1,
) )
# fmt: on
class EMBOSS(BuiltinFilter): class EMBOSS(BuiltinFilter):
name = "Emboss" name = "Emboss"
# fmt: off
filterargs = (3, 3), 1, 128, ( filterargs = (3, 3), 1, 128, (
-1, 0, 0, -1, 0, 0,
0, 1, 0, 0, 1, 0,
0, 0, 0 0, 0, 0,
) )
# fmt: on
class FIND_EDGES(BuiltinFilter): class FIND_EDGES(BuiltinFilter):
name = "Find Edges" name = "Find Edges"
# fmt: off
filterargs = (3, 3), 1, 0, ( filterargs = (3, 3), 1, 0, (
-1, -1, -1, -1, -1, -1,
-1, 8, -1, -1, 8, -1,
-1, -1, -1 -1, -1, -1,
) )
# fmt: on
class SHARPEN(BuiltinFilter): class SHARPEN(BuiltinFilter):
name = "Sharpen" name = "Sharpen"
# fmt: off
filterargs = (3, 3), 16, 0, ( filterargs = (3, 3), 16, 0, (
-2, -2, -2, -2, -2, -2,
-2, 32, -2, -2, 32, -2,
-2, -2, -2 -2, -2, -2,
) )
# fmt: on
class SMOOTH(BuiltinFilter): class SMOOTH(BuiltinFilter):
name = "Smooth" name = "Smooth"
# fmt: off
filterargs = (3, 3), 13, 0, ( filterargs = (3, 3), 13, 0, (
1, 1, 1, 1, 1, 1,
1, 5, 1, 1, 5, 1,
1, 1, 1 1, 1, 1,
) )
# fmt: on
class SMOOTH_MORE(BuiltinFilter): class SMOOTH_MORE(BuiltinFilter):
name = "Smooth More" name = "Smooth More"
# fmt: off
filterargs = (5, 5), 100, 0, ( filterargs = (5, 5), 100, 0, (
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 5, 5, 5, 1, 1, 5, 5, 5, 1,
1, 5, 44, 5, 1, 1, 5, 44, 5, 1,
1, 5, 5, 5, 1, 1, 5, 5, 5, 1,
1, 1, 1, 1, 1 1, 1, 1, 1, 1,
) )
# fmt: on
class Color3DLUT(MultibandFilter): class Color3DLUT(MultibandFilter):
@ -327,6 +356,7 @@ class Color3DLUT(MultibandFilter):
than ``channels`` channels. Default is ``None``, than ``channels`` channels. Default is ``None``,
which means that mode wouldn't be changed. which means that mode wouldn't be changed.
""" """
name = "Color 3D LUT" name = "Color 3D LUT"
def __init__(self, size, table, channels=3, target_mode=None, **kwargs): def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
@ -338,7 +368,7 @@ class Color3DLUT(MultibandFilter):
# Hidden flag `_copy_table=False` could be used to avoid extra copying # Hidden flag `_copy_table=False` could be used to avoid extra copying
# of the table if the table is specially made for the constructor. # of the table if the table is specially made for the constructor.
copy_table = kwargs.get('_copy_table', True) copy_table = kwargs.get("_copy_table", True)
items = size[0] * size[1] * size[2] items = size[0] * size[1] * size[2]
wrong_size = False wrong_size = False
@ -346,8 +376,11 @@ class Color3DLUT(MultibandFilter):
if copy_table: if copy_table:
table = table.copy() table = table.copy()
if table.shape in [(items * channels,), (items, channels), if table.shape in [
(size[2], size[1], size[0], channels)]: (items * channels,),
(items, channels),
(size[2], size[1], size[0], channels),
]:
table = table.reshape(items * channels) table = table.reshape(items * channels)
else: else:
wrong_size = True wrong_size = True
@ -363,7 +396,8 @@ class Color3DLUT(MultibandFilter):
if len(pixel) != channels: if len(pixel) != channels:
raise ValueError( raise ValueError(
"The elements of the table should " "The elements of the table should "
"have a length of {}.".format(channels)) "have a length of {}.".format(channels)
)
table.extend(pixel) table.extend(pixel)
if wrong_size or len(table) != items * channels: if wrong_size or len(table) != items * channels:
@ -371,7 +405,9 @@ class Color3DLUT(MultibandFilter):
"The table should have either channels * size**3 float items " "The table should have either channels * size**3 float items "
"or size**3 items of channels-sized tuples with floats. " "or size**3 items of channels-sized tuples with floats. "
"Table should be: {}x{}x{}x{}. Actual length: {}".format( "Table should be: {}x{}x{}x{}. Actual length: {}".format(
channels, size[0], size[1], size[2], len(table))) channels, size[0], size[1], size[2], len(table)
)
)
self.table = table self.table = table
@staticmethod @staticmethod
@ -379,8 +415,9 @@ class Color3DLUT(MultibandFilter):
try: try:
_, _, _ = size _, _, _ = size
except ValueError: except ValueError:
raise ValueError("Size should be either an integer or " raise ValueError(
"a tuple of three integers.") "Size should be either an integer or a tuple of three integers."
)
except TypeError: except TypeError:
size = (size, size, size) size = (size, size, size)
size = [int(x) for x in size] size = [int(x) for x in size]
@ -411,15 +448,20 @@ class Color3DLUT(MultibandFilter):
for b in range(size3D): for b in range(size3D):
for g in range(size2D): for g in range(size2D):
for r in range(size1D): for r in range(size1D):
table[idx_out:idx_out + channels] = callback( table[idx_out : idx_out + channels] = callback(
r / (size1D-1), g / (size2D-1), b / (size3D-1)) r / (size1D - 1), g / (size2D - 1), b / (size3D - 1)
)
idx_out += channels idx_out += channels
return cls((size1D, size2D, size3D), table, channels=channels, return cls(
target_mode=target_mode, _copy_table=False) (size1D, size2D, size3D),
table,
channels=channels,
target_mode=target_mode,
_copy_table=False,
)
def transform(self, callback, with_normals=False, channels=None, def transform(self, callback, with_normals=False, channels=None, target_mode=None):
target_mode=None):
"""Transforms the table values using provided callback and returns """Transforms the table values using provided callback and returns
a new LUT with altered values. a new LUT with altered values.
@ -450,24 +492,31 @@ class Color3DLUT(MultibandFilter):
for b in range(size3D): for b in range(size3D):
for g in range(size2D): for g in range(size2D):
for r in range(size1D): for r in range(size1D):
values = self.table[idx_in:idx_in + ch_in] values = self.table[idx_in : idx_in + ch_in]
if with_normals: if with_normals:
values = callback(r / (size1D-1), g / (size2D-1), values = callback(
b / (size3D-1), *values) r / (size1D - 1),
g / (size2D - 1),
b / (size3D - 1),
*values
)
else: else:
values = callback(*values) values = callback(*values)
table[idx_out:idx_out + ch_out] = values table[idx_out : idx_out + ch_out] = values
idx_in += ch_in idx_in += ch_in
idx_out += ch_out idx_out += ch_out
return type(self)(self.size, table, channels=ch_out, return type(self)(
target_mode=target_mode or self.mode, self.size,
_copy_table=False) table,
channels=ch_out,
target_mode=target_mode or self.mode,
_copy_table=False,
)
def __repr__(self): def __repr__(self):
r = [ r = [
"{} from {}".format(self.__class__.__name__, "{} from {}".format(self.__class__.__name__, self.table.__class__.__name__),
self.table.__class__.__name__),
"size={:d}x{:d}x{:d}".format(*self.size), "size={:d}x{:d}x{:d}".format(*self.size),
"channels={:d}".format(self.channels), "channels={:d}".format(self.channels),
] ]
@ -479,5 +528,11 @@ class Color3DLUT(MultibandFilter):
from . import Image from . import Image
return image.color_lut_3d( return image.color_lut_3d(
self.mode or image.mode, Image.LINEAR, self.channels, self.mode or image.mode,
self.size[0], self.size[1], self.size[2], self.table) Image.LINEAR,
self.channels,
self.size[0],
self.size[1],
self.size[2],
self.table,
)

View File

@ -98,7 +98,7 @@ class ImageFont(object):
self.info.append(s) self.info.append(s)
# read PILfont metrics # read PILfont metrics
data = file.read(256*20) data = file.read(256 * 20)
# check image # check image
if image.mode not in ("1", "L"): if image.mode not in ("1", "L"):
@ -119,11 +119,11 @@ class ImageFont(object):
# Wrapper for FreeType fonts. Application code should use the # Wrapper for FreeType fonts. Application code should use the
# <b>truetype</b> factory function to create font objects. # <b>truetype</b> factory function to create font objects.
class FreeTypeFont(object): class FreeTypeFont(object):
"FreeType font wrapper (requires _imagingft service)" "FreeType font wrapper (requires _imagingft service)"
def __init__(self, font=None, size=10, index=0, encoding="", def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
layout_engine=None):
# FIXME: use service provider instead # FIXME: use service provider instead
self.path = font self.path = font
@ -143,21 +143,23 @@ class FreeTypeFont(object):
def load_from_bytes(f): def load_from_bytes(f):
self.font_bytes = f.read() self.font_bytes = f.read()
self.font = core.getfont( self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine) "", size, index, encoding, self.font_bytes, layout_engine
)
if isPath(font): if isPath(font):
if sys.platform == "win32": if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode() font_bytes_path = font if isinstance(font, bytes) else font.encode()
try: try:
font_bytes_path.decode('ascii') font_bytes_path.decode("ascii")
except UnicodeDecodeError: except UnicodeDecodeError:
# FreeType cannot load fonts with non-ASCII characters on Windows # FreeType cannot load fonts with non-ASCII characters on Windows
# So load it into memory first # So load it into memory first
with open(font, 'rb') as f: with open(font, "rb") as f:
load_from_bytes(f) load_from_bytes(f)
return return
self.font = core.getfont(font, size, index, encoding, self.font = core.getfont(
layout_engine=layout_engine) font, size, index, encoding, layout_engine=layout_engine
)
else: else:
load_from_bytes(font) load_from_bytes(font)
@ -221,8 +223,9 @@ class FreeTypeFont(object):
size, offset = self.font.getsize(text, direction, features, language) size, offset = self.font.getsize(text, direction, features, language)
return (size[0] + offset[0], size[1] + offset[1]) return (size[0] + offset[0], size[1] + offset[1])
def getsize_multiline(self, text, direction=None, spacing=4, def getsize_multiline(
features=None, language=None): self, text, direction=None, spacing=4, features=None, language=None
):
""" """
Returns width and height (in pixels) of given text if rendered in font Returns width and height (in pixels) of given text if rendered in font
with provided direction, features, and language, while respecting with provided direction, features, and language, while respecting
@ -261,12 +264,12 @@ class FreeTypeFont(object):
""" """
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.getsize('A')[1] + spacing line_spacing = self.getsize("A")[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.getsize(line, direction, features, language) line_width, line_height = self.getsize(line, direction, features, language)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing return max_width, len(lines) * line_spacing - spacing
def getoffset(self, text): def getoffset(self, text):
""" """
@ -327,11 +330,21 @@ class FreeTypeFont(object):
:return: An internal PIL storage memory instance as defined by the :return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module. :py:mod:`PIL.Image.core` interface module.
""" """
return self.getmask2(text, mode, direction=direction, features=features, return self.getmask2(
language=language)[0] text, mode, direction=direction, features=features, language=language
)[0]
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, def getmask2(
features=None, language=None, *args, **kwargs): self,
text,
mode="",
fill=Image.core.fill,
direction=None,
features=None,
language=None,
*args,
**kwargs
):
""" """
Create a bitmap for the text. Create a bitmap for the text.
@ -384,8 +397,9 @@ class FreeTypeFont(object):
self.font.render(text, im.id, mode == "1", direction, features, language) self.font.render(text, im.id, mode == "1", direction, features, language)
return im, offset return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None, def font_variant(
layout_engine=None): self, font=None, size=None, index=None, encoding=None, layout_engine=None
):
""" """
Create a copy of this FreeTypeFont object, Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings. using any specified arguments to override the settings.
@ -400,7 +414,7 @@ class FreeTypeFont(object):
size=self.size if size is None else size, size=self.size if size is None else size,
index=self.index if index is None else index, index=self.index if index is None else index,
encoding=self.encoding if encoding is None else encoding, encoding=self.encoding if encoding is None else encoding,
layout_engine=layout_engine or self.layout_engine layout_engine=layout_engine or self.layout_engine,
) )
@ -447,8 +461,7 @@ def load(filename):
return f return f
def truetype(font=None, size=10, index=0, encoding="", def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
layout_engine=None):
""" """
Load a TrueType or OpenType font from a file or file-like object, Load a TrueType or OpenType font from a file or file-like object,
and create a font object. and create a font object.
@ -472,8 +485,10 @@ def truetype(font=None, size=10, index=0, encoding="",
:return: A font object. :return: A font object.
:exception IOError: If the file could not be read. :exception IOError: If the file could not be read.
""" """
def freetype(font): def freetype(font):
return FreeTypeFont(font, size, index, encoding, layout_engine) return FreeTypeFont(font, size, index, encoding, layout_engine)
try: try:
return freetype(font) return freetype(font)
except IOError: except IOError:
@ -487,17 +502,19 @@ def truetype(font=None, size=10, index=0, encoding="",
windir = os.environ.get("WINDIR") windir = os.environ.get("WINDIR")
if windir: if windir:
dirs.append(os.path.join(windir, "fonts")) dirs.append(os.path.join(windir, "fonts"))
elif sys.platform in ('linux', 'linux2'): elif sys.platform in ("linux", "linux2"):
lindirs = os.environ.get("XDG_DATA_DIRS", "") lindirs = os.environ.get("XDG_DATA_DIRS", "")
if not lindirs: if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should # According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share # default to /usr/share
lindirs = '/usr/share' lindirs = "/usr/share"
dirs += [os.path.join(lindir, "fonts") dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
for lindir in lindirs.split(":")] elif sys.platform == "darwin":
elif sys.platform == 'darwin': dirs += [
dirs += ['/Library/Fonts', '/System/Library/Fonts', "/Library/Fonts",
os.path.expanduser('~/Library/Fonts')] "/System/Library/Fonts",
os.path.expanduser("~/Library/Fonts"),
]
ext = os.path.splitext(ttf_filename)[1] ext = os.path.splitext(ttf_filename)[1]
first_font_with_a_different_extension = None first_font_with_a_different_extension = None
@ -506,13 +523,11 @@ def truetype(font=None, size=10, index=0, encoding="",
for walkfilename in walkfilenames: for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename: if ext and walkfilename == ttf_filename:
return freetype(os.path.join(walkroot, walkfilename)) return freetype(os.path.join(walkroot, walkfilename))
elif (not ext and elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
os.path.splitext(walkfilename)[0] == ttf_filename):
fontpath = os.path.join(walkroot, walkfilename) fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == '.ttf': if os.path.splitext(fontpath)[1] == ".ttf":
return freetype(fontpath) return freetype(fontpath)
if not ext \ if not ext and first_font_with_a_different_extension is None:
and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension: if first_font_with_a_different_extension:
return freetype(first_font_with_a_different_extension) return freetype(first_font_with_a_different_extension)
@ -551,10 +566,13 @@ def load_default():
""" """
from io import BytesIO from io import BytesIO
import base64 import base64
f = ImageFont() f = ImageFont()
f._load_pilfont_data( f._load_pilfont_data(
# courB08 # courB08
BytesIO(base64.b64decode(b''' BytesIO(
base64.b64decode(
b"""
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
@ -646,7 +664,13 @@ AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA//// AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
+QAGAAIAzgAKANUAEw== +QAGAAIAzgAKANUAEw==
''')), Image.open(BytesIO(base64.b64decode(b''' """
)
),
Image.open(
BytesIO(
base64.b64decode(
b"""
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9 Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
@ -670,5 +694,9 @@ evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v// AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
w7IkEbzhVQAAAABJRU5ErkJggg== w7IkEbzhVQAAAABJRU5ErkJggg==
''')))) """
)
)
),
)
return f return f

View File

@ -18,6 +18,7 @@
from . import Image from . import Image
import sys import sys
if sys.platform == "win32": if sys.platform == "win32":
grabber = Image.core.grabscreen grabber = Image.core.grabscreen
elif sys.platform == "darwin": elif sys.platform == "darwin":
@ -30,19 +31,24 @@ else:
def grab(bbox=None, include_layered_windows=False): def grab(bbox=None, include_layered_windows=False):
if sys.platform == "darwin": if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp('.png') fh, filepath = tempfile.mkstemp(".png")
os.close(fh) os.close(fh)
subprocess.call(['screencapture', '-x', filepath]) subprocess.call(["screencapture", "-x", filepath])
im = Image.open(filepath) im = Image.open(filepath)
im.load() im.load()
os.unlink(filepath) os.unlink(filepath)
else: else:
size, data = grabber(include_layered_windows) size, data = grabber(include_layered_windows)
im = Image.frombytes( im = Image.frombytes(
"RGB", size, data, "RGB",
size,
data,
# RGB, 32-bit line padding, origin lower left corner # RGB, 32-bit line padding, origin lower left corner
"raw", "BGR", (size[0]*3 + 3) & -4, -1 "raw",
) "BGR",
(size[0] * 3 + 3) & -4,
-1,
)
if bbox: if bbox:
im = im.crop(bbox) im = im.crop(bbox)
return im return im
@ -50,15 +56,16 @@ def grab(bbox=None, include_layered_windows=False):
def grabclipboard(): def grabclipboard():
if sys.platform == "darwin": if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp('.jpg') fh, filepath = tempfile.mkstemp(".jpg")
os.close(fh) os.close(fh)
commands = [ commands = [
"set theFile to (open for access POSIX file \"" 'set theFile to (open for access POSIX file "'
+ filepath + "\" with write permission)", + filepath
+ '" with write permission)',
"try", "try",
" write (the clipboard as JPEG picture) to theFile", " write (the clipboard as JPEG picture) to theFile",
"end try", "end try",
"close access theFile" "close access theFile",
] ]
script = ["osascript"] script = ["osascript"]
for command in commands: for command in commands:
@ -76,5 +83,6 @@ def grabclipboard():
if isinstance(data, bytes): if isinstance(data, bytes):
from . import BmpImagePlugin from . import BmpImagePlugin
import io import io
return BmpImagePlugin.DibImageFile(io.BytesIO(data)) return BmpImagePlugin.DibImageFile(io.BytesIO(data))
return data return data

View File

@ -22,6 +22,7 @@ try:
import builtins import builtins
except ImportError: except ImportError:
import __builtin__ import __builtin__
builtins = __builtin__ builtins = __builtin__
VERBOSE = 0 VERBOSE = 0
@ -61,7 +62,7 @@ class _Operand(object):
out = Image.new(mode or im1.mode, im1.size, None) out = Image.new(mode or im1.mode, im1.size, None)
im1.load() im1.load()
try: try:
op = getattr(_imagingmath, op+"_"+im1.mode) op = getattr(_imagingmath, op + "_" + im1.mode)
except AttributeError: except AttributeError:
raise TypeError("bad operand type for '%s'" % op) raise TypeError("bad operand type for '%s'" % op)
_imagingmath.unop(op, out.im.id, im1.im.id) _imagingmath.unop(op, out.im.id, im1.im.id)
@ -78,8 +79,7 @@ class _Operand(object):
raise ValueError("mode mismatch") raise ValueError("mode mismatch")
if im1.size != im2.size: if im1.size != im2.size:
# crop both arguments to a common size # crop both arguments to a common size
size = (min(im1.size[0], im2.size[0]), size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))
min(im1.size[1], im2.size[1]))
if im1.size != size: if im1.size != size:
im1 = im1.crop((0, 0) + size) im1 = im1.crop((0, 0) + size)
if im2.size != size: if im2.size != size:
@ -90,7 +90,7 @@ class _Operand(object):
im1.load() im1.load()
im2.load() im2.load()
try: try:
op = getattr(_imagingmath, op+"_"+im1.mode) op = getattr(_imagingmath, op + "_" + im1.mode)
except AttributeError: except AttributeError:
raise TypeError("bad operand type for '%s'" % op) raise TypeError("bad operand type for '%s'" % op)
_imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id) _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)

View File

@ -37,13 +37,13 @@ def getmode(mode):
# initialize mode cache # initialize mode cache
from . import Image from . import Image
modes = {} modes = {}
# core modes # core modes
for m, (basemode, basetype, bands) in Image._MODEINFO.items(): for m, (basemode, basetype, bands) in Image._MODEINFO.items():
modes[m] = ModeDescriptor(m, bands, basemode, basetype) modes[m] = ModeDescriptor(m, bands, basemode, basetype)
# extra experimental modes # extra experimental modes
modes["RGBa"] = ModeDescriptor("RGBa", modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
("R", "G", "B", "a"), "RGB", "L")
modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")

View File

@ -12,6 +12,19 @@ import re
LUT_SIZE = 1 << 9 LUT_SIZE = 1 << 9
# fmt: off
ROTATION_MATRIX = [
6, 3, 0,
7, 4, 1,
8, 5, 2,
]
MIRROR_MATRIX = [
2, 1, 0,
5, 4, 3,
8, 7, 6,
]
# fmt: on
class LutBuilder(object): class LutBuilder(object):
"""A class for building a MorphLut from a descriptive language """A class for building a MorphLut from a descriptive language
@ -48,6 +61,7 @@ class LutBuilder(object):
lut = lb.build_lut() lut = lb.build_lut()
""" """
def __init__(self, patterns=None, op_name=None): def __init__(self, patterns=None, op_name=None):
if patterns is not None: if patterns is not None:
self.patterns = patterns self.patterns = patterns
@ -56,20 +70,19 @@ class LutBuilder(object):
self.lut = None self.lut = None
if op_name is not None: if op_name is not None:
known_patterns = { known_patterns = {
'corner': ['1:(... ... ...)->0', "corner": ["1:(... ... ...)->0", "4:(00. 01. ...)->1"],
'4:(00. 01. ...)->1'], "dilation4": ["4:(... .0. .1.)->1"],
'dilation4': ['4:(... .0. .1.)->1'], "dilation8": ["4:(... .0. .1.)->1", "4:(... .0. ..1)->1"],
'dilation8': ['4:(... .0. .1.)->1', "erosion4": ["4:(... .1. .0.)->0"],
'4:(... .0. ..1)->1'], "erosion8": ["4:(... .1. .0.)->0", "4:(... .1. ..0)->0"],
'erosion4': ['4:(... .1. .0.)->0'], "edge": [
'erosion8': ['4:(... .1. .0.)->0', "1:(... ... ...)->0",
'4:(... .1. ..0)->0'], "4:(.0. .1. ...)->1",
'edge': ['1:(... ... ...)->0', "4:(01. .1. ...)->1",
'4:(.0. .1. ...)->1', ],
'4:(01. .1. ...)->1']
} }
if op_name not in known_patterns: if op_name not in known_patterns:
raise Exception('Unknown pattern '+op_name+'!') raise Exception("Unknown pattern " + op_name + "!")
self.patterns = known_patterns[op_name] self.patterns = known_patterns[op_name]
@ -88,8 +101,8 @@ class LutBuilder(object):
"""string_permute takes a pattern and a permutation and returns the """string_permute takes a pattern and a permutation and returns the
string permuted according to the permutation list. string permuted according to the permutation list.
""" """
assert(len(permutation) == 9) assert len(permutation) == 9
return ''.join(pattern[p] for p in permutation) return "".join(pattern[p] for p in permutation)
def _pattern_permute(self, basic_pattern, options, basic_result): def _pattern_permute(self, basic_pattern, options, basic_result):
"""pattern_permute takes a basic pattern and its result and clones """pattern_permute takes a basic pattern and its result and clones
@ -98,32 +111,25 @@ class LutBuilder(object):
patterns = [(basic_pattern, basic_result)] patterns = [(basic_pattern, basic_result)]
# rotations # rotations
if '4' in options: if "4" in options:
res = patterns[-1][1] res = patterns[-1][1]
for i in range(4): for i in range(4):
patterns.append( patterns.append(
(self._string_permute(patterns[-1][0], [6, 3, 0, (self._string_permute(patterns[-1][0], ROTATION_MATRIX), res)
7, 4, 1, )
8, 5, 2]), res))
# mirror # mirror
if 'M' in options: if "M" in options:
n = len(patterns) n = len(patterns)
for pattern, res in patterns[0:n]: for pattern, res in patterns[0:n]:
patterns.append( patterns.append((self._string_permute(pattern, MIRROR_MATRIX), res))
(self._string_permute(pattern, [2, 1, 0,
5, 4, 3,
8, 7, 6]), res))
# negate # negate
if 'N' in options: if "N" in options:
n = len(patterns) n = len(patterns)
for pattern, res in patterns[0:n]: for pattern, res in patterns[0:n]:
# Swap 0 and 1 # Swap 0 and 1
pattern = (pattern pattern = pattern.replace("0", "Z").replace("1", "0").replace("Z", "1")
.replace('0', 'Z') res = 1 - int(res)
.replace('1', '0')
.replace('Z', '1'))
res = 1-int(res)
patterns.append((pattern, res)) patterns.append((pattern, res))
return patterns return patterns
@ -138,22 +144,21 @@ class LutBuilder(object):
# Parse and create symmetries of the patterns strings # Parse and create symmetries of the patterns strings
for p in self.patterns: for p in self.patterns:
m = re.search( m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', ''))
if not m: if not m:
raise Exception('Syntax error in pattern "'+p+'"') raise Exception('Syntax error in pattern "' + p + '"')
options = m.group(1) options = m.group(1)
pattern = m.group(2) pattern = m.group(2)
result = int(m.group(3)) result = int(m.group(3))
# Get rid of spaces # Get rid of spaces
pattern = pattern.replace(' ', '').replace('\n', '') pattern = pattern.replace(" ", "").replace("\n", "")
patterns += self._pattern_permute(pattern, options, result) patterns += self._pattern_permute(pattern, options, result)
# compile the patterns into regular expressions for speed # compile the patterns into regular expressions for speed
for i, pattern in enumerate(patterns): for i, pattern in enumerate(patterns):
p = pattern[0].replace('.', 'X').replace('X', '[01]') p = pattern[0].replace(".", "X").replace("X", "[01]")
p = re.compile(p) p = re.compile(p)
patterns[i] = (p, pattern[1]) patterns[i] = (p, pattern[1])
@ -163,7 +168,7 @@ class LutBuilder(object):
for i in range(LUT_SIZE): for i in range(LUT_SIZE):
# Build the bit pattern # Build the bit pattern
bitpattern = bin(i)[2:] bitpattern = bin(i)[2:]
bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1] bitpattern = ("0" * (9 - len(bitpattern)) + bitpattern)[::-1]
for p, r in patterns: for p, r in patterns:
if p.match(bitpattern): if p.match(bitpattern):
@ -175,10 +180,7 @@ class LutBuilder(object):
class MorphOp(object): class MorphOp(object):
"""A class for binary morphological operators""" """A class for binary morphological operators"""
def __init__(self, def __init__(self, lut=None, op_name=None, patterns=None):
lut=None,
op_name=None,
patterns=None):
"""Create a binary morphological operator""" """Create a binary morphological operator"""
self.lut = lut self.lut = lut
if op_name is not None: if op_name is not None:
@ -192,13 +194,12 @@ class MorphOp(object):
Returns a tuple of the number of changed pixels and the Returns a tuple of the number of changed pixels and the
morphed image""" morphed image"""
if self.lut is None: if self.lut is None:
raise Exception('No operator loaded') raise Exception("No operator loaded")
if image.mode != 'L': if image.mode != "L":
raise Exception('Image must be binary, meaning it must use mode L') raise Exception("Image must be binary, meaning it must use mode L")
outimage = Image.new(image.mode, image.size, None) outimage = Image.new(image.mode, image.size, None)
count = _imagingmorph.apply( count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id)
bytes(self.lut), image.im.id, outimage.im.id)
return count, outimage return count, outimage
def match(self, image): def match(self, image):
@ -208,10 +209,10 @@ class MorphOp(object):
Returns a list of tuples of (x,y) coordinates Returns a list of tuples of (x,y) coordinates
of all matching pixels. See :ref:`coordinate-system`.""" of all matching pixels. See :ref:`coordinate-system`."""
if self.lut is None: if self.lut is None:
raise Exception('No operator loaded') raise Exception("No operator loaded")
if image.mode != 'L': if image.mode != "L":
raise Exception('Image must be binary, meaning it must use mode L') raise Exception("Image must be binary, meaning it must use mode L")
return _imagingmorph.match(bytes(self.lut), image.im.id) return _imagingmorph.match(bytes(self.lut), image.im.id)
def get_on_pixels(self, image): def get_on_pixels(self, image):
@ -220,24 +221,24 @@ class MorphOp(object):
Returns a list of tuples of (x,y) coordinates Returns a list of tuples of (x,y) coordinates
of all matching pixels. See :ref:`coordinate-system`.""" of all matching pixels. See :ref:`coordinate-system`."""
if image.mode != 'L': if image.mode != "L":
raise Exception('Image must be binary, meaning it must use mode L') raise Exception("Image must be binary, meaning it must use mode L")
return _imagingmorph.get_on_pixels(image.im.id) return _imagingmorph.get_on_pixels(image.im.id)
def load_lut(self, filename): def load_lut(self, filename):
"""Load an operator from an mrl file""" """Load an operator from an mrl file"""
with open(filename, 'rb') as f: with open(filename, "rb") as f:
self.lut = bytearray(f.read()) self.lut = bytearray(f.read())
if len(self.lut) != LUT_SIZE: if len(self.lut) != LUT_SIZE:
self.lut = None self.lut = None
raise Exception('Wrong size operator file!') raise Exception("Wrong size operator file!")
def save_lut(self, filename): def save_lut(self, filename):
"""Save an operator to an mrl file""" """Save an operator to an mrl file"""
if self.lut is None: if self.lut is None:
raise Exception('No operator loaded') raise Exception("No operator loaded")
with open(filename, 'wb') as f: with open(filename, "wb") as f:
f.write(self.lut) f.write(self.lut)
def set_lut(self, lut): def set_lut(self, lut):

View File

@ -26,6 +26,7 @@ import functools
# #
# helpers # helpers
def _border(border): def _border(border):
if isinstance(border, tuple): if isinstance(border, tuple):
if len(border) == 2: if len(border) == 2:
@ -40,6 +41,7 @@ def _border(border):
def _color(color, mode): def _color(color, mode):
if isStringType(color): if isStringType(color):
from . import ImageColor from . import ImageColor
color = ImageColor.getcolor(color, mode) color = ImageColor.getcolor(color, mode)
return color return color
@ -55,6 +57,7 @@ def _lut(image, lut):
else: else:
raise IOError("not supported for this image mode") raise IOError("not supported for this image mode")
# #
# actions # actions
@ -75,7 +78,7 @@ def autocontrast(image, cutoff=0, ignore=None):
histogram = image.histogram() histogram = image.histogram()
lut = [] lut = []
for layer in range(0, len(histogram), 256): for layer in range(0, len(histogram), 256):
h = histogram[layer:layer+256] h = histogram[layer : layer + 256]
if ignore is not None: if ignore is not None:
# get rid of outliers # get rid of outliers
try: try:
@ -135,8 +138,7 @@ def autocontrast(image, cutoff=0, ignore=None):
return _lut(image, lut) return _lut(image, lut)
def colorize(image, black, white, mid=None, blackpoint=0, def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoint=127):
whitepoint=255, midpoint=127):
""" """
Colorize grayscale image. Colorize grayscale image.
This function calculates a color wedge which maps all black pixels in This function calculates a color wedge which maps all black pixels in
@ -276,9 +278,7 @@ def crop(image, border=0):
:return: An image. :return: An image.
""" """
left, top, right, bottom = _border(border) left, top, right, bottom = _border(border)
return image.crop( return image.crop((left, top, image.size[0] - right, image.size[1] - bottom))
(left, top, image.size[0]-right, image.size[1]-bottom)
)
def scale(image, factor, resample=Image.NEAREST): def scale(image, factor, resample=Image.NEAREST):
@ -298,8 +298,7 @@ def scale(image, factor, resample=Image.NEAREST):
elif factor <= 0: elif factor <= 0:
raise ValueError("the factor must be greater than 0") raise ValueError("the factor must be greater than 0")
else: else:
size = (int(round(factor * image.width)), size = (int(round(factor * image.width)), int(round(factor * image.height)))
int(round(factor * image.height)))
return image.resize(size, resample) return image.resize(size, resample)
@ -314,9 +313,7 @@ def deform(image, deformer, resample=Image.BILINEAR):
in the PIL.Image.transform function. in the PIL.Image.transform function.
:return: An image. :return: An image.
""" """
return image.transform( return image.transform(image.size, Image.MESH, deformer.getmesh(image), resample)
image.size, Image.MESH, deformer.getmesh(image), resample
)
def equalize(image, mask=None): def equalize(image, mask=None):
@ -335,7 +332,7 @@ def equalize(image, mask=None):
h = image.histogram(mask) h = image.histogram(mask)
lut = [] lut = []
for b in range(0, len(h), 256): for b in range(0, len(h), 256):
histo = [_f for _f in h[b:b+256] if _f] histo = [_f for _f in h[b : b + 256] if _f]
if len(histo) <= 1: if len(histo) <= 1:
lut.extend(list(range(256))) lut.extend(list(range(256)))
else: else:
@ -346,7 +343,7 @@ def equalize(image, mask=None):
n = step // 2 n = step // 2
for i in range(256): for i in range(256):
lut.append(n // step) lut.append(n // step)
n = n + h[i+b] n = n + h[i + b]
return _lut(image, lut) return _lut(image, lut)
@ -417,8 +414,10 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
# number of pixels to trim off on Top and Bottom, Left and Right # number of pixels to trim off on Top and Bottom, Left and Right
bleed_pixels = (bleed * image.size[0], bleed * image.size[1]) bleed_pixels = (bleed * image.size[0], bleed * image.size[1])
live_size = (image.size[0] - bleed_pixels[0] * 2, live_size = (
image.size[1] - bleed_pixels[1] * 2) image.size[0] - bleed_pixels[0] * 2,
image.size[1] - bleed_pixels[1] * 2,
)
# calculate the aspect ratio of the live_size # calculate the aspect ratio of the live_size
live_size_ratio = float(live_size[0]) / live_size[1] live_size_ratio = float(live_size[0]) / live_size[1]
@ -437,13 +436,10 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
crop_height = live_size[0] / output_ratio crop_height = live_size[0] / output_ratio
# make the crop # make the crop
crop_left = bleed_pixels[0] + (live_size[0]-crop_width) * centering[0] crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering[0]
crop_top = bleed_pixels[1] + (live_size[1]-crop_height) * centering[1] crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering[1]
crop = ( crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height)
crop_left, crop_top,
crop_left + crop_width, crop_top + crop_height
)
# resize the image and return it # resize the image and return it
return image.resize(size, method, box=crop) return image.resize(size, method, box=crop)
@ -478,7 +474,7 @@ def invert(image):
""" """
lut = [] lut = []
for i in range(256): for i in range(256):
lut.append(255-i) lut.append(255 - i)
return _lut(image, lut) return _lut(image, lut)
@ -501,7 +497,7 @@ def posterize(image, bits):
:return: An image. :return: An image.
""" """
lut = [] lut = []
mask = ~(2**(8-bits)-1) mask = ~(2 ** (8 - bits) - 1)
for i in range(256): for i in range(256):
lut.append(i & mask) lut.append(i & mask)
return _lut(image, lut) return _lut(image, lut)
@ -520,7 +516,7 @@ def solarize(image, threshold=128):
if i < threshold: if i < threshold:
lut.append(i) lut.append(i)
else: else:
lut.append(255-i) lut.append(255 - i)
return _lut(image, lut) return _lut(image, lut)
@ -541,7 +537,7 @@ def exif_transpose(image):
5: Image.TRANSPOSE, 5: Image.TRANSPOSE,
6: Image.ROTATE_270, 6: Image.ROTATE_270,
7: Image.TRANSVERSE, 7: Image.TRANSVERSE,
8: Image.ROTATE_90 8: Image.ROTATE_90,
}.get(orientation) }.get(orientation)
if method is not None: if method is not None:
transposed_image = image.transpose(method) transposed_image = image.transpose(method)

View File

@ -38,11 +38,12 @@ class ImagePalette(object):
def __init__(self, mode="RGB", palette=None, size=0): def __init__(self, mode="RGB", palette=None, size=0):
self.mode = mode self.mode = mode
self.rawmode = None # if set, palette contains raw data self.rawmode = None # if set, palette contains raw data
self.palette = palette or bytearray(range(256))*len(self.mode) self.palette = palette or bytearray(range(256)) * len(self.mode)
self.colors = {} self.colors = {}
self.dirty = None self.dirty = None
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or if (size == 0 and len(self.mode) * 256 != len(self.palette)) or (
(size != 0 and size != len(self.palette))): size != 0 and size != len(self.palette)
):
raise ValueError("wrong palette size") raise ValueError("wrong palette size")
def copy(self): def copy(self):
@ -78,7 +79,7 @@ class ImagePalette(object):
if isinstance(self.palette, bytes): if isinstance(self.palette, bytes):
return self.palette return self.palette
arr = array.array("B", self.palette) arr = array.array("B", self.palette)
if hasattr(arr, 'tobytes'): if hasattr(arr, "tobytes"):
return arr.tobytes() return arr.tobytes()
return arr.tostring() return arr.tostring()
@ -104,8 +105,8 @@ class ImagePalette(object):
raise ValueError("cannot allocate more than 256 colors") raise ValueError("cannot allocate more than 256 colors")
self.colors[color] = index self.colors[color] = index
self.palette[index] = color[0] self.palette[index] = color[0]
self.palette[index+256] = color[1] self.palette[index + 256] = color[1]
self.palette[index+512] = color[2] self.palette[index + 512] = color[2]
self.dirty = 1 self.dirty = 1
return index return index
else: else:
@ -124,7 +125,7 @@ class ImagePalette(object):
fp.write("# Mode: %s\n" % self.mode) fp.write("# Mode: %s\n" % self.mode)
for i in range(256): for i in range(256):
fp.write("%d" % i) fp.write("%d" % i)
for j in range(i*len(self.mode), (i+1)*len(self.mode)): for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
try: try:
fp.write(" %d" % self.palette[j]) fp.write(" %d" % self.palette[j])
except IndexError: except IndexError:
@ -136,6 +137,7 @@ class ImagePalette(object):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Internal # Internal
def raw(rawmode, data): def raw(rawmode, data):
palette = ImagePalette() palette = ImagePalette()
palette.rawmode = rawmode palette.rawmode = rawmode
@ -147,11 +149,12 @@ def raw(rawmode, data):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Factories # Factories
def make_linear_lut(black, white): def make_linear_lut(black, white):
lut = [] lut = []
if black == 0: if black == 0:
for i in range(256): for i in range(256):
lut.append(white*i//255) lut.append(white * i // 255)
else: else:
raise NotImplementedError # FIXME raise NotImplementedError # FIXME
return lut return lut
@ -172,8 +175,9 @@ def negative(mode="RGB"):
def random(mode="RGB"): def random(mode="RGB"):
from random import randint from random import randint
palette = [] palette = []
for i in range(256*len(mode)): for i in range(256 * len(mode)):
palette.append(randint(0, 255)) palette.append(randint(0, 255))
return ImagePalette(mode, palette) return ImagePalette(mode, palette)
@ -199,7 +203,7 @@ def load(filename):
for paletteHandler in [ for paletteHandler in [
GimpPaletteFile.GimpPaletteFile, GimpPaletteFile.GimpPaletteFile,
GimpGradientFile.GimpGradientFile, GimpGradientFile.GimpGradientFile,
PaletteFile.PaletteFile PaletteFile.PaletteFile,
]: ]:
try: try:
fp.seek(0) fp.seek(0)

View File

@ -22,12 +22,7 @@ from io import BytesIO
import sys import sys
import warnings import warnings
qt_versions = [ qt_versions = [["5", "PyQt5"], ["side2", "PySide2"], ["4", "PyQt4"], ["side", "PySide"]]
['5', 'PyQt5'],
['side2', 'PySide2'],
['4', 'PyQt4'],
['side', 'PySide']
]
WARNING_TEXT = ( WARNING_TEXT = (
"Support for EOL {} is deprecated and will be removed in a future version. " "Support for EOL {} is deprecated and will be removed in a future version. "
@ -35,22 +30,21 @@ WARNING_TEXT = (
) )
# If a version has already been imported, attempt it first # If a version has already been imported, attempt it first
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
reverse=True)
for qt_version, qt_module in qt_versions: for qt_version, qt_module in qt_versions:
try: try:
if qt_module == 'PyQt5': if qt_module == "PyQt5":
from PyQt5.QtGui import QImage, qRgba, QPixmap from PyQt5.QtGui import QImage, qRgba, QPixmap
from PyQt5.QtCore import QBuffer, QIODevice from PyQt5.QtCore import QBuffer, QIODevice
elif qt_module == 'PySide2': elif qt_module == "PySide2":
from PySide2.QtGui import QImage, qRgba, QPixmap from PySide2.QtGui import QImage, qRgba, QPixmap
from PySide2.QtCore import QBuffer, QIODevice from PySide2.QtCore import QBuffer, QIODevice
elif qt_module == 'PyQt4': elif qt_module == "PyQt4":
from PyQt4.QtGui import QImage, qRgba, QPixmap from PyQt4.QtGui import QImage, qRgba, QPixmap
from PyQt4.QtCore import QBuffer, QIODevice from PyQt4.QtCore import QBuffer, QIODevice
warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning) warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
elif qt_module == 'PySide': elif qt_module == "PySide":
from PySide.QtGui import QImage, qRgba, QPixmap from PySide.QtGui import QImage, qRgba, QPixmap
from PySide.QtCore import QBuffer, QIODevice from PySide.QtCore import QBuffer, QIODevice
@ -68,7 +62,7 @@ def rgb(r, g, b, a=255):
"""(Internal) Turns an RGB color into a Qt compatible color integer.""" """(Internal) Turns an RGB color into a Qt compatible color integer."""
# use qRgb to pack the colors, and then turn the resulting long # use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern. # into a negative integer with the same bitpattern.
return (qRgba(r, g, b, a) & 0xffffffff) return qRgba(r, g, b, a) & 0xFFFFFFFF
def fromqimage(im): def fromqimage(im):
@ -81,9 +75,9 @@ def fromqimage(im):
# preserve alpha channel with png # preserve alpha channel with png
# otherwise ppm is more friendly with Image.open # otherwise ppm is more friendly with Image.open
if im.hasAlphaChannel(): if im.hasAlphaChannel():
im.save(buffer, 'png') im.save(buffer, "png")
else: else:
im.save(buffer, 'ppm') im.save(buffer, "ppm")
b = BytesIO() b = BytesIO()
try: try:
@ -116,11 +110,7 @@ def align8to32(bytes, width, mode):
converts each scanline of data from 8 bit to 32 bit aligned converts each scanline of data from 8 bit to 32 bit aligned
""" """
bits_per_pixel = { bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode]
'1': 1,
'L': 8,
'P': 8,
}[mode]
# calculate bytes per line and the extra padding if needed # calculate bytes per line and the extra padding if needed
bits_per_line = bits_per_pixel * width bits_per_line = bits_per_pixel * width
@ -135,10 +125,12 @@ def align8to32(bytes, width, mode):
new_data = [] new_data = []
for i in range(len(bytes) // bytes_per_line): for i in range(len(bytes) // bytes_per_line):
new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] new_data.append(
+ b'\x00' * extra_padding) bytes[i * bytes_per_line : (i + 1) * bytes_per_line]
+ b"\x00" * extra_padding
)
return b''.join(new_data) return b"".join(new_data)
def _toqclass_helper(im): def _toqclass_helper(im):
@ -167,7 +159,7 @@ def _toqclass_helper(im):
colortable = [] colortable = []
palette = im.getpalette() palette = im.getpalette()
for i in range(0, len(palette), 3): for i in range(0, len(palette), 3):
colortable.append(rgb(*palette[i:i+3])) colortable.append(rgb(*palette[i : i + 3]))
elif im.mode == "RGB": elif im.mode == "RGB":
data = im.tobytes("raw", "BGRX") data = im.tobytes("raw", "BGRX")
format = QImage.Format_RGB32 format = QImage.Format_RGB32
@ -183,14 +175,12 @@ def _toqclass_helper(im):
raise ValueError("unsupported image mode %r" % im.mode) raise ValueError("unsupported image mode %r" % im.mode)
__data = data or align8to32(im.tobytes(), im.size[0], im.mode) __data = data or align8to32(im.tobytes(), im.size[0], im.mode)
return { return {"data": __data, "im": im, "format": format, "colortable": colortable}
'data': __data, 'im': im, 'format': format, 'colortable': colortable
}
if qt_is_installed: if qt_is_installed:
class ImageQt(QImage):
class ImageQt(QImage):
def __init__(self, im): def __init__(self, im):
""" """
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
@ -204,12 +194,16 @@ if qt_is_installed:
# All QImage constructors that take data operate on an existing # All QImage constructors that take data operate on an existing
# buffer, so this buffer has to hang on for the life of the image. # buffer, so this buffer has to hang on for the life of the image.
# Fixes https://github.com/python-pillow/Pillow/issues/1370 # Fixes https://github.com/python-pillow/Pillow/issues/1370
self.__data = im_data['data'] self.__data = im_data["data"]
QImage.__init__(self, QImage.__init__(
self.__data, im_data['im'].size[0], self,
im_data['im'].size[1], im_data['format']) self.__data,
if im_data['colortable']: im_data["im"].size[0],
self.setColorTable(im_data['colortable']) im_data["im"].size[1],
im_data["format"],
)
if im_data["colortable"]:
self.setColorTable(im_data["colortable"])
def toqimage(im): def toqimage(im):
@ -222,8 +216,8 @@ def toqpixmap(im):
# result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
# result.loadFromData(im_data['data']) # result.loadFromData(im_data['data'])
# Fix some strange bug that causes # Fix some strange bug that causes
if im.mode == 'RGB': if im.mode == "RGB":
im = im.convert('RGBA') im = im.convert("RGBA")
qimage = toqimage(im) qimage = toqimage(im)
return QPixmap.fromImage(qimage) return QPixmap.fromImage(qimage)

View File

@ -101,6 +101,7 @@ class Viewer(object):
os.system(self.get_command(file, **options)) os.system(self.get_command(file, **options))
return 1 return 1
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -110,9 +111,11 @@ if sys.platform == "win32":
format = "BMP" format = "BMP"
def get_command(self, file, **options): def get_command(self, file, **options):
return ('start "Pillow" /WAIT "%s" ' return (
'&& ping -n 2 127.0.0.1 >NUL ' 'start "Pillow" /WAIT "%s" '
'&& del /f "%s"' % (file, file)) "&& ping -n 2 127.0.0.1 >NUL "
'&& del /f "%s"' % (file, file)
)
register(WindowsViewer) register(WindowsViewer)
@ -120,28 +123,35 @@ elif sys.platform == "darwin":
class MacViewer(Viewer): class MacViewer(Viewer):
format = "PNG" format = "PNG"
options = {'compress_level': 1} options = {"compress_level": 1}
def get_command(self, file, **options): def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
# file removal while app is opening # file removal while app is opening
command = "open -a /Applications/Preview.app" command = "open -a /Applications/Preview.app"
command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), command = "(%s %s; sleep 20; rm -f %s)&" % (
quote(file)) command,
quote(file),
quote(file),
)
return command return command
def show_file(self, file, **options): def show_file(self, file, **options):
"""Display given file""" """Display given file"""
fd, path = tempfile.mkstemp() fd, path = tempfile.mkstemp()
with os.fdopen(fd, 'w') as f: with os.fdopen(fd, "w") as f:
f.write(file) f.write(file)
with open(path, "r") as f: with open(path, "r") as f:
subprocess.Popen([ subprocess.Popen(
'im=$(cat);' [
'open -a /Applications/Preview.app $im;' "im=$(cat);"
'sleep 20;' "open -a /Applications/Preview.app $im;"
'rm -f $im' "sleep 20;"
], shell=True, stdin=f) "rm -f $im"
],
shell=True,
stdin=f,
)
os.remove(path) os.remove(path)
return 1 return 1
@ -163,7 +173,7 @@ else:
class UnixViewer(Viewer): class UnixViewer(Viewer):
format = "PNG" format = "PNG"
options = {'compress_level': 1} options = {"compress_level": 1}
def get_command(self, file, **options): def get_command(self, file, **options):
command = self.get_command_ex(file, **options)[0] command = self.get_command_ex(file, **options)[0]
@ -172,15 +182,13 @@ else:
def show_file(self, file, **options): def show_file(self, file, **options):
"""Display given file""" """Display given file"""
fd, path = tempfile.mkstemp() fd, path = tempfile.mkstemp()
with os.fdopen(fd, 'w') as f: with os.fdopen(fd, "w") as f:
f.write(file) f.write(file)
with open(path, "r") as f: with open(path, "r") as f:
command = self.get_command_ex(file, **options)[0] command = self.get_command_ex(file, **options)[0]
subprocess.Popen([ subprocess.Popen(
'im=$(cat);' + ["im=$(cat);" + command + " $im;" "rm -f $im"], shell=True, stdin=f
command+' $im;' )
'rm -f $im'
], shell=True, stdin=f)
os.remove(path) os.remove(path)
return 1 return 1

View File

@ -27,7 +27,6 @@ import functools
class Stat(object): class Stat(object):
def __init__(self, image_or_list, mask=None): def __init__(self, image_or_list, mask=None):
try: try:
if mask: if mask:
@ -71,7 +70,7 @@ class Stat(object):
v = [] v = []
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
v.append(functools.reduce(operator.add, self.h[i:i+256])) v.append(functools.reduce(operator.add, self.h[i : i + 256]))
return v return v
def _getsum(self): def _getsum(self):
@ -110,10 +109,10 @@ class Stat(object):
v = [] v = []
for i in self.bands: for i in self.bands:
s = 0 s = 0
half = self.count[i]//2 half = self.count[i] // 2
b = i * 256 b = i * 256
for j in range(256): for j in range(256):
s = s + self.h[b+j] s = s + self.h[b + j]
if s > half: if s > half:
break break
v.append(j) v.append(j)
@ -133,7 +132,7 @@ class Stat(object):
v = [] v = []
for i in self.bands: for i in self.bands:
n = self.count[i] n = self.count[i]
v.append((self.sum2[i]-(self.sum[i]**2.0)/n)/n) v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n)
return v return v
def _getstddev(self): def _getstddev(self):

View File

@ -67,6 +67,7 @@ def _get_image_from_kw(kw):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PhotoImage # PhotoImage
class PhotoImage(object): class PhotoImage(object):
""" """
A Tkinter-compatible photo image. This can be used A Tkinter-compatible photo image. This can be used
@ -183,17 +184,18 @@ class PhotoImage(object):
# activate Tkinter hook # activate Tkinter hook
try: try:
from . import _imagingtk from . import _imagingtk
try: try:
if hasattr(tk, 'interp'): if hasattr(tk, "interp"):
# Required for PyPy, which always has CFFI installed # Required for PyPy, which always has CFFI installed
from cffi import FFI from cffi import FFI
ffi = FFI() ffi = FFI()
# PyPy is using an FFI CDATA element # PyPy is using an FFI CDATA element
# (Pdb) self.tk.interp # (Pdb) self.tk.interp
# <cdata 'Tcl_Interp *' 0x3061b50> # <cdata 'Tcl_Interp *' 0x3061b50>
_imagingtk.tkinit( _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
int(ffi.cast("uintptr_t", tk.interp)), 1)
else: else:
_imagingtk.tkinit(tk.interpaddr(), 1) _imagingtk.tkinit(tk.interpaddr(), 1)
except AttributeError: except AttributeError:
@ -202,6 +204,7 @@ class PhotoImage(object):
except (ImportError, AttributeError, tkinter.TclError): except (ImportError, AttributeError, tkinter.TclError):
raise # configuration problem; cannot attach to Tkinter raise # configuration problem; cannot attach to Tkinter
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# BitmapImage # BitmapImage
@ -293,8 +296,7 @@ def _show(image, title):
self.image = BitmapImage(im, foreground="white", master=master) self.image = BitmapImage(im, foreground="white", master=master)
else: else:
self.image = PhotoImage(im, master=master) self.image = PhotoImage(im, master=master)
tkinter.Label.__init__(self, master, image=self.image, tkinter.Label.__init__(self, master, image=self.image, bg="black", bd=0)
bg="black", bd=0)
if not tkinter._default_root: if not tkinter._default_root:
raise IOError("tkinter not initialized") raise IOError("tkinter not initialized")

View File

@ -46,6 +46,7 @@ class AffineTransform(Transform):
:param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows :param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
from an affine transform matrix. from an affine transform matrix.
""" """
method = Image.AFFINE method = Image.AFFINE
@ -67,6 +68,7 @@ class ExtentTransform(Transform):
:param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the :param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the
input image's coordinate system. See :ref:`coordinate-system`. input image's coordinate system. See :ref:`coordinate-system`.
""" """
method = Image.EXTENT method = Image.EXTENT
@ -83,6 +85,7 @@ class QuadTransform(Transform):
upper left, lower left, lower right, and upper right corner of the upper left, lower left, lower right, and upper right corner of the
source quadrilateral. source quadrilateral.
""" """
method = Image.QUAD method = Image.QUAD
@ -95,4 +98,5 @@ class MeshTransform(Transform):
:param data: A list of (bbox, quad) tuples. :param data: A list of (bbox, quad) tuples.
""" """
method = Image.MESH method = Image.MESH

View File

@ -26,6 +26,7 @@ class HDC(object):
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
methods. methods.
""" """
def __init__(self, dc): def __init__(self, dc):
self.dc = dc self.dc = dc
@ -39,6 +40,7 @@ class HWND(object):
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
methods, instead of a DC. methods, instead of a DC.
""" """
def __init__(self, wnd): def __init__(self, wnd):
self.wnd = wnd self.wnd = wnd
@ -190,7 +192,7 @@ class Window(object):
def __init__(self, title="PIL", width=None, height=None): def __init__(self, title="PIL", width=None, height=None):
self.hwnd = Image.core.createwindow( self.hwnd = Image.core.createwindow(
title, self.__dispatcher, width or 0, height or 0 title, self.__dispatcher, width or 0, height or 0
) )
def __dispatcher(self, action, *args): def __dispatcher(self, action, *args):
return getattr(self, "ui_handle_" + action)(*args) return getattr(self, "ui_handle_" + action)(*args)

View File

@ -33,6 +33,7 @@ field = re.compile(br"([a-z]*) ([^ \r\n]*)")
## ##
# Image plugin for IM Tools images. # Image plugin for IM Tools images.
class ImtImageFile(ImageFile.ImageFile): class ImtImageFile(ImageFile.ImageFile):
format = "IMT" format = "IMT"
@ -55,12 +56,12 @@ class ImtImageFile(ImageFile.ImageFile):
if not s: if not s:
break break
if s == b'\x0C': if s == b"\x0C":
# image data begins # image data begins
self.tile = [("raw", (0, 0)+self.size, self.tile = [
self.fp.tell(), ("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))
(self.mode, 0, 1))] ]
break break

View File

@ -26,10 +26,7 @@ import tempfile
# PIL.__version__ instead. # PIL.__version__ instead.
__version__ = "0.3" __version__ = "0.3"
COMPRESSION = { COMPRESSION = {1: "raw", 5: "jpeg"}
1: "raw",
5: "jpeg"
}
PAD = o8(0) * 4 PAD = o8(0) * 4
@ -37,13 +34,14 @@ PAD = o8(0) * 4
# #
# Helpers # Helpers
def i(c): def i(c):
return i32((PAD + c)[-4:]) return i32((PAD + c)[-4:])
def dump(c): def dump(c):
for i in c: for i in c:
print("%02x" % i8(i), end=' ') print("%02x" % i8(i), end=" ")
print() print()
@ -51,6 +49,7 @@ def dump(c):
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields # Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function. # from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
class IptcImageFile(ImageFile.ImageFile): class IptcImageFile(ImageFile.ImageFile):
format = "IPTC" format = "IPTC"
@ -79,7 +78,7 @@ class IptcImageFile(ImageFile.ImageFile):
elif size == 128: elif size == 128:
size = 0 size = 0
elif size > 128: elif size > 128:
size = i(self.fp.read(size-128)) size = i(self.fp.read(size - 128))
else: else:
size = i16(s[3:]) size = i16(s[3:])
@ -109,7 +108,7 @@ class IptcImageFile(ImageFile.ImageFile):
layers = i8(self.info[(3, 60)][0]) layers = i8(self.info[(3, 60)][0])
component = i8(self.info[(3, 60)][1]) component = i8(self.info[(3, 60)][1])
if (3, 65) in self.info: if (3, 65) in self.info:
id = i8(self.info[(3, 65)][0])-1 id = i8(self.info[(3, 65)][0]) - 1
else: else:
id = 0 id = 0
if layers == 1 and not component: if layers == 1 and not component:
@ -130,8 +129,9 @@ class IptcImageFile(ImageFile.ImageFile):
# tile # tile
if tag == (8, 10): if tag == (8, 10):
self.tile = [("iptc", (compression, offset), self.tile = [
(0, 0, self.size[0], self.size[1]))] ("iptc", (compression, offset), (0, 0, self.size[0], self.size[1]))
]
def load(self): def load(self):
@ -216,6 +216,7 @@ def getiptcinfo(im):
# create an IptcImagePlugin object without initializing it # create an IptcImagePlugin object without initializing it
class FakeImage(object): class FakeImage(object):
pass pass
im = FakeImage() im = FakeImage()
im.__class__ = IptcImageFile im.__class__ = IptcImageFile

View File

@ -27,30 +27,29 @@ def _parse_codestream(fp):
count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
hdr = fp.read(2) hdr = fp.read(2)
lsiz = struct.unpack('>H', hdr)[0] lsiz = struct.unpack(">H", hdr)[0]
siz = hdr + fp.read(lsiz - 2) siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \ lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
xtosiz, ytosiz, csiz \ ">HHIIIIIIIIH", siz
= struct.unpack_from('>HHIIIIIIIIH', siz) )
ssiz = [None]*csiz ssiz = [None] * csiz
xrsiz = [None]*csiz xrsiz = [None] * csiz
yrsiz = [None]*csiz yrsiz = [None] * csiz
for i in range(csiz): for i in range(csiz):
ssiz[i], xrsiz[i], yrsiz[i] \ ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack_from(">BBB", siz, 36 + 3 * i)
= struct.unpack_from('>BBB', siz, 36 + 3 * i)
size = (xsiz - xosiz, ysiz - yosiz) size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1: if csiz == 1:
if (yrsiz[0] & 0x7f) > 8: if (yrsiz[0] & 0x7F) > 8:
mode = 'I;16' mode = "I;16"
else: else:
mode = 'L' mode = "L"
elif csiz == 2: elif csiz == 2:
mode = 'LA' mode = "LA"
elif csiz == 3: elif csiz == 3:
mode = 'RGB' mode = "RGB"
elif csiz == 4: elif csiz == 4:
mode = 'RGBA' mode = "RGBA"
else: else:
mode = None mode = None
@ -65,28 +64,28 @@ def _parse_jp2_header(fp):
header = None header = None
mimetype = None mimetype = None
while True: while True:
lbox, tbox = struct.unpack('>I4s', fp.read(8)) lbox, tbox = struct.unpack(">I4s", fp.read(8))
if lbox == 1: if lbox == 1:
lbox = struct.unpack('>Q', fp.read(8))[0] lbox = struct.unpack(">Q", fp.read(8))[0]
hlen = 16 hlen = 16
else: else:
hlen = 8 hlen = 8
if lbox < hlen: if lbox < hlen:
raise SyntaxError('Invalid JP2 header length') raise SyntaxError("Invalid JP2 header length")
if tbox == b'jp2h': if tbox == b"jp2h":
header = fp.read(lbox - hlen) header = fp.read(lbox - hlen)
break break
elif tbox == b'ftyp': elif tbox == b"ftyp":
if fp.read(4) == b'jpx ': if fp.read(4) == b"jpx ":
mimetype = 'image/jpx' mimetype = "image/jpx"
fp.seek(lbox - hlen - 4, os.SEEK_CUR) fp.seek(lbox - hlen - 4, os.SEEK_CUR)
else: else:
fp.seek(lbox - hlen, os.SEEK_CUR) fp.seek(lbox - hlen, os.SEEK_CUR)
if header is None: if header is None:
raise SyntaxError('could not find JP2 header') raise SyntaxError("could not find JP2 header")
size = None size = None
mode = None mode = None
@ -95,58 +94,57 @@ def _parse_jp2_header(fp):
hio = io.BytesIO(header) hio = io.BytesIO(header)
while True: while True:
lbox, tbox = struct.unpack('>I4s', hio.read(8)) lbox, tbox = struct.unpack(">I4s", hio.read(8))
if lbox == 1: if lbox == 1:
lbox = struct.unpack('>Q', hio.read(8))[0] lbox = struct.unpack(">Q", hio.read(8))[0]
hlen = 16 hlen = 16
else: else:
hlen = 8 hlen = 8
content = hio.read(lbox - hlen) content = hio.read(lbox - hlen)
if tbox == b'ihdr': if tbox == b"ihdr":
height, width, nc, bpc, c, unkc, ipr \ height, width, nc, bpc, c, unkc, ipr = struct.unpack(">IIHBBBB", content)
= struct.unpack('>IIHBBBB', content)
size = (width, height) size = (width, height)
if unkc: if unkc:
if nc == 1 and (bpc & 0x7f) > 8: if nc == 1 and (bpc & 0x7F) > 8:
mode = 'I;16' mode = "I;16"
elif nc == 1: elif nc == 1:
mode = 'L' mode = "L"
elif nc == 2: elif nc == 2:
mode = 'LA' mode = "LA"
elif nc == 3: elif nc == 3:
mode = 'RGB' mode = "RGB"
elif nc == 4: elif nc == 4:
mode = 'RGBA' mode = "RGBA"
break break
elif tbox == b'colr': elif tbox == b"colr":
meth, prec, approx = struct.unpack_from('>BBB', content) meth, prec, approx = struct.unpack_from(">BBB", content)
if meth == 1: if meth == 1:
cs = struct.unpack_from('>I', content, 3)[0] cs = struct.unpack_from(">I", content, 3)[0]
if cs == 16: # sRGB if cs == 16: # sRGB
if nc == 1 and (bpc & 0x7f) > 8: if nc == 1 and (bpc & 0x7F) > 8:
mode = 'I;16' mode = "I;16"
elif nc == 1: elif nc == 1:
mode = 'L' mode = "L"
elif nc == 3: elif nc == 3:
mode = 'RGB' mode = "RGB"
elif nc == 4: elif nc == 4:
mode = 'RGBA' mode = "RGBA"
break break
elif cs == 17: # grayscale elif cs == 17: # grayscale
if nc == 1 and (bpc & 0x7f) > 8: if nc == 1 and (bpc & 0x7F) > 8:
mode = 'I;16' mode = "I;16"
elif nc == 1: elif nc == 1:
mode = 'L' mode = "L"
elif nc == 2: elif nc == 2:
mode = 'LA' mode = "LA"
break break
elif cs == 18: # sYCC elif cs == 18: # sYCC
if nc == 3: if nc == 3:
mode = 'RGB' mode = "RGB"
elif nc == 4: elif nc == 4:
mode = 'RGBA' mode = "RGBA"
break break
if size is None or mode is None: if size is None or mode is None:
@ -154,6 +152,7 @@ def _parse_jp2_header(fp):
return (size, mode, mimetype) return (size, mode, mimetype)
## ##
# Image plugin for JPEG2000 images. # Image plugin for JPEG2000 images.
@ -164,21 +163,21 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
sig = self.fp.read(4) sig = self.fp.read(4)
if sig == b'\xff\x4f\xff\x51': if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k" self.codec = "j2k"
self._size, self.mode = _parse_codestream(self.fp) self._size, self.mode = _parse_codestream(self.fp)
else: else:
sig = sig + self.fp.read(8) sig = sig + self.fp.read(8)
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a":
self.codec = "jp2" self.codec = "jp2"
header = _parse_jp2_header(self.fp) header = _parse_jp2_header(self.fp)
self._size, self.mode, self.custom_mimetype = header self._size, self.mode, self.custom_mimetype = header
else: else:
raise SyntaxError('not a JPEG 2000 file') raise SyntaxError("not a JPEG 2000 file")
if self.size is None or self.mode is None: if self.size is None or self.mode is None:
raise SyntaxError('unable to determine size/mode') raise SyntaxError("unable to determine size/mode")
self.reduce = 0 self.reduce = 0
self.layers = 0 self.layers = 0
@ -199,15 +198,23 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
except Exception: except Exception:
length = -1 length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0, self.tile = [
(self.codec, self.reduce, self.layers, fd, length))] (
"jpeg2k",
(0, 0) + self.size,
0,
(self.codec, self.reduce, self.layers, fd, length),
)
]
def load(self): def load(self):
if self.reduce: if self.reduce:
power = 1 << self.reduce power = 1 << self.reduce
adjust = power >> 1 adjust = power >> 1
self._size = (int((self.size[0] + adjust) / power), self._size = (
int((self.size[1] + adjust) / power)) int((self.size[0] + adjust) / power),
int((self.size[1] + adjust) / power),
)
if self.tile: if self.tile:
# Update the reduce and layers settings # Update the reduce and layers settings
@ -219,40 +226,47 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _accept(prefix): def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51' or return (
prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') prefix[:4] == b"\xff\x4f\xff\x51"
or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
)
# ------------------------------------------------------------ # ------------------------------------------------------------
# Save support # Save support
def _save(im, fp, filename): def _save(im, fp, filename):
if filename.endswith('.j2k'): if filename.endswith(".j2k"):
kind = 'j2k' kind = "j2k"
else: else:
kind = 'jp2' kind = "jp2"
# Get the keyword arguments # Get the keyword arguments
info = im.encoderinfo info = im.encoderinfo
offset = info.get('offset', None) offset = info.get("offset", None)
tile_offset = info.get('tile_offset', None) tile_offset = info.get("tile_offset", None)
tile_size = info.get('tile_size', None) tile_size = info.get("tile_size", None)
quality_mode = info.get('quality_mode', 'rates') quality_mode = info.get("quality_mode", "rates")
quality_layers = info.get('quality_layers', None) quality_layers = info.get("quality_layers", None)
if quality_layers is not None and not ( if quality_layers is not None and not (
isinstance(quality_layers, (list, tuple)) and isinstance(quality_layers, (list, tuple))
all([isinstance(quality_layer, (int, float)) and all(
for quality_layer in quality_layers]) [
isinstance(quality_layer, (int, float))
for quality_layer in quality_layers
]
)
): ):
raise ValueError('quality_layers must be a sequence of numbers') raise ValueError("quality_layers must be a sequence of numbers")
num_resolutions = info.get('num_resolutions', 0) num_resolutions = info.get("num_resolutions", 0)
cblk_size = info.get('codeblock_size', None) cblk_size = info.get("codeblock_size", None)
precinct_size = info.get('precinct_size', None) precinct_size = info.get("precinct_size", None)
irreversible = info.get('irreversible', False) irreversible = info.get("irreversible", False)
progression = info.get('progression', 'LRCP') progression = info.get("progression", "LRCP")
cinema_mode = info.get('cinema_mode', 'no') cinema_mode = info.get("cinema_mode", "no")
fd = -1 fd = -1
if hasattr(fp, "fileno"): if hasattr(fp, "fileno"):
@ -273,10 +287,11 @@ def _save(im, fp, filename):
irreversible, irreversible,
progression, progression,
cinema_mode, cinema_mode,
fd fd,
) )
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)])
# ------------------------------------------------------------ # ------------------------------------------------------------
# Registry stuff # Registry stuff
@ -285,7 +300,8 @@ def _save(im, fp, filename):
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept) Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
Image.register_save(Jpeg2KImageFile.format, _save) Image.register_save(Jpeg2KImageFile.format, _save)
Image.register_extensions(Jpeg2KImageFile.format, Image.register_extensions(
[".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]) Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]
)
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2') Image.register_mime(Jpeg2KImageFile.format, "image/jp2")

View File

@ -51,8 +51,9 @@ __version__ = "0.6"
# #
# Parser # Parser
def Skip(self, marker): def Skip(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2)) - 2
ImageFile._safe_read(self.fp, n) ImageFile._safe_read(self.fp, n)
@ -61,7 +62,7 @@ def APP(self, marker):
# Application marker. Store these in the APP dictionary. # Application marker. Store these in the APP dictionary.
# Also look for well-known application markers. # Also look for well-known application markers.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
app = "APP%d" % (marker & 15) app = "APP%d" % (marker & 15)
@ -110,7 +111,7 @@ def APP(self, marker):
# parse the image resource block # parse the image resource block
offset = 0 offset = 0
photoshop = {} photoshop = {}
while blocks[offset:offset+4] == b"8BIM": while blocks[offset : offset + 4] == b"8BIM":
offset += 4 offset += 4
# resource code # resource code
code = i16(blocks, offset) code = i16(blocks, offset)
@ -124,13 +125,13 @@ def APP(self, marker):
# resource data block # resource data block
size = i32(blocks, offset) size = i32(blocks, offset)
offset += 4 offset += 4
data = blocks[offset:offset+size] data = blocks[offset : offset + size]
if code == 0x03ED: # ResolutionInfo if code == 0x03ED: # ResolutionInfo
data = { data = {
'XResolution': i32(data[:4]) / 65536, "XResolution": i32(data[:4]) / 65536,
'DisplayedUnitsX': i16(data[4:8]), "DisplayedUnitsX": i16(data[4:8]),
'YResolution': i32(data[8:12]) / 65536, "YResolution": i32(data[8:12]) / 65536,
'DisplayedUnitsY': i16(data[12:]), "DisplayedUnitsY": i16(data[12:]),
} }
photoshop[code] = data photoshop[code] = data
offset = offset + size offset = offset + size
@ -177,7 +178,7 @@ def APP(self, marker):
def COM(self, marker): def COM(self, marker):
# #
# Comment marker. Store these in the APP dictionary. # Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self.app["COM"] = s # compatibility self.app["COM"] = s # compatibility
@ -192,7 +193,7 @@ def SOF(self, marker):
# mode. Note that this could be made a bit brighter, by # mode. Note that this could be made a bit brighter, by
# looking for JFIF and Adobe APP markers. # looking for JFIF and Adobe APP markers.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self._size = i16(s[3:]), i16(s[1:]) self._size = i16(s[3:]), i16(s[1:])
@ -227,9 +228,9 @@ def SOF(self, marker):
self.icclist = None self.icclist = None
for i in range(6, len(s), 3): for i in range(6, len(s), 3):
t = s[i:i+3] t = s[i : i + 3]
# 4-tuples: id, vsamp, hsamp, qtable # 4-tuples: id, vsamp, hsamp, qtable
self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2]))) self.layer.append((t[0], i8(t[1]) // 16, i8(t[1]) & 15, i8(t[2])))
def DQT(self, marker): def DQT(self, marker):
@ -241,13 +242,13 @@ def DQT(self, marker):
# FIXME: The quantization tables can be used to estimate the # FIXME: The quantization tables can be used to estimate the
# compression quality. # compression quality.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
while len(s): while len(s):
if len(s) < 65: if len(s) < 65:
raise SyntaxError("bad quantization table marker") raise SyntaxError("bad quantization table marker")
v = i8(s[0]) v = i8(s[0])
if v//16 == 0: if v // 16 == 0:
self.quantization[v & 15] = array.array("B", s[1:65]) self.quantization[v & 15] = array.array("B", s[1:65])
s = s[65:] s = s[65:]
else: else:
@ -321,7 +322,7 @@ MARKER = {
0xFFFB: ("JPG11", "Extension 11", None), 0xFFFB: ("JPG11", "Extension 11", None),
0xFFFC: ("JPG12", "Extension 12", None), 0xFFFC: ("JPG12", "Extension 12", None),
0xFFFD: ("JPG13", "Extension 13", None), 0xFFFD: ("JPG13", "Extension 13", None),
0xFFFE: ("COM", "Comment", COM) 0xFFFE: ("COM", "Comment", COM),
} }
@ -332,6 +333,7 @@ def _accept(prefix):
## ##
# Image plugin for JPEG and JFIF images. # Image plugin for JPEG and JFIF images.
class JpegImageFile(ImageFile.ImageFile): class JpegImageFile(ImageFile.ImageFile):
format = "JPEG" format = "JPEG"
@ -375,8 +377,7 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0, 0) + self.size, 0, self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
(rawmode, ""))]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
@ -424,8 +425,13 @@ class JpegImageFile(ImageFile.ImageFile):
for s in [8, 4, 2, 1]: for s in [8, 4, 2, 1]:
if scale >= s: if scale >= s:
break break
e = e[0], e[1], (e[2]-e[0]+s-1)//s+e[0], (e[3]-e[1]+s-1)//s+e[1] e = (
self._size = ((self.size[0]+s-1)//s, (self.size[1]+s-1)//s) e[0],
e[1],
(e[2] - e[0] + s - 1) // s + e[0],
(e[3] - e[1] + s - 1) // s + e[1],
)
self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
scale = s scale = s
self.tile = [(d, e, o, a)] self.tile = [(d, e, o, a)]
@ -440,6 +446,7 @@ class JpegImageFile(ImageFile.ImageFile):
import subprocess import subprocess
import tempfile import tempfile
import os import os
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()
os.close(f) os.close(f)
if os.path.exists(self.filename): if os.path.exists(self.filename):
@ -505,7 +512,7 @@ def _getmp(self):
return None return None
file_contents = io.BytesIO(data) file_contents = io.BytesIO(data)
head = file_contents.read(8) head = file_contents.read(8)
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<' endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<"
# process dictionary # process dictionary
try: try:
info = TiffImagePlugin.ImageFileDirectory_v2(head) info = TiffImagePlugin.ImageFileDirectory_v2(head)
@ -525,37 +532,33 @@ def _getmp(self):
rawmpentries = mp[0xB002] rawmpentries = mp[0xB002]
for entrynum in range(0, quant): for entrynum in range(0, quant):
unpackedentry = struct.unpack_from( unpackedentry = struct.unpack_from(
'{}LLLHH'.format(endianness), rawmpentries, entrynum * 16) "{}LLLHH".format(endianness), rawmpentries, entrynum * 16
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', )
'EntryNo2') labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2")
mpentry = dict(zip(labels, unpackedentry)) mpentry = dict(zip(labels, unpackedentry))
mpentryattr = { mpentryattr = {
'DependentParentImageFlag': bool(mpentry['Attribute'] & "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)),
(1 << 31)), "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)),
'DependentChildImageFlag': bool(mpentry['Attribute'] & "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)),
(1 << 30)), "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27,
'RepresentativeImageFlag': bool(mpentry['Attribute'] & "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24,
(1 << 29)), "MPType": mpentry["Attribute"] & 0x00FFFFFF,
'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
'MPType': mpentry['Attribute'] & 0x00FFFFFF
} }
if mpentryattr['ImageDataFormat'] == 0: if mpentryattr["ImageDataFormat"] == 0:
mpentryattr['ImageDataFormat'] = 'JPEG' mpentryattr["ImageDataFormat"] = "JPEG"
else: else:
raise SyntaxError("unsupported picture format in MPO") raise SyntaxError("unsupported picture format in MPO")
mptypemap = { mptypemap = {
0x000000: 'Undefined', 0x000000: "Undefined",
0x010001: 'Large Thumbnail (VGA Equivalent)', 0x010001: "Large Thumbnail (VGA Equivalent)",
0x010002: 'Large Thumbnail (Full HD Equivalent)', 0x010002: "Large Thumbnail (Full HD Equivalent)",
0x020001: 'Multi-Frame Image (Panorama)', 0x020001: "Multi-Frame Image (Panorama)",
0x020002: 'Multi-Frame Image: (Disparity)', 0x020002: "Multi-Frame Image: (Disparity)",
0x020003: 'Multi-Frame Image: (Multi-Angle)', 0x020003: "Multi-Frame Image: (Multi-Angle)",
0x030000: 'Baseline MP Primary Image' 0x030000: "Baseline MP Primary Image",
} }
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'], mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown")
'Unknown') mpentry["Attribute"] = mpentryattr
mpentry['Attribute'] = mpentryattr
mpentries.append(mpentry) mpentries.append(mpentry)
mp[0xB002] = mpentries mp[0xB002] = mpentries
except KeyError: except KeyError:
@ -578,19 +581,24 @@ RAWMODE = {
"YCbCr": "YCbCr", "YCbCr": "YCbCr",
} }
zigzag_index = (0, 1, 5, 6, 14, 15, 27, 28, # noqa: E128 # fmt: off
2, 4, 7, 13, 16, 26, 29, 42, zigzag_index = (
3, 8, 12, 17, 25, 30, 41, 43, 0, 1, 5, 6, 14, 15, 27, 28,
9, 11, 18, 24, 31, 40, 44, 53, 2, 4, 7, 13, 16, 26, 29, 42,
10, 19, 23, 32, 39, 45, 52, 54, 3, 8, 12, 17, 25, 30, 41, 43,
20, 22, 33, 38, 46, 51, 55, 60, 9, 11, 18, 24, 31, 40, 44, 53,
21, 34, 37, 47, 50, 56, 59, 61, 10, 19, 23, 32, 39, 45, 52, 54,
35, 36, 48, 49, 57, 58, 62, 63) 20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63,
)
samplings = {(1, 1, 1, 1, 1, 1): 0, samplings = {
(2, 1, 1, 1, 1, 1): 1, (1, 1, 1, 1, 1, 1): 0,
(2, 2, 1, 1, 1, 1): 2, (2, 1, 1, 1, 1, 1): 1,
} (2, 2, 1, 1, 1, 1): 2,
}
# fmt: on
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
@ -608,7 +616,7 @@ def get_sampling(im):
# NOTE: currently Pillow can't encode JPEG to YCCK format. # NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have # If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers. # to be updated (here and in JpegEncode.c) to deal with 4 layers.
if not hasattr(im, 'layers') or im.layers in (1, 4): if not hasattr(im, "layers") or im.layers in (1, 4):
return -1 return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1) return samplings.get(sampling, -1)
@ -636,15 +644,15 @@ def _save(im, fp, filename):
elif quality in presets: elif quality in presets:
preset = presets[quality] preset = presets[quality]
quality = 0 quality = 0
subsampling = preset.get('subsampling', -1) subsampling = preset.get("subsampling", -1)
qtables = preset.get('quantization') qtables = preset.get("quantization")
elif not isinstance(quality, int): elif not isinstance(quality, int):
raise ValueError("Invalid quality setting") raise ValueError("Invalid quality setting")
else: else:
if subsampling in presets: if subsampling in presets:
subsampling = presets[subsampling].get('subsampling', -1) subsampling = presets[subsampling].get("subsampling", -1)
if isStringType(qtables) and qtables in presets: if isStringType(qtables) and qtables in presets:
qtables = presets[qtables].get('quantization') qtables = presets[qtables].get("quantization")
if subsampling == "4:4:4": if subsampling == "4:4:4":
subsampling = 0 subsampling = 0
@ -658,8 +666,7 @@ def _save(im, fp, filename):
subsampling = 2 subsampling = 2
elif subsampling == "keep": elif subsampling == "keep":
if im.format != "JPEG": if im.format != "JPEG":
raise ValueError( raise ValueError("Cannot use 'keep' when original image is not a JPEG")
"Cannot use 'keep' when original image is not a JPEG")
subsampling = get_sampling(im) subsampling = get_sampling(im)
def validate_qtables(qtables): def validate_qtables(qtables):
@ -667,12 +674,15 @@ def _save(im, fp, filename):
return qtables return qtables
if isStringType(qtables): if isStringType(qtables):
try: try:
lines = [int(num) for line in qtables.splitlines() lines = [
for num in line.split('#', 1)[0].split()] int(num)
for line in qtables.splitlines()
for num in line.split("#", 1)[0].split()
]
except ValueError: except ValueError:
raise ValueError("Invalid quantization table") raise ValueError("Invalid quantization table")
else: else:
qtables = [lines[s:s+64] for s in range(0, len(lines), 64)] qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, (tuple, list, dict)):
if isinstance(qtables, dict): if isinstance(qtables, dict):
qtables = convert_dict_qtables(qtables) qtables = convert_dict_qtables(qtables)
@ -684,7 +694,7 @@ def _save(im, fp, filename):
try: try:
if len(table) != 64: if len(table) != 64:
raise TypeError raise TypeError
table = array.array('B', table) table = array.array("B", table)
except TypeError: except TypeError:
raise ValueError("Invalid quantization table") raise ValueError("Invalid quantization table")
else: else:
@ -693,8 +703,7 @@ def _save(im, fp, filename):
if qtables == "keep": if qtables == "keep":
if im.format != "JPEG": if im.format != "JPEG":
raise ValueError( raise ValueError("Cannot use 'keep' when original image is not a JPEG")
"Cannot use 'keep' when original image is not a JPEG")
qtables = getattr(im, "quantization", None) qtables = getattr(im, "quantization", None)
qtables = validate_qtables(qtables) qtables = validate_qtables(qtables)
@ -712,15 +721,20 @@ def _save(im, fp, filename):
i = 1 i = 1
for marker in markers: for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + extra += (
o8(len(markers)) + marker) b"\xFF\xE2"
+ size
+ b"ICC_PROFILE\0"
+ o8(i)
+ o8(len(markers))
+ marker
)
i += 1 i += 1
# "progressive" is the official name, but older documentation # "progressive" is the official name, but older documentation
# says "progression" # says "progression"
# FIXME: issue a warning if the wrong form is used (post-1.1.7) # FIXME: issue a warning if the wrong form is used (post-1.1.7)
progressive = (info.get("progressive", False) or progressive = info.get("progressive", False) or info.get("progression", False)
info.get("progression", False))
optimize = info.get("optimize", False) optimize = info.get("optimize", False)
@ -735,12 +749,13 @@ def _save(im, fp, filename):
info.get("smooth", 0), info.get("smooth", 0),
optimize, optimize,
info.get("streamtype", 0), info.get("streamtype", 0),
dpi[0], dpi[1], dpi[0],
dpi[1],
subsampling, subsampling,
qtables, qtables,
extra, extra,
exif exif,
) )
# if we optimize, libjpeg needs a buffer big enough to hold the whole image # if we optimize, libjpeg needs a buffer big enough to hold the whole image
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
@ -749,7 +764,7 @@ def _save(im, fp, filename):
bufsize = 0 bufsize = 0
if optimize or progressive: if optimize or progressive:
# CMYK can be bigger # CMYK can be bigger
if im.mode == 'CMYK': if im.mode == "CMYK":
bufsize = 4 * im.size[0] * im.size[1] bufsize = 4 * im.size[0] * im.size[1]
# keep sets quality to 0, but the actual value may be high. # keep sets quality to 0, but the actual value may be high.
elif quality >= 95 or quality == 0: elif quality >= 95 or quality == 0:
@ -759,16 +774,16 @@ def _save(im, fp, filename):
# The EXIF info needs to be written as one block, + APP1, + one spare byte. # The EXIF info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough. Same with the icc_profile block. # Ensure that our buffer is big enough. Same with the icc_profile block.
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)
len(extra) + 1)
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize) ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)
def _save_cjpeg(im, fp, filename): def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os import os
import subprocess import subprocess
tempfile = im._dump() tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try: try:
@ -786,14 +801,17 @@ def jpeg_factory(fp=None, filename=None):
if mpheader[45057] > 1: if mpheader[45057] > 1:
# It's actually an MPO # It's actually an MPO
from .MpoImagePlugin import MpoImageFile from .MpoImagePlugin import MpoImageFile
# Don't reload everything, just convert it. # Don't reload everything, just convert it.
im = MpoImageFile.adopt(im, mpheader) im = MpoImageFile.adopt(im, mpheader)
except (TypeError, IndexError): except (TypeError, IndexError):
# It is really a JPEG # It is really a JPEG
pass pass
except SyntaxError: except SyntaxError:
warnings.warn("Image appears to be a malformed MPO file, it will be " warnings.warn(
"interpreted as a base JPEG file") "Image appears to be a malformed MPO file, it will be "
"interpreted as a base JPEG file"
)
return im return im
@ -803,7 +821,6 @@ def jpeg_factory(fp=None, filename=None):
Image.register_open(JpegImageFile.format, jpeg_factory, _accept) Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save) Image.register_save(JpegImageFile.format, _save)
Image.register_extensions(JpegImageFile.format, Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])
[".jfif", ".jpe", ".jpg", ".jpeg"])
Image.register_mime(JpegImageFile.format, "image/jpeg") Image.register_mime(JpegImageFile.format, "image/jpeg")

View File

@ -67,6 +67,7 @@ https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/li
""" """
# fmt: off
presets = { # noqa: E128 presets = { # noqa: E128
'web_low': {'subsampling': 2, # "4:2:0" 'web_low': {'subsampling': 2, # "4:2:0"
'quantization': [ 'quantization': [
@ -240,3 +241,4 @@ presets = { # noqa: E128
15, 12, 12, 12, 12, 12, 12, 12] 15, 12, 12, 12, 12, 12, 12, 12]
]}, ]},
} }
# fmt: on

View File

@ -31,6 +31,7 @@ def _accept(s):
## ##
# Image plugin for McIdas area images. # Image plugin for McIdas area images.
class McIdasImageFile(ImageFile.ImageFile): class McIdasImageFile(ImageFile.ImageFile):
format = "MCIDAS" format = "MCIDAS"
@ -64,7 +65,7 @@ class McIdasImageFile(ImageFile.ImageFile):
self._size = w[10], w[9] self._size = w[10], w[9]
offset = w[34] + w[15] offset = w[34] + w[15]
stride = w[15] + w[10]*w[11]*w[14] stride = w[15] + w[10] * w[11] * w[14]
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))] self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]

View File

@ -37,6 +37,7 @@ def _accept(prefix):
## ##
# Image plugin for Microsoft's Image Composer file format. # Image plugin for Microsoft's Image Composer file format.
class MicImageFile(TiffImagePlugin.TiffImageFile): class MicImageFile(TiffImagePlugin.TiffImageFile):
format = "MIC" format = "MIC"

View File

@ -25,8 +25,8 @@ __version__ = "0.1"
# #
# Bitstream parser # Bitstream parser
class BitStream(object):
class BitStream(object):
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
self.bits = 0 self.bits = 0
@ -61,6 +61,7 @@ class BitStream(object):
# Image plugin for MPEG streams. This plugin can identify a stream, # Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it. # but it cannot read it.
class MpegImageFile(ImageFile.ImageFile): class MpegImageFile(ImageFile.ImageFile):
format = "MPEG" format = "MPEG"

View File

@ -38,6 +38,7 @@ def _save(im, fp, filename):
## ##
# Image plugin for MPO images. # Image plugin for MPO images.
class MpoImageFile(JpegImagePlugin.JpegImageFile): class MpoImageFile(JpegImagePlugin.JpegImageFile):
format = "MPO" format = "MPO"
@ -52,13 +53,14 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def _after_jpeg_open(self, mpheader=None): def _after_jpeg_open(self, mpheader=None):
self.mpinfo = mpheader if mpheader is not None else self._getmp() self.mpinfo = mpheader if mpheader is not None else self._getmp()
self.__framecount = self.mpinfo[0xB001] self.__framecount = self.mpinfo[0xB001]
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] self.__mpoffsets = [
for mpent in self.mpinfo[0xB002]] mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
]
self.__mpoffsets[0] = 0 self.__mpoffsets[0] = 0
# Note that the following assertion will only be invalid if something # Note that the following assertion will only be invalid if something
# gets broken within JpegImagePlugin. # gets broken within JpegImagePlugin.
assert self.__framecount == len(self.__mpoffsets) assert self.__framecount == len(self.__mpoffsets)
del self.info['mpoffset'] # no longer needed del self.info["mpoffset"] # no longer needed
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0 self.__frame = 0
@ -87,7 +89,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
if "parsed_exif" in self.info: if "parsed_exif" in self.info:
del self.info["parsed_exif"] del self.info["parsed_exif"]
if i16(self.fp.read(2)) == 0xFFE1: # APP1 if i16(self.fp.read(2)) == 0xFFE1: # APP1
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2)) - 2
self.info["exif"] = ImageFile._safe_read(self.fp, n) self.info["exif"] = ImageFile._safe_read(self.fp, n)
exif = self._getexif() exif = self._getexif()
@ -96,9 +98,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
elif "exif" in self.info: elif "exif" in self.info:
del self.info["exif"] del self.info["exif"]
self.tile = [ self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))]
("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
]
self.__frame = frame self.__frame = frame
def tell(self): def tell(self):

View File

@ -45,6 +45,7 @@ def _accept(prefix):
# Image plugin for Windows MSP images. This plugin supports both # Image plugin for Windows MSP images. This plugin supports both
# uncompressed (Windows 1.0). # uncompressed (Windows 1.0).
class MspImageFile(ImageFile.ImageFile): class MspImageFile(ImageFile.ImageFile):
format = "MSP" format = "MSP"
@ -60,7 +61,7 @@ class MspImageFile(ImageFile.ImageFile):
# Header checksum # Header checksum
checksum = 0 checksum = 0
for i in range(0, 32, 2): for i in range(0, 32, 2):
checksum = checksum ^ i16(s[i:i+2]) checksum = checksum ^ i16(s[i : i + 2])
if checksum != 0: if checksum != 0:
raise SyntaxError("bad MSP checksum") raise SyntaxError("bad MSP checksum")
@ -68,9 +69,9 @@ class MspImageFile(ImageFile.ImageFile):
self._size = i16(s[4:]), i16(s[6:]) self._size = i16(s[4:]), i16(s[6:])
if s[:4] == b"DanM": if s[:4] == b"DanM":
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))] self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
else: else:
self.tile = [("MSP", (0, 0)+self.size, 32, None)] self.tile = [("MSP", (0, 0) + self.size, 32, None)]
class MspDecoder(ImageFile.PyDecoder): class MspDecoder(ImageFile.PyDecoder):
@ -113,11 +114,12 @@ class MspDecoder(ImageFile.PyDecoder):
def decode(self, buffer): def decode(self, buffer):
img = io.BytesIO() img = io.BytesIO()
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8)) blank_line = bytearray((0xFF,) * ((self.state.xsize + 7) // 8))
try: try:
self.fd.seek(32) self.fd.seek(32)
rowmap = struct.unpack_from("<%dH" % (self.state.ysize), rowmap = struct.unpack_from(
self.fd.read(self.state.ysize*2)) "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2)
)
except struct.error: except struct.error:
raise IOError("Truncated MSP file in row map") raise IOError("Truncated MSP file in row map")
@ -129,8 +131,8 @@ class MspDecoder(ImageFile.PyDecoder):
row = self.fd.read(rowlen) row = self.fd.read(rowlen)
if len(row) != rowlen: if len(row) != rowlen:
raise IOError( raise IOError(
"Truncated MSP file, expected %d bytes on row %s", "Truncated MSP file, expected %d bytes on row %s", (rowlen, x)
(rowlen, x)) )
idx = 0 idx = 0
while idx < rowlen: while idx < rowlen:
runtype = i8(row[idx]) runtype = i8(row[idx])
@ -141,7 +143,7 @@ class MspDecoder(ImageFile.PyDecoder):
idx += 2 idx += 2
else: else:
runcount = runtype runcount = runtype
img.write(row[idx:idx+runcount]) img.write(row[idx : idx + runcount])
idx += runcount idx += runcount
except struct.error: except struct.error:
@ -152,7 +154,7 @@ class MspDecoder(ImageFile.PyDecoder):
return 0, 0 return 0, 0
Image.register_decoder('MSP', MspDecoder) Image.register_decoder("MSP", MspDecoder)
# #
@ -183,7 +185,7 @@ def _save(im, fp, filename):
fp.write(o16(h)) fp.write(o16(h))
# image body # image body
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
# #

View File

@ -38,16 +38,18 @@ class PSDraw(object):
if not py3 or self.fp == sys.stdout: if not py3 or self.fp == sys.stdout:
self.fp.write(to_write) self.fp.write(to_write)
else: else:
self.fp.write(bytes(to_write, 'UTF-8')) self.fp.write(bytes(to_write, "UTF-8"))
def begin_document(self, id=None): def begin_document(self, id=None):
"""Set up printing of a document. (Write Postscript DSC header.)""" """Set up printing of a document. (Write Postscript DSC header.)"""
# FIXME: incomplete # FIXME: incomplete
self._fp_write("%!PS-Adobe-3.0\n" self._fp_write(
"save\n" "%!PS-Adobe-3.0\n"
"/showpage { } def\n" "save\n"
"%%EndComments\n" "/showpage { } def\n"
"%%BeginDocument\n") "%%EndComments\n"
"%%BeginDocument\n"
)
# self._fp_write(ERROR_PS) # debugging! # self._fp_write(ERROR_PS) # debugging!
self._fp_write(EDROFF_PS) self._fp_write(EDROFF_PS)
self._fp_write(VDI_PS) self._fp_write(VDI_PS)
@ -56,9 +58,7 @@ class PSDraw(object):
def end_document(self): def end_document(self):
"""Ends printing. (Write Postscript DSC footer.)""" """Ends printing. (Write Postscript DSC footer.)"""
self._fp_write("%%EndDocument\n" self._fp_write("%%EndDocument\nrestore showpage\n%%End\n")
"restore showpage\n"
"%%End\n")
if hasattr(self.fp, "flush"): if hasattr(self.fp, "flush"):
self.fp.flush() self.fp.flush()
@ -71,8 +71,7 @@ class PSDraw(object):
""" """
if font not in self.isofont: if font not in self.isofont:
# reencode font # reencode font
self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" % self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font))
(font, font))
self.isofont[font] = 1 self.isofont[font] = 1
# rough # rough
self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font)) self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font))
@ -142,6 +141,7 @@ class PSDraw(object):
EpsImagePlugin._save(im, self.fp, None, 0) EpsImagePlugin._save(im, self.fp, None, 0)
self._fp_write("\ngrestore\n") self._fp_write("\ngrestore\n")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Postscript driver # Postscript driver

View File

@ -19,6 +19,7 @@ from ._binary import o8
## ##
# File handler for Teragon-style palette files. # File handler for Teragon-style palette files.
class PaletteFile(object): class PaletteFile(object):
rawmode = "RGB" rawmode = "RGB"

View File

@ -14,6 +14,7 @@ from ._binary import o8, o16be as o16b
# PIL.__version__ instead. # PIL.__version__ instead.
__version__ = "1.0" __version__ = "1.0"
# fmt: off
_Palm8BitColormapValues = ( # noqa: E131 _Palm8BitColormapValues = ( # noqa: E131
(255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255), (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
(255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204), (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
@ -79,6 +80,7 @@ _Palm8BitColormapValues = ( # noqa: E131
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)) (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0))
# fmt: on
# so build a prototype image to be used for palette resampling # so build a prototype image to be used for palette resampling
@ -88,7 +90,7 @@ def build_prototype_image():
palettedata = () palettedata = ()
for colormapValue in _Palm8BitColormapValues: for colormapValue in _Palm8BitColormapValues:
palettedata += colormapValue palettedata += colormapValue
palettedata += (0, 0, 0)*(256 - len(_Palm8BitColormapValues)) palettedata += (0, 0, 0) * (256 - len(_Palm8BitColormapValues))
image.putpalette(palettedata) image.putpalette(palettedata)
return image return image
@ -100,17 +102,9 @@ Palm8BitColormapImage = build_prototype_image()
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
_FLAGS = { _FLAGS = {"custom-colormap": 0x4000, "is-compressed": 0x8000, "has-transparent": 0x2000}
"custom-colormap": 0x4000,
"is-compressed": 0x8000,
"has-transparent": 0x2000,
}
_COMPRESSION_TYPES = { _COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00}
"none": 0xFF,
"rle": 0x01,
"scanline": 0x00,
}
# #
@ -119,6 +113,7 @@ _COMPRESSION_TYPES = {
## ##
# (Internal) Image save plugin for the Palm format. # (Internal) Image save plugin for the Palm format.
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode == "P": if im.mode == "P":
@ -137,13 +132,14 @@ def _save(im, fp, filename):
# Palm does greyscale from white (0) to black (1) # Palm does greyscale from white (0) to black (1)
bpp = im.encoderinfo["bpp"] bpp = im.encoderinfo["bpp"]
im = im.point( im = im.point(
lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift)) lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: maxval - (x >> shift)
)
elif im.info.get("bpp") in (1, 2, 4): elif im.info.get("bpp") in (1, 2, 4):
# here we assume that even though the inherent mode is 8-bit grayscale, # here we assume that even though the inherent mode is 8-bit grayscale,
# only the lower bpp bits are significant. # only the lower bpp bits are significant.
# We invert them to match the Palm. # We invert them to match the Palm.
bpp = im.info["bpp"] bpp = im.info["bpp"]
im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval)) im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval))
else: else:
raise IOError("cannot write mode %s as Palm" % im.mode) raise IOError("cannot write mode %s as Palm" % im.mode)
@ -172,7 +168,7 @@ def _save(im, fp, filename):
cols = im.size[0] cols = im.size[0]
rows = im.size[1] rows = im.size[1]
rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2 rowbytes = int((cols + (16 // bpp - 1)) / (16 // bpp)) * 2
transparent_index = 0 transparent_index = 0
compression_type = _COMPRESSION_TYPES["none"] compression_type = _COMPRESSION_TYPES["none"]
@ -196,7 +192,7 @@ def _save(im, fp, filename):
fp.write(o16b(offset)) fp.write(o16b(offset))
fp.write(o8(transparent_index)) fp.write(o8(transparent_index))
fp.write(o8(compression_type)) fp.write(o8(compression_type))
fp.write(o16b(0)) # reserved by Palm fp.write(o16b(0)) # reserved by Palm
# now write colormap if necessary # now write colormap if necessary
@ -204,20 +200,21 @@ def _save(im, fp, filename):
fp.write(o16b(256)) fp.write(o16b(256))
for i in range(256): for i in range(256):
fp.write(o8(i)) fp.write(o8(i))
if colormapmode == 'RGB': if colormapmode == "RGB":
fp.write( fp.write(
o8(colormap[3 * i]) + o8(colormap[3 * i])
o8(colormap[3 * i + 1]) + + o8(colormap[3 * i + 1])
o8(colormap[3 * i + 2])) + o8(colormap[3 * i + 2])
elif colormapmode == 'RGBA': )
elif colormapmode == "RGBA":
fp.write( fp.write(
o8(colormap[4 * i]) + o8(colormap[4 * i])
o8(colormap[4 * i + 1]) + + o8(colormap[4 * i + 1])
o8(colormap[4 * i + 2])) + o8(colormap[4 * i + 2])
)
# now convert data to raw form # now convert data to raw form
ImageFile._save( ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))])
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
if hasattr(fp, "flush"): if hasattr(fp, "flush"):
fp.flush() fp.flush()

View File

@ -28,6 +28,7 @@ __version__ = "0.1"
# image from the file; higher resolutions are encoded in a proprietary # image from the file; higher resolutions are encoded in a proprietary
# encoding. # encoding.
class PcdImageFile(ImageFile.ImageFile): class PcdImageFile(ImageFile.ImageFile):
format = "PCD" format = "PCD"
@ -51,7 +52,7 @@ class PcdImageFile(ImageFile.ImageFile):
self.mode = "RGB" self.mode = "RGB"
self._size = 768, 512 # FIXME: not correct for rotated images! self._size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)] self.tile = [("pcd", (0, 0) + self.size, 96 * 2048, None)]
def load_end(self): def load_end(self):
if self.tile_post_rotate: if self.tile_post_rotate:

View File

@ -25,31 +25,32 @@ from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32
PCF_MAGIC = 0x70636601 # "\x01fcp" PCF_MAGIC = 0x70636601 # "\x01fcp"
PCF_PROPERTIES = (1 << 0) PCF_PROPERTIES = 1 << 0
PCF_ACCELERATORS = (1 << 1) PCF_ACCELERATORS = 1 << 1
PCF_METRICS = (1 << 2) PCF_METRICS = 1 << 2
PCF_BITMAPS = (1 << 3) PCF_BITMAPS = 1 << 3
PCF_INK_METRICS = (1 << 4) PCF_INK_METRICS = 1 << 4
PCF_BDF_ENCODINGS = (1 << 5) PCF_BDF_ENCODINGS = 1 << 5
PCF_SWIDTHS = (1 << 6) PCF_SWIDTHS = 1 << 6
PCF_GLYPH_NAMES = (1 << 7) PCF_GLYPH_NAMES = 1 << 7
PCF_BDF_ACCELERATORS = (1 << 8) PCF_BDF_ACCELERATORS = 1 << 8
BYTES_PER_ROW = [ BYTES_PER_ROW = [
lambda bits: ((bits+7) >> 3), lambda bits: ((bits + 7) >> 3),
lambda bits: ((bits+15) >> 3) & ~1, lambda bits: ((bits + 15) >> 3) & ~1,
lambda bits: ((bits+31) >> 3) & ~3, lambda bits: ((bits + 31) >> 3) & ~3,
lambda bits: ((bits+63) >> 3) & ~7, lambda bits: ((bits + 63) >> 3) & ~7,
] ]
def sz(s, o): def sz(s, o):
return s[o:s.index(b"\0", o)] return s[o : s.index(b"\0", o)]
## ##
# Font file plugin for the X11 PCF format. # Font file plugin for the X11 PCF format.
class PcfFontFile(FontFile.FontFile): class PcfFontFile(FontFile.FontFile):
name = "name" name = "name"
@ -83,7 +84,7 @@ class PcfFontFile(FontFile.FontFile):
ix = encoding[ch] ix = encoding[ch]
if ix is not None: if ix is not None:
x, y, l, r, w, a, d, f = metrics[ix] x, y, l, r, w, a, d, f = metrics[ix]
glyph = (w, 0), (l, d-y, x+l, d), (0, 0, x, y), bitmaps[ix] glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
self.glyph[ch] = glyph self.glyph[ch] = glyph
def _getformat(self, tag): def _getformat(self, tag):
@ -141,7 +142,7 @@ class PcfFontFile(FontFile.FontFile):
append = metrics.append append = metrics.append
if (format & 0xff00) == 0x100: if (format & 0xFF00) == 0x100:
# "compressed" metrics # "compressed" metrics
for i in range(i16(fp.read(2))): for i in range(i16(fp.read(2))):
@ -152,10 +153,7 @@ class PcfFontFile(FontFile.FontFile):
descent = i8(fp.read(1)) - 128 descent = i8(fp.read(1)) - 128
xsize = right - left xsize = right - left
ysize = ascent + descent ysize = ascent + descent
append( append((xsize, ysize, left, right, width, ascent, descent, 0))
(xsize, ysize, left, right, width,
ascent, descent, 0)
)
else: else:
@ -169,10 +167,7 @@ class PcfFontFile(FontFile.FontFile):
attributes = i16(fp.read(2)) attributes = i16(fp.read(2))
xsize = right - left xsize = right - left
ysize = ascent + descent ysize = ascent + descent
append( append((xsize, ysize, left, right, width, ascent, descent, attributes))
(xsize, ysize, left, right, width,
ascent, descent, attributes)
)
return metrics return metrics
@ -199,7 +194,7 @@ class PcfFontFile(FontFile.FontFile):
bitmapSizes.append(i32(fp.read(4))) bitmapSizes.append(i32(fp.read(4)))
# byteorder = format & 4 # non-zero => MSB # byteorder = format & 4 # non-zero => MSB
bitorder = format & 8 # non-zero => MSB bitorder = format & 8 # non-zero => MSB
padindex = format & 3 padindex = format & 3
bitmapsize = bitmapSizes[padindex] bitmapsize = bitmapSizes[padindex]
@ -214,10 +209,8 @@ class PcfFontFile(FontFile.FontFile):
for i in range(nbitmaps): for i in range(nbitmaps):
x, y, l, r, w, a, d, f = metrics[i] x, y, l, r, w, a, d, f = metrics[i]
b, e = offsets[i], offsets[i+1] b, e = offsets[i], offsets[i + 1]
bitmaps.append( bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x)))
Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x))
)
return bitmaps return bitmaps
@ -239,7 +232,7 @@ class PcfFontFile(FontFile.FontFile):
encodingOffset = i16(fp.read(2)) encodingOffset = i16(fp.read(2))
if encodingOffset != 0xFFFF: if encodingOffset != 0xFFFF:
try: try:
encoding[i+firstCol] = encodingOffset encoding[i + firstCol] = encodingOffset
except IndexError: except IndexError:
break # only load ISO-8859-1 glyphs break # only load ISO-8859-1 glyphs

View File

@ -44,6 +44,7 @@ def _accept(prefix):
## ##
# Image plugin for Paintbrush images. # Image plugin for Paintbrush images.
class PcxImageFile(ImageFile.ImageFile): class PcxImageFile(ImageFile.ImageFile):
format = "PCX" format = "PCX"
@ -57,7 +58,7 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError("not a PCX file") raise SyntaxError("not a PCX file")
# image # image
bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1 bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
raise SyntaxError("bad PCX image size") raise SyntaxError("bad PCX image size")
logger.debug("BBox: %s %s %s %s", *bbox) logger.debug("BBox: %s %s %s %s", *bbox)
@ -67,8 +68,13 @@ class PcxImageFile(ImageFile.ImageFile):
bits = i8(s[3]) bits = i8(s[3])
planes = i8(s[65]) planes = i8(s[65])
stride = i16(s, 66) stride = i16(s, 66)
logger.debug("PCX version %s, bits %s, planes %s, stride %s", logger.debug(
version, bits, planes, stride) "PCX version %s, bits %s, planes %s, stride %s",
version,
bits,
planes,
stride,
)
self.info["dpi"] = i16(s, 12), i16(s, 14) self.info["dpi"] = i16(s, 12), i16(s, 14)
@ -88,7 +94,7 @@ class PcxImageFile(ImageFile.ImageFile):
if len(s) == 769 and i8(s[0]) == 12: if len(s) == 769 and i8(s[0]) == 12:
# check if the palette is linear greyscale # check if the palette is linear greyscale
for i in range(256): for i in range(256):
if s[i*3+1:i*3+4] != o8(i)*3: if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:
mode = rawmode = "P" mode = rawmode = "P"
break break
if mode == "P": if mode == "P":
@ -103,13 +109,14 @@ class PcxImageFile(ImageFile.ImageFile):
raise IOError("unknown PCX mode") raise IOError("unknown PCX mode")
self.mode = mode self.mode = mode
self._size = bbox[2]-bbox[0], bbox[3]-bbox[1] self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]
bbox = (0, 0) + self.size bbox = (0, 0) + self.size
logger.debug("size: %sx%s", *self.size) logger.debug("size: %sx%s", *self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))] self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# save PCX files # save PCX files
@ -138,8 +145,12 @@ def _save(im, fp, filename):
# Ideally it should be passed in in the state, but the bytes value # Ideally it should be passed in in the state, but the bytes value
# gets overwritten. # gets overwritten.
logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d", logger.debug(
im.size[0], bits, stride) "PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
im.size[0],
bits,
stride,
)
# under windows, we could determine the current screen size with # under windows, we could determine the current screen size with
# "Image.core.display_mode()[1]", but I think that's overkill... # "Image.core.display_mode()[1]", but I think that's overkill...
@ -150,17 +161,30 @@ def _save(im, fp, filename):
# PCX header # PCX header
fp.write( fp.write(
o8(10) + o8(version) + o8(1) + o8(bits) + o16(0) + o8(10)
o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) + + o8(version)
o16(dpi[1]) + b"\0"*24 + b"\xFF"*24 + b"\0" + o8(planes) + + o8(1)
o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) + + o8(bits)
b"\0"*54 + o16(0)
) + o16(0)
+ o16(im.size[0] - 1)
+ o16(im.size[1] - 1)
+ o16(dpi[0])
+ o16(dpi[1])
+ b"\0" * 24
+ b"\xFF" * 24
+ b"\0"
+ o8(planes)
+ o16(stride)
+ o16(1)
+ o16(screen[0])
+ o16(screen[1])
+ b"\0" * 54
)
assert fp.tell() == 128 assert fp.tell() == 128
ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0, ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))])
(rawmode, bits*planes))])
if im.mode == "P": if im.mode == "P":
# colour palette # colour palette
@ -170,7 +194,8 @@ def _save(im, fp, filename):
# greyscale palette # greyscale palette
fp.write(o8(12)) fp.write(o8(12))
for i in range(256): for i in range(256):
fp.write(o8(i)*3) fp.write(o8(i) * 3)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# registry # registry

View File

@ -48,6 +48,7 @@ def _save_all(im, fp, filename):
## ##
# (Internal) Image save plugin for the PDF format. # (Internal) Image save plugin for the PDF format.
def _save(im, fp, filename, save_all=False): def _save(im, fp, filename, save_all=False):
is_appending = im.encoderinfo.get("append", False) is_appending = im.encoderinfo.get("append", False)
if is_appending: if is_appending:
@ -58,16 +59,16 @@ def _save(im, fp, filename, save_all=False):
resolution = im.encoderinfo.get("resolution", 72.0) resolution = im.encoderinfo.get("resolution", 72.0)
info = { info = {
"title": None if is_appending else os.path.splitext( "title": None
os.path.basename(filename) if is_appending
)[0], else os.path.splitext(os.path.basename(filename))[0],
"author": None, "author": None,
"subject": None, "subject": None,
"keywords": None, "keywords": None,
"creator": None, "creator": None,
"producer": None, "producer": None,
"creationDate": None if is_appending else time.gmtime(), "creationDate": None if is_appending else time.gmtime(),
"modDate": None if is_appending else time.gmtime() "modDate": None if is_appending else time.gmtime(),
} }
for k, default in info.items(): for k, default in info.items():
v = im.encoderinfo.get(k) if k in im.encoderinfo else default v = im.encoderinfo.get(k) if k in im.encoderinfo else default
@ -142,7 +143,7 @@ def _save(im, fp, filename, save_all=False):
PdfParser.PdfName("Indexed"), PdfParser.PdfName("Indexed"),
PdfParser.PdfName("DeviceRGB"), PdfParser.PdfName("DeviceRGB"),
255, 255,
PdfParser.PdfBinary(palette) PdfParser.PdfBinary(palette),
] ]
procset = "ImageI" # indexed color procset = "ImageI" # indexed color
elif im.mode == "RGB": elif im.mode == "RGB":
@ -168,14 +169,13 @@ def _save(im, fp, filename, save_all=False):
data = im.tobytes("raw", "1") data = im.tobytes("raw", "1")
im = Image.new("L", im.size) im = Image.new("L", im.size)
im.putdata(data) im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
elif filter == "DCTDecode": elif filter == "DCTDecode":
Image.SAVE["JPEG"](im, op, filename) Image.SAVE["JPEG"](im, op, filename)
elif filter == "FlateDecode": elif filter == "FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
elif filter == "RunLengthDecode": elif filter == "RunLengthDecode":
ImageFile._save(im, op, ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)])
[("packbits", (0, 0)+im.size, 0, im.mode)])
else: else:
raise ValueError("unsupported PDF filter (%s)" % filter) raise ValueError("unsupported PDF filter (%s)" % filter)
@ -184,48 +184,46 @@ def _save(im, fp, filename, save_all=False):
width, height = im.size width, height = im.size
existing_pdf.write_obj(image_refs[pageNumber], existing_pdf.write_obj(
stream=op.getvalue(), image_refs[pageNumber],
Type=PdfParser.PdfName("XObject"), stream=op.getvalue(),
Subtype=PdfParser.PdfName("Image"), Type=PdfParser.PdfName("XObject"),
Width=width, # * 72.0 / resolution, Subtype=PdfParser.PdfName("Image"),
Height=height, # * 72.0 / resolution, Width=width, # * 72.0 / resolution,
Filter=PdfParser.PdfName(filter), Height=height, # * 72.0 / resolution,
BitsPerComponent=bits, Filter=PdfParser.PdfName(filter),
DecodeParams=params, BitsPerComponent=bits,
ColorSpace=colorspace) DecodeParams=params,
ColorSpace=colorspace,
)
# #
# page # page
existing_pdf.write_page(page_refs[pageNumber], existing_pdf.write_page(
Resources=PdfParser.PdfDict( page_refs[pageNumber],
ProcSet=[ Resources=PdfParser.PdfDict(
PdfParser.PdfName("PDF"), ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
PdfParser.PdfName(procset) XObject=PdfParser.PdfDict(image=image_refs[pageNumber]),
], ),
XObject=PdfParser.PdfDict( MediaBox=[
image=image_refs[pageNumber] 0,
) 0,
), int(width * 72.0 / resolution),
MediaBox=[ int(height * 72.0 / resolution),
0, ],
0, Contents=contents_refs[pageNumber],
int(width * 72.0 / resolution), )
int(height * 72.0 / resolution)
],
Contents=contents_refs[pageNumber])
# #
# page contents # page contents
page_contents = PdfParser.make_bytes( page_contents = PdfParser.make_bytes(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % ( "q %d 0 0 %d 0 0 cm /image Do Q\n"
int(width * 72.0 / resolution), % (int(width * 72.0 / resolution), int(height * 72.0 / resolution))
int(height * 72.0 / resolution))) )
existing_pdf.write_obj(contents_refs[pageNumber], existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents)
stream=page_contents)
pageNumber += 1 pageNumber += 1
@ -236,6 +234,7 @@ def _save(im, fp, filename, save_all=False):
fp.flush() fp.flush()
existing_pdf.close() existing_pdf.close()
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -15,11 +15,15 @@ except ImportError:
if py3: # Python 3.x if py3: # Python 3.x
def make_bytes(s): def make_bytes(s):
return s.encode("us-ascii") return s.encode("us-ascii")
else: # Python 2.x else: # Python 2.x
def make_bytes(s): # pragma: no cover def make_bytes(s): # pragma: no cover
return s # pragma: no cover return s # pragma: no cover
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
@ -74,8 +78,8 @@ PDFDocEncoding = {
def decode_text(b): def decode_text(b):
if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: if b[: len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
return b[len(codecs.BOM_UTF16_BE):].decode("utf_16_be") return b[len(codecs.BOM_UTF16_BE) :].decode("utf_16_be")
elif py3: # Python 3.x elif py3: # Python 3.x
return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b) return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)
else: # Python 2.x else: # Python 2.x
@ -85,6 +89,7 @@ def decode_text(b):
class PdfFormatError(RuntimeError): class PdfFormatError(RuntimeError):
"""An error that probably indicates a syntactic or semantic error in the """An error that probably indicates a syntactic or semantic error in the
PDF file structure""" PDF file structure"""
pass pass
@ -93,8 +98,9 @@ def check_format_condition(condition, error_message):
raise PdfFormatError(error_message) raise PdfFormatError(error_message)
class IndirectReference(collections.namedtuple("IndirectReferenceTuple", class IndirectReference(
["object_id", "generation"])): collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])
):
def __str__(self): def __str__(self):
return "%s %s R" % self return "%s %s R" % self
@ -102,9 +108,11 @@ class IndirectReference(collections.namedtuple("IndirectReferenceTuple",
return self.__str__().encode("us-ascii") return self.__str__().encode("us-ascii")
def __eq__(self, other): def __eq__(self, other):
return other.__class__ is self.__class__ and \ return (
other.object_id == self.object_id and \ other.__class__ is self.__class__
other.generation == self.generation and other.object_id == self.object_id
and other.generation == self.generation
)
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
@ -120,9 +128,9 @@ class IndirectObjectDef(IndirectReference):
class XrefTable: class XrefTable:
def __init__(self): def __init__(self):
self.existing_entries = {} # object ID => (offset, generation) self.existing_entries = {} # object ID => (offset, generation)
self.new_entries = {} # object ID => (offset, generation) self.new_entries = {} # object ID => (offset, generation)
self.deleted_entries = {0: 65536} # object ID => generation self.deleted_entries = {0: 65536} # object ID => generation
self.reading_finished = False self.reading_finished = False
def __setitem__(self, key, value): def __setitem__(self, key, value):
@ -150,26 +158,27 @@ class XrefTable:
elif key in self.deleted_entries: elif key in self.deleted_entries:
generation = self.deleted_entries[key] generation = self.deleted_entries[key]
else: else:
raise IndexError("object ID " + str(key) + raise IndexError(
" cannot be deleted because it doesn't exist") "object ID " + str(key) + " cannot be deleted because it doesn't exist"
)
def __contains__(self, key): def __contains__(self, key):
return key in self.existing_entries or key in self.new_entries return key in self.existing_entries or key in self.new_entries
def __len__(self): def __len__(self):
return len(set(self.existing_entries.keys()) | return len(
set(self.new_entries.keys()) | set(self.existing_entries.keys())
set(self.deleted_entries.keys())) | set(self.new_entries.keys())
| set(self.deleted_entries.keys())
)
def keys(self): def keys(self):
return ( return (
set(self.existing_entries.keys()) - set(self.existing_entries.keys()) - set(self.deleted_entries.keys())
set(self.deleted_entries.keys())
) | set(self.new_entries.keys()) ) | set(self.new_entries.keys())
def write(self, f): def write(self, f):
keys = sorted(set(self.new_entries.keys()) | keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))
set(self.deleted_entries.keys()))
deleted_keys = sorted(set(self.deleted_entries.keys())) deleted_keys = sorted(set(self.deleted_entries.keys()))
startxref = f.tell() startxref = f.tell()
f.write(b"xref\n") f.write(b"xref\n")
@ -177,7 +186,7 @@ class XrefTable:
# find a contiguous sequence of object IDs # find a contiguous sequence of object IDs
prev = None prev = None
for index, key in enumerate(keys): for index, key in enumerate(keys):
if prev is None or prev+1 == key: if prev is None or prev + 1 == key:
prev = key prev = key
else: else:
contiguous_keys = keys[:index] contiguous_keys = keys[:index]
@ -186,25 +195,27 @@ class XrefTable:
else: else:
contiguous_keys = keys contiguous_keys = keys
keys = None keys = None
f.write(make_bytes("%d %d\n" % f.write(make_bytes("%d %d\n" % (contiguous_keys[0], len(contiguous_keys))))
(contiguous_keys[0], len(contiguous_keys))))
for object_id in contiguous_keys: for object_id in contiguous_keys:
if object_id in self.new_entries: if object_id in self.new_entries:
f.write(make_bytes("%010d %05d n \n" % f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id]))
self.new_entries[object_id]))
else: else:
this_deleted_object_id = deleted_keys.pop(0) this_deleted_object_id = deleted_keys.pop(0)
check_format_condition(object_id == this_deleted_object_id, check_format_condition(
"expected the next deleted object " object_id == this_deleted_object_id,
"ID to be %s, instead found %s" % "expected the next deleted object ID to be %s, instead found %s"
(object_id, this_deleted_object_id)) % (object_id, this_deleted_object_id),
)
try: try:
next_in_linked_list = deleted_keys[0] next_in_linked_list = deleted_keys[0]
except IndexError: except IndexError:
next_in_linked_list = 0 next_in_linked_list = 0
f.write(make_bytes("%010d %05d f \n" % f.write(
(next_in_linked_list, make_bytes(
self.deleted_entries[object_id]))) "%010d %05d f \n"
% (next_in_linked_list, self.deleted_entries[object_id])
)
)
return startxref return startxref
@ -221,8 +232,9 @@ class PdfName:
return self.name.decode("us-ascii") return self.name.decode("us-ascii")
def __eq__(self, other): def __eq__(self, other):
return (isinstance(other, PdfName) and other.name == self.name) or \ return (
other == self.name isinstance(other, PdfName) and other.name == self.name
) or other == self.name
def __hash__(self): def __hash__(self):
return hash(self.name) return hash(self.name)
@ -282,18 +294,18 @@ class PdfDict(UserDict):
if value.startswith("D:"): if value.startswith("D:"):
value = value[2:] value = value[2:]
relationship = 'Z' relationship = "Z"
if len(value) > 17: if len(value) > 17:
relationship = value[14] relationship = value[14]
offset = int(value[15:17]) * 60 offset = int(value[15:17]) * 60
if len(value) > 20: if len(value) > 20:
offset += int(value[18:20]) offset += int(value[18:20])
format = '%Y%m%d%H%M%S'[:len(value) - 2] format = "%Y%m%d%H%M%S"[: len(value) - 2]
value = time.strptime(value[:len(format)+2], format) value = time.strptime(value[: len(format) + 2], format)
if relationship in ['+', '-']: if relationship in ["+", "-"]:
offset *= 60 offset *= 60
if relationship == '+': if relationship == "+":
offset *= -1 offset *= -1
value = time.gmtime(calendar.timegm(value) + offset) value = time.gmtime(calendar.timegm(value) + offset)
return value return value
@ -320,9 +332,12 @@ class PdfBinary:
self.data = data self.data = data
if py3: # Python 3.x if py3: # Python 3.x
def __bytes__(self): def __bytes__(self):
return make_bytes("<%s>" % "".join("%02X" % b for b in self.data)) return make_bytes("<%s>" % "".join("%02X" % b for b in self.data))
else: # Python 2.x else: # Python 2.x
def __str__(self): def __str__(self):
return "<%s>" % "".join("%02X" % ord(b) for b in self.data) return "<%s>" % "".join("%02X" % ord(b) for b in self.data)
@ -345,8 +360,8 @@ class PdfStream:
return zlib.decompress(self.buf, bufsize=int(expected_length)) return zlib.decompress(self.buf, bufsize=int(expected_length))
else: else:
raise NotImplementedError( raise NotImplementedError(
"stream filter %s unknown/unsupported" % "stream filter %s unknown/unsupported" % repr(self.dictionary.Filter)
repr(self.dictionary.Filter)) )
def pdf_repr(x): def pdf_repr(x):
@ -361,13 +376,14 @@ def pdf_repr(x):
elif isinstance(x, int): elif isinstance(x, int):
return str(x).encode("us-ascii") return str(x).encode("us-ascii")
elif isinstance(x, time.struct_time): elif isinstance(x, time.struct_time):
return b'(D:'+time.strftime('%Y%m%d%H%M%SZ', x).encode("us-ascii")+b')' return b"(D:" + time.strftime("%Y%m%d%H%M%SZ", x).encode("us-ascii") + b")"
elif isinstance(x, dict): elif isinstance(x, dict):
return bytes(PdfDict(x)) return bytes(PdfDict(x))
elif isinstance(x, list): elif isinstance(x, list):
return bytes(PdfArray(x)) return bytes(PdfArray(x))
elif ((py3 and isinstance(x, str)) or elif (py3 and isinstance(x, str)) or (
(not py3 and isinstance(x, unicode))): # noqa: F821 not py3 and isinstance(x, unicode) # noqa: F821
):
return pdf_repr(encode_text(x)) return pdf_repr(encode_text(x))
elif isinstance(x, bytes): elif isinstance(x, bytes):
# XXX escape more chars? handle binary garbage # XXX escape more chars? handle binary garbage
@ -385,11 +401,9 @@ class PdfParser:
Supports PDF up to 1.4 Supports PDF up to 1.4
""" """
def __init__(self, filename=None, f=None, def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"):
buf=None, start_offset=0, mode="rb"):
if buf and f: if buf and f:
raise RuntimeError( raise RuntimeError("specify buf or f or filename, but not both buf and f")
"specify buf or f or filename, but not both buf and f")
self.filename = filename self.filename = filename
self.buf = buf self.buf = buf
self.f = f self.f = f
@ -463,13 +477,13 @@ class PdfParser:
self.root_ref = self.next_object_id(self.f.tell()) self.root_ref = self.next_object_id(self.f.tell())
self.pages_ref = self.next_object_id(0) self.pages_ref = self.next_object_id(0)
self.rewrite_pages() self.rewrite_pages()
self.write_obj(self.root_ref, self.write_obj(self.root_ref, Type=PdfName(b"Catalog"), Pages=self.pages_ref)
Type=PdfName(b"Catalog"), self.write_obj(
Pages=self.pages_ref) self.pages_ref,
self.write_obj(self.pages_ref, Type=PdfName(b"Pages"),
Type=PdfName(b"Pages"), Count=len(self.pages),
Count=len(self.pages), Kids=self.pages,
Kids=self.pages) )
return self.root_ref return self.root_ref
def rewrite_pages(self): def rewrite_pages(self):
@ -515,8 +529,11 @@ class PdfParser:
if self.info: if self.info:
trailer_dict[b"Info"] = self.info_ref trailer_dict[b"Info"] = self.info_ref
self.last_xref_section_offset = start_xref self.last_xref_section_offset = start_xref
self.f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + self.f.write(
make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref)) b"trailer\n"
+ bytes(PdfDict(trailer_dict))
+ make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref)
)
def write_page(self, ref, *objs, **dict_obj): def write_page(self, ref, *objs, **dict_obj):
if isinstance(ref, int): if isinstance(ref, int):
@ -578,12 +595,14 @@ class PdfParser:
else: else:
self.info = PdfDict(self.read_indirect(self.info_ref)) self.info = PdfDict(self.read_indirect(self.info_ref))
check_format_condition(b"Type" in self.root, "/Type missing in Root") check_format_condition(b"Type" in self.root, "/Type missing in Root")
check_format_condition(self.root[b"Type"] == b"Catalog", check_format_condition(
"/Type in Root is not /Catalog") self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog"
)
check_format_condition(b"Pages" in self.root, "/Pages missing in Root") check_format_condition(b"Pages" in self.root, "/Pages missing in Root")
check_format_condition(isinstance(self.root[b"Pages"], check_format_condition(
IndirectReference), isinstance(self.root[b"Pages"], IndirectReference),
"/Pages in Root is not an indirect reference") "/Pages in Root is not an indirect reference",
)
self.pages_ref = self.root[b"Pages"] self.pages_ref = self.root[b"Pages"]
self.page_tree_root = self.read_indirect(self.pages_ref) self.page_tree_root = self.read_indirect(self.pages_ref)
self.pages = self.linearize_page_tree(self.page_tree_root) self.pages = self.linearize_page_tree(self.page_tree_root)
@ -611,13 +630,34 @@ class PdfParser:
newline_only = br"[\r\n]+" newline_only = br"[\r\n]+"
newline = whitespace_optional + newline_only + whitespace_optional newline = whitespace_optional + newline_only + whitespace_optional
re_trailer_end = re.compile( re_trailer_end = re.compile(
whitespace_mandatory + br"trailer" + whitespace_optional + whitespace_mandatory
br"\<\<(.*\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" + + br"trailer"
newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL) + whitespace_optional
+ br"\<\<(.*\>\>)"
+ newline
+ br"startxref"
+ newline
+ br"([0-9]+)"
+ newline
+ br"%%EOF"
+ whitespace_optional
+ br"$",
re.DOTALL,
)
re_trailer_prev = re.compile( re_trailer_prev = re.compile(
whitespace_optional + br"trailer" + whitespace_optional + whitespace_optional
br"\<\<(.*?\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" + + br"trailer"
newline + br"%%EOF" + whitespace_optional, re.DOTALL) + whitespace_optional
+ br"\<\<(.*?\>\>)"
+ newline
+ br"startxref"
+ newline
+ br"([0-9]+)"
+ newline
+ br"%%EOF"
+ whitespace_optional,
re.DOTALL,
)
def read_trailer(self): def read_trailer(self):
search_start_offset = len(self.buf) - 16384 search_start_offset = len(self.buf) - 16384
@ -629,7 +669,7 @@ class PdfParser:
last_match = m last_match = m
while m: while m:
last_match = m last_match = m
m = self.re_trailer_end.search(self.buf, m.start()+16) m = self.re_trailer_end.search(self.buf, m.start() + 16)
if not m: if not m:
m = last_match m = last_match
trailer_data = m.group(1) trailer_data = m.group(1)
@ -641,26 +681,29 @@ class PdfParser:
self.read_prev_trailer(self.trailer_dict[b"Prev"]) self.read_prev_trailer(self.trailer_dict[b"Prev"])
def read_prev_trailer(self, xref_section_offset): def read_prev_trailer(self, xref_section_offset):
trailer_offset = self.read_xref_table( trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)
xref_section_offset=xref_section_offset)
m = self.re_trailer_prev.search( m = self.re_trailer_prev.search(
self.buf[trailer_offset:trailer_offset+16384]) self.buf[trailer_offset : trailer_offset + 16384]
)
check_format_condition(m, "previous trailer not found") check_format_condition(m, "previous trailer not found")
trailer_data = m.group(1) trailer_data = m.group(1)
check_format_condition(int(m.group(2)) == xref_section_offset, check_format_condition(
"xref section offset in previous trailer " int(m.group(2)) == xref_section_offset,
"doesn't match what was expected") "xref section offset in previous trailer doesn't match what was expected",
)
trailer_dict = self.interpret_trailer(trailer_data) trailer_dict = self.interpret_trailer(trailer_data)
if b"Prev" in trailer_dict: if b"Prev" in trailer_dict:
self.read_prev_trailer(trailer_dict[b"Prev"]) self.read_prev_trailer(trailer_dict[b"Prev"])
re_whitespace_optional = re.compile(whitespace_optional) re_whitespace_optional = re.compile(whitespace_optional)
re_name = re.compile( re_name = re.compile(
whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + whitespace_optional
delimiter_or_ws + br")") + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?="
+ delimiter_or_ws
+ br")"
)
re_dict_start = re.compile(whitespace_optional + br"\<\<") re_dict_start = re.compile(whitespace_optional + br"\<\<")
re_dict_end = re.compile( re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional)
whitespace_optional + br"\>\>" + whitespace_optional)
@classmethod @classmethod
def interpret_trailer(cls, trailer_data): def interpret_trailer(cls, trailer_data):
@ -672,19 +715,21 @@ class PdfParser:
m = cls.re_dict_end.match(trailer_data, offset) m = cls.re_dict_end.match(trailer_data, offset)
check_format_condition( check_format_condition(
m and m.end() == len(trailer_data), m and m.end() == len(trailer_data),
"name not found in trailer, remaining data: " + "name not found in trailer, remaining data: "
repr(trailer_data[offset:])) + repr(trailer_data[offset:]),
)
break break
key = cls.interpret_name(m.group(1)) key = cls.interpret_name(m.group(1))
value, offset = cls.get_value(trailer_data, m.end()) value, offset = cls.get_value(trailer_data, m.end())
trailer[key] = value trailer[key] = value
check_format_condition( check_format_condition(
b"Size" in trailer and isinstance(trailer[b"Size"], int), b"Size" in trailer and isinstance(trailer[b"Size"], int),
"/Size not in trailer or not an integer") "/Size not in trailer or not an integer",
)
check_format_condition( check_format_condition(
b"Root" in trailer and b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference),
isinstance(trailer[b"Root"], IndirectReference), "/Root not in trailer or not an indirect reference",
"/Root not in trailer or not an indirect reference") )
return trailer return trailer
re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?") re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?")
@ -694,8 +739,7 @@ class PdfParser:
name = b"" name = b""
for m in cls.re_hashes_in_name.finditer(raw): for m in cls.re_hashes_in_name.finditer(raw):
if m.group(3): if m.group(3):
name += m.group(1) + \ name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii"))
bytearray.fromhex(m.group(3).decode("us-ascii"))
else: else:
name += m.group(1) name += m.group(1)
if as_text: if as_text:
@ -703,37 +747,54 @@ class PdfParser:
else: else:
return bytes(name) return bytes(name)
re_null = re.compile( re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")")
whitespace_optional + br"null(?=" + delimiter_or_ws + br")") re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
re_true = re.compile( re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
re_false = re.compile(
whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
re_int = re.compile( re_int = re.compile(
whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")") whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")"
)
re_real = re.compile( re_real = re.compile(
whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + whitespace_optional
delimiter_or_ws + br")") + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?="
+ delimiter_or_ws
+ br")"
)
re_array_start = re.compile(whitespace_optional + br"\[") re_array_start = re.compile(whitespace_optional + br"\[")
re_array_end = re.compile(whitespace_optional + br"]") re_array_end = re.compile(whitespace_optional + br"]")
re_string_hex = re.compile( re_string_hex = re.compile(
whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>") whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>"
)
re_string_lit = re.compile(whitespace_optional + br"\(") re_string_lit = re.compile(whitespace_optional + br"\(")
re_indirect_reference = re.compile( re_indirect_reference = re.compile(
whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + whitespace_optional
br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + + br"([-+]?[0-9]+)"
br")") + whitespace_mandatory
+ br"([-+]?[0-9]+)"
+ whitespace_mandatory
+ br"R(?="
+ delimiter_or_ws
+ br")"
)
re_indirect_def_start = re.compile( re_indirect_def_start = re.compile(
whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + whitespace_optional
br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + + br"([-+]?[0-9]+)"
delimiter_or_ws + br")") + whitespace_mandatory
+ br"([-+]?[0-9]+)"
+ whitespace_mandatory
+ br"obj(?="
+ delimiter_or_ws
+ br")"
)
re_indirect_def_end = re.compile( re_indirect_def_end = re.compile(
whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")") whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")"
)
re_comment = re.compile( re_comment = re.compile(
br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*") br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*"
)
re_stream_start = re.compile(whitespace_optional + br"stream\r?\n") re_stream_start = re.compile(whitespace_optional + br"stream\r?\n")
re_stream_end = re.compile( re_stream_end = re.compile(
whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")") whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")"
)
@classmethod @classmethod
def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
@ -746,32 +807,37 @@ class PdfParser:
if m: if m:
check_format_condition( check_format_condition(
int(m.group(1)) > 0, int(m.group(1)) > 0,
"indirect object definition: object ID must be greater than 0") "indirect object definition: object ID must be greater than 0",
)
check_format_condition( check_format_condition(
int(m.group(2)) >= 0, int(m.group(2)) >= 0,
"indirect object definition: generation must be non-negative") "indirect object definition: generation must be non-negative",
)
check_format_condition( check_format_condition(
expect_indirect is None or expect_indirect == expect_indirect is None
IndirectReference(int(m.group(1)), int(m.group(2))), or expect_indirect
"indirect object definition different than expected") == IndirectReference(int(m.group(1)), int(m.group(2))),
object, offset = cls.get_value( "indirect object definition different than expected",
data, m.end(), max_nesting=max_nesting-1) )
object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting - 1)
if offset is None: if offset is None:
return object, None return object, None
m = cls.re_indirect_def_end.match(data, offset) m = cls.re_indirect_def_end.match(data, offset)
check_format_condition( check_format_condition(m, "indirect object definition end not found")
m, "indirect object definition end not found")
return object, m.end() return object, m.end()
check_format_condition( check_format_condition(
not expect_indirect, "indirect object definition not found") not expect_indirect, "indirect object definition not found"
)
m = cls.re_indirect_reference.match(data, offset) m = cls.re_indirect_reference.match(data, offset)
if m: if m:
check_format_condition( check_format_condition(
int(m.group(1)) > 0, int(m.group(1)) > 0,
"indirect object reference: object ID must be greater than 0") "indirect object reference: object ID must be greater than 0",
)
check_format_condition( check_format_condition(
int(m.group(2)) >= 0, int(m.group(2)) >= 0,
"indirect object reference: generation must be non-negative") "indirect object reference: generation must be non-negative",
)
return IndirectReference(int(m.group(1)), int(m.group(2))), m.end() return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()
m = cls.re_dict_start.match(data, offset) m = cls.re_dict_start.match(data, offset)
if m: if m:
@ -779,12 +845,10 @@ class PdfParser:
result = {} result = {}
m = cls.re_dict_end.match(data, offset) m = cls.re_dict_end.match(data, offset)
while not m: while not m:
key, offset = cls.get_value( key, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
data, offset, max_nesting=max_nesting-1)
if offset is None: if offset is None:
return result, None return result, None
value, offset = cls.get_value( value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
data, offset, max_nesting=max_nesting-1)
result[key] = value result[key] = value
if offset is None: if offset is None:
return result, None return result, None
@ -796,9 +860,10 @@ class PdfParser:
stream_len = int(result[b"Length"]) stream_len = int(result[b"Length"])
except (TypeError, KeyError, ValueError): except (TypeError, KeyError, ValueError):
raise PdfFormatError( raise PdfFormatError(
"bad or missing Length in stream dict (%r)" % "bad or missing Length in stream dict (%r)"
result.get(b"Length", None)) % result.get(b"Length", None)
stream_data = data[m.end():m.end() + stream_len] )
stream_data = data[m.end() : m.end() + stream_len]
m = cls.re_stream_end.match(data, m.end() + stream_len) m = cls.re_stream_end.match(data, m.end() + stream_len)
check_format_condition(m, "stream end not found") check_format_condition(m, "stream end not found")
offset = m.end() offset = m.end()
@ -812,8 +877,7 @@ class PdfParser:
result = [] result = []
m = cls.re_array_end.match(data, offset) m = cls.re_array_end.match(data, offset)
while not m: while not m:
value, offset = cls.get_value( value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
data, offset, max_nesting=max_nesting-1)
result.append(value) result.append(value)
if offset is None: if offset is None:
return result, None return result, None
@ -841,10 +905,9 @@ class PdfParser:
m = cls.re_string_hex.match(data, offset) m = cls.re_string_hex.match(data, offset)
if m: if m:
# filter out whitespace # filter out whitespace
hex_string = bytearray([ hex_string = bytearray(
b for b in m.group(1) [b for b in m.group(1) if b in b"0123456789abcdefABCDEF"]
if b in b"0123456789abcdefABCDEF" )
])
if len(hex_string) % 2 == 1: if len(hex_string) % 2 == 1:
# append a 0 if the length is not even - yes, at the end # append a 0 if the length is not even - yes, at the end
hex_string.append(ord(b"0")) hex_string.append(ord(b"0"))
@ -853,11 +916,11 @@ class PdfParser:
if m: if m:
return cls.get_literal_string(data, m.end()) return cls.get_literal_string(data, m.end())
# return None, offset # fallback (only for debugging) # return None, offset # fallback (only for debugging)
raise PdfFormatError( raise PdfFormatError("unrecognized object: " + repr(data[offset : offset + 32]))
"unrecognized object: " + repr(data[offset:offset+32]))
re_lit_str_token = re.compile( re_lit_str_token = re.compile(
br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))") br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))"
)
escaped_chars = { escaped_chars = {
b"n": b"\n", b"n": b"\n",
b"r": b"\r", b"r": b"\r",
@ -875,14 +938,14 @@ class PdfParser:
ord(b"("): b"(", ord(b"("): b"(",
ord(b")"): b")", ord(b")"): b")",
ord(b"\\"): b"\\", ord(b"\\"): b"\\",
} }
@classmethod @classmethod
def get_literal_string(cls, data, offset): def get_literal_string(cls, data, offset):
nesting_depth = 0 nesting_depth = 0
result = bytearray() result = bytearray()
for m in cls.re_lit_str_token.finditer(data, offset): for m in cls.re_lit_str_token.finditer(data, offset):
result.extend(data[offset:m.start()]) result.extend(data[offset : m.start()])
if m.group(1): if m.group(1):
result.extend(cls.escaped_chars[m.group(1)[1]]) result.extend(cls.escaped_chars[m.group(1)[1]])
elif m.group(2): elif m.group(2):
@ -902,30 +965,36 @@ class PdfParser:
offset = m.end() offset = m.end()
raise PdfFormatError("unfinished literal string") raise PdfFormatError("unfinished literal string")
re_xref_section_start = re.compile( re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline)
whitespace_optional + br"xref" + newline)
re_xref_subsection_start = re.compile( re_xref_subsection_start = re.compile(
whitespace_optional + br"([0-9]+)" + whitespace_mandatory + whitespace_optional
br"([0-9]+)" + whitespace_optional + newline_only) + br"([0-9]+)"
+ whitespace_mandatory
+ br"([0-9]+)"
+ whitespace_optional
+ newline_only
)
re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)") re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
def read_xref_table(self, xref_section_offset): def read_xref_table(self, xref_section_offset):
subsection_found = False subsection_found = False
m = self.re_xref_section_start.match( m = self.re_xref_section_start.match(
self.buf, xref_section_offset + self.start_offset) self.buf, xref_section_offset + self.start_offset
)
check_format_condition(m, "xref section start not found") check_format_condition(m, "xref section start not found")
offset = m.end() offset = m.end()
while True: while True:
m = self.re_xref_subsection_start.match(self.buf, offset) m = self.re_xref_subsection_start.match(self.buf, offset)
if not m: if not m:
check_format_condition( check_format_condition(
subsection_found, "xref subsection start not found") subsection_found, "xref subsection start not found"
)
break break
subsection_found = True subsection_found = True
offset = m.end() offset = m.end()
first_object = int(m.group(1)) first_object = int(m.group(1))
num_objects = int(m.group(2)) num_objects = int(m.group(2))
for i in range(first_object, first_object+num_objects): for i in range(first_object, first_object + num_objects):
m = self.re_xref_entry.match(self.buf, offset) m = self.re_xref_entry.match(self.buf, offset)
check_format_condition(m, "xref entry not found") check_format_condition(m, "xref entry not found")
offset = m.end() offset = m.end()
@ -934,9 +1003,9 @@ class PdfParser:
if not is_free: if not is_free:
new_entry = (int(m.group(1)), generation) new_entry = (int(m.group(1)), generation)
check_format_condition( check_format_condition(
i not in self.xref_table or i not in self.xref_table or self.xref_table[i] == new_entry,
self.xref_table[i] == new_entry, "xref entry duplicated (and not identical)",
"xref entry duplicated (and not identical)") )
self.xref_table[i] = new_entry self.xref_table[i] = new_entry
return offset return offset
@ -946,10 +1015,14 @@ class PdfParser:
generation == ref[1], generation == ref[1],
"expected to find generation %s for object ID %s in xref table, " "expected to find generation %s for object ID %s in xref table, "
"instead found generation %s at offset %s" "instead found generation %s at offset %s"
% (ref[1], ref[0], generation, offset)) % (ref[1], ref[0], generation, offset),
value = self.get_value(self.buf, offset + self.start_offset, )
expect_indirect=IndirectReference(*ref), value = self.get_value(
max_nesting=max_nesting)[0] self.buf,
offset + self.start_offset,
expect_indirect=IndirectReference(*ref),
max_nesting=max_nesting,
)[0]
self.cached_objects[ref] = value self.cached_objects[ref] = value
return value return value
@ -957,7 +1030,8 @@ class PdfParser:
if node is None: if node is None:
node = self.page_tree_root node = self.page_tree_root
check_format_condition( check_format_condition(
node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages") node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages"
)
pages = [] pages = []
for kid in node[b"Kids"]: for kid in node[b"Kids"]:
kid_object = self.read_indirect(kid) kid_object = self.read_indirect(kid)

View File

@ -30,6 +30,7 @@ __version__ = "0.1"
# #
# helpers # helpers
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"\200\350\000\000" return prefix[:4] == b"\200\350\000\000"
@ -37,6 +38,7 @@ def _accept(prefix):
## ##
# Image plugin for PIXAR raster images. # Image plugin for PIXAR raster images.
class PixarImageFile(ImageFile.ImageFile): class PixarImageFile(ImageFile.ImageFile):
format = "PIXAR" format = "PIXAR"
@ -62,7 +64,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued... # FIXME: to be continued...
# create tile descriptor (assuming "dumped") # create tile descriptor (assuming "dumped")
self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))] self.tile = [("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))]
# #

View File

@ -55,29 +55,29 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = { _MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes # supported bits/color combinations, and corresponding modes/rawmodes
# Greyscale # Greyscale
(1, 0): ("1", "1"), (1, 0): ("1", "1"),
(2, 0): ("L", "L;2"), (2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"), (4, 0): ("L", "L;4"),
(8, 0): ("L", "L"), (8, 0): ("L", "L"),
(16, 0): ("I", "I;16B"), (16, 0): ("I", "I;16B"),
# Truecolour # Truecolour
(8, 2): ("RGB", "RGB"), (8, 2): ("RGB", "RGB"),
(16, 2): ("RGB", "RGB;16B"), (16, 2): ("RGB", "RGB;16B"),
# Indexed-colour # Indexed-colour
(1, 3): ("P", "P;1"), (1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"), (2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"), (4, 3): ("P", "P;4"),
(8, 3): ("P", "P"), (8, 3): ("P", "P"),
# Greyscale with alpha # Greyscale with alpha
(8, 4): ("LA", "LA"), (8, 4): ("LA", "LA"),
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
# Truecolour with alpha # Truecolour with alpha
(8, 6): ("RGBA", "RGBA"), (8, 6): ("RGBA", "RGBA"),
(16, 6): ("RGBA", "RGBA;16B"), (16, 6): ("RGBA", "RGBA;16B"),
} }
_simple_palette = re.compile(b'^\xff*\x00\xff*$') _simple_palette = re.compile(b"^\xff*\x00\xff*$")
# Maximum decompressed size for a iTXt or zTXt chunk. # Maximum decompressed size for a iTXt or zTXt chunk.
# Eliminates decompression bombs where compressed chunks can expand 1000x # Eliminates decompression bombs where compressed chunks can expand 1000x
@ -95,14 +95,14 @@ def _safe_zlib_decompress(s):
def _crc32(data, seed=0): def _crc32(data, seed=0):
return zlib.crc32(data, seed) & 0xffffffff return zlib.crc32(data, seed) & 0xFFFFFFFF
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Support classes. Suitable for PNG and related formats like MNG etc. # Support classes. Suitable for PNG and related formats like MNG etc.
class ChunkStream(object):
class ChunkStream(object):
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
@ -144,7 +144,7 @@ class ChunkStream(object):
"""Call the appropriate chunk handler""" """Call the appropriate chunk handler"""
logger.debug("STREAM %r %s %s", cid, pos, length) logger.debug("STREAM %r %s %s", cid, pos, length)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length) return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length)
def crc(self, cid, data): def crc(self, cid, data):
"""Read and verify checksum""" """Read and verify checksum"""
@ -160,11 +160,9 @@ class ChunkStream(object):
crc1 = _crc32(data, _crc32(cid)) crc1 = _crc32(data, _crc32(cid))
crc2 = i32(self.fp.read(4)) crc2 = i32(self.fp.read(4))
if crc1 != crc2: if crc1 != crc2:
raise SyntaxError("broken PNG file (bad header checksum in %r)" raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid)
% cid)
except struct.error: except struct.error:
raise SyntaxError("broken PNG file (incomplete checksum in %r)" raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid)
% cid)
def crc_skip(self, cid, data): def crc_skip(self, cid, data):
"""Read checksum. Used if the C module is not present""" """Read checksum. Used if the C module is not present"""
@ -198,6 +196,7 @@ class iTXt(str):
keeping their extra information keeping their extra information
""" """
@staticmethod @staticmethod
def __new__(cls, text, lang=None, tkey=None): def __new__(cls, text, lang=None, tkey=None):
""" """
@ -253,11 +252,12 @@ class PngInfo(object):
tkey = tkey.encode("utf-8", "strict") tkey = tkey.encode("utf-8", "strict")
if zip: if zip:
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + self.add(
zlib.compress(value)) b"iTXt",
key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value),
)
else: else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
value)
def add_text(self, key, value, zip=False): def add_text(self, key, value, zip=False):
"""Appends a text chunk. """Appends a text chunk.
@ -274,12 +274,12 @@ class PngInfo(object):
# The tEXt chunk stores latin-1 text # The tEXt chunk stores latin-1 text
if not isinstance(value, bytes): if not isinstance(value, bytes):
try: try:
value = value.encode('latin-1', 'strict') value = value.encode("latin-1", "strict")
except UnicodeError: except UnicodeError:
return self.add_itxt(key, value, zip=zip) return self.add_itxt(key, value, zip=zip)
if not isinstance(key, bytes): if not isinstance(key, bytes):
key = key.encode('latin-1', 'strict') key = key.encode("latin-1", "strict")
if zip: if zip:
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
@ -290,8 +290,8 @@ class PngInfo(object):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG image stream (IHDR/IEND) # PNG image stream (IHDR/IEND)
class PngStream(ChunkStream):
class PngStream(ChunkStream):
def __init__(self, fp): def __init__(self, fp):
ChunkStream.__init__(self, fp) ChunkStream.__init__(self, fp)
@ -310,8 +310,10 @@ class PngStream(ChunkStream):
def check_text_memory(self, chunklen): def check_text_memory(self, chunklen):
self.text_memory += chunklen self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY: if self.text_memory > MAX_TEXT_MEMORY:
raise ValueError("Too much memory used in text chunks: " raise ValueError(
"%s>MAX_TEXT_MEMORY" % self.text_memory) "Too much memory used in text chunks: %s>MAX_TEXT_MEMORY"
% self.text_memory
)
def chunk_iCCP(self, pos, length): def chunk_iCCP(self, pos, length):
@ -327,10 +329,11 @@ class PngStream(ChunkStream):
logger.debug("Compression method %s", i8(s[i])) logger.debug("Compression method %s", i8(s[i]))
comp_method = i8(s[i]) comp_method = i8(s[i])
if comp_method != 0: if comp_method != 0:
raise SyntaxError("Unknown compression method %s in iCCP chunk" % raise SyntaxError(
comp_method) "Unknown compression method %s in iCCP chunk" % comp_method
)
try: try:
icc_profile = _safe_zlib_decompress(s[i+2:]) icc_profile = _safe_zlib_decompress(s[i + 2 :])
except ValueError: except ValueError:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
icc_profile = None icc_profile = None
@ -359,7 +362,7 @@ class PngStream(ChunkStream):
def chunk_IDAT(self, pos, length): def chunk_IDAT(self, pos, length):
# image data # image data
self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)] self.im_tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
self.im_idat = length self.im_idat = length
raise EOFError raise EOFError
@ -408,8 +411,8 @@ class PngStream(ChunkStream):
# WP x,y, Red x,y, Green x,y Blue x,y # WP x,y, Red x,y, Green x,y Blue x,y
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
raw_vals = struct.unpack('>%dI' % (len(s) // 4), s) raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
self.im_info['chromaticity'] = tuple(elt/100000.0 for elt in raw_vals) self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
return s return s
def chunk_sRGB(self, pos, length): def chunk_sRGB(self, pos, length):
@ -420,7 +423,7 @@ class PngStream(ChunkStream):
# 3 absolute colorimetric # 3 absolute colorimetric
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
self.im_info['srgb'] = i8(s) self.im_info["srgb"] = i8(s)
return s return s
def chunk_pHYs(self, pos, length): def chunk_pHYs(self, pos, length):
@ -448,8 +451,8 @@ class PngStream(ChunkStream):
v = b"" v = b""
if k: if k:
if py3: if py3:
k = k.decode('latin-1', 'strict') k = k.decode("latin-1", "strict")
v = v.decode('latin-1', 'replace') v = v.decode("latin-1", "replace")
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v)) self.check_text_memory(len(v))
@ -470,8 +473,9 @@ class PngStream(ChunkStream):
else: else:
comp_method = 0 comp_method = 0
if comp_method != 0: if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" % raise SyntaxError(
comp_method) "Unknown compression method %s in zTXt chunk" % comp_method
)
try: try:
v = _safe_zlib_decompress(v[1:]) v = _safe_zlib_decompress(v[1:])
except ValueError: except ValueError:
@ -484,8 +488,8 @@ class PngStream(ChunkStream):
if k: if k:
if py3: if py3:
k = k.decode('latin-1', 'strict') k = k.decode("latin-1", "strict")
v = v.decode('latin-1', 'replace') v = v.decode("latin-1", "replace")
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v)) self.check_text_memory(len(v))
@ -536,19 +540,20 @@ class PngStream(ChunkStream):
def chunk_eXIf(self, pos, length): def chunk_eXIf(self, pos, length):
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
self.im_info["exif"] = b"Exif\x00\x00"+s self.im_info["exif"] = b"Exif\x00\x00" + s
return s return s
# APNG chunks # APNG chunks
def chunk_acTL(self, pos, length): def chunk_acTL(self, pos, length):
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
self.im_custom_mimetype = 'image/apng' self.im_custom_mimetype = "image/apng"
return s return s
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG reader # PNG reader
def _accept(prefix): def _accept(prefix):
return prefix[:8] == _MAGIC return prefix[:8] == _MAGIC
@ -556,6 +561,7 @@ def _accept(prefix):
## ##
# Image plugin for PNG images. # Image plugin for PNG images.
class PngImageFile(ImageFile.ImageFile): class PngImageFile(ImageFile.ImageFile):
format = "PNG" format = "PNG"
@ -711,20 +717,20 @@ class PngImageFile(ImageFile.ImageFile):
_OUTMODES = { _OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations # supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'), "1": ("1", b"\x01\x00"),
"L;1": ("L;1", b'\x01\x00'), "L;1": ("L;1", b"\x01\x00"),
"L;2": ("L;2", b'\x02\x00'), "L;2": ("L;2", b"\x02\x00"),
"L;4": ("L;4", b'\x04\x00'), "L;4": ("L;4", b"\x04\x00"),
"L": ("L", b'\x08\x00'), "L": ("L", b"\x08\x00"),
"LA": ("LA", b'\x08\x04'), "LA": ("LA", b"\x08\x04"),
"I": ("I;16B", b'\x10\x00'), "I": ("I;16B", b"\x10\x00"),
"I;16": ("I;16B", b'\x10\x00'), "I;16": ("I;16B", b"\x10\x00"),
"P;1": ("P;1", b'\x01\x03'), "P;1": ("P;1", b"\x01\x03"),
"P;2": ("P;2", b'\x02\x03'), "P;2": ("P;2", b"\x02\x03"),
"P;4": ("P;4", b'\x04\x03'), "P;4": ("P;4", b"\x04\x03"),
"P": ("P", b'\x08\x03'), "P": ("P", b"\x08\x03"),
"RGB": ("RGB", b'\x08\x02'), "RGB": ("RGB", b"\x08\x02"),
"RGBA": ("RGBA", b'\x08\x06'), "RGBA": ("RGBA", b"\x08\x06"),
} }
@ -765,7 +771,7 @@ def _save(im, fp, filename, chunk=putchunk):
else: else:
# check palette contents # check palette contents
if im.palette: if im.palette:
colors = max(min(len(im.palette.getdata()[1])//3, 256), 2) colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2)
else: else:
colors = 256 colors = 256
@ -781,10 +787,12 @@ def _save(im, fp, filename, chunk=putchunk):
mode = "%s;%d" % (mode, bits) mode = "%s;%d" % (mode, bits)
# encoder options # encoder options
im.encoderconfig = (im.encoderinfo.get("optimize", False), im.encoderconfig = (
im.encoderinfo.get("compress_level", -1), im.encoderinfo.get("optimize", False),
im.encoderinfo.get("compress_type", -1), im.encoderinfo.get("compress_level", -1),
im.encoderinfo.get("dictionary", b"")) im.encoderinfo.get("compress_type", -1),
im.encoderinfo.get("dictionary", b""),
)
# get the corresponding PNG mode # get the corresponding PNG mode
try: try:
@ -797,12 +805,16 @@ def _save(im, fp, filename, chunk=putchunk):
fp.write(_MAGIC) fp.write(_MAGIC)
chunk(fp, b"IHDR", chunk(
o32(im.size[0]), o32(im.size[1]), # 0: size fp,
mode, # 8: depth/type b"IHDR",
b'\0', # 10: compression o32(im.size[0]), # 0: size
b'\0', # 11: filter category o32(im.size[1]),
b'\0') # 12: interlace flag mode, # 8: depth/type
b"\0", # 10: compression
b"\0", # 11: filter category
b"\0", # 12: interlace flag
)
chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"] chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"]
@ -836,21 +848,20 @@ def _save(im, fp, filename, chunk=putchunk):
palette_byte_number = (2 ** bits) * 3 palette_byte_number = (2 ** bits) * 3
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
while len(palette_bytes) < palette_byte_number: while len(palette_bytes) < palette_byte_number:
palette_bytes += b'\0' palette_bytes += b"\0"
chunk(fp, b"PLTE", palette_bytes) chunk(fp, b"PLTE", palette_bytes)
transparency = im.encoderinfo.get('transparency', transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None))
im.info.get('transparency', None))
if transparency or transparency == 0: if transparency or transparency == 0:
if im.mode == "P": if im.mode == "P":
# limit to actual palette size # limit to actual palette size
alpha_bytes = 2**bits alpha_bytes = 2 ** bits
if isinstance(transparency, bytes): if isinstance(transparency, bytes):
chunk(fp, b"tRNS", transparency[:alpha_bytes]) chunk(fp, b"tRNS", transparency[:alpha_bytes])
else: else:
transparency = max(0, min(255, transparency)) transparency = max(0, min(255, transparency))
alpha = b'\xFF' * transparency + b'\0' alpha = b"\xFF" * transparency + b"\0"
chunk(fp, b"tRNS", alpha[:alpha_bytes]) chunk(fp, b"tRNS", alpha[:alpha_bytes])
elif im.mode in ("1", "L", "I"): elif im.mode in ("1", "L", "I"):
transparency = max(0, min(65535, transparency)) transparency = max(0, min(65535, transparency))
@ -866,15 +877,18 @@ def _save(im, fp, filename, chunk=putchunk):
else: else:
if im.mode == "P" and im.im.getpalettemode() == "RGBA": if im.mode == "P" and im.im.getpalettemode() == "RGBA":
alpha = im.im.getpalette("RGBA", "A") alpha = im.im.getpalette("RGBA", "A")
alpha_bytes = 2**bits alpha_bytes = 2 ** bits
chunk(fp, b"tRNS", alpha[:alpha_bytes]) chunk(fp, b"tRNS", alpha[:alpha_bytes])
dpi = im.encoderinfo.get("dpi") dpi = im.encoderinfo.get("dpi")
if dpi: if dpi:
chunk(fp, b"pHYs", chunk(
o32(int(dpi[0] / 0.0254 + 0.5)), fp,
o32(int(dpi[1] / 0.0254 + 0.5)), b"pHYs",
b'\x01') o32(int(dpi[0] / 0.0254 + 0.5)),
o32(int(dpi[1] / 0.0254 + 0.5)),
b"\x01",
)
info = im.encoderinfo.get("pnginfo") info = im.encoderinfo.get("pnginfo")
if info: if info:
@ -892,8 +906,7 @@ def _save(im, fp, filename, chunk=putchunk):
exif = exif[6:] exif = exif[6:]
chunk(fp, b"eXIf", exif) chunk(fp, b"eXIf", exif)
ImageFile._save(im, _idat(fp, chunk), ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
[("zip", (0, 0)+im.size, 0, rawmode)])
chunk(fp, b"IEND", b"") chunk(fp, b"IEND", b"")
@ -904,6 +917,7 @@ def _save(im, fp, filename, chunk=putchunk):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG chunk converter # PNG chunk converter
def getchunks(im, **params): def getchunks(im, **params):
"""Return a list of PNG chunks representing this image.""" """Return a list of PNG chunks representing this image."""

View File

@ -24,7 +24,7 @@ __version__ = "0.2"
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
b_whitespace = b'\x20\x09\x0a\x0b\x0c\x0d' b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d"
MODES = { MODES = {
# standard # standard
@ -36,7 +36,7 @@ MODES = {
# PIL extensions (for test purposes only) # PIL extensions (for test purposes only)
b"PyP": "P", b"PyP": "P",
b"PyRGBA": "RGBA", b"PyRGBA": "RGBA",
b"PyCMYK": "CMYK" b"PyCMYK": "CMYK",
} }
@ -47,6 +47,7 @@ def _accept(prefix):
## ##
# Image plugin for PBM, PGM, and PPM images. # Image plugin for PBM, PGM, and PPM images.
class PpmImageFile(ImageFile.ImageFile): class PpmImageFile(ImageFile.ImageFile):
format = "PPM" format = "PPM"
@ -57,10 +58,10 @@ class PpmImageFile(ImageFile.ImageFile):
c = self.fp.read(1) c = self.fp.read(1)
if not c or c in b_whitespace: if not c or c in b_whitespace:
break break
if c > b'\x79': if c > b"\x79":
raise ValueError("Expected ASCII value, found binary") raise ValueError("Expected ASCII value, found binary")
s = s + c s = s + c
if (len(s) > 9): if len(s) > 9:
raise ValueError("Expected int, got > 9 digits") raise ValueError("Expected int, got > 9 digits")
return s return s
@ -92,8 +93,7 @@ class PpmImageFile(ImageFile.ImageFile):
if s not in b_whitespace: if s not in b_whitespace:
break break
if s == b"": if s == b"":
raise ValueError( raise ValueError("File does not extend beyond magic number")
"File does not extend beyond magic number")
if s != b"#": if s != b"#":
break break
s = self.fp.readline() s = self.fp.readline()
@ -107,32 +107,30 @@ class PpmImageFile(ImageFile.ImageFile):
elif ix == 2: elif ix == 2:
# maxgrey # maxgrey
if s > 255: if s > 255:
if not mode == 'L': if not mode == "L":
raise ValueError("Too many colors for band: %s" % s) raise ValueError("Too many colors for band: %s" % s)
if s < 2**16: if s < 2 ** 16:
self.mode = 'I' self.mode = "I"
rawmode = 'I;16B' rawmode = "I;16B"
else: else:
self.mode = 'I' self.mode = "I"
rawmode = 'I;32B' rawmode = "I;32B"
self._size = xsize, ysize self._size = xsize, ysize
self.tile = [("raw", self.tile = [("raw", (0, 0, xsize, ysize), self.fp.tell(), (rawmode, 0, 1))]
(0, 0, xsize, ysize),
self.fp.tell(),
(rawmode, 0, 1))]
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode == "1": if im.mode == "1":
rawmode, head = "1;I", b"P4" rawmode, head = "1;I", b"P4"
elif im.mode == "L": elif im.mode == "L":
rawmode, head = "L", b"P5" rawmode, head = "L", b"P5"
elif im.mode == "I": elif im.mode == "I":
if im.getextrema()[1] < 2**16: if im.getextrema()[1] < 2 ** 16:
rawmode, head = "I;16B", b"P5" rawmode, head = "I;16B", b"P5"
else: else:
rawmode, head = "I;32B", b"P5" rawmode, head = "I;32B", b"P5"
@ -142,7 +140,7 @@ def _save(im, fp, filename):
rawmode, head = "RGB", b"P6" rawmode, head = "RGB", b"P6"
else: else:
raise IOError("cannot write mode %s as PPM" % im.mode) raise IOError("cannot write mode %s as PPM" % im.mode)
fp.write(head + ("\n%d %d\n" % im.size).encode('ascii')) fp.write(head + ("\n%d %d\n" % im.size).encode("ascii"))
if head == b"P6": if head == b"P6":
fp.write(b"255\n") fp.write(b"255\n")
if head == b"P5": if head == b"P5":
@ -152,11 +150,12 @@ def _save(im, fp, filename):
fp.write(b"65535\n") fp.write(b"65535\n")
elif rawmode == "I;32B": elif rawmode == "I;32B":
fp.write(b"2147483648\n") fp.write(b"2147483648\n")
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
# ALTERNATIVE: save via builtin debug function # ALTERNATIVE: save via builtin debug function
# im._dump(filename) # im._dump(filename)
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -34,13 +34,14 @@ MODES = {
(4, 8): ("CMYK", 4), (4, 8): ("CMYK", 4),
(7, 8): ("L", 1), # FIXME: multilayer (7, 8): ("L", 1), # FIXME: multilayer
(8, 8): ("L", 1), # duotone (8, 8): ("L", 1), # duotone
(9, 8): ("LAB", 3) (9, 8): ("LAB", 3),
} }
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
# read PSD images # read PSD images
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"8BPS" return prefix[:4] == b"8BPS"
@ -48,6 +49,7 @@ def _accept(prefix):
## ##
# Image plugin for Photoshop images. # Image plugin for Photoshop images.
class PsdImageFile(ImageFile.ImageFile): class PsdImageFile(ImageFile.ImageFile):
format = "PSD" format = "PSD"
@ -101,7 +103,7 @@ class PsdImageFile(ImageFile.ImageFile):
if not (len(name) & 1): if not (len(name) & 1):
read(1) # padding read(1) # padding
data = read(i32(read(4))) data = read(i32(read(4)))
if (len(data) & 1): if len(data) & 1:
read(1) # padding read(1) # padding
self.resources.append((id, name, data)) self.resources.append((id, name, data))
if id == 1039: # ICC profile if id == 1039: # ICC profile
@ -144,7 +146,7 @@ class PsdImageFile(ImageFile.ImageFile):
# seek to given layer (1..max) # seek to given layer (1..max)
try: try:
name, mode, bbox, tile = self.layers[layer-1] name, mode, bbox, tile = self.layers[layer - 1]
self.mode = mode self.mode = mode
self.tile = tile self.tile = tile
self.frame = layer self.frame = layer
@ -159,8 +161,7 @@ class PsdImageFile(ImageFile.ImageFile):
def load_prepare(self): def load_prepare(self):
# create image memory if necessary # create image memory if necessary
if not self.im or\ if not self.im or self.im.mode != self.mode or self.im.size != self.size:
self.im.mode != self.mode or self.im.size != self.size:
self.im = Image.core.fill(self.mode, self.size, 0) self.im = Image.core.fill(self.mode, self.size, 0)
# create palette (optional) # create palette (optional)
if self.mode == "P": if self.mode == "P":
@ -229,7 +230,7 @@ def _layerinfo(file):
if length: if length:
# Don't know the proper encoding, # Don't know the proper encoding,
# Latin-1 should be a good guess # Latin-1 should be a good guess
name = read(length).decode('latin-1', 'replace') name = read(length).decode("latin-1", "replace")
combined += length + 1 combined += length + 1
file.seek(size - combined, io.SEEK_CUR) file.seek(size - combined, io.SEEK_CUR)
@ -270,7 +271,7 @@ def _maketile(file, mode, bbox, channels):
if mode == "CMYK": if mode == "CMYK":
layer += ";I" layer += ";I"
tile.append(("raw", bbox, offset, layer)) tile.append(("raw", bbox, offset, layer))
offset = offset + xsize*ysize offset = offset + xsize * ysize
elif compression == 1: elif compression == 1:
# #
@ -283,11 +284,9 @@ def _maketile(file, mode, bbox, channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer += ";I" layer += ";I"
tile.append( tile.append(("packbits", bbox, offset, layer))
("packbits", bbox, offset, layer)
)
for y in range(ysize): for y in range(ysize):
offset = offset + i16(bytecount[i:i+2]) offset = offset + i16(bytecount[i : i + 2])
i += 2 i += 2
file.seek(offset) file.seek(offset)
@ -297,6 +296,7 @@ def _maketile(file, mode, bbox, channels):
return tile return tile
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# registry # registry

View File

@ -42,13 +42,12 @@ ffi.cdef(defs)
class PyAccess(object): class PyAccess(object):
def __init__(self, img, readonly=False): def __init__(self, img, readonly=False):
vals = dict(img.im.unsafe_ptrs) vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly self.readonly = readonly
self.image8 = ffi.cast('unsigned char **', vals['image8']) self.image8 = ffi.cast("unsigned char **", vals["image8"])
self.image32 = ffi.cast('int **', vals['image32']) self.image32 = ffi.cast("int **", vals["image32"])
self.image = ffi.cast('unsigned char **', vals['image']) self.image = ffi.cast("unsigned char **", vals["image"])
self.xsize, self.ysize = img.im.size self.xsize, self.ysize = img.im.size
# Keep pointer to im object to prevent dereferencing. # Keep pointer to im object to prevent dereferencing.
@ -75,7 +74,7 @@ class PyAccess(object):
:param color: The pixel value. :param color: The pixel value.
""" """
if self.readonly: if self.readonly:
raise ValueError('Attempt to putpixel a read only image') raise ValueError("Attempt to putpixel a read only image")
(x, y) = xy (x, y) = xy
if x < 0: if x < 0:
x = self.xsize + x x = self.xsize + x
@ -83,8 +82,11 @@ class PyAccess(object):
y = self.ysize + y y = self.ysize + y
(x, y) = self.check_xy((x, y)) (x, y) = self.check_xy((x, y))
if self._im.mode == "P" and \ if (
isinstance(color, (list, tuple)) and len(color) in [3, 4]: self._im.mode == "P"
and isinstance(color, (list, tuple))
and len(color) in [3, 4]
):
# RGB or RGBA value for a P image # RGB or RGBA value for a P image
color = self._palette.getcolor(color) color = self._palette.getcolor(color)
@ -115,12 +117,13 @@ class PyAccess(object):
def check_xy(self, xy): def check_xy(self, xy):
(x, y) = xy (x, y) = xy
if not (0 <= x < self.xsize and 0 <= y < self.ysize): if not (0 <= x < self.xsize and 0 <= y < self.ysize):
raise ValueError('pixel location out of range') raise ValueError("pixel location out of range")
return xy return xy
class _PyAccess32_2(PyAccess): class _PyAccess32_2(PyAccess):
""" PA, LA, stored in first and last bytes of a 32 bit word """ """ PA, LA, stored in first and last bytes of a 32 bit word """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
@ -156,6 +159,7 @@ class _PyAccess32_3(PyAccess):
class _PyAccess32_4(PyAccess): class _PyAccess32_4(PyAccess):
""" RGBA etc, all 4 bytes of a 32 bit word """ """ RGBA etc, all 4 bytes of a 32 bit word """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
@ -174,6 +178,7 @@ class _PyAccess32_4(PyAccess):
class _PyAccess8(PyAccess): class _PyAccess8(PyAccess):
""" 1, L, P, 8 bit images stored as uint8 """ """ 1, L, P, 8 bit images stored as uint8 """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image8 self.pixels = self.image8
@ -191,8 +196,9 @@ class _PyAccess8(PyAccess):
class _PyAccessI16_N(PyAccess): class _PyAccessI16_N(PyAccess):
""" I;16 access, native bitendian without conversion """ """ I;16 access, native bitendian without conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('unsigned short **', self.image) self.pixels = ffi.cast("unsigned short **", self.image)
def get_pixel(self, x, y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
@ -208,8 +214,9 @@ class _PyAccessI16_N(PyAccess):
class _PyAccessI16_L(PyAccess): class _PyAccessI16_L(PyAccess):
""" I;16L access, with conversion """ """ I;16L access, with conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image) self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
def get_pixel(self, x, y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
@ -228,8 +235,9 @@ class _PyAccessI16_L(PyAccess):
class _PyAccessI16_B(PyAccess): class _PyAccessI16_B(PyAccess):
""" I;16B access, with conversion """ """ I;16B access, with conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image) self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
def get_pixel(self, x, y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
@ -248,6 +256,7 @@ class _PyAccessI16_B(PyAccess):
class _PyAccessI32_N(PyAccess): class _PyAccessI32_N(PyAccess):
""" Signed Int32 access, native endian """ """ Signed Int32 access, native endian """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image32 self.pixels = self.image32
@ -260,15 +269,15 @@ class _PyAccessI32_N(PyAccess):
class _PyAccessI32_Swap(PyAccess): class _PyAccessI32_Swap(PyAccess):
""" I;32L/B access, with byteswapping conversion """ """ I;32L/B access, with byteswapping conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image32 self.pixels = self.image32
def reverse(self, i): def reverse(self, i):
orig = ffi.new('int *', i) orig = ffi.new("int *", i)
chars = ffi.cast('unsigned char *', orig) chars = ffi.cast("unsigned char *", orig)
chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \ chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0]
chars[1], chars[0] return ffi.cast("int *", chars)[0]
return ffi.cast('int *', chars)[0]
def get_pixel(self, x, y): def get_pixel(self, x, y):
return self.reverse(self.pixels[y][x]) return self.reverse(self.pixels[y][x])
@ -279,8 +288,9 @@ class _PyAccessI32_Swap(PyAccess):
class _PyAccessF(PyAccess): class _PyAccessF(PyAccess):
""" 32 bit float access """ """ 32 bit float access """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('float **', self.image32) self.pixels = ffi.cast("float **", self.image32)
def get_pixel(self, x, y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
@ -294,38 +304,39 @@ class _PyAccessF(PyAccess):
self.pixels[y][x] = color[0] self.pixels[y][x] = color[0]
mode_map = {'1': _PyAccess8, mode_map = {
'L': _PyAccess8, "1": _PyAccess8,
'P': _PyAccess8, "L": _PyAccess8,
'LA': _PyAccess32_2, "P": _PyAccess8,
'La': _PyAccess32_2, "LA": _PyAccess32_2,
'PA': _PyAccess32_2, "La": _PyAccess32_2,
'RGB': _PyAccess32_3, "PA": _PyAccess32_2,
'LAB': _PyAccess32_3, "RGB": _PyAccess32_3,
'HSV': _PyAccess32_3, "LAB": _PyAccess32_3,
'YCbCr': _PyAccess32_3, "HSV": _PyAccess32_3,
'RGBA': _PyAccess32_4, "YCbCr": _PyAccess32_3,
'RGBa': _PyAccess32_4, "RGBA": _PyAccess32_4,
'RGBX': _PyAccess32_4, "RGBa": _PyAccess32_4,
'CMYK': _PyAccess32_4, "RGBX": _PyAccess32_4,
'F': _PyAccessF, "CMYK": _PyAccess32_4,
'I': _PyAccessI32_N, "F": _PyAccessF,
} "I": _PyAccessI32_N,
}
if sys.byteorder == 'little': if sys.byteorder == "little":
mode_map['I;16'] = _PyAccessI16_N mode_map["I;16"] = _PyAccessI16_N
mode_map['I;16L'] = _PyAccessI16_N mode_map["I;16L"] = _PyAccessI16_N
mode_map['I;16B'] = _PyAccessI16_B mode_map["I;16B"] = _PyAccessI16_B
mode_map['I;32L'] = _PyAccessI32_N mode_map["I;32L"] = _PyAccessI32_N
mode_map['I;32B'] = _PyAccessI32_Swap mode_map["I;32B"] = _PyAccessI32_Swap
else: else:
mode_map['I;16'] = _PyAccessI16_L mode_map["I;16"] = _PyAccessI16_L
mode_map['I;16L'] = _PyAccessI16_L mode_map["I;16L"] = _PyAccessI16_L
mode_map['I;16B'] = _PyAccessI16_N mode_map["I;16B"] = _PyAccessI16_N
mode_map['I;32L'] = _PyAccessI32_Swap mode_map["I;32L"] = _PyAccessI32_Swap
mode_map['I;32B'] = _PyAccessI32_N mode_map["I;32B"] = _PyAccessI32_N
def new(img, readonly=False): def new(img, readonly=False):

View File

@ -46,7 +46,7 @@ MODES = {
(1, 3, 3): "RGB", (1, 3, 3): "RGB",
(2, 3, 3): "RGB;16B", (2, 3, 3): "RGB;16B",
(1, 3, 4): "RGBA", (1, 3, 4): "RGBA",
(2, 3, 4): "RGBA;16B" (2, 3, 4): "RGBA;16B",
} }
@ -100,8 +100,8 @@ class SgiImageFile(ImageFile.ImageFile):
self._size = xsize, ysize self._size = xsize, ysize
self.mode = rawmode.split(";")[0] self.mode = rawmode.split(";")[0]
if self.mode == 'RGB': if self.mode == "RGB":
self.custom_mimetype = 'image/rgb' self.custom_mimetype = "image/rgb"
# orientation -1 : scanlines begins at the bottom-left corner # orientation -1 : scanlines begins at the bottom-left corner
orientation = -1 orientation = -1
@ -110,19 +110,21 @@ class SgiImageFile(ImageFile.ImageFile):
if compression == 0: if compression == 0:
pagesize = xsize * ysize * bpc pagesize = xsize * ysize * bpc
if bpc == 2: if bpc == 2:
self.tile = [("SGI16", (0, 0) + self.size, self.tile = [
headlen, (self.mode, 0, orientation))] ("SGI16", (0, 0) + self.size, headlen, (self.mode, 0, orientation))
]
else: else:
self.tile = [] self.tile = []
offset = headlen offset = headlen
for layer in self.mode: for layer in self.mode:
self.tile.append( self.tile.append(
("raw", (0, 0) + self.size, ("raw", (0, 0) + self.size, offset, (layer, 0, orientation))
offset, (layer, 0, orientation))) )
offset += pagesize offset += pagesize
elif compression == 1: elif compression == 1:
self.tile = [("sgi_rle", (0, 0) + self.size, self.tile = [
headlen, (rawmode, orientation, bpc))] ("sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc))
]
def _save(im, fp, filename): def _save(im, fp, filename):
@ -161,8 +163,9 @@ def _save(im, fp, filename):
# assert we've got the right number of bands. # assert we've got the right number of bands.
if len(im.getbands()) != z: if len(im.getbands()) != z:
raise ValueError("incorrect number of bands in SGI write: %s vs %s" % raise ValueError(
(z, len(im.getbands()))) "incorrect number of bands in SGI write: %s vs %s" % (z, len(im.getbands()))
)
# Minimum Byte value # Minimum Byte value
pinmin = 0 pinmin = 0
@ -171,30 +174,30 @@ def _save(im, fp, filename):
# Image name (79 characters max, truncated below in write) # Image name (79 characters max, truncated below in write)
imgName = os.path.splitext(os.path.basename(filename))[0] imgName = os.path.splitext(os.path.basename(filename))[0]
if py3: if py3:
imgName = imgName.encode('ascii', 'ignore') imgName = imgName.encode("ascii", "ignore")
# Standard representation of pixel in the file # Standard representation of pixel in the file
colormap = 0 colormap = 0
fp.write(struct.pack('>h', magicNumber)) fp.write(struct.pack(">h", magicNumber))
fp.write(o8(rle)) fp.write(o8(rle))
fp.write(o8(bpc)) fp.write(o8(bpc))
fp.write(struct.pack('>H', dim)) fp.write(struct.pack(">H", dim))
fp.write(struct.pack('>H', x)) fp.write(struct.pack(">H", x))
fp.write(struct.pack('>H', y)) fp.write(struct.pack(">H", y))
fp.write(struct.pack('>H', z)) fp.write(struct.pack(">H", z))
fp.write(struct.pack('>l', pinmin)) fp.write(struct.pack(">l", pinmin))
fp.write(struct.pack('>l', pinmax)) fp.write(struct.pack(">l", pinmax))
fp.write(struct.pack('4s', b'')) # dummy fp.write(struct.pack("4s", b"")) # dummy
fp.write(struct.pack('79s', imgName)) # truncates to 79 chars fp.write(struct.pack("79s", imgName)) # truncates to 79 chars
fp.write(struct.pack('s', b'')) # force null byte after imgname fp.write(struct.pack("s", b"")) # force null byte after imgname
fp.write(struct.pack('>l', colormap)) fp.write(struct.pack(">l", colormap))
fp.write(struct.pack('404s', b'')) # dummy fp.write(struct.pack("404s", b"")) # dummy
rawmode = 'L' rawmode = "L"
if bpc == 2: if bpc == 2:
rawmode = 'L;16B' rawmode = "L;16B"
for channel in im.split(): for channel in im.split():
fp.write(channel.tobytes('raw', rawmode, 0, orientation)) fp.write(channel.tobytes("raw", rawmode, 0, orientation))
fp.close() fp.close()
@ -209,13 +212,15 @@ class SGI16Decoder(ImageFile.PyDecoder):
self.fd.seek(512) self.fd.seek(512)
for band in range(zsize): for band in range(zsize):
channel = Image.new('L', (self.state.xsize, self.state.ysize)) channel = Image.new("L", (self.state.xsize, self.state.ysize))
channel.frombytes(self.fd.read(2 * pagesize), 'raw', channel.frombytes(
'L;16B', stride, orientation) self.fd.read(2 * pagesize), "raw", "L;16B", stride, orientation
)
self.im.putband(channel.im, band) self.im.putband(channel.im, band)
return -1, 0 return -1, 0
# #
# registry # registry
@ -225,7 +230,6 @@ Image.register_open(SgiImageFile.format, SgiImageFile, _accept)
Image.register_save(SgiImageFile.format, _save) Image.register_save(SgiImageFile.format, _save)
Image.register_mime(SgiImageFile.format, "image/sgi") Image.register_mime(SgiImageFile.format, "image/sgi")
Image.register_extensions(SgiImageFile.format, Image.register_extensions(SgiImageFile.format, [".bw", ".rgb", ".rgba", ".sgi"])
[".bw", ".rgb", ".rgba", ".sgi"])
# End of file # End of file

View File

@ -44,7 +44,7 @@ import sys
def isInt(f): def isInt(f):
try: try:
i = int(f) i = int(f)
if f-i == 0: if f - i == 0:
return 1 return 1
else: else:
return 0 return 0
@ -60,8 +60,9 @@ iforms = [1, 3, -11, -12, -21, -22]
# Returns no. of bytes in the header, if it is a valid Spider header, # Returns no. of bytes in the header, if it is a valid Spider header,
# otherwise returns 0 # otherwise returns 0
def isSpiderHeader(t): def isSpiderHeader(t):
h = (99,) + t # add 1 value so can use spider header index start=1 h = (99,) + t # add 1 value so can use spider header index start=1
# header values 1,2,5,12,13,22,23 should be integers # header values 1,2,5,12,13,22,23 should be integers
for i in [1, 2, 5, 12, 13, 22, 23]: for i in [1, 2, 5, 12, 13, 22, 23]:
if not isInt(h[i]): if not isInt(h[i]):
@ -71,9 +72,9 @@ def isSpiderHeader(t):
if iform not in iforms: if iform not in iforms:
return 0 return 0
# check other header values # check other header values
labrec = int(h[13]) # no. records in file header labrec = int(h[13]) # no. records in file header
labbyt = int(h[22]) # total no. of bytes in header labbyt = int(h[22]) # total no. of bytes in header
lenbyt = int(h[23]) # record length in bytes lenbyt = int(h[23]) # record length in bytes
if labbyt != (labrec * lenbyt): if labbyt != (labrec * lenbyt):
return 0 return 0
# looks like a valid header # looks like a valid header
@ -81,12 +82,12 @@ def isSpiderHeader(t):
def isSpiderImage(filename): def isSpiderImage(filename):
with open(filename, 'rb') as fp: with open(filename, "rb") as fp:
f = fp.read(92) # read 23 * 4 bytes f = fp.read(92) # read 23 * 4 bytes
t = struct.unpack('>23f', f) # try big-endian first t = struct.unpack(">23f", f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
t = struct.unpack('<23f', f) # little-endian t = struct.unpack("<23f", f) # little-endian
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
return hdrlen return hdrlen
@ -104,18 +105,18 @@ class SpiderImageFile(ImageFile.ImageFile):
try: try:
self.bigendian = 1 self.bigendian = 1
t = struct.unpack('>27f', f) # try big-endian first t = struct.unpack(">27f", f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
self.bigendian = 0 self.bigendian = 0
t = struct.unpack('<27f', f) # little-endian t = struct.unpack("<27f", f) # little-endian
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
raise SyntaxError("not a valid Spider file") raise SyntaxError("not a valid Spider file")
except struct.error: except struct.error:
raise SyntaxError("not a valid Spider file") raise SyntaxError("not a valid Spider file")
h = (99,) + t # add 1 value : spider header index starts at 1 h = (99,) + t # add 1 value : spider header index starts at 1
iform = int(h[5]) iform = int(h[5])
if iform != 1: if iform != 1:
raise SyntaxError("not a Spider 2D image") raise SyntaxError("not a Spider 2D image")
@ -149,9 +150,7 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F" self.rawmode = "F;32F"
self.mode = "F" self.mode = "F"
self.tile = [ self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))]
("raw", (0, 0) + self.size, offset,
(self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
@property @property
@ -184,13 +183,14 @@ class SpiderImageFile(ImageFile.ImageFile):
(minimum, maximum) = self.getextrema() (minimum, maximum) = self.getextrema()
m = 1 m = 1
if maximum != minimum: if maximum != minimum:
m = depth / (maximum-minimum) m = depth / (maximum - minimum)
b = -m * minimum b = -m * minimum
return self.point(lambda i, m=m, b=b: i * m + b).convert("L") return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
# returns a ImageTk.PhotoImage object, after rescaling to 0..255 # returns a ImageTk.PhotoImage object, after rescaling to 0..255
def tkPhotoImage(self): def tkPhotoImage(self):
from PIL import ImageTk from PIL import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256) return ImageTk.PhotoImage(self.convert2byte(), palette=256)
def _close__fp(self): def _close__fp(self):
@ -223,7 +223,7 @@ def loadImageSeries(filelist=None):
if not isSpiderImage(img): if not isSpiderImage(img):
print(img + " is not a Spider image file") print(img + " is not a Spider image file")
continue continue
im.info['filename'] = img im.info["filename"] = img
imglist.append(im) imglist.append(im)
return imglist return imglist
@ -231,6 +231,7 @@ def loadImageSeries(filelist=None):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# For saving images in Spider format # For saving images in Spider format
def makeSpiderHeader(im): def makeSpiderHeader(im):
nsam, nrow = im.size nsam, nrow = im.size
lenbyt = nsam * 4 # There are labrec records in the header lenbyt = nsam * 4 # There are labrec records in the header
@ -247,10 +248,10 @@ def makeSpiderHeader(im):
return [] return []
# NB these are Fortran indices # NB these are Fortran indices
hdr[1] = 1.0 # nslice (=1 for an image) hdr[1] = 1.0 # nslice (=1 for an image)
hdr[2] = float(nrow) # number of rows per slice hdr[2] = float(nrow) # number of rows per slice
hdr[5] = 1.0 # iform for 2D image hdr[5] = 1.0 # iform for 2D image
hdr[12] = float(nsam) # number of pixels per line hdr[12] = float(nsam) # number of pixels per line
hdr[13] = float(labrec) # number of records in file header hdr[13] = float(labrec) # number of records in file header
hdr[22] = float(labbyt) # total number of bytes in header hdr[22] = float(labbyt) # total number of bytes in header
hdr[23] = float(lenbyt) # record length in bytes hdr[23] = float(lenbyt) # record length in bytes
@ -261,13 +262,13 @@ def makeSpiderHeader(im):
# pack binary data into a string # pack binary data into a string
hdrstr = [] hdrstr = []
for v in hdr: for v in hdr:
hdrstr.append(struct.pack('f', v)) hdrstr.append(struct.pack("f", v))
return hdrstr return hdrstr
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode[0] != "F": if im.mode[0] != "F":
im = im.convert('F') im = im.convert("F")
hdr = makeSpiderHeader(im) hdr = makeSpiderHeader(im)
if len(hdr) < 256: if len(hdr) < 256:
@ -277,7 +278,7 @@ def _save(im, fp, filename):
fp.writelines(hdr) fp.writelines(hdr)
rawmode = "F;32NF" # 32-bit native floating point rawmode = "F;32NF" # 32-bit native floating point
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
def _save_spider(im, fp, filename): def _save_spider(im, fp, filename):
@ -286,6 +287,7 @@ def _save_spider(im, fp, filename):
Image.register_extension(SpiderImageFile.format, ext) Image.register_extension(SpiderImageFile.format, ext)
_save(im, fp, filename) _save(im, fp, filename)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -308,7 +310,7 @@ if __name__ == "__main__":
print("format: " + str(im.format)) print("format: " + str(im.format))
print("size: " + str(im.size)) print("size: " + str(im.size))
print("mode: " + str(im.mode)) print("mode: " + str(im.mode))
print("max, min: ", end=' ') print("max, min: ", end=" ")
print(im.getextrema()) print(im.getextrema())
if len(sys.argv) > 2: if len(sys.argv) > 2:
@ -317,6 +319,7 @@ if __name__ == "__main__":
# perform some image operation # perform some image operation
im = im.transpose(Image.FLIP_LEFT_RIGHT) im = im.transpose(Image.FLIP_LEFT_RIGHT)
print( print(
"saving a flipped version of %s as %s " % "saving a flipped version of %s as %s "
(os.path.basename(filename), outfile)) % (os.path.basename(filename), outfile)
)
im.save(outfile, SpiderImageFile.format) im.save(outfile, SpiderImageFile.format)

View File

@ -26,12 +26,13 @@ __version__ = "0.3"
def _accept(prefix): def _accept(prefix):
return len(prefix) >= 4 and i32(prefix) == 0x59a66a95 return len(prefix) >= 4 and i32(prefix) == 0x59A66A95
## ##
# Image plugin for Sun raster files. # Image plugin for Sun raster files.
class SunImageFile(ImageFile.ImageFile): class SunImageFile(ImageFile.ImageFile):
format = "SUN" format = "SUN"
@ -56,7 +57,7 @@ class SunImageFile(ImageFile.ImageFile):
# HEAD # HEAD
s = self.fp.read(32) s = self.fp.read(32)
if i32(s) != 0x59a66a95: if i32(s) != 0x59A66A95:
raise SyntaxError("not an SUN raster file") raise SyntaxError("not an SUN raster file")
offset = 32 offset = 32
@ -82,9 +83,9 @@ class SunImageFile(ImageFile.ImageFile):
self.mode, rawmode = "RGB", "BGR" self.mode, rawmode = "RGB", "BGR"
elif depth == 32: elif depth == 32:
if file_type == 3: if file_type == 3:
self.mode, rawmode = 'RGB', 'RGBX' self.mode, rawmode = "RGB", "RGBX"
else: else:
self.mode, rawmode = 'RGB', 'BGRX' self.mode, rawmode = "RGB", "BGRX"
else: else:
raise SyntaxError("Unsupported Mode/Bit Depth") raise SyntaxError("Unsupported Mode/Bit Depth")
@ -96,11 +97,10 @@ class SunImageFile(ImageFile.ImageFile):
raise SyntaxError("Unsupported Palette Type") raise SyntaxError("Unsupported Palette Type")
offset = offset + palette_length offset = offset + palette_length
self.palette = ImagePalette.raw("RGB;L", self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length))
self.fp.read(palette_length))
if self.mode == "L": if self.mode == "L":
self.mode = "P" self.mode = "P"
rawmode = rawmode.replace('L', 'P') rawmode = rawmode.replace("L", "P")
# 16 bit boundaries on stride # 16 bit boundaries on stride
stride = ((self.size[0] * depth + 15) // 16) * 2 stride = ((self.size[0] * depth + 15) // 16) * 2
@ -124,11 +124,12 @@ class SunImageFile(ImageFile.ImageFile):
# (https://www.fileformat.info/format/sunraster/egff.htm) # (https://www.fileformat.info/format/sunraster/egff.htm)
if file_type in (0, 1, 3, 4, 5): if file_type in (0, 1, 3, 4, 5):
self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride))]
elif file_type == 2: elif file_type == 2:
self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)]
else: else:
raise SyntaxError('Unsupported Sun Raster file type') raise SyntaxError("Unsupported Sun Raster file type")
# #
# registry # registry

View File

@ -23,8 +23,8 @@ from . import ContainerIO
# A file object that provides read access to a given member of a TAR # A file object that provides read access to a given member of a TAR
# file. # file.
class TarIO(ContainerIO.ContainerIO):
class TarIO(ContainerIO.ContainerIO):
def __init__(self, tarfile, file): def __init__(self, tarfile, file):
""" """
Create file object. Create file object.
@ -40,8 +40,8 @@ class TarIO(ContainerIO.ContainerIO):
if len(s) != 512: if len(s) != 512:
raise IOError("unexpected end of tar file") raise IOError("unexpected end of tar file")
name = s[:100].decode('utf-8') name = s[:100].decode("utf-8")
i = name.find('\0') i = name.find("\0")
if i == 0: if i == 0:
raise IOError("cannot find subfile") raise IOError("cannot find subfile")
if i > 0: if i > 0:
@ -65,6 +65,7 @@ class TarIO(ContainerIO.ContainerIO):
self.close() self.close()
if sys.version_info.major >= 3: if sys.version_info.major >= 3:
def __del__(self): def __del__(self):
self.close() self.close()

View File

@ -34,9 +34,9 @@ __version__ = "0.3"
MODES = { MODES = {
# map imagetype/depth to rawmode # map imagetype/depth to rawmode
(1, 8): "P", (1, 8): "P",
(3, 1): "1", (3, 1): "1",
(3, 8): "L", (3, 8): "L",
(3, 16): "LA", (3, 16): "LA",
(2, 16): "BGR;5", (2, 16): "BGR;5",
(2, 24): "BGR", (2, 24): "BGR",
@ -47,6 +47,7 @@ MODES = {
## ##
# Image plugin for Targa files. # Image plugin for Targa files.
class TgaImageFile(ImageFile.ImageFile): class TgaImageFile(ImageFile.ImageFile):
format = "TGA" format = "TGA"
@ -69,9 +70,12 @@ class TgaImageFile(ImageFile.ImageFile):
self._size = i16(s[12:]), i16(s[14:]) self._size = i16(s[12:]), i16(s[14:])
# validate header fields # validate header fields
if colormaptype not in (0, 1) or\ if (
self.size[0] <= 0 or self.size[1] <= 0 or\ colormaptype not in (0, 1)
depth not in (1, 8, 16, 24, 32): or self.size[0] <= 0
or self.size[1] <= 0
or depth not in (1, 8, 16, 24, 32)
):
raise SyntaxError("not a TGA file") raise SyntaxError("not a TGA file")
# image mode # image mode
@ -112,27 +116,43 @@ class TgaImageFile(ImageFile.ImageFile):
start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:])
if mapdepth == 16: if mapdepth == 16:
self.palette = ImagePalette.raw( self.palette = ImagePalette.raw(
"BGR;16", b"\0"*2*start + self.fp.read(2*size)) "BGR;16", b"\0" * 2 * start + self.fp.read(2 * size)
)
elif mapdepth == 24: elif mapdepth == 24:
self.palette = ImagePalette.raw( self.palette = ImagePalette.raw(
"BGR", b"\0"*3*start + self.fp.read(3*size)) "BGR", b"\0" * 3 * start + self.fp.read(3 * size)
)
elif mapdepth == 32: elif mapdepth == 32:
self.palette = ImagePalette.raw( self.palette = ImagePalette.raw(
"BGRA", b"\0"*4*start + self.fp.read(4*size)) "BGRA", b"\0" * 4 * start + self.fp.read(4 * size)
)
# setup tile descriptor # setup tile descriptor
try: try:
rawmode = MODES[(imagetype & 7, depth)] rawmode = MODES[(imagetype & 7, depth)]
if imagetype & 8: if imagetype & 8:
# compressed # compressed
self.tile = [("tga_rle", (0, 0)+self.size, self.tile = [
self.fp.tell(), (rawmode, orientation, depth))] (
"tga_rle",
(0, 0) + self.size,
self.fp.tell(),
(rawmode, orientation, depth),
)
]
else: else:
self.tile = [("raw", (0, 0)+self.size, self.tile = [
self.fp.tell(), (rawmode, 0, orientation))] (
"raw",
(0, 0) + self.size,
self.fp.tell(),
(rawmode, 0, orientation),
)
]
except KeyError: except KeyError:
pass # cannot decode pass # cannot decode
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write TGA file # Write TGA file
@ -158,14 +178,12 @@ def _save(im, fp, filename):
if "rle" in im.encoderinfo: if "rle" in im.encoderinfo:
rle = im.encoderinfo["rle"] rle = im.encoderinfo["rle"]
else: else:
compression = im.encoderinfo.get("compression", compression = im.encoderinfo.get("compression", im.info.get("compression"))
im.info.get("compression"))
rle = compression == "tga_rle" rle = compression == "tga_rle"
if rle: if rle:
imagetype += 8 imagetype += 8
id_section = im.encoderinfo.get("id_section", id_section = im.encoderinfo.get("id_section", im.info.get("id_section", ""))
im.info.get("id_section", ""))
id_len = len(id_section) id_len = len(id_section)
if id_len > 255: if id_len > 255:
id_len = 255 id_len = 255
@ -182,23 +200,24 @@ def _save(im, fp, filename):
else: else:
flags = 0 flags = 0
orientation = im.encoderinfo.get("orientation", orientation = im.encoderinfo.get("orientation", im.info.get("orientation", -1))
im.info.get("orientation", -1))
if orientation > 0: if orientation > 0:
flags = flags | 0x20 flags = flags | 0x20
fp.write(o8(id_len) + fp.write(
o8(colormaptype) + o8(id_len)
o8(imagetype) + + o8(colormaptype)
o16(colormapfirst) + + o8(imagetype)
o16(colormaplength) + + o16(colormapfirst)
o8(colormapentry) + + o16(colormaplength)
o16(0) + + o8(colormapentry)
o16(0) + + o16(0)
o16(im.size[0]) + + o16(0)
o16(im.size[1]) + + o16(im.size[0])
o8(bits) + + o16(im.size[1])
o8(flags)) + o8(bits)
+ o8(flags)
)
if id_section: if id_section:
fp.write(id_section) fp.write(id_section)
@ -208,16 +227,17 @@ def _save(im, fp, filename):
if rle: if rle:
ImageFile._save( ImageFile._save(
im, im, fp, [("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))]
fp, )
[("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))])
else: else:
ImageFile._save( ImageFile._save(
im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]) im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]
)
# write targa version 2 footer # write targa version 2 footer
fp.write(b"\000" * 8 + b"TRUEVISION-XFILE." + b"\000") fp.write(b"\000" * 8 + b"TRUEVISION-XFILE." + b"\000")
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Registry # Registry

View File

@ -153,7 +153,6 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (1,), ()): ("1", "1"), (MM, 1, (1,), 1, (1,), ()): ("1", "1"),
(II, 1, (1,), 2, (1,), ()): ("1", "1;R"), (II, 1, (1,), 2, (1,), ()): ("1", "1;R"),
(MM, 1, (1,), 2, (1,), ()): ("1", "1;R"), (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"),
(II, 0, (1,), 1, (2,), ()): ("L", "L;2I"), (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
(MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"), (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
(II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
@ -162,7 +161,6 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (2,), ()): ("L", "L;2"), (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"),
(II, 1, (1,), 2, (2,), ()): ("L", "L;2R"), (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
(MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"), (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
(II, 0, (1,), 1, (4,), ()): ("L", "L;4I"), (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
(MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"), (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
(II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
@ -171,7 +169,6 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (4,), ()): ("L", "L;4"), (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"),
(II, 1, (1,), 2, (4,), ()): ("L", "L;4R"), (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
(MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"), (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
(II, 0, (1,), 1, (8,), ()): ("L", "L;I"), (II, 0, (1,), 1, (8,), ()): ("L", "L;I"),
(MM, 0, (1,), 1, (8,), ()): ("L", "L;I"), (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"),
(II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
@ -180,14 +177,11 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (8,), ()): ("L", "L"), (MM, 1, (1,), 1, (8,), ()): ("L", "L"),
(II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
(II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"),
(MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
(II, 1, (2,), 1, (16,), ()): ("I", "I;16S"), (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"),
(MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"), (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"),
(II, 0, (3,), 1, (32,), ()): ("F", "F;32F"), (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"),
(MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"), (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"),
(II, 1, (1,), 1, (32,), ()): ("I", "I;32N"), (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"),
@ -195,10 +189,8 @@ OPEN_INFO = {
(MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"), (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"),
(II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"),
(MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"),
(II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
(MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
(II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
(MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
(II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
@ -225,7 +217,6 @@ OPEN_INFO = {
(MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
(II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"), (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"),
(MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"), (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"),
(II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"), (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"),
@ -236,7 +227,6 @@ OPEN_INFO = {
(MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"), (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"),
(II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"), (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"),
(MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"), (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"),
(II, 3, (1,), 1, (1,), ()): ("P", "P;1"), (II, 3, (1,), 1, (1,), ()): ("P", "P;1"),
(MM, 3, (1,), 1, (1,), ()): ("P", "P;1"), (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"),
(II, 3, (1,), 2, (1,), ()): ("P", "P;1R"), (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
@ -255,21 +245,17 @@ OPEN_INFO = {
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(II, 3, (1,), 2, (8,), ()): ("P", "P;R"), (II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
(MM, 3, (1,), 2, (8,), ()): ("P", "P;R"), (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"),
(II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
(MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
(II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"), (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"),
# JPEG compressed images handled by LibTiff and auto-converted to RGBX # JPEG compressed images handled by LibTiff and auto-converted to RGBX
# Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
(II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
(MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
(II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
} }
@ -315,7 +301,7 @@ class IFDRational(Rational):
""" """
__slots__ = ('_numerator', '_denominator', '_val') __slots__ = ("_numerator", "_denominator", "_val")
def __init__(self, value, denominator=1): def __init__(self, value, denominator=1):
""" """
@ -339,7 +325,7 @@ class IFDRational(Rational):
return return
if denominator == 0: if denominator == 0:
self._val = float('nan') self._val = float("nan")
return return
elif denominator == 1: elif denominator == 1:
@ -380,6 +366,7 @@ class IFDRational(Rational):
def _delegate(op): def _delegate(op):
def delegate(self, *args): def delegate(self, *args):
return getattr(self._val, op)(*args) return getattr(self._val, op)(*args)
return delegate return delegate
""" a = ['add','radd', 'sub', 'rsub','div', 'rdiv', 'mul', 'rmul', """ a = ['add','radd', 'sub', 'rsub','div', 'rdiv', 'mul', 'rmul',
@ -390,34 +377,34 @@ class IFDRational(Rational):
print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)) print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a))
""" """
__add__ = _delegate('__add__') __add__ = _delegate("__add__")
__radd__ = _delegate('__radd__') __radd__ = _delegate("__radd__")
__sub__ = _delegate('__sub__') __sub__ = _delegate("__sub__")
__rsub__ = _delegate('__rsub__') __rsub__ = _delegate("__rsub__")
__div__ = _delegate('__div__') __div__ = _delegate("__div__")
__rdiv__ = _delegate('__rdiv__') __rdiv__ = _delegate("__rdiv__")
__mul__ = _delegate('__mul__') __mul__ = _delegate("__mul__")
__rmul__ = _delegate('__rmul__') __rmul__ = _delegate("__rmul__")
__truediv__ = _delegate('__truediv__') __truediv__ = _delegate("__truediv__")
__rtruediv__ = _delegate('__rtruediv__') __rtruediv__ = _delegate("__rtruediv__")
__floordiv__ = _delegate('__floordiv__') __floordiv__ = _delegate("__floordiv__")
__rfloordiv__ = _delegate('__rfloordiv__') __rfloordiv__ = _delegate("__rfloordiv__")
__mod__ = _delegate('__mod__') __mod__ = _delegate("__mod__")
__rmod__ = _delegate('__rmod__') __rmod__ = _delegate("__rmod__")
__pow__ = _delegate('__pow__') __pow__ = _delegate("__pow__")
__rpow__ = _delegate('__rpow__') __rpow__ = _delegate("__rpow__")
__pos__ = _delegate('__pos__') __pos__ = _delegate("__pos__")
__neg__ = _delegate('__neg__') __neg__ = _delegate("__neg__")
__abs__ = _delegate('__abs__') __abs__ = _delegate("__abs__")
__trunc__ = _delegate('__trunc__') __trunc__ = _delegate("__trunc__")
__lt__ = _delegate('__lt__') __lt__ = _delegate("__lt__")
__gt__ = _delegate('__gt__') __gt__ = _delegate("__gt__")
__le__ = _delegate('__le__') __le__ = _delegate("__le__")
__ge__ = _delegate('__ge__') __ge__ = _delegate("__ge__")
__nonzero__ = _delegate('__nonzero__') __nonzero__ = _delegate("__nonzero__")
__ceil__ = _delegate('__ceil__') __ceil__ = _delegate("__ceil__")
__floor__ = _delegate('__floor__') __floor__ = _delegate("__floor__")
__round__ = _delegate('__round__') __round__ = _delegate("__round__")
class ImageFileDirectory_v2(MutableMapping): class ImageFileDirectory_v2(MutableMapping):
@ -451,6 +438,7 @@ class ImageFileDirectory_v2(MutableMapping):
.. versionadded:: 3.0.0 .. versionadded:: 3.0.0
""" """
""" """
Documentation: Documentation:
@ -510,7 +498,7 @@ class ImageFileDirectory_v2(MutableMapping):
self._tags_v1 = {} # will remain empty if legacy_api is false self._tags_v1 = {} # will remain empty if legacy_api is false
self._tags_v2 = {} # main tag storage self._tags_v2 = {} # main tag storage
self._tagdata = {} self._tagdata = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech self.tagtype = {} # added 2008-06-05 by Florian Hoech
self._next = None self._next = None
self._offset = None self._offset = None
@ -536,13 +524,14 @@ class ImageFileDirectory_v2(MutableMapping):
self[tag] = handler(self, data, self.legacy_api) # check type self[tag] = handler(self, data, self.legacy_api) # check type
val = self._tags_v2[tag] val = self._tags_v2[tag]
if self.legacy_api and not isinstance(val, (tuple, bytes)): if self.legacy_api and not isinstance(val, (tuple, bytes)):
val = val, val = (val,)
return val return val
def __contains__(self, tag): def __contains__(self, tag):
return tag in self._tags_v2 or tag in self._tagdata return tag in self._tags_v2 or tag in self._tagdata
if not py3: if not py3:
def has_key(self, tag): def has_key(self, tag):
return tag in self return tag in self
@ -552,7 +541,7 @@ class ImageFileDirectory_v2(MutableMapping):
def _setitem(self, tag, value, legacy_api): def _setitem(self, tag, value, legacy_api):
basetypes = (Number, bytes, str) basetypes = (Number, bytes, str)
if not py3: if not py3:
basetypes += unicode, # noqa: F821 basetypes += (unicode,) # noqa: F821
info = TiffTags.lookup(tag) info = TiffTags.lookup(tag)
values = [value] if isinstance(value, basetypes) else value values = [value] if isinstance(value, basetypes) else value
@ -580,11 +569,11 @@ class ImageFileDirectory_v2(MutableMapping):
self.tagtype[tag] = TiffTags.ASCII self.tagtype[tag] = TiffTags.ASCII
if self.tagtype[tag] == TiffTags.UNDEFINED and py3: if self.tagtype[tag] == TiffTags.UNDEFINED and py3:
values = [value.encode("ascii", 'replace') if isinstance( values = [
value, str) else value] value.encode("ascii", "replace") if isinstance(value, str) else value
]
elif self.tagtype[tag] == TiffTags.RATIONAL: elif self.tagtype[tag] == TiffTags.RATIONAL:
values = [float(v) if isinstance(v, int) else v values = [float(v) if isinstance(v, int) else v for v in values]
for v in values]
values = tuple(info.cvt_enum(value) for value in values) values = tuple(info.cvt_enum(value) for value in values)
@ -595,22 +584,23 @@ class ImageFileDirectory_v2(MutableMapping):
# Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
# No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
# Don't mess with the legacy api, since it's frozen. # Don't mess with the legacy api, since it's frozen.
if (info.length == 1) or \ if (info.length == 1) or (
(info.length is None and len(values) == 1 and not legacy_api): info.length is None and len(values) == 1 and not legacy_api
):
# Don't mess with the legacy api, since it's frozen. # Don't mess with the legacy api, since it's frozen.
if legacy_api and self.tagtype[tag] in [ if legacy_api and self.tagtype[tag] in [
TiffTags.RATIONAL, TiffTags.RATIONAL,
TiffTags.SIGNED_RATIONAL TiffTags.SIGNED_RATIONAL,
]: # rationals ]: # rationals
values = values, values = (values,)
try: try:
dest[tag], = values dest[tag], = values
except ValueError: except ValueError:
# We've got a builtin tag with 1 expected entry # We've got a builtin tag with 1 expected entry
warnings.warn( warnings.warn(
"Metadata Warning, tag %s had too many entries: " "Metadata Warning, tag %s had too many entries: %s, expected 1"
"%s, expected 1" % ( % (tag, len(values))
tag, len(values))) )
dest[tag] = values[0] dest[tag] = values[0]
else: else:
@ -635,36 +625,51 @@ class ImageFileDirectory_v2(MutableMapping):
def _register_loader(idx, size): def _register_loader(idx, size):
def decorator(func): def decorator(func):
from .TiffTags import TYPES from .TiffTags import TYPES
if func.__name__.startswith("load_"): if func.__name__.startswith("load_"):
TYPES[idx] = func.__name__[5:].replace("_", " ") TYPES[idx] = func.__name__[5:].replace("_", " ")
_load_dispatch[idx] = size, func # noqa: F821 _load_dispatch[idx] = size, func # noqa: F821
return func return func
return decorator return decorator
def _register_writer(idx): def _register_writer(idx):
def decorator(func): def decorator(func):
_write_dispatch[idx] = func # noqa: F821 _write_dispatch[idx] = func # noqa: F821
return func return func
return decorator return decorator
def _register_basic(idx_fmt_name): def _register_basic(idx_fmt_name):
from .TiffTags import TYPES from .TiffTags import TYPES
idx, fmt, name = idx_fmt_name idx, fmt, name = idx_fmt_name
TYPES[idx] = name TYPES[idx] = name
size = struct.calcsize("=" + fmt) size = struct.calcsize("=" + fmt)
_load_dispatch[idx] = size, lambda self, data, legacy_api=True: ( # noqa: F821 _load_dispatch[idx] = ( # noqa: F821
self._unpack("{}{}".format(len(data) // size, fmt), data)) size,
lambda self, data, legacy_api=True: (
self._unpack("{}{}".format(len(data) // size, fmt), data)
),
)
_write_dispatch[idx] = lambda self, *values: ( # noqa: F821 _write_dispatch[idx] = lambda self, *values: ( # noqa: F821
b"".join(self._pack(fmt, value) for value in values)) b"".join(self._pack(fmt, value) for value in values)
)
list(map(_register_basic, list(
[(TiffTags.SHORT, "H", "short"), map(
(TiffTags.LONG, "L", "long"), _register_basic,
(TiffTags.SIGNED_BYTE, "b", "signed byte"), [
(TiffTags.SIGNED_SHORT, "h", "signed short"), (TiffTags.SHORT, "H", "short"),
(TiffTags.SIGNED_LONG, "l", "signed long"), (TiffTags.LONG, "L", "long"),
(TiffTags.FLOAT, "f", "float"), (TiffTags.SIGNED_BYTE, "b", "signed byte"),
(TiffTags.DOUBLE, "d", "double")])) (TiffTags.SIGNED_SHORT, "h", "signed short"),
(TiffTags.SIGNED_LONG, "l", "signed long"),
(TiffTags.FLOAT, "f", "float"),
(TiffTags.DOUBLE, "d", "double"),
],
)
)
@_register_loader(1, 1) # Basic type, except for the legacy API. @_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True): def load_byte(self, data, legacy_api=True):
@ -684,21 +689,23 @@ class ImageFileDirectory_v2(MutableMapping):
def write_string(self, value): def write_string(self, value):
# remerge of https://github.com/python-pillow/Pillow/pull/1416 # remerge of https://github.com/python-pillow/Pillow/pull/1416
if sys.version_info.major == 2: if sys.version_info.major == 2:
value = value.decode('ascii', 'replace') value = value.decode("ascii", "replace")
return b"" + value.encode('ascii', 'replace') + b"\0" return b"" + value.encode("ascii", "replace") + b"\0"
@_register_loader(5, 8) @_register_loader(5, 8)
def load_rational(self, data, legacy_api=True): def load_rational(self, data, legacy_api=True):
vals = self._unpack("{}L".format(len(data) // 4), data) vals = self._unpack("{}L".format(len(data) // 4), data)
def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) def combine(a, b):
return tuple(combine(num, denom) return (a, b) if legacy_api else IFDRational(a, b)
for num, denom in zip(vals[::2], vals[1::2]))
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(5) @_register_writer(5)
def write_rational(self, *values): def write_rational(self, *values):
return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 31)) return b"".join(
for frac in values) self._pack("2L", *_limit_rational(frac, 2 ** 31)) for frac in values
)
@_register_loader(7, 1) @_register_loader(7, 1)
def load_undefined(self, data, legacy_api=True): def load_undefined(self, data, legacy_api=True):
@ -712,21 +719,24 @@ class ImageFileDirectory_v2(MutableMapping):
def load_signed_rational(self, data, legacy_api=True): def load_signed_rational(self, data, legacy_api=True):
vals = self._unpack("{}l".format(len(data) // 4), data) vals = self._unpack("{}l".format(len(data) // 4), data)
def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) def combine(a, b):
return tuple(combine(num, denom) return (a, b) if legacy_api else IFDRational(a, b)
for num, denom in zip(vals[::2], vals[1::2]))
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(10) @_register_writer(10)
def write_signed_rational(self, *values): def write_signed_rational(self, *values):
return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 30)) return b"".join(
for frac in values) self._pack("2L", *_limit_rational(frac, 2 ** 30)) for frac in values
)
def _ensure_read(self, fp, size): def _ensure_read(self, fp, size):
ret = fp.read(size) ret = fp.read(size)
if len(ret) != size: if len(ret) != size:
raise IOError("Corrupt EXIF data. " + raise IOError(
"Expecting to read %d bytes but only got %d. " % "Corrupt EXIF data. "
(size, len(ret))) + "Expecting to read %d bytes but only got %d. " % (size, len(ret))
)
return ret return ret
def load(self, fp): def load(self, fp):
@ -736,13 +746,14 @@ class ImageFileDirectory_v2(MutableMapping):
try: try:
for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]): for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]):
tag, typ, count, data = self._unpack("HHL4s", tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12))
self._ensure_read(fp, 12))
if DEBUG: if DEBUG:
tagname = TiffTags.lookup(tag).name tagname = TiffTags.lookup(tag).name
typname = TYPES.get(typ, "unknown") typname = TYPES.get(typ, "unknown")
print("tag: %s (%d) - type: %s (%d)" % print(
(tagname, tag, typname, typ), end=" ") "tag: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ),
end=" ",
)
try: try:
unit_size, handler = self._load_dispatch[typ] unit_size, handler = self._load_dispatch[typ]
@ -755,8 +766,10 @@ class ImageFileDirectory_v2(MutableMapping):
here = fp.tell() here = fp.tell()
offset, = self._unpack("L", data) offset, = self._unpack("L", data)
if DEBUG: if DEBUG:
print("Tag Location: %s - Data Location: %s" % print(
(here, offset), end=" ") "Tag Location: %s - Data Location: %s" % (here, offset),
end=" ",
)
fp.seek(offset) fp.seek(offset)
data = ImageFile._safe_read(fp, size) data = ImageFile._safe_read(fp, size)
fp.seek(here) fp.seek(here)
@ -764,9 +777,11 @@ class ImageFileDirectory_v2(MutableMapping):
data = data[:size] data = data[:size]
if len(data) != size: if len(data) != size:
warnings.warn("Possibly corrupt EXIF data. " warnings.warn(
"Expecting to read %d bytes but only got %d." "Possibly corrupt EXIF data. "
" Skipping tag %s" % (size, len(data), tag)) "Expecting to read %d bytes but only got %d."
" Skipping tag %s" % (size, len(data), tag)
)
continue continue
if not data: if not data:
@ -807,8 +822,10 @@ class ImageFileDirectory_v2(MutableMapping):
if DEBUG: if DEBUG:
tagname = TiffTags.lookup(tag).name tagname = TiffTags.lookup(tag).name
typname = TYPES.get(typ, "unknown") typname = TYPES.get(typ, "unknown")
print("save: %s (%d) - type: %s (%d)" % print(
(tagname, tag, typname, typ), end=" ") "save: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ),
end=" ",
)
if len(data) >= 16: if len(data) >= 16:
print("- value: <table: %d bytes>" % len(data)) print("- value: <table: %d bytes>" % len(data))
else: else:
@ -823,16 +840,14 @@ class ImageFileDirectory_v2(MutableMapping):
if len(data) <= 4: if len(data) <= 4:
entries.append((tag, typ, count, data.ljust(4, b"\0"), b"")) entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
else: else:
entries.append((tag, typ, count, self._pack("L", offset), entries.append((tag, typ, count, self._pack("L", offset), data))
data))
offset += (len(data) + 1) // 2 * 2 # pad to word offset += (len(data) + 1) // 2 * 2 # pad to word
# update strip offset data to point beyond auxiliary data # update strip offset data to point beyond auxiliary data
if stripoffsets is not None: if stripoffsets is not None:
tag, typ, count, value, data = entries[stripoffsets] tag, typ, count, value, data = entries[stripoffsets]
if data: if data:
raise NotImplementedError( raise NotImplementedError("multistrip support not yet implemented")
"multistrip support not yet implemented")
value = self._pack("L", self._unpack("L", value)[0] + offset) value = self._pack("L", self._unpack("L", value)[0] + offset)
entries[stripoffsets] = tag, typ, count, value, data entries[stripoffsets] = tag, typ, count, value, data
@ -893,6 +908,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
.. deprecated:: 3.0.0 .. deprecated:: 3.0.0
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
ImageFileDirectory_v2.__init__(self, *args, **kwargs) ImageFileDirectory_v2.__init__(self, *args, **kwargs)
self._legacy_api = True self._legacy_api = True
@ -957,7 +973,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
self._setitem(tag, handler(self, data, legacy), legacy) self._setitem(tag, handler(self, data, legacy), legacy)
val = self._tags_v1[tag] val = self._tags_v1[tag]
if not isinstance(val, (tuple, bytes)): if not isinstance(val, (tuple, bytes)):
val = val, val = (val,)
return val return val
@ -968,6 +984,7 @@ ImageFileDirectory = ImageFileDirectory_v1
## ##
# Image plugin for TIFF files. # Image plugin for TIFF files.
class TiffImageFile(ImageFile.ImageFile): class TiffImageFile(ImageFile.ImageFile):
format = "TIFF" format = "TIFF"
@ -1032,9 +1049,10 @@ class TiffImageFile(ImageFile.ImageFile):
if not self.__next: if not self.__next:
raise EOFError("no more images in TIFF file") raise EOFError("no more images in TIFF file")
if DEBUG: if DEBUG:
print("Seeking to frame %s, on frame %s, " print(
"__next %s, location: %s" % "Seeking to frame %s, on frame %s, __next %s, location: %s"
(frame, self.__frame, self.__next, self.fp.tell())) % (frame, self.__frame, self.__next, self.fp.tell())
)
# reset python3 buffered io handle in case fp # reset python3 buffered io handle in case fp
# was passed to libtiff, invalidating the buffer # was passed to libtiff, invalidating the buffer
self.fp.tell() self.fp.tell()
@ -1067,9 +1085,9 @@ class TiffImageFile(ImageFile.ImageFile):
@size.setter @size.setter
def size(self, value): def size(self, value):
warnings.warn( warnings.warn(
'Setting the size of a TIFF image directly is deprecated, and will' "Setting the size of a TIFF image directly is deprecated, and will"
' be removed in a future version. Use the resize method instead.', " be removed in a future version. Use the resize method instead.",
DeprecationWarning DeprecationWarning,
) )
self._size = value self._size = value
@ -1124,8 +1142,9 @@ class TiffImageFile(ImageFile.ImageFile):
if fp: if fp:
args[2] = fp args[2] = fp
decoder = Image._getdecoder(self.mode, 'libtiff', tuple(args), decoder = Image._getdecoder(
self.decoderconfig) self.mode, "libtiff", tuple(args), self.decoderconfig
)
try: try:
decoder.setimage(self.im, extents) decoder.setimage(self.im, extents)
except ValueError: except ValueError:
@ -1207,8 +1226,7 @@ class TiffImageFile(ImageFile.ImageFile):
print("- size:", self.size) print("- size:", self.size)
sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,)) sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
if (len(sampleFormat) > 1 if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:
and max(sampleFormat) == min(sampleFormat) == 1):
# SAMPLEFORMAT is properly per band, so an RGB image will # SAMPLEFORMAT is properly per band, so an RGB image will
# be (1,1,1). But, we don't support per band pixel types, # be (1,1,1). But, we don't support per band pixel types,
# and anything more than one band is a uint8. So, just # and anything more than one band is a uint8. So, just
@ -1231,8 +1249,14 @@ class TiffImageFile(ImageFile.ImageFile):
bps_tuple = bps_tuple * bps_count bps_tuple = bps_tuple * bps_count
# mode: check photometric interpretation and bits per pixel # mode: check photometric interpretation and bits per pixel
key = (self.tag_v2.prefix, photo, sampleFormat, fillorder, key = (
bps_tuple, extra_tuple) self.tag_v2.prefix,
photo,
sampleFormat,
fillorder,
bps_tuple,
extra_tuple,
)
if DEBUG: if DEBUG:
print("format key:", key) print("format key:", key)
try: try:
@ -1268,7 +1292,7 @@ class TiffImageFile(ImageFile.ImageFile):
# build tile descriptors # build tile descriptors
x = y = layer = 0 x = y = layer = 0
self.tile = [] self.tile = []
self.use_load_libtiff = READ_LIBTIFF or self._compression != 'raw' self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw"
if self.use_load_libtiff: if self.use_load_libtiff:
# Decoder expects entire file as one tile. # Decoder expects entire file as one tile.
# There's a buffer size limit in load (64k) # There's a buffer size limit in load (64k)
@ -1295,20 +1319,17 @@ class TiffImageFile(ImageFile.ImageFile):
# we're expecting image byte order. So, if the rawmode # we're expecting image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image # contains I;16, we need to convert from native to image
# byte order. # byte order.
if rawmode == 'I;16': if rawmode == "I;16":
rawmode = 'I;16N' rawmode = "I;16N"
if ';16B' in rawmode: if ";16B" in rawmode:
rawmode = rawmode.replace(';16B', ';16N') rawmode = rawmode.replace(";16B", ";16N")
if ';16L' in rawmode: if ";16L" in rawmode:
rawmode = rawmode.replace(';16L', ';16N') rawmode = rawmode.replace(";16L", ";16N")
# Offset in the tile tuple is 0, we go from 0,0 to # Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds # w,h, and we only do this once -- eds
a = (rawmode, self._compression, False) a = (rawmode, self._compression, False)
self.tile.append( self.tile.append((self._compression, (0, 0, xsize, ysize), 0, a))
(self._compression,
(0, 0, xsize, ysize),
0, a))
elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2: elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
# striped image # striped image
@ -1337,9 +1358,13 @@ class TiffImageFile(ImageFile.ImageFile):
a = (tile_rawmode, int(stride), 1) a = (tile_rawmode, int(stride), 1)
self.tile.append( self.tile.append(
(self._compression, (
(x, y, min(x+w, xsize), min(y+h, ysize)), self._compression,
offset, a)) (x, y, min(x + w, xsize), min(y + h, ysize)),
offset,
a,
)
)
x = x + w x = x + w
if x >= self.size[0]: if x >= self.size[0]:
x, y = 0, y + h x, y = 0, y + h
@ -1353,7 +1378,7 @@ class TiffImageFile(ImageFile.ImageFile):
# Fix up info. # Fix up info.
if ICCPROFILE in self.tag_v2: if ICCPROFILE in self.tag_v2:
self.info['icc_profile'] = self.tag_v2[ICCPROFILE] self.info["icc_profile"] = self.tag_v2[ICCPROFILE]
# fixup palette descriptor # fixup palette descriptor
@ -1396,7 +1421,6 @@ SAVE_INFO = {
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None), "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None), "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None),
"I;16BS": ("I;16BS", MM, 1, 2, (16,), None), "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
@ -1413,14 +1437,14 @@ def _save(im, fp, filename):
ifd = ImageFileDirectory_v2(prefix=prefix) ifd = ImageFileDirectory_v2(prefix=prefix)
compression = im.encoderinfo.get('compression', im.info.get('compression')) compression = im.encoderinfo.get("compression", im.info.get("compression"))
if compression is None: if compression is None:
compression = 'raw' compression = "raw"
libtiff = WRITE_LIBTIFF or compression != 'raw' libtiff = WRITE_LIBTIFF or compression != "raw"
# required for color libtiff images # required for color libtiff images
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) ifd[PLANAR_CONFIGURATION] = getattr(im, "_planar_configuration", 1)
ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGEWIDTH] = im.size[0]
ifd[IMAGELENGTH] = im.size[1] ifd[IMAGELENGTH] = im.size[1]
@ -1440,10 +1464,16 @@ def _save(im, fp, filename):
# additions written by Greg Couch, gregc@cgl.ucsf.edu # additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
if hasattr(im, 'tag_v2'): if hasattr(im, "tag_v2"):
# preserve tags from original TIFF image file # preserve tags from original TIFF image file
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION, for key in (
IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): RESOLUTION_UNIT,
X_RESOLUTION,
Y_RESOLUTION,
IPTC_NAA_CHUNK,
PHOTOSHOP_CHUNK,
XMP,
):
if key in im.tag_v2: if key in im.tag_v2:
ifd[key] = im.tag_v2[key] ifd[key] = im.tag_v2[key]
ifd.tagtype[key] = im.tag_v2.tagtype[key] ifd.tagtype[key] = im.tag_v2.tagtype[key]
@ -1453,16 +1483,18 @@ def _save(im, fp, filename):
if "icc_profile" in im.info: if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"] ifd[ICCPROFILE] = im.info["icc_profile"]
for key, name in [(IMAGEDESCRIPTION, "description"), for key, name in [
(X_RESOLUTION, "resolution"), (IMAGEDESCRIPTION, "description"),
(Y_RESOLUTION, "resolution"), (X_RESOLUTION, "resolution"),
(X_RESOLUTION, "x_resolution"), (Y_RESOLUTION, "resolution"),
(Y_RESOLUTION, "y_resolution"), (X_RESOLUTION, "x_resolution"),
(RESOLUTION_UNIT, "resolution_unit"), (Y_RESOLUTION, "y_resolution"),
(SOFTWARE, "software"), (RESOLUTION_UNIT, "resolution_unit"),
(DATE_TIME, "date_time"), (SOFTWARE, "software"),
(ARTIST, "artist"), (DATE_TIME, "date_time"),
(COPYRIGHT, "copyright")]: (ARTIST, "artist"),
(COPYRIGHT, "copyright"),
]:
if name in im.encoderinfo: if name in im.encoderinfo:
ifd[key] = im.encoderinfo[name] ifd[key] = im.encoderinfo[name]
@ -1487,7 +1519,7 @@ def _save(im, fp, filename):
lut = im.im.getpalette("RGB", "RGB;L") lut = im.im.getpalette("RGB", "RGB;L")
ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut) ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut)
# data orientation # data orientation
stride = len(bits) * ((im.size[0]*bits[0]+7)//8) stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
ifd[ROWSPERSTRIP] = im.size[1] ifd[ROWSPERSTRIP] = im.size[1]
ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1]
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
@ -1516,11 +1548,11 @@ def _save(im, fp, filename):
# the original file, e.g x,y resolution so that we can # the original file, e.g x,y resolution so that we can
# save(load('')) == original file. # save(load('')) == original file.
legacy_ifd = {} legacy_ifd = {}
if hasattr(im, 'tag'): if hasattr(im, "tag"):
legacy_ifd = im.tag.to_v2() legacy_ifd = im.tag.to_v2()
for tag, value in itertools.chain(ifd.items(), for tag, value in itertools.chain(
getattr(im, 'tag_v2', {}).items(), ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items()
legacy_ifd.items()): ):
# Libtiff can only process certain core items without adding # Libtiff can only process certain core items without adding
# them to the custom dictionary. # them to the custom dictionary.
# Support for custom items has only been been added # Support for custom items has only been been added
@ -1528,14 +1560,17 @@ def _save(im, fp, filename):
if tag not in TiffTags.LIBTIFF_CORE: if tag not in TiffTags.LIBTIFF_CORE:
if TiffTags.lookup(tag).type == TiffTags.UNDEFINED: if TiffTags.lookup(tag).type == TiffTags.UNDEFINED:
continue continue
if (distutils.version.StrictVersion(_libtiff_version()) < if (
distutils.version.StrictVersion("4.0")) \ distutils.version.StrictVersion(_libtiff_version())
or not (isinstance(value, (int, float, str, bytes)) or < distutils.version.StrictVersion("4.0")
(not py3 and isinstance(value, unicode))): # noqa: F821 ) or not (
isinstance(value, (int, float, str, bytes))
or (not py3 and isinstance(value, unicode)) # noqa: F821
):
continue continue
if tag not in atts and tag not in blocklist: if tag not in atts and tag not in blocklist:
if isinstance(value, str if py3 else unicode): # noqa: F821 if isinstance(value, str if py3 else unicode): # noqa: F821
atts[tag] = value.encode('ascii', 'replace') + b"\0" atts[tag] = value.encode("ascii", "replace") + b"\0"
elif isinstance(value, IFDRational): elif isinstance(value, IFDRational):
atts[tag] = float(value) atts[tag] = float(value)
else: else:
@ -1548,15 +1583,15 @@ def _save(im, fp, filename):
# we're storing image byte order. So, if the rawmode # we're storing image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image # contains I;16, we need to convert from native to image
# byte order. # byte order.
if im.mode in ('I;16B', 'I;16'): if im.mode in ("I;16B", "I;16"):
rawmode = 'I;16N' rawmode = "I;16N"
a = (rawmode, compression, _fp, filename, atts) a = (rawmode, compression, _fp, filename, atts)
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig)
e.setimage(im.im, (0, 0)+im.size) e.setimage(im.im, (0, 0) + im.size)
while True: while True:
# undone, change to self.decodermaxblock: # undone, change to self.decodermaxblock:
l, s, d = e.encode(16*1024) l, s, d = e.encode(16 * 1024)
if not _fp: if not _fp:
fp.write(d) fp.write(d)
if s: if s:
@ -1567,9 +1602,9 @@ def _save(im, fp, filename):
else: else:
offset = ifd.save(fp) offset = ifd.save(fp)
ImageFile._save(im, fp, [ ImageFile._save(
("raw", (0, 0)+im.size, offset, (rawmode, stride, 1)) im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
]) )
# -- helper for multi-page save -- # -- helper for multi-page save --
if "_debug_multipage" in im.encoderinfo: if "_debug_multipage" in im.encoderinfo:
@ -1603,7 +1638,7 @@ class AppendingTiffWriter:
Tags = {273, 288, 324, 519, 520, 521} Tags = {273, 288, 324, 519, 520, 521}
def __init__(self, fn, new=False): def __init__(self, fn, new=False):
if hasattr(fn, 'read'): if hasattr(fn, "read"):
self.f = fn self.f = fn
self.close_fp = False self.close_fp = False
else: else:
@ -1654,8 +1689,7 @@ class AppendingTiffWriter:
return return
if IIMM != self.IIMM: if IIMM != self.IIMM:
raise RuntimeError("IIMM of new page doesn't match IIMM of " raise RuntimeError("IIMM of new page doesn't match IIMM of first page")
"first page")
IFDoffset = self.readLong() IFDoffset = self.readLong()
IFDoffset += self.offsetOfNewPage IFDoffset += self.offsetOfNewPage
@ -1729,34 +1763,29 @@ class AppendingTiffWriter:
self.f.seek(-2, os.SEEK_CUR) self.f.seek(-2, os.SEEK_CUR)
bytesWritten = self.f.write(struct.pack(self.longFmt, value)) bytesWritten = self.f.write(struct.pack(self.longFmt, value))
if bytesWritten is not None and bytesWritten != 4: if bytesWritten is not None and bytesWritten != 4:
raise RuntimeError("wrote only %u bytes but wanted 4" % raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten)
bytesWritten)
def rewriteLastShort(self, value): def rewriteLastShort(self, value):
self.f.seek(-2, os.SEEK_CUR) self.f.seek(-2, os.SEEK_CUR)
bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
if bytesWritten is not None and bytesWritten != 2: if bytesWritten is not None and bytesWritten != 2:
raise RuntimeError("wrote only %u bytes but wanted 2" % raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten)
bytesWritten)
def rewriteLastLong(self, value): def rewriteLastLong(self, value):
self.f.seek(-4, os.SEEK_CUR) self.f.seek(-4, os.SEEK_CUR)
bytesWritten = self.f.write(struct.pack(self.longFmt, value)) bytesWritten = self.f.write(struct.pack(self.longFmt, value))
if bytesWritten is not None and bytesWritten != 4: if bytesWritten is not None and bytesWritten != 4:
raise RuntimeError("wrote only %u bytes but wanted 4" % raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten)
bytesWritten)
def writeShort(self, value): def writeShort(self, value):
bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
if bytesWritten is not None and bytesWritten != 2: if bytesWritten is not None and bytesWritten != 2:
raise RuntimeError("wrote only %u bytes but wanted 2" % raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten)
bytesWritten)
def writeLong(self, value): def writeLong(self, value):
bytesWritten = self.f.write(struct.pack(self.longFmt, value)) bytesWritten = self.f.write(struct.pack(self.longFmt, value))
if bytesWritten is not None and bytesWritten != 4: if bytesWritten is not None and bytesWritten != 4:
raise RuntimeError("wrote only %u bytes but wanted 4" % raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten)
bytesWritten)
def close(self): def close(self):
self.finalize() self.finalize()
@ -1766,12 +1795,11 @@ class AppendingTiffWriter:
numTags = self.readShort() numTags = self.readShort()
for i in range(numTags): for i in range(numTags):
tag, fieldType, count = struct.unpack(self.tagFormat, tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))
self.f.read(8))
fieldSize = self.fieldSizes[fieldType] fieldSize = self.fieldSizes[fieldType]
totalSize = fieldSize * count totalSize = fieldSize * count
isLocal = (totalSize <= 4) isLocal = totalSize <= 4
if not isLocal: if not isLocal:
offset = self.readLong() offset = self.readLong()
offset += self.offsetOfNewPage offset += self.offsetOfNewPage
@ -1781,13 +1809,15 @@ class AppendingTiffWriter:
curPos = self.f.tell() curPos = self.f.tell()
if isLocal: if isLocal:
self.fixOffsets(count, isShort=(fieldSize == 2), self.fixOffsets(
isLong=(fieldSize == 4)) count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
)
self.f.seek(curPos + 4) self.f.seek(curPos + 4)
else: else:
self.f.seek(offset) self.f.seek(offset)
self.fixOffsets(count, isShort=(fieldSize == 2), self.fixOffsets(
isLong=(fieldSize == 4)) count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
)
self.f.seek(curPos) self.f.seek(curPos)
offset = curPos = None offset = curPos = None
@ -1830,7 +1860,7 @@ def _save_all(im, fp, filename):
cur_idx = im.tell() cur_idx = im.tell()
try: try:
with AppendingTiffWriter(fp) as tf: with AppendingTiffWriter(fp) as tf:
for ims in [im]+append_images: for ims in [im] + append_images:
ims.encoderinfo = encoderinfo ims.encoderinfo = encoderinfo
ims.encoderconfig = encoderconfig ims.encoderconfig = encoderconfig
if not hasattr(ims, "n_frames"): if not hasattr(ims, "n_frames"):

View File

@ -23,10 +23,8 @@ from collections import namedtuple
class TagInfo(namedtuple("_TagInfo", "value name type length enum")): class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
__slots__ = [] __slots__ = []
def __new__(cls, value=None, name="unknown", def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
type=None, length=None, enum=None): return super(TagInfo, cls).__new__(cls, value, name, type, length, enum or {})
return super(TagInfo, cls).__new__(
cls, value, name, type, length, enum or {})
def cvt_enum(self, value): def cvt_enum(self, value):
# Using get will call hash(value), which can be expensive # Using get will call hash(value), which can be expensive
@ -44,7 +42,7 @@ def lookup(tag):
""" """
return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, 'unknown'))) return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, "unknown")))
## ##
@ -73,27 +71,47 @@ FLOAT = 11
DOUBLE = 12 DOUBLE = 12
TAGS_V2 = { TAGS_V2 = {
254: ("NewSubfileType", LONG, 1), 254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1), 255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1), 256: ("ImageWidth", LONG, 1),
257: ("ImageLength", LONG, 1), 257: ("ImageLength", LONG, 1),
258: ("BitsPerSample", SHORT, 0), 258: ("BitsPerSample", SHORT, 0),
259: ("Compression", SHORT, 1, 259: (
{"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, "Compression",
"Group 4 Fax": 4, "LZW": 5, "JPEG": 6, "PackBits": 32773}), SHORT,
1,
262: ("PhotometricInterpretation", SHORT, 1, {
{"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RGB Palette": 3, "Uncompressed": 1,
"Transparency Mask": 4, "CMYK": 5, "YCbCr": 6, "CieLAB": 8, "CCITT 1d": 2,
"CFA": 32803, # TIFF/EP, Adobe DNG "Group 3 Fax": 3,
"LinearRaw": 32892}), # Adobe DNG "Group 4 Fax": 4,
"LZW": 5,
"JPEG": 6,
"PackBits": 32773,
},
),
262: (
"PhotometricInterpretation",
SHORT,
1,
{
"WhiteIsZero": 0,
"BlackIsZero": 1,
"RGB": 2,
"RGB Palette": 3,
"Transparency Mask": 4,
"CMYK": 5,
"YCbCr": 6,
"CieLAB": 8,
"CFA": 32803, # TIFF/EP, Adobe DNG
"LinearRaw": 32892, # Adobe DNG
},
),
263: ("Threshholding", SHORT, 1), 263: ("Threshholding", SHORT, 1),
264: ("CellWidth", SHORT, 1), 264: ("CellWidth", SHORT, 1),
265: ("CellLength", SHORT, 1), 265: ("CellLength", SHORT, 1),
266: ("FillOrder", SHORT, 1), 266: ("FillOrder", SHORT, 1),
269: ("DocumentName", ASCII, 1), 269: ("DocumentName", ASCII, 1),
270: ("ImageDescription", ASCII, 1), 270: ("ImageDescription", ASCII, 1),
271: ("Make", ASCII, 1), 271: ("Make", ASCII, 1),
272: ("Model", ASCII, 1), 272: ("Model", ASCII, 1),
@ -102,7 +120,6 @@ TAGS_V2 = {
277: ("SamplesPerPixel", SHORT, 1), 277: ("SamplesPerPixel", SHORT, 1),
278: ("RowsPerStrip", LONG, 1), 278: ("RowsPerStrip", LONG, 1),
279: ("StripByteCounts", LONG, 0), 279: ("StripByteCounts", LONG, 0),
280: ("MinSampleValue", LONG, 0), 280: ("MinSampleValue", LONG, 0),
281: ("MaxSampleValue", SHORT, 0), 281: ("MaxSampleValue", SHORT, 0),
282: ("XResolution", RATIONAL, 1), 282: ("XResolution", RATIONAL, 1),
@ -113,31 +130,26 @@ TAGS_V2 = {
287: ("YPosition", RATIONAL, 1), 287: ("YPosition", RATIONAL, 1),
288: ("FreeOffsets", LONG, 1), 288: ("FreeOffsets", LONG, 1),
289: ("FreeByteCounts", LONG, 1), 289: ("FreeByteCounts", LONG, 1),
290: ("GrayResponseUnit", SHORT, 1), 290: ("GrayResponseUnit", SHORT, 1),
291: ("GrayResponseCurve", SHORT, 0), 291: ("GrayResponseCurve", SHORT, 0),
292: ("T4Options", LONG, 1), 292: ("T4Options", LONG, 1),
293: ("T6Options", LONG, 1), 293: ("T6Options", LONG, 1),
296: ("ResolutionUnit", SHORT, 1, {"none": 1, "inch": 2, "cm": 3}), 296: ("ResolutionUnit", SHORT, 1, {"none": 1, "inch": 2, "cm": 3}),
297: ("PageNumber", SHORT, 2), 297: ("PageNumber", SHORT, 2),
301: ("TransferFunction", SHORT, 0), 301: ("TransferFunction", SHORT, 0),
305: ("Software", ASCII, 1), 305: ("Software", ASCII, 1),
306: ("DateTime", ASCII, 1), 306: ("DateTime", ASCII, 1),
315: ("Artist", ASCII, 1), 315: ("Artist", ASCII, 1),
316: ("HostComputer", ASCII, 1), 316: ("HostComputer", ASCII, 1),
317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}), 317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}),
318: ("WhitePoint", RATIONAL, 2), 318: ("WhitePoint", RATIONAL, 2),
319: ("PrimaryChromaticities", RATIONAL, 6), 319: ("PrimaryChromaticities", RATIONAL, 6),
320: ("ColorMap", SHORT, 0), 320: ("ColorMap", SHORT, 0),
321: ("HalftoneHints", SHORT, 2), 321: ("HalftoneHints", SHORT, 2),
322: ("TileWidth", LONG, 1), 322: ("TileWidth", LONG, 1),
323: ("TileLength", LONG, 1), 323: ("TileLength", LONG, 1),
324: ("TileOffsets", LONG, 0), 324: ("TileOffsets", LONG, 0),
325: ("TileByteCounts", LONG, 0), 325: ("TileByteCounts", LONG, 0),
332: ("InkSet", SHORT, 1), 332: ("InkSet", SHORT, 1),
333: ("InkNames", ASCII, 1), 333: ("InkNames", ASCII, 1),
334: ("NumberOfInks", SHORT, 1), 334: ("NumberOfInks", SHORT, 1),
@ -145,13 +157,10 @@ TAGS_V2 = {
337: ("TargetPrinter", ASCII, 1), 337: ("TargetPrinter", ASCII, 1),
338: ("ExtraSamples", SHORT, 0), 338: ("ExtraSamples", SHORT, 0),
339: ("SampleFormat", SHORT, 0), 339: ("SampleFormat", SHORT, 0),
340: ("SMinSampleValue", DOUBLE, 0), 340: ("SMinSampleValue", DOUBLE, 0),
341: ("SMaxSampleValue", DOUBLE, 0), 341: ("SMaxSampleValue", DOUBLE, 0),
342: ("TransferRange", SHORT, 6), 342: ("TransferRange", SHORT, 6),
347: ("JPEGTables", UNDEFINED, 1), 347: ("JPEGTables", UNDEFINED, 1),
# obsolete JPEG tags # obsolete JPEG tags
512: ("JPEGProc", SHORT, 1), 512: ("JPEGProc", SHORT, 1),
513: ("JPEGInterchangeFormat", LONG, 1), 513: ("JPEGInterchangeFormat", LONG, 1),
@ -162,22 +171,17 @@ TAGS_V2 = {
519: ("JPEGQTables", LONG, 0), 519: ("JPEGQTables", LONG, 0),
520: ("JPEGDCTables", LONG, 0), 520: ("JPEGDCTables", LONG, 0),
521: ("JPEGACTables", LONG, 0), 521: ("JPEGACTables", LONG, 0),
529: ("YCbCrCoefficients", RATIONAL, 3), 529: ("YCbCrCoefficients", RATIONAL, 3),
530: ("YCbCrSubSampling", SHORT, 2), 530: ("YCbCrSubSampling", SHORT, 2),
531: ("YCbCrPositioning", SHORT, 1), 531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", RATIONAL, 6), 532: ("ReferenceBlackWhite", RATIONAL, 6),
700: ("XMP", BYTE, 1),
700: ('XMP', BYTE, 1),
33432: ("Copyright", ASCII, 1), 33432: ("Copyright", ASCII, 1),
34377: ('PhotoshopInfo', BYTE, 1), 34377: ("PhotoshopInfo", BYTE, 1),
# FIXME add more tags here # FIXME add more tags here
34665: ("ExifIFD", SHORT, 1), 34665: ("ExifIFD", SHORT, 1),
34675: ('ICCProfile', UNDEFINED, 1), 34675: ("ICCProfile", UNDEFINED, 1),
34853: ('GPSInfoIFD', BYTE, 1), 34853: ("GPSInfoIFD", BYTE, 1),
# MPInfo # MPInfo
45056: ("MPFVersion", UNDEFINED, 1), 45056: ("MPFVersion", UNDEFINED, 1),
45057: ("NumberOfImages", LONG, 1), 45057: ("NumberOfImages", LONG, 1),
@ -198,159 +202,157 @@ TAGS_V2 = {
45579: ("YawAngle", SIGNED_RATIONAL, 1), 45579: ("YawAngle", SIGNED_RATIONAL, 1),
45580: ("PitchAngle", SIGNED_RATIONAL, 1), 45580: ("PitchAngle", SIGNED_RATIONAL, 1),
45581: ("RollAngle", SIGNED_RATIONAL, 1), 45581: ("RollAngle", SIGNED_RATIONAL, 1),
50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}), 50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
50780: ("BestQualityScale", RATIONAL, 1), 50780: ("BestQualityScale", RATIONAL, 1),
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
50839: ("ImageJMetaData", UNDEFINED, 1) # see Issue #2006 50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
} }
# Legacy Tags structure # Legacy Tags structure
# these tags aren't included above, but were in the previous versions # these tags aren't included above, but were in the previous versions
TAGS = {347: 'JPEGTables', TAGS = {
700: 'XMP', 347: "JPEGTables",
700: "XMP",
# Additional Exif Info # Additional Exif Info
32932: 'Wang Annotation', 32932: "Wang Annotation",
33434: 'ExposureTime', 33434: "ExposureTime",
33437: 'FNumber', 33437: "FNumber",
33445: 'MD FileTag', 33445: "MD FileTag",
33446: 'MD ScalePixel', 33446: "MD ScalePixel",
33447: 'MD ColorTable', 33447: "MD ColorTable",
33448: 'MD LabName', 33448: "MD LabName",
33449: 'MD SampleInfo', 33449: "MD SampleInfo",
33450: 'MD PrepDate', 33450: "MD PrepDate",
33451: 'MD PrepTime', 33451: "MD PrepTime",
33452: 'MD FileUnits', 33452: "MD FileUnits",
33550: 'ModelPixelScaleTag', 33550: "ModelPixelScaleTag",
33723: 'IptcNaaInfo', 33723: "IptcNaaInfo",
33918: 'INGR Packet Data Tag', 33918: "INGR Packet Data Tag",
33919: 'INGR Flag Registers', 33919: "INGR Flag Registers",
33920: 'IrasB Transformation Matrix', 33920: "IrasB Transformation Matrix",
33922: 'ModelTiepointTag', 33922: "ModelTiepointTag",
34264: 'ModelTransformationTag', 34264: "ModelTransformationTag",
34377: 'PhotoshopInfo', 34377: "PhotoshopInfo",
34735: 'GeoKeyDirectoryTag', 34735: "GeoKeyDirectoryTag",
34736: 'GeoDoubleParamsTag', 34736: "GeoDoubleParamsTag",
34737: 'GeoAsciiParamsTag', 34737: "GeoAsciiParamsTag",
34850: 'ExposureProgram', 34850: "ExposureProgram",
34852: 'SpectralSensitivity', 34852: "SpectralSensitivity",
34855: 'ISOSpeedRatings', 34855: "ISOSpeedRatings",
34856: 'OECF', 34856: "OECF",
34864: 'SensitivityType', 34864: "SensitivityType",
34865: 'StandardOutputSensitivity', 34865: "StandardOutputSensitivity",
34866: 'RecommendedExposureIndex', 34866: "RecommendedExposureIndex",
34867: 'ISOSpeed', 34867: "ISOSpeed",
34868: 'ISOSpeedLatitudeyyy', 34868: "ISOSpeedLatitudeyyy",
34869: 'ISOSpeedLatitudezzz', 34869: "ISOSpeedLatitudezzz",
34908: 'HylaFAX FaxRecvParams', 34908: "HylaFAX FaxRecvParams",
34909: 'HylaFAX FaxSubAddress', 34909: "HylaFAX FaxSubAddress",
34910: 'HylaFAX FaxRecvTime', 34910: "HylaFAX FaxRecvTime",
36864: 'ExifVersion', 36864: "ExifVersion",
36867: 'DateTimeOriginal', 36867: "DateTimeOriginal",
36868: 'DateTImeDigitized', 36868: "DateTImeDigitized",
37121: 'ComponentsConfiguration', 37121: "ComponentsConfiguration",
37122: 'CompressedBitsPerPixel', 37122: "CompressedBitsPerPixel",
37724: 'ImageSourceData', 37724: "ImageSourceData",
37377: 'ShutterSpeedValue', 37377: "ShutterSpeedValue",
37378: 'ApertureValue', 37378: "ApertureValue",
37379: 'BrightnessValue', 37379: "BrightnessValue",
37380: 'ExposureBiasValue', 37380: "ExposureBiasValue",
37381: 'MaxApertureValue', 37381: "MaxApertureValue",
37382: 'SubjectDistance', 37382: "SubjectDistance",
37383: 'MeteringMode', 37383: "MeteringMode",
37384: 'LightSource', 37384: "LightSource",
37385: 'Flash', 37385: "Flash",
37386: 'FocalLength', 37386: "FocalLength",
37396: 'SubjectArea', 37396: "SubjectArea",
37500: 'MakerNote', 37500: "MakerNote",
37510: 'UserComment', 37510: "UserComment",
37520: 'SubSec', 37520: "SubSec",
37521: 'SubSecTimeOriginal', 37521: "SubSecTimeOriginal",
37522: 'SubsecTimeDigitized', 37522: "SubsecTimeDigitized",
40960: 'FlashPixVersion', 40960: "FlashPixVersion",
40961: 'ColorSpace', 40961: "ColorSpace",
40962: 'PixelXDimension', 40962: "PixelXDimension",
40963: 'PixelYDimension', 40963: "PixelYDimension",
40964: 'RelatedSoundFile', 40964: "RelatedSoundFile",
40965: 'InteroperabilityIFD', 40965: "InteroperabilityIFD",
41483: 'FlashEnergy', 41483: "FlashEnergy",
41484: 'SpatialFrequencyResponse', 41484: "SpatialFrequencyResponse",
41486: 'FocalPlaneXResolution', 41486: "FocalPlaneXResolution",
41487: 'FocalPlaneYResolution', 41487: "FocalPlaneYResolution",
41488: 'FocalPlaneResolutionUnit', 41488: "FocalPlaneResolutionUnit",
41492: 'SubjectLocation', 41492: "SubjectLocation",
41493: 'ExposureIndex', 41493: "ExposureIndex",
41495: 'SensingMethod', 41495: "SensingMethod",
41728: 'FileSource', 41728: "FileSource",
41729: 'SceneType', 41729: "SceneType",
41730: 'CFAPattern', 41730: "CFAPattern",
41985: 'CustomRendered', 41985: "CustomRendered",
41986: 'ExposureMode', 41986: "ExposureMode",
41987: 'WhiteBalance', 41987: "WhiteBalance",
41988: 'DigitalZoomRatio', 41988: "DigitalZoomRatio",
41989: 'FocalLengthIn35mmFilm', 41989: "FocalLengthIn35mmFilm",
41990: 'SceneCaptureType', 41990: "SceneCaptureType",
41991: 'GainControl', 41991: "GainControl",
41992: 'Contrast', 41992: "Contrast",
41993: 'Saturation', 41993: "Saturation",
41994: 'Sharpness', 41994: "Sharpness",
41995: 'DeviceSettingDescription', 41995: "DeviceSettingDescription",
41996: 'SubjectDistanceRange', 41996: "SubjectDistanceRange",
42016: 'ImageUniqueID', 42016: "ImageUniqueID",
42032: 'CameraOwnerName', 42032: "CameraOwnerName",
42033: 'BodySerialNumber', 42033: "BodySerialNumber",
42034: 'LensSpecification', 42034: "LensSpecification",
42035: 'LensMake', 42035: "LensMake",
42036: 'LensModel', 42036: "LensModel",
42037: 'LensSerialNumber', 42037: "LensSerialNumber",
42112: 'GDAL_METADATA', 42112: "GDAL_METADATA",
42113: 'GDAL_NODATA', 42113: "GDAL_NODATA",
42240: 'Gamma', 42240: "Gamma",
50215: 'Oce Scanjob Description', 50215: "Oce Scanjob Description",
50216: 'Oce Application Selector', 50216: "Oce Application Selector",
50217: 'Oce Identification Number', 50217: "Oce Identification Number",
50218: 'Oce ImageLogic Characteristics', 50218: "Oce ImageLogic Characteristics",
# Adobe DNG
# Adobe DNG 50706: "DNGVersion",
50706: 'DNGVersion', 50707: "DNGBackwardVersion",
50707: 'DNGBackwardVersion', 50708: "UniqueCameraModel",
50708: 'UniqueCameraModel', 50709: "LocalizedCameraModel",
50709: 'LocalizedCameraModel', 50710: "CFAPlaneColor",
50710: 'CFAPlaneColor', 50711: "CFALayout",
50711: 'CFALayout', 50712: "LinearizationTable",
50712: 'LinearizationTable', 50713: "BlackLevelRepeatDim",
50713: 'BlackLevelRepeatDim', 50714: "BlackLevel",
50714: 'BlackLevel', 50715: "BlackLevelDeltaH",
50715: 'BlackLevelDeltaH', 50716: "BlackLevelDeltaV",
50716: 'BlackLevelDeltaV', 50717: "WhiteLevel",
50717: 'WhiteLevel', 50718: "DefaultScale",
50718: 'DefaultScale', 50719: "DefaultCropOrigin",
50719: 'DefaultCropOrigin', 50720: "DefaultCropSize",
50720: 'DefaultCropSize', 50721: "ColorMatrix1",
50721: 'ColorMatrix1', 50722: "ColorMatrix2",
50722: 'ColorMatrix2', 50723: "CameraCalibration1",
50723: 'CameraCalibration1', 50724: "CameraCalibration2",
50724: 'CameraCalibration2', 50725: "ReductionMatrix1",
50725: 'ReductionMatrix1', 50726: "ReductionMatrix2",
50726: 'ReductionMatrix2', 50727: "AnalogBalance",
50727: 'AnalogBalance', 50728: "AsShotNeutral",
50728: 'AsShotNeutral', 50729: "AsShotWhiteXY",
50729: 'AsShotWhiteXY', 50730: "BaselineExposure",
50730: 'BaselineExposure', 50731: "BaselineNoise",
50731: 'BaselineNoise', 50732: "BaselineSharpness",
50732: 'BaselineSharpness', 50733: "BayerGreenSplit",
50733: 'BayerGreenSplit', 50734: "LinearResponseLimit",
50734: 'LinearResponseLimit', 50735: "CameraSerialNumber",
50735: 'CameraSerialNumber', 50736: "LensInfo",
50736: 'LensInfo', 50737: "ChromaBlurRadius",
50737: 'ChromaBlurRadius', 50738: "AntiAliasStrength",
50738: 'AntiAliasStrength', 50740: "DNGPrivateData",
50740: 'DNGPrivateData', 50778: "CalibrationIlluminant1",
50778: 'CalibrationIlluminant1', 50779: "CalibrationIlluminant2",
50779: 'CalibrationIlluminant2', 50784: "Alias Layer Metadata",
50784: 'Alias Layer Metadata' }
}
def _populate(): def _populate():
@ -433,13 +435,48 @@ TYPES = {}
# some of these are not in our TAGS_V2 dict and were included from tiff.h # some of these are not in our TAGS_V2 dict and were included from tiff.h
# This list also exists in encode.c # This list also exists in encode.c
LIBTIFF_CORE = {255, 256, 257, 258, 259, 262, 263, 266, 274, 277, LIBTIFF_CORE = {
278, 280, 281, 340, 341, 282, 283, 284, 286, 287, 255,
296, 297, 321, 320, 338, 32995, 322, 323, 32998, 256,
32996, 339, 32997, 330, 531, 530, 301, 532, 333, 257,
# as above 258,
269 # this has been in our tests forever, and works 259,
} 262,
263,
266,
274,
277,
278,
280,
281,
340,
341,
282,
283,
284,
286,
287,
296,
297,
321,
320,
338,
32995,
322,
323,
32998,
32996,
339,
32997,
330,
531,
530,
301,
532,
333,
# as above
269, # this has been in our tests forever, and works
}
LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(320) # Array of short, crashes
LIBTIFF_CORE.remove(301) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes

View File

@ -28,6 +28,7 @@ try:
import builtins import builtins
except ImportError: except ImportError:
import __builtin__ import __builtin__
builtins = __builtin__ builtins = __builtin__
@ -46,7 +47,7 @@ def open(filename):
def imopen(fp): def imopen(fp):
# read header fields # read header fields
header = fp.read(32+24+32+12) header = fp.read(32 + 24 + 32 + 12)
size = i32(header, 32), i32(header, 36) size = i32(header, 32), i32(header, 36)
offset = i32(header, 40) offset = i32(header, 40)
@ -62,7 +63,7 @@ def open(filename):
# strings are null-terminated # strings are null-terminated
im.info["name"] = header[:32].split(b"\0", 1)[0] im.info["name"] = header[:32].split(b"\0", 1)[0]
next_name = header[56:56+32].split(b"\0", 1)[0] next_name = header[56 : 56 + 32].split(b"\0", 1)[0]
if next_name: if next_name:
im.info["next_name"] = next_name im.info["next_name"] = next_name

View File

@ -1,28 +1,23 @@
from . import Image, ImageFile from . import Image, ImageFile
try: try:
from . import _webp from . import _webp
SUPPORTED = True SUPPORTED = True
except ImportError: except ImportError:
SUPPORTED = False SUPPORTED = False
from io import BytesIO from io import BytesIO
_VALID_WEBP_MODES = { _VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True}
"RGBX": True,
"RGBA": True,
"RGB": True,
}
_VALID_WEBP_LEGACY_MODES = { _VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True}
"RGB": True,
"RGBA": True,
}
_VP8_MODES_BY_IDENTIFIER = { _VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB", b"VP8 ": "RGB",
b"VP8X": "RGBA", b"VP8X": "RGBA",
b"VP8L": "RGBA", # lossless b"VP8L": "RGBA", # lossless
} }
def _accept(prefix): def _accept(prefix):
@ -32,8 +27,9 @@ def _accept(prefix):
if is_riff_file_format and is_webp_file and is_valid_vp8_mode: if is_riff_file_format and is_webp_file and is_valid_vp8_mode:
if not SUPPORTED: if not SUPPORTED:
return "image file could not be identified " \ return (
"because WEBP support not installed" "image file could not be identified because WEBP support not installed"
)
return True return True
@ -45,8 +41,9 @@ class WebPImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
if not _webp.HAVE_WEBPANIM: if not _webp.HAVE_WEBPANIM:
# Legacy mode # Legacy mode
data, width, height, self.mode, icc_profile, exif = \ data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(
_webp.WebPDecode(self.fp.read()) self.fp.read()
)
if icc_profile: if icc_profile:
self.info["icc_profile"] = icc_profile self.info["icc_profile"] = icc_profile
if exif: if exif:
@ -62,18 +59,18 @@ class WebPImageFile(ImageFile.ImageFile):
self._decoder = _webp.WebPAnimDecoder(self.fp.read()) self._decoder = _webp.WebPAnimDecoder(self.fp.read())
# Get info from decoder # Get info from decoder
width, height, loop_count, bgcolor, frame_count, mode = \ width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
self._decoder.get_info()
self._size = width, height self._size = width, height
self.info["loop"] = loop_count self.info["loop"] = loop_count
bg_a, bg_r, bg_g, bg_b = \ bg_a, bg_r, bg_g, bg_b = (
(bgcolor >> 24) & 0xFF, \ (bgcolor >> 24) & 0xFF,
(bgcolor >> 16) & 0xFF, \ (bgcolor >> 16) & 0xFF,
(bgcolor >> 8) & 0xFF, \ (bgcolor >> 8) & 0xFF,
bgcolor & 0xFF bgcolor & 0xFF,
)
self.info["background"] = (bg_r, bg_g, bg_b, bg_a) self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
self._n_frames = frame_count self._n_frames = frame_count
self.mode = 'RGB' if mode == 'RGBX' else mode self.mode = "RGB" if mode == "RGBX" else mode
self.rawmode = mode self.rawmode = mode
self.tile = [] self.tile = []
@ -132,7 +129,7 @@ class WebPImageFile(ImageFile.ImageFile):
# Check if an error occurred # Check if an error occurred
if ret is None: if ret is None:
self._reset() # Reset just to be safe self._reset() # Reset just to be safe
self.seek(0) self.seek(0)
raise EOFError("failed to decode next frame in WebP file") raise EOFError("failed to decode next frame in WebP file")
@ -147,11 +144,11 @@ class WebPImageFile(ImageFile.ImageFile):
def _seek(self, frame): def _seek(self, frame):
if self.__physical_frame == frame: if self.__physical_frame == frame:
return # Nothing to do return # Nothing to do
if frame < self.__physical_frame: if frame < self.__physical_frame:
self._reset() # Rewind to beginning self._reset() # Rewind to beginning
while self.__physical_frame < frame: while self.__physical_frame < frame:
self._get_next() # Advance to the requested frame self._get_next() # Advance to the requested frame
def load(self): def load(self):
if _webp.HAVE_WEBPANIM: if _webp.HAVE_WEBPANIM:
@ -186,7 +183,7 @@ def _save_all(im, fp, filename):
# If total frame count is 1, then save using the legacy API, which # If total frame count is 1, then save using the legacy API, which
# will preserve non-alpha modes # will preserve non-alpha modes
total = 0 total = 0
for ims in [im]+append_images: for ims in [im] + append_images:
total += getattr(ims, "n_frames", 1) total += getattr(ims, "n_frames", 1)
if total == 1: if total == 1:
_save(im, fp, filename) _save(im, fp, filename)
@ -202,7 +199,7 @@ def _save_all(im, fp, filename):
# info["background"]. So it must be converted to an RGBA value # info["background"]. So it must be converted to an RGBA value
palette = im.getpalette() palette = im.getpalette()
if palette: if palette:
r, g, b = palette[background*3:(background+1)*3] r, g, b = palette[background * 3 : (background + 1) * 3]
background = (r, g, b, 0) background = (r, g, b, 0)
duration = im.encoderinfo.get("duration", 0) duration = im.encoderinfo.get("duration", 0)
@ -230,10 +227,15 @@ def _save_all(im, fp, filename):
kmax = 17 if lossless else 5 kmax = 17 if lossless else 5
# Validate background color # Validate background color
if (not isinstance(background, (list, tuple)) or len(background) != 4 or if (
not all(v >= 0 and v < 256 for v in background)): not isinstance(background, (list, tuple))
raise IOError("Background color is not an RGBA tuple clamped " or len(background) != 4
"to (0-255): %s" % str(background)) or not all(v >= 0 and v < 256 for v in background)
):
raise IOError(
"Background color is not an RGBA tuple clamped to (0-255): %s"
% str(background)
)
# Convert to packed uint # Convert to packed uint
bg_r, bg_g, bg_b, bg_a = background bg_r, bg_g, bg_b, bg_a = background
@ -241,13 +243,15 @@ def _save_all(im, fp, filename):
# Setup the WebP animation encoder # Setup the WebP animation encoder
enc = _webp.WebPAnimEncoder( enc = _webp.WebPAnimEncoder(
im.size[0], im.size[1], im.size[0],
im.size[1],
background, background,
loop, loop,
minimize_size, minimize_size,
kmin, kmax, kmin,
kmax,
allow_mixed, allow_mixed,
verbose verbose,
) )
# Add each frame # Add each frame
@ -255,7 +259,7 @@ def _save_all(im, fp, filename):
timestamp = 0 timestamp = 0
cur_idx = im.tell() cur_idx = im.tell()
try: try:
for ims in [im]+append_images: for ims in [im] + append_images:
# Get # of frames in this image # Get # of frames in this image
nfr = getattr(ims, "n_frames", 1) nfr = getattr(ims, "n_frames", 1)
@ -267,25 +271,28 @@ def _save_all(im, fp, filename):
frame = ims frame = ims
rawmode = ims.mode rawmode = ims.mode
if ims.mode not in _VALID_WEBP_MODES: if ims.mode not in _VALID_WEBP_MODES:
alpha = 'A' in ims.mode or 'a' in ims.mode \ alpha = (
or (ims.mode == 'P' and "A" in ims.mode
'A' in ims.im.getpalettemode()) or "a" in ims.mode
rawmode = 'RGBA' if alpha else 'RGB' or (ims.mode == "P" and "A" in ims.im.getpalettemode())
)
rawmode = "RGBA" if alpha else "RGB"
frame = ims.convert(rawmode) frame = ims.convert(rawmode)
if rawmode == 'RGB': if rawmode == "RGB":
# For faster conversion, use RGBX # For faster conversion, use RGBX
rawmode = 'RGBX' rawmode = "RGBX"
# Append the frame to the animation encoder # Append the frame to the animation encoder
enc.add( enc.add(
frame.tobytes('raw', rawmode), frame.tobytes("raw", rawmode),
timestamp, timestamp,
frame.size[0], frame.size[1], frame.size[0],
frame.size[1],
rawmode, rawmode,
lossless, lossless,
quality, quality,
method method,
) )
# Update timestamp and frame index # Update timestamp and frame index
@ -299,11 +306,7 @@ def _save_all(im, fp, filename):
im.seek(cur_idx) im.seek(cur_idx)
# Force encoder to flush frames # Force encoder to flush frames
enc.add( enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)
None,
timestamp,
0, 0, "", lossless, quality, 0
)
# Get the final output from the encoder # Get the final output from the encoder
data = enc.assemble(icc_profile, exif, xmp) data = enc.assemble(icc_profile, exif, xmp)
@ -323,9 +326,12 @@ def _save(im, fp, filename):
xmp = im.encoderinfo.get("xmp", "") xmp = im.encoderinfo.get("xmp", "")
if im.mode not in _VALID_WEBP_LEGACY_MODES: if im.mode not in _VALID_WEBP_LEGACY_MODES:
alpha = 'A' in im.mode or 'a' in im.mode \ alpha = (
or (im.mode == 'P' and 'A' in im.im.getpalettemode()) "A" in im.mode
im = im.convert('RGBA' if alpha else 'RGB') or "a" in im.mode
or (im.mode == "P" and "A" in im.im.getpalettemode())
)
im = im.convert("RGBA" if alpha else "RGB")
data = _webp.WebPEncode( data = _webp.WebPEncode(
im.tobytes(), im.tobytes(),
@ -336,7 +342,7 @@ def _save(im, fp, filename):
im.mode, im.mode,
icc_profile, icc_profile,
exif, exif,
xmp xmp,
) )
if data is None: if data is None:
raise IOError("cannot write file as WebP (encoder returned None)") raise IOError("cannot write file as WebP (encoder returned None)")

View File

@ -22,8 +22,7 @@
from __future__ import print_function from __future__ import print_function
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i16le as word, si16le as short, \ from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long
i32le as dword, si32le as _long
from ._util import py3 from ._util import py3
@ -51,7 +50,6 @@ if hasattr(Image.core, "drawwmf"):
# install default handler (windows only) # install default handler (windows only)
class WmfHandler(object): class WmfHandler(object):
def open(self, im): def open(self, im):
im.mode = "RGB" im.mode = "RGB"
self.bbox = im.info["wmf_bbox"] self.bbox = im.info["wmf_bbox"]
@ -59,10 +57,14 @@ if hasattr(Image.core, "drawwmf"):
def load(self, im): def load(self, im):
im.fp.seek(0) # rewind im.fp.seek(0) # rewind
return Image.frombytes( return Image.frombytes(
"RGB", im.size, "RGB",
im.size,
Image.core.drawwmf(im.fp.read(), im.size, self.bbox), Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
"raw", "BGR", (im.size[0]*3 + 3) & -4, -1 "raw",
) "BGR",
(im.size[0] * 3 + 3) & -4,
-1,
)
register_handler(WmfHandler()) register_handler(WmfHandler())
@ -73,14 +75,14 @@ if hasattr(Image.core, "drawwmf"):
def _accept(prefix): def _accept(prefix):
return ( return (
prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:4] == b"\x01\x00\x00\x00"
prefix[:4] == b"\x01\x00\x00\x00" )
)
## ##
# Image plugin for Windows metafiles. # Image plugin for Windows metafiles.
class WmfStubImageFile(ImageFile.StubImageFile): class WmfStubImageFile(ImageFile.StubImageFile):
format = "WMF" format = "WMF"
@ -160,6 +162,7 @@ def _save(im, fp, filename):
raise IOError("WMF save handler not installed") raise IOError("WMF save handler not installed")
_handler.save(im, fp, filename) _handler.save(im, fp, filename)
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Registry stuff # Registry stuff

View File

@ -31,7 +31,9 @@ PALETTE = b""
for r in range(8): for r in range(8):
for g in range(8): for g in range(8):
for b in range(4): for b in range(4):
PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3)) PALETTE = PALETTE + (
o8((r * 255) // 7) + o8((g * 255) // 7) + o8((b * 255) // 3)
)
def _accept(prefix): def _accept(prefix):
@ -41,6 +43,7 @@ def _accept(prefix):
## ##
# Image plugin for XV thumbnail images. # Image plugin for XV thumbnail images.
class XVThumbImageFile(ImageFile.ImageFile): class XVThumbImageFile(ImageFile.ImageFile):
format = "XVThumb" format = "XVThumb"
@ -71,10 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", PALETTE) self.palette = ImagePalette.raw("RGB", PALETTE)
self.tile = [ self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))]
("raw", (0, 0)+self.size,
self.fp.tell(), (self.mode, 0, 1)
)]
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -45,6 +45,7 @@ def _accept(prefix):
## ##
# Image plugin for X11 bitmaps. # Image plugin for X11 bitmaps.
class XbmImageFile(ImageFile.ImageFile): class XbmImageFile(ImageFile.ImageFile):
format = "XBM" format = "XBM"
@ -60,14 +61,12 @@ class XbmImageFile(ImageFile.ImageFile):
ysize = int(m.group("height")) ysize = int(m.group("height"))
if m.group("hotspot"): if m.group("hotspot"):
self.info["hotspot"] = ( self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot")))
int(m.group("xhot")), int(m.group("yhot"))
)
self.mode = "1" self.mode = "1"
self._size = xsize, ysize self._size = xsize, ysize
self.tile = [("xbm", (0, 0)+self.size, m.end(), None)] self.tile = [("xbm", (0, 0) + self.size, m.end(), None)]
def _save(im, fp, filename): def _save(im, fp, filename):
@ -75,17 +74,17 @@ def _save(im, fp, filename):
if im.mode != "1": if im.mode != "1":
raise IOError("cannot write mode %s as XBM" % im.mode) raise IOError("cannot write mode %s as XBM" % im.mode)
fp.write(("#define im_width %d\n" % im.size[0]).encode('ascii')) fp.write(("#define im_width %d\n" % im.size[0]).encode("ascii"))
fp.write(("#define im_height %d\n" % im.size[1]).encode('ascii')) fp.write(("#define im_height %d\n" % im.size[1]).encode("ascii"))
hotspot = im.encoderinfo.get("hotspot") hotspot = im.encoderinfo.get("hotspot")
if hotspot: if hotspot:
fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode('ascii')) fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode("ascii"))
fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode('ascii')) fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode("ascii"))
fp.write(b"static char im_bits[] = {\n") fp.write(b"static char im_bits[] = {\n")
ImageFile._save(im, fp, [("xbm", (0, 0)+im.size, 0, None)]) ImageFile._save(im, fp, [("xbm", (0, 0) + im.size, 0, None)])
fp.write(b"};\n") fp.write(b"};\n")

View File

@ -24,7 +24,7 @@ from ._binary import i8, o8
__version__ = "0.2" __version__ = "0.2"
# XPM header # XPM header
xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)") xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
def _accept(prefix): def _accept(prefix):
@ -34,6 +34,7 @@ def _accept(prefix):
## ##
# Image plugin for X11 pixel maps. # Image plugin for X11 pixel maps.
class XpmImageFile(ImageFile.ImageFile): class XpmImageFile(ImageFile.ImageFile):
format = "XPM" format = "XPM"
@ -69,9 +70,9 @@ class XpmImageFile(ImageFile.ImageFile):
for i in range(pal): for i in range(pal):
s = self.fp.readline() s = self.fp.readline()
if s[-2:] == b'\r\n': if s[-2:] == b"\r\n":
s = s[:-2] s = s[:-2]
elif s[-1:] in b'\r\n': elif s[-1:] in b"\r\n":
s = s[:-1] s = s[:-1]
c = i8(s[1]) c = i8(s[1])
@ -82,15 +83,15 @@ class XpmImageFile(ImageFile.ImageFile):
if s[i] == b"c": if s[i] == b"c":
# process colour key # process colour key
rgb = s[i+1] rgb = s[i + 1]
if rgb == b"None": if rgb == b"None":
self.info["transparency"] = c self.info["transparency"] = c
elif rgb[0:1] == b"#": elif rgb[0:1] == b"#":
# FIXME: handle colour names (see ImagePalette.py) # FIXME: handle colour names (see ImagePalette.py)
rgb = int(rgb[1:], 16) rgb = int(rgb[1:], 16)
palette[c] = (o8((rgb >> 16) & 255) + palette[c] = (
o8((rgb >> 8) & 255) + o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255)
o8(rgb & 255)) )
else: else:
# unknown colour # unknown colour
raise ValueError("cannot read this XPM file") raise ValueError("cannot read this XPM file")
@ -104,7 +105,7 @@ class XpmImageFile(ImageFile.ImageFile):
self.mode = "P" self.mode = "P"
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))
self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), ("P", 0, 1))] self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))]
def load_read(self, bytes): def load_read(self, bytes):
@ -116,10 +117,11 @@ class XpmImageFile(ImageFile.ImageFile):
s = [None] * ysize s = [None] * ysize
for i in range(ysize): for i in range(ysize):
s[i] = self.fp.readline()[1:xsize+1].ljust(xsize) s[i] = self.fp.readline()[1 : xsize + 1].ljust(xsize)
return b"".join(s) return b"".join(s)
# #
# Registry # Registry

View File

@ -24,48 +24,50 @@ PILLOW_VERSION = __version__ = _version.__version__
del _version del _version
_plugins = ['BlpImagePlugin', _plugins = [
'BmpImagePlugin', "BlpImagePlugin",
'BufrStubImagePlugin', "BmpImagePlugin",
'CurImagePlugin', "BufrStubImagePlugin",
'DcxImagePlugin', "CurImagePlugin",
'DdsImagePlugin', "DcxImagePlugin",
'EpsImagePlugin', "DdsImagePlugin",
'FitsStubImagePlugin', "EpsImagePlugin",
'FliImagePlugin', "FitsStubImagePlugin",
'FpxImagePlugin', "FliImagePlugin",
'FtexImagePlugin', "FpxImagePlugin",
'GbrImagePlugin', "FtexImagePlugin",
'GifImagePlugin', "GbrImagePlugin",
'GribStubImagePlugin', "GifImagePlugin",
'Hdf5StubImagePlugin', "GribStubImagePlugin",
'IcnsImagePlugin', "Hdf5StubImagePlugin",
'IcoImagePlugin', "IcnsImagePlugin",
'ImImagePlugin', "IcoImagePlugin",
'ImtImagePlugin', "ImImagePlugin",
'IptcImagePlugin', "ImtImagePlugin",
'JpegImagePlugin', "IptcImagePlugin",
'Jpeg2KImagePlugin', "JpegImagePlugin",
'McIdasImagePlugin', "Jpeg2KImagePlugin",
'MicImagePlugin', "McIdasImagePlugin",
'MpegImagePlugin', "MicImagePlugin",
'MpoImagePlugin', "MpegImagePlugin",
'MspImagePlugin', "MpoImagePlugin",
'PalmImagePlugin', "MspImagePlugin",
'PcdImagePlugin', "PalmImagePlugin",
'PcxImagePlugin', "PcdImagePlugin",
'PdfImagePlugin', "PcxImagePlugin",
'PixarImagePlugin', "PdfImagePlugin",
'PngImagePlugin', "PixarImagePlugin",
'PpmImagePlugin', "PngImagePlugin",
'PsdImagePlugin', "PpmImagePlugin",
'SgiImagePlugin', "PsdImagePlugin",
'SpiderImagePlugin', "SgiImagePlugin",
'SunImagePlugin', "SpiderImagePlugin",
'TgaImagePlugin', "SunImagePlugin",
'TiffImagePlugin', "TgaImagePlugin",
'WebPImagePlugin', "TiffImagePlugin",
'WmfImagePlugin', "WebPImagePlugin",
'XbmImagePlugin', "WmfImagePlugin",
'XpmImagePlugin', "XbmImagePlugin",
'XVThumbImagePlugin'] "XpmImagePlugin",
"XVThumbImagePlugin",
]

View File

@ -15,12 +15,16 @@ from struct import unpack_from, pack
from ._util import py3 from ._util import py3
if py3: if py3:
def i8(c): def i8(c):
return c if c.__class__ is int else c[0] return c if c.__class__ is int else c[0]
def o8(i): def o8(i):
return bytes((i & 255,)) return bytes((i & 255,))
else: else:
def i8(c): def i8(c):
return ord(c) return ord(c)

View File

@ -7,7 +7,7 @@ if sys.version_info.major > 2:
else: else:
from Tkinter import tkinter as tk from Tkinter import tkinter as tk
if hasattr(sys, 'pypy_find_executable'): if hasattr(sys, "pypy_find_executable"):
# Tested with packages at https://bitbucket.org/pypy/pypy/downloads. # Tested with packages at https://bitbucket.org/pypy/pypy/downloads.
# PyPies 1.6, 2.0 do not have tkinter built in. PyPy3-2.3.1 gives an # PyPies 1.6, 2.0 do not have tkinter built in. PyPy3-2.3.1 gives an
# OSError trying to import tkinter. Otherwise: # OSError trying to import tkinter. Otherwise:

View File

@ -5,6 +5,7 @@ py3 = sys.version_info.major >= 3
py36 = sys.version_info[0:2] >= (3, 6) py36 = sys.version_info[0:2] >= (3, 6)
if py3: if py3:
def isStringType(t): def isStringType(t):
return isinstance(t, str) return isinstance(t, str)
@ -13,10 +14,15 @@ if py3:
def isPath(f): def isPath(f):
return isinstance(f, (bytes, str, Path)) return isinstance(f, (bytes, str, Path))
else: else:
def isPath(f): def isPath(f):
return isinstance(f, (bytes, str)) return isinstance(f, (bytes, str))
else: else:
def isStringType(t): def isStringType(t):
return isinstance(t, basestring) # noqa: F821 return isinstance(t, basestring) # noqa: F821

View File

@ -1,2 +1,2 @@
# Master version for Pillow # Master version for Pillow
__version__ = '6.1.0.dev0' __version__ = "6.1.0.dev0"

View File

@ -26,12 +26,7 @@ def get_supported_modules():
return [f for f in modules if check_module(f)] return [f for f in modules if check_module(f)]
codecs = { codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtiff"}
"jpg": "jpeg",
"jpg_2000": "jpeg2k",
"zlib": "zip",
"libtiff": "libtiff"
}
def check_codec(feature): def check_codec(feature):
@ -48,8 +43,8 @@ def get_supported_codecs():
features = { features = {
"webp_anim": ("PIL._webp", 'HAVE_WEBPANIM'), "webp_anim": ("PIL._webp", "HAVE_WEBPANIM"),
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'), "webp_mux": ("PIL._webp", "HAVE_WEBPMUX"),
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"), "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
"raqm": ("PIL._imagingft", "HAVE_RAQM"), "raqm": ("PIL._imagingft", "HAVE_RAQM"),
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"),
@ -63,7 +58,7 @@ def check_feature(feature):
module, flag = features[feature] module, flag = features[feature]
try: try:
imported_module = __import__(module, fromlist=['PIL']) imported_module = __import__(module, fromlist=["PIL"])
return getattr(imported_module, flag) return getattr(imported_module, flag)
except ImportError: except ImportError:
return None return None
@ -74,9 +69,14 @@ def get_supported_features():
def check(feature): def check(feature):
return (feature in modules and check_module(feature) or return (
feature in codecs and check_codec(feature) or feature in modules
feature in features and check_feature(feature)) and check_module(feature)
or feature in codecs
and check_codec(feature)
or feature in features
and check_feature(feature)
)
def get_supported(): def get_supported():

View File

@ -24,9 +24,11 @@ deps =
[testenv:lint] [testenv:lint]
commands = commands =
black --check --diff src
flake8 --statistics --count flake8 --statistics --count
check-manifest check-manifest
deps = deps =
black
check-manifest check-manifest
flake8 flake8
skip_install = true skip_install = true