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 .
alabaster
Babel
black; python_version >= '3.6'
check-manifest
cov-core
coverage

View File

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

View File

@ -32,14 +32,10 @@ bdf_slant = {
"O": "Oblique",
"RI": "Reverse Italic",
"RO": "Reverse Oblique",
"OT": "Other"
"OT": "Other",
}
bdf_spacing = {
"P": "Proportional",
"M": "Monospaced",
"C": "Cell"
}
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
def bdf_char(f):
@ -50,7 +46,7 @@ def bdf_char(f):
return None
if s[:9] == b"STARTCHAR":
break
id = s[9:].strip().decode('ascii')
id = s[9:].strip().decode("ascii")
# load symbol properties
props = {}
@ -59,7 +55,7 @@ def bdf_char(f):
if not s or s[:6] == b"BITMAP":
break
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
bitmap = []
@ -73,7 +69,7 @@ def bdf_char(f):
[x, y, l, d] = [int(p) for p in props["BBX"].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:
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.
class BdfFontFile(FontFile.FontFile):
class BdfFontFile(FontFile.FontFile):
def __init__(self, fp):
FontFile.FontFile.__init__(self)
@ -105,10 +101,10 @@ class BdfFontFile(FontFile.FontFile):
if not s or s[:13] == b"ENDPROPERTIES":
break
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.find(b"LogicalFontDescription") < 0:
comments.append(s[i+1:-1].decode('ascii'))
comments.append(s[i + 1 : -1].decode("ascii"))
while True:
c = bdf_char(fp)

View File

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

View File

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

View File

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

View File

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

View File

@ -36,6 +36,7 @@ def _accept(prefix):
##
# Image plugin for Windows Cursor files.
class CurImageFile(BmpImagePlugin.BmpImageFile):
format = "CUR"
@ -65,9 +66,9 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
self._bitmap(i32(m[12:]) + offset)
# 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]
self.tile[0] = d, (0, 0)+self.size, o, a
self.tile[0] = d, (0, 0) + self.size, o, a
return

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,7 @@ def puti16(fp, values):
##
# Base class for raster font file handlers.
class FontFile(object):
bitmap = None
@ -61,7 +62,7 @@ class FontFile(object):
w = w + (src[2] - src[0])
if w > WIDTH:
lines += 1
w = (src[2] - src[0])
w = src[2] - src[0]
maxwidth = max(maxwidth, w)
xsize = maxwidth
@ -103,7 +104,7 @@ class FontFile(object):
# font metrics
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
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")
for id in range(256):
m = self.metrics[id]

View File

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

View File

@ -87,10 +87,9 @@ class FtexImageFile(ImageFile.ImageFile):
self.mode = "RGBA"
self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
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:
raise ValueError(
"Invalid texture compression format: %r" % (format))
raise ValueError("Invalid texture compression format: %r" % (format))
self.fp.close()
self.fp = BytesIO(data)

View File

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

View File

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

View File

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

View File

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

View File

@ -21,13 +21,14 @@ from ._binary import o8
##
# File handler for GIMP's palette format.
class GimpPaletteFile(object):
rawmode = "RGB"
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":
raise SyntaxError("not a GIMP palette file")

View File

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

View File

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

View File

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

View File

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

View File

@ -48,8 +48,17 @@ SCALE = "Scale (x,y)"
SIZE = "Image size (x*y)"
MODE = "Image type"
TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
SCALE: 0, SIZE: 0, MODE: 0}
TAGS = {
COMMENT: 0,
DATE: 0,
EQUIPMENT: 0,
FRAMES: 0,
LUT: 0,
NAME: 0,
SCALE: 0,
SIZE: 0,
MODE: 0,
}
OPEN = {
# ifunc93/p3cfunc formats
@ -108,6 +117,7 @@ def number(s):
##
# Image plugin for the IFUNC IM file format.
class ImImageFile(ImageFile.ImageFile):
format = "IM"
@ -140,7 +150,7 @@ class ImImageFile(ImageFile.ImageFile):
if s == b"\r":
continue
if not s or s == b'\0' or s == b'\x1A':
if not s or s == b"\0" or s == b"\x1A":
break
# FIXME: this may read whole file if not a text file
@ -149,9 +159,9 @@ class ImImageFile(ImageFile.ImageFile):
if len(s) > 100:
raise SyntaxError("not an IM file")
if s[-2:] == b'\r\n':
if s[-2:] == b"\r\n":
s = s[:-2]
elif s[-1:] == b'\n':
elif s[-1:] == b"\n":
s = s[:-1]
try:
@ -165,8 +175,8 @@ class ImImageFile(ImageFile.ImageFile):
# Don't know if this is the correct encoding,
# but a decent guess (I guess)
k = k.decode('latin-1', 'replace')
v = v.decode('latin-1', 'replace')
k = k.decode("latin-1", "replace")
v = v.decode("latin-1", "replace")
# Convert value as appropriate
if k in [FRAMES, SCALE, SIZE]:
@ -192,8 +202,9 @@ class ImImageFile(ImageFile.ImageFile):
else:
raise SyntaxError("Syntax error in IM header: " +
s.decode('ascii', 'replace'))
raise SyntaxError(
"Syntax error in IM header: " + s.decode("ascii", "replace")
)
if not n:
raise SyntaxError("Not an IM file")
@ -203,7 +214,7 @@ class ImImageFile(ImageFile.ImageFile):
self.mode = self.info[MODE]
# 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)
if not s:
raise SyntaxError("File truncated")
@ -214,7 +225,7 @@ class ImImageFile(ImageFile.ImageFile):
greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette
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:
linear = 0
else:
@ -247,8 +258,7 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary)
bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]:
self.tile = [("bit", (0, 0)+self.size, offs,
(bits, 8, 3, 0, -1))]
self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
return
except ValueError:
pass
@ -257,13 +267,14 @@ class ImImageFile(ImageFile.ImageFile):
# Old LabEye/3PC files. Would be very surprised if anyone
# ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1]
self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
self.tile = [
("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
]
else:
# LabEye/IFUNC files
self.tile = [("raw", (0, 0)+self.size, offs,
(self.rawmode, 0, -1))]
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
@property
def n_frames(self):
@ -289,7 +300,7 @@ class ImImageFile(ImageFile.ImageFile):
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):
return self.frame
@ -303,6 +314,7 @@ class ImImageFile(ImageFile.ImageFile):
finally:
self.__fp = None
#
# --------------------------------------------------------------------
# Save IM files
@ -324,7 +336,7 @@ SAVE = {
"RGBA": ("RGBA", "RGBA;L"),
"RGBX": ("RGBX", "RGBX;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)
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:
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii'))
fp.write(("Name: %s\r\n" % filename).encode("ascii"))
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii"))
fp.write(("File size (no of images): %d\r\n" % frames).encode("ascii"))
if im.mode in ["P", "PA"]:
fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511-fp.tell()) + b"\032")
fp.write(b"\000" * (511 - fp.tell()) + b"\032")
if im.mode in ["P", "PA"]:
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
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
from PIL import Image
try:
from PIL import _imagingcms
except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing
# anything in core.
from ._util import deferred_error
_imagingcms = deferred_error(ex)
from PIL._util import isStringType
@ -132,7 +134,7 @@ FLAGS = {
"SOFTPROOFING": 16384, # Do softproofing
"PRESERVEBLACK": 32768, # Black preservation
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16, # Gridpoints
}
_MAX_FLAG = 0
@ -148,8 +150,8 @@ for flag in FLAGS.values():
##
# Profile.
class ImageCmsProfile(object):
class ImageCmsProfile(object):
def __init__(self, profile):
"""
: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'].
"""
def __init__(self, input, output, input_mode, output_mode,
intent=INTENT_PERCEPTUAL, proof=None,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
def __init__(
self,
input,
output,
input_mode,
output_mode,
intent=INTENT_PERCEPTUAL,
proof=None,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC,
flags=0,
):
if proof is None:
self.transform = core.buildTransform(
input.profile, output.profile,
input_mode, output_mode,
intent,
flags
input.profile, output.profile, input_mode, output_mode, intent, flags
)
else:
self.transform = core.buildProofTransform(
input.profile, output.profile, proof.profile,
input_mode, output_mode,
intent, proof_intent,
flags
input.profile,
output.profile,
proof.profile,
input_mode,
output_mode,
intent,
proof_intent,
flags,
)
# Note: inputMode and outputMode are for pyCMS compatibility only
self.input_mode = self.inputMode = input_mode
@ -228,7 +239,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
if imOut is None:
imOut = Image.new(self.output_mode, im.size, None)
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
def apply_in_place(self, im):
@ -236,7 +247,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
if im.mode != self.output_mode:
raise ValueError("mode mismatch") # wrong output mode
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
@ -247,6 +258,7 @@ def get_display_profile(handle=None):
if sys.platform == "win32":
from PIL import ImageWin
if isinstance(handle, ImageWin.HDC):
profile = core.get_display_profile_win32(handle, 1)
else:
@ -265,16 +277,24 @@ def get_display_profile(handle=None):
# pyCMS compatible layer
# --------------------------------------------------------------------.
class PyCMSError(Exception):
""" (pyCMS) Exception class.
This is used for all errors in the pyCMS API. """
pass
def profileToProfile(
im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL,
outputMode=None, inPlace=False, flags=0):
im,
inputProfile,
outputProfile,
renderingIntent=INTENT_PERCEPTUAL,
outputMode=None,
inPlace=False,
flags=0,
):
"""
(pyCMS) Applies an ICC transformation to a given image, mapping from
inputProfile to outputProfile.
@ -333,8 +353,7 @@ def profileToProfile(
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError(
"flags must be an integer between 0 and %s" + _MAX_FLAG)
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
try:
if not isinstance(inputProfile, ImageCmsProfile):
@ -342,8 +361,12 @@ def profileToProfile(
if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile)
transform = ImageCmsTransform(
inputProfile, outputProfile, im.mode, outputMode,
renderingIntent, flags=flags
inputProfile,
outputProfile,
im.mode,
outputMode,
renderingIntent,
flags=flags,
)
if inPlace:
transform.apply_in_place(im)
@ -379,8 +402,13 @@ def getOpenProfile(profileFilename):
def buildTransform(
inputProfile, outputProfile, inMode, outMode,
renderingIntent=INTENT_PERCEPTUAL, flags=0):
inputProfile,
outputProfile,
inMode,
outMode,
renderingIntent=INTENT_PERCEPTUAL,
flags=0,
):
"""
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
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")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError(
"flags must be an integer between 0 and %s" + _MAX_FLAG)
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
try:
if not isinstance(inputProfile, ImageCmsProfile):
@ -449,17 +476,22 @@ def buildTransform(
if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile)
return ImageCmsTransform(
inputProfile, outputProfile, inMode, outMode,
renderingIntent, flags=flags)
inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
)
except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def buildProofTransform(
inputProfile, outputProfile, proofProfile, inMode, outMode,
inputProfile,
outputProfile,
proofProfile,
inMode,
outMode,
renderingIntent=INTENT_PERCEPTUAL,
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
flags=FLAGS["SOFTPROOFING"]):
flags=FLAGS["SOFTPROOFING"],
):
"""
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
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")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError(
"flags must be an integer between 0 and %s" + _MAX_FLAG)
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
try:
if not isinstance(inputProfile, ImageCmsProfile):
@ -549,8 +580,15 @@ def buildProofTransform(
if not isinstance(proofProfile, ImageCmsProfile):
proofProfile = ImageCmsProfile(proofProfile)
return ImageCmsTransform(
inputProfile, outputProfile, inMode, outMode, renderingIntent,
proofProfile, proofRenderingIntent, flags)
inputProfile,
outputProfile,
inMode,
outMode,
renderingIntent,
proofProfile,
proofRenderingIntent,
flags,
)
except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
@ -641,15 +679,16 @@ def createProfile(colorSpace, colorTemp=-1):
if colorSpace not in ["LAB", "XYZ", "sRGB"]:
raise PyCMSError(
"Color space not supported for on-the-fly profile creation (%s)"
% colorSpace)
% colorSpace
)
if colorSpace == "LAB":
try:
colorTemp = float(colorTemp)
except (TypeError, ValueError):
raise PyCMSError(
"Color temperature must be numeric, \"%s\" not valid"
% colorTemp)
'Color temperature must be numeric, "%s" not valid' % colorTemp
)
try:
return core.createProfile(colorSpace, colorTemp)
@ -948,7 +987,4 @@ def versions():
(pyCMS) Fetches versions.
"""
return (
VERSION, core.littlecms_version,
sys.version.split()[0], Image.__version__
)
return (VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__)

View File

@ -41,29 +41,21 @@ def getrgb(color):
return rgb
# check for known string formats
if re.match('#[a-f0-9]{3}$', color):
if re.match("#[a-f0-9]{3}$", color):
return (int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16))
if re.match("#[a-f0-9]{4}$", color):
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),
int(color[4] * 2, 16),
)
if re.match('#[a-f0-9]{4}$', color):
return (
int(color[1]*2, 16),
int(color[2]*2, 16),
int(color[3]*2, 16),
int(color[4]*2, 16),
)
if re.match("#[a-f0-9]{6}$", color):
return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16))
if re.match('#[a-f0-9]{6}$', color):
return (
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 (
int(color[1:3], 16),
int(color[3:5], 16),
@ -73,26 +65,22 @@ def getrgb(color):
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
if m:
return (
int(m.group(1)),
int(m.group(2)),
int(m.group(3))
)
return (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)
if m:
return (
int((int(m.group(1)) * 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(
r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$",
color,
r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
)
if m:
from colorsys import hls_to_rgb
rgb = hls_to_rgb(
float(m.group(1)) / 360.0,
float(m.group(3)) / 100.0,
@ -101,15 +89,15 @@ def getrgb(color):
return (
int(rgb[0] * 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"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$",
color,
r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
)
if m:
from colorsys import hsv_to_rgb
rgb = hsv_to_rgb(
float(m.group(1)) / 360.0,
float(m.group(2)) / 100.0,
@ -118,18 +106,12 @@ def getrgb(color):
return (
int(rgb[0] * 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*\)$",
color)
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
if m:
return (
int(m.group(1)),
int(m.group(2)),
int(m.group(3)),
int(m.group(4))
)
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)))
raise ValueError("unknown color specifier: %r" % color)
@ -151,11 +133,11 @@ def getcolor(color, mode):
if Image.getmodebase(mode) == "L":
r, g, b = color
color = (r*299 + g*587 + b*114)//1000
if mode[-1] == 'A':
color = (r * 299 + g * 587 + b * 114) // 1000
if mode[-1] == "A":
return (color, alpha)
else:
if mode[-1] == 'A':
if mode[-1] == "A":
return color + (alpha,)
return color

View File

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

View File

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

View File

@ -22,7 +22,6 @@ from . import Image, ImageFilter, ImageStat
class _Enhance(object):
def enhance(self, factor):
"""
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
the original image.
"""
def __init__(self, image):
self.image = image
self.intermediate_mode = 'L'
if 'A' in image.getbands():
self.intermediate_mode = 'LA'
self.intermediate_mode = "L"
if "A" in image.getbands():
self.intermediate_mode = "LA"
self.degenerate = image.convert(
self.intermediate_mode).convert(image.mode)
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
class Contrast(_Enhance):
@ -62,13 +61,14 @@ class Contrast(_Enhance):
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.
"""
def __init__(self, image):
self.image = image
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
if 'A' in image.getbands():
self.degenerate.putalpha(image.getchannel('A'))
if "A" in image.getbands():
self.degenerate.putalpha(image.getchannel("A"))
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
original image.
"""
def __init__(self, image):
self.image = image
self.degenerate = Image.new(image.mode, image.size, 0)
if 'A' in image.getbands():
self.degenerate.putalpha(image.getchannel('A'))
if "A" in image.getbands():
self.degenerate.putalpha(image.getchannel("A"))
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
original image, and a factor of 2.0 gives a sharpened image.
"""
def __init__(self, image):
self.image = image
self.degenerate = image.filter(ImageFilter.SMOOTH)
if 'A' in image.getbands():
self.degenerate.putalpha(image.getchannel('A'))
if "A" in image.getbands():
self.degenerate.putalpha(image.getchannel("A"))

View File

@ -35,7 +35,7 @@ import struct
MAXBLOCK = 65536
SAFEBLOCK = 1024*1024
SAFEBLOCK = 1024 * 1024
LOAD_TRUNCATED_IMAGES = False
@ -44,7 +44,7 @@ ERRORS = {
-2: "decoding error",
-3: "unknown error",
-8: "bad configuration",
-9: "out of memory error"
-9: "out of memory error",
}
@ -62,6 +62,7 @@ def raise_ioerror(error):
# --------------------------------------------------------------------
# Helpers
def _tilesort(t):
# sort on offset
return t[2]
@ -71,6 +72,7 @@ def _tilesort(t):
# --------------------------------------------------------------------
# ImageFile base class
class ImageFile(Image.Image):
"Base class for image file format handlers."
@ -101,11 +103,13 @@ class ImageFile(Image.Image):
try:
self._open()
except (IndexError, # end of data
except (
IndexError, # end of data
TypeError, # end of data (ord)
KeyError, # unsupported mode
EOFError, # got header but not the first frame
struct.error) as v:
struct.error,
) as v:
# close the file only if we have opened it this constructor
if self._exclusive_fp:
self.fp.close()
@ -147,7 +151,7 @@ class ImageFile(Image.Image):
self.map = None
use_mmap = self.filename and len(self.tile) == 1
# 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
@ -168,9 +172,12 @@ class ImageFile(Image.Image):
if use_mmap:
# try memory mapping
decoder_name, extents, offset, args = self.tile[0]
if decoder_name == "raw" and len(args) >= 3 and \
args[0] == self.mode and \
args[0] in Image._MAPMODES:
if (
decoder_name == "raw"
and len(args) >= 3
and args[0] == self.mode
and args[0] in Image._MAPMODES
):
try:
if hasattr(Image.core, "map"):
# use built-in mapper WIN32 only
@ -182,12 +189,14 @@ class ImageFile(Image.Image):
else:
# use mmap, if possible
import mmap
with open(self.filename, "r") as fp:
self.map = mmap.mmap(fp.fileno(), 0,
access=mmap.ACCESS_READ)
self.map = mmap.mmap(
fp.fileno(), 0, access=mmap.ACCESS_READ
)
self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, extents,
offset, args)
self.map, self.size, decoder_name, extents, offset, args
)
readonly = 1
# After trashing self.im,
# we might need to reload the palette data.
@ -209,8 +218,9 @@ class ImageFile(Image.Image):
prefix = b""
for decoder_name, extents, offset, args in self.tile:
decoder = Image._getdecoder(self.mode, decoder_name,
args, self.decoderconfig)
decoder = Image._getdecoder(
self.mode, decoder_name, args, self.decoderconfig
)
try:
seek(offset)
decoder.setimage(self.im, extents)
@ -234,9 +244,10 @@ class ImageFile(Image.Image):
break
else:
self.tile = []
raise IOError("image file is truncated "
"(%d bytes not processed)" %
len(b))
raise IOError(
"image file is truncated "
"(%d bytes not processed)" % len(b)
)
b = b + s
n, err_code = decoder.decode(b)
@ -264,8 +275,7 @@ class ImageFile(Image.Image):
def load_prepare(self):
# create image memory if necessary
if not self.im or\
self.im.mode != self.mode or self.im.size != self.size:
if not self.im or self.im.mode != self.mode or self.im.size != self.size:
self.im = Image.core.new(self.mode, self.size)
# create palette (optional)
if self.mode == "P":
@ -284,11 +294,15 @@ class ImageFile(Image.Image):
# pass
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
# are not required to do so
(not (hasattr(self, "_n_frames") and self._n_frames is None) and
frame >= self.n_frames+self._min_frame)):
or (
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")
return self.tell() != frame
@ -303,9 +317,7 @@ class StubImageFile(ImageFile):
"""
def _open(self):
raise NotImplementedError(
"StubImageFile subclass must implement _open"
)
raise NotImplementedError("StubImageFile subclass must implement _open")
def load(self):
loader = self._load()
@ -319,9 +331,7 @@ class StubImageFile(ImageFile):
def _load(self):
"""(Hook) Find actual image loader."""
raise NotImplementedError(
"StubImageFile subclass must implement _load"
)
raise NotImplementedError("StubImageFile subclass must implement _load")
class Parser(object):
@ -329,6 +339,7 @@ class Parser(object):
Incremental image parser. This class implements the standard
feed/close consumer interface.
"""
incremental = None
image = None
data = None
@ -413,15 +424,13 @@ class Parser(object):
im.load_prepare()
d, e, o, a = im.tile[0]
im.tile = []
self.decoder = Image._getdecoder(
im.mode, d, a, im.decoderconfig
)
self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig)
self.decoder.setimage(im.im, e)
# calculate decoder offset
self.offset = o
if self.offset <= len(self.data):
self.data = self.data[self.offset:]
self.data = self.data[self.offset :]
self.offset = 0
self.image = im
@ -463,6 +472,7 @@ class Parser(object):
# --------------------------------------------------------------------
def _save(im, fp, tile, bufsize=0):
"""Helper to save image based on tile list
@ -557,8 +567,7 @@ class PyCodecState(object):
self.yoff = 0
def extents(self):
return (self.xoff, self.yoff,
self.xoff+self.xsize, self.yoff+self.ysize)
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
class PyDecoder(object):
@ -648,8 +657,10 @@ class PyDecoder(object):
if self.state.xsize <= 0 or self.state.ysize <= 0:
raise ValueError("Size cannot be negative")
if (self.state.xsize + self.state.xoff > self.im.size[0] or
self.state.ysize + self.state.yoff > self.im.size[1]):
if (
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")
def set_as_raw(self, data, rawmode=None):
@ -664,7 +675,7 @@ class PyDecoder(object):
if not rawmode:
rawmode = self.mode
d = Image._getdecoder(self.mode, 'raw', (rawmode))
d = Image._getdecoder(self.mode, "raw", (rawmode))
d.setimage(self.im, self.state.extents())
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,
after it has been divided by the scale factor.
"""
name = "Kernel"
def __init__(self, size, kernel, scale=None, offset=0):
if scale is None:
# 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):
raise ValueError("not enough coefficients in kernel")
self.filterargs = size, scale, offset, kernel
@ -78,6 +79,7 @@ class RankFilter(Filter):
``size * size / 2`` for a median filter, ``size * size - 1``
for a max filter, etc.
"""
name = "Rank"
def __init__(self, size, rank):
@ -87,7 +89,7 @@ class RankFilter(Filter):
def filter(self, image):
if image.mode == "P":
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)
@ -98,11 +100,12 @@ class MedianFilter(RankFilter):
:param size: The kernel size, in pixels.
"""
name = "Median"
def __init__(self, size=3):
self.size = size
self.rank = size*size//2
self.rank = size * size // 2
class MinFilter(RankFilter):
@ -112,6 +115,7 @@ class MinFilter(RankFilter):
:param size: The kernel size, in pixels.
"""
name = "Min"
def __init__(self, size=3):
@ -126,11 +130,12 @@ class MaxFilter(RankFilter):
:param size: The kernel size, in pixels.
"""
name = "Max"
def __init__(self, size=3):
self.size = size
self.rank = size*size-1
self.rank = size * size - 1
class ModeFilter(Filter):
@ -141,6 +146,7 @@ class ModeFilter(Filter):
:param size: The kernel size, in pixels.
"""
name = "Mode"
def __init__(self, size=3):
@ -155,6 +161,7 @@ class GaussianBlur(MultibandFilter):
:param radius: Blur radius.
"""
name = "GaussianBlur"
def __init__(self, radius=2):
@ -175,6 +182,7 @@ class BoxBlur(MultibandFilter):
returns an identical image. Radius 1 takes 1 pixel
in each direction, i.e. 9 pixels in total.
"""
name = "BoxBlur"
def __init__(self, radius):
@ -198,6 +206,7 @@ class UnsharpMask(MultibandFilter):
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
""" # noqa: E501
name = "UnsharpMask"
def __init__(self, radius=2, percent=150, threshold=3):
@ -211,96 +220,116 @@ class UnsharpMask(MultibandFilter):
class BLUR(BuiltinFilter):
name = "Blur"
# fmt: off
filterargs = (5, 5), 16, 0, (
1, 1, 1, 1, 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):
name = "Contour"
# fmt: off
filterargs = (3, 3), 1, 255, (
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
-1, -1, -1,
)
# fmt: on
class DETAIL(BuiltinFilter):
name = "Detail"
# fmt: off
filterargs = (3, 3), 6, 0, (
0, -1, 0,
-1, 10, -1,
0, -1, 0
0, -1, 0,
)
# fmt: on
class EDGE_ENHANCE(BuiltinFilter):
name = "Edge-enhance"
# fmt: off
filterargs = (3, 3), 2, 0, (
-1, -1, -1,
-1, 10, -1,
-1, -1, -1
-1, -1, -1,
)
# fmt: on
class EDGE_ENHANCE_MORE(BuiltinFilter):
name = "Edge-enhance More"
# fmt: off
filterargs = (3, 3), 1, 0, (
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
-1, -1, -1,
)
# fmt: on
class EMBOSS(BuiltinFilter):
name = "Emboss"
# fmt: off
filterargs = (3, 3), 1, 128, (
-1, 0, 0,
0, 1, 0,
0, 0, 0
0, 0, 0,
)
# fmt: on
class FIND_EDGES(BuiltinFilter):
name = "Find Edges"
# fmt: off
filterargs = (3, 3), 1, 0, (
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
-1, -1, -1,
)
# fmt: on
class SHARPEN(BuiltinFilter):
name = "Sharpen"
# fmt: off
filterargs = (3, 3), 16, 0, (
-2, -2, -2,
-2, 32, -2,
-2, -2, -2
-2, -2, -2,
)
# fmt: on
class SMOOTH(BuiltinFilter):
name = "Smooth"
# fmt: off
filterargs = (3, 3), 13, 0, (
1, 1, 1,
1, 5, 1,
1, 1, 1
1, 1, 1,
)
# fmt: on
class SMOOTH_MORE(BuiltinFilter):
name = "Smooth More"
# fmt: off
filterargs = (5, 5), 100, 0, (
1, 1, 1, 1, 1,
1, 5, 5, 5, 1,
1, 5, 44, 5, 1,
1, 5, 5, 5, 1,
1, 1, 1, 1, 1
1, 1, 1, 1, 1,
)
# fmt: on
class Color3DLUT(MultibandFilter):
@ -327,6 +356,7 @@ class Color3DLUT(MultibandFilter):
than ``channels`` channels. Default is ``None``,
which means that mode wouldn't be changed.
"""
name = "Color 3D LUT"
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
# 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]
wrong_size = False
@ -346,8 +376,11 @@ class Color3DLUT(MultibandFilter):
if copy_table:
table = table.copy()
if table.shape in [(items * channels,), (items, channels),
(size[2], size[1], size[0], channels)]:
if table.shape in [
(items * channels,),
(items, channels),
(size[2], size[1], size[0], channels),
]:
table = table.reshape(items * channels)
else:
wrong_size = True
@ -363,7 +396,8 @@ class Color3DLUT(MultibandFilter):
if len(pixel) != channels:
raise ValueError(
"The elements of the table should "
"have a length of {}.".format(channels))
"have a length of {}.".format(channels)
)
table.extend(pixel)
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 "
"or size**3 items of channels-sized tuples with floats. "
"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
@staticmethod
@ -379,8 +415,9 @@ class Color3DLUT(MultibandFilter):
try:
_, _, _ = size
except ValueError:
raise ValueError("Size should be either an integer or "
"a tuple of three integers.")
raise ValueError(
"Size should be either an integer or a tuple of three integers."
)
except TypeError:
size = (size, size, size)
size = [int(x) for x in size]
@ -411,15 +448,20 @@ class Color3DLUT(MultibandFilter):
for b in range(size3D):
for g in range(size2D):
for r in range(size1D):
table[idx_out:idx_out + channels] = callback(
r / (size1D-1), g / (size2D-1), b / (size3D-1))
table[idx_out : idx_out + channels] = callback(
r / (size1D - 1), g / (size2D - 1), b / (size3D - 1)
)
idx_out += channels
return cls((size1D, size2D, size3D), table, channels=channels,
target_mode=target_mode, _copy_table=False)
return cls(
(size1D, size2D, size3D),
table,
channels=channels,
target_mode=target_mode,
_copy_table=False,
)
def transform(self, callback, with_normals=False, channels=None,
target_mode=None):
def transform(self, callback, with_normals=False, channels=None, target_mode=None):
"""Transforms the table values using provided callback and returns
a new LUT with altered values.
@ -450,24 +492,31 @@ class Color3DLUT(MultibandFilter):
for b in range(size3D):
for g in range(size2D):
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:
values = callback(r / (size1D-1), g / (size2D-1),
b / (size3D-1), *values)
values = callback(
r / (size1D - 1),
g / (size2D - 1),
b / (size3D - 1),
*values
)
else:
values = callback(*values)
table[idx_out:idx_out + ch_out] = values
table[idx_out : idx_out + ch_out] = values
idx_in += ch_in
idx_out += ch_out
return type(self)(self.size, table, channels=ch_out,
return type(self)(
self.size,
table,
channels=ch_out,
target_mode=target_mode or self.mode,
_copy_table=False)
_copy_table=False,
)
def __repr__(self):
r = [
"{} from {}".format(self.__class__.__name__,
self.table.__class__.__name__),
"{} from {}".format(self.__class__.__name__, self.table.__class__.__name__),
"size={:d}x{:d}x{:d}".format(*self.size),
"channels={:d}".format(self.channels),
]
@ -479,5 +528,11 @@ class Color3DLUT(MultibandFilter):
from . import Image
return image.color_lut_3d(
self.mode or image.mode, Image.LINEAR, self.channels,
self.size[0], self.size[1], self.size[2], self.table)
self.mode or image.mode,
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)
# read PILfont metrics
data = file.read(256*20)
data = file.read(256 * 20)
# check image
if image.mode not in ("1", "L"):
@ -119,11 +119,11 @@ class ImageFont(object):
# Wrapper for FreeType fonts. Application code should use the
# <b>truetype</b> factory function to create font objects.
class FreeTypeFont(object):
"FreeType font wrapper (requires _imagingft service)"
def __init__(self, font=None, size=10, index=0, encoding="",
layout_engine=None):
def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
# FIXME: use service provider instead
self.path = font
@ -143,21 +143,23 @@ class FreeTypeFont(object):
def load_from_bytes(f):
self.font_bytes = f.read()
self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine)
"", size, index, encoding, self.font_bytes, layout_engine
)
if isPath(font):
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
font_bytes_path.decode('ascii')
font_bytes_path.decode("ascii")
except UnicodeDecodeError:
# FreeType cannot load fonts with non-ASCII characters on Windows
# So load it into memory first
with open(font, 'rb') as f:
with open(font, "rb") as f:
load_from_bytes(f)
return
self.font = core.getfont(font, size, index, encoding,
layout_engine=layout_engine)
self.font = core.getfont(
font, size, index, encoding, layout_engine=layout_engine
)
else:
load_from_bytes(font)
@ -221,8 +223,9 @@ class FreeTypeFont(object):
size, offset = self.font.getsize(text, direction, features, language)
return (size[0] + offset[0], size[1] + offset[1])
def getsize_multiline(self, text, direction=None, spacing=4,
features=None, language=None):
def getsize_multiline(
self, text, direction=None, spacing=4, features=None, language=None
):
"""
Returns width and height (in pixels) of given text if rendered in font
with provided direction, features, and language, while respecting
@ -261,12 +264,12 @@ class FreeTypeFont(object):
"""
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.getsize('A')[1] + spacing
line_spacing = self.getsize("A")[1] + spacing
for line in lines:
line_width, line_height = self.getsize(line, direction, features, language)
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):
"""
@ -327,11 +330,21 @@ class FreeTypeFont(object):
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
"""
return self.getmask2(text, mode, direction=direction, features=features,
language=language)[0]
return self.getmask2(
text, mode, direction=direction, features=features, language=language
)[0]
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
features=None, language=None, *args, **kwargs):
def getmask2(
self,
text,
mode="",
fill=Image.core.fill,
direction=None,
features=None,
language=None,
*args,
**kwargs
):
"""
Create a bitmap for the text.
@ -384,8 +397,9 @@ class FreeTypeFont(object):
self.font.render(text, im.id, mode == "1", direction, features, language)
return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None,
layout_engine=None):
def font_variant(
self, font=None, size=None, index=None, encoding=None, layout_engine=None
):
"""
Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings.
@ -400,7 +414,7 @@ class FreeTypeFont(object):
size=self.size if size is None else size,
index=self.index if index is None else index,
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
def truetype(font=None, size=10, index=0, encoding="",
layout_engine=None):
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
"""
Load a TrueType or OpenType font from a file or file-like object,
and create a font object.
@ -472,8 +485,10 @@ def truetype(font=None, size=10, index=0, encoding="",
:return: A font object.
:exception IOError: If the file could not be read.
"""
def freetype(font):
return FreeTypeFont(font, size, index, encoding, layout_engine)
try:
return freetype(font)
except IOError:
@ -487,17 +502,19 @@ def truetype(font=None, size=10, index=0, encoding="",
windir = os.environ.get("WINDIR")
if windir:
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", "")
if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share
lindirs = '/usr/share'
dirs += [os.path.join(lindir, "fonts")
for lindir in lindirs.split(":")]
elif sys.platform == 'darwin':
dirs += ['/Library/Fonts', '/System/Library/Fonts',
os.path.expanduser('~/Library/Fonts')]
lindirs = "/usr/share"
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
elif sys.platform == "darwin":
dirs += [
"/Library/Fonts",
"/System/Library/Fonts",
os.path.expanduser("~/Library/Fonts"),
]
ext = os.path.splitext(ttf_filename)[1]
first_font_with_a_different_extension = None
@ -506,13 +523,11 @@ def truetype(font=None, size=10, index=0, encoding="",
for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename:
return freetype(os.path.join(walkroot, walkfilename))
elif (not ext and
os.path.splitext(walkfilename)[0] == ttf_filename):
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == '.ttf':
if os.path.splitext(fontpath)[1] == ".ttf":
return freetype(fontpath)
if not ext \
and first_font_with_a_different_extension is None:
if not ext and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath
if 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
import base64
f = ImageFont()
f._load_pilfont_data(
# courB08
BytesIO(base64.b64decode(b'''
BytesIO(
base64.b64decode(
b"""
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
@ -646,7 +664,13 @@ AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
+QAGAAIAzgAKANUAEw==
''')), Image.open(BytesIO(base64.b64decode(b'''
"""
)
),
Image.open(
BytesIO(
base64.b64decode(
b"""
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
@ -670,5 +694,9 @@ evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
w7IkEbzhVQAAAABJRU5ErkJggg==
'''))))
"""
)
)
),
)
return f

View File

@ -18,6 +18,7 @@
from . import Image
import sys
if sys.platform == "win32":
grabber = Image.core.grabscreen
elif sys.platform == "darwin":
@ -30,18 +31,23 @@ else:
def grab(bbox=None, include_layered_windows=False):
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp('.png')
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
subprocess.call(['screencapture', '-x', filepath])
subprocess.call(["screencapture", "-x", filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
else:
size, data = grabber(include_layered_windows)
im = Image.frombytes(
"RGB", size, data,
"RGB",
size,
data,
# 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:
im = im.crop(bbox)
@ -50,15 +56,16 @@ def grab(bbox=None, include_layered_windows=False):
def grabclipboard():
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp('.jpg')
fh, filepath = tempfile.mkstemp(".jpg")
os.close(fh)
commands = [
"set theFile to (open for access POSIX file \""
+ filepath + "\" with write permission)",
'set theFile to (open for access POSIX file "'
+ filepath
+ '" with write permission)',
"try",
" write (the clipboard as JPEG picture) to theFile",
"end try",
"close access theFile"
"close access theFile",
]
script = ["osascript"]
for command in commands:
@ -76,5 +83,6 @@ def grabclipboard():
if isinstance(data, bytes):
from . import BmpImagePlugin
import io
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
return data

View File

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

View File

@ -37,13 +37,13 @@ def getmode(mode):
# initialize mode cache
from . import Image
modes = {}
# core modes
for m, (basemode, basetype, bands) in Image._MODEINFO.items():
modes[m] = ModeDescriptor(m, bands, basemode, basetype)
# extra experimental modes
modes["RGBa"] = ModeDescriptor("RGBa",
("R", "G", "B", "a"), "RGB", "L")
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "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")

View File

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

View File

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

View File

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

View File

@ -22,12 +22,7 @@ from io import BytesIO
import sys
import warnings
qt_versions = [
['5', 'PyQt5'],
['side2', 'PySide2'],
['4', 'PyQt4'],
['side', 'PySide']
]
qt_versions = [["5", "PyQt5"], ["side2", "PySide2"], ["4", "PyQt4"], ["side", "PySide"]]
WARNING_TEXT = (
"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
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules,
reverse=True)
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
for qt_version, qt_module in qt_versions:
try:
if qt_module == 'PyQt5':
if qt_module == "PyQt5":
from PyQt5.QtGui import QImage, qRgba, QPixmap
from PyQt5.QtCore import QBuffer, QIODevice
elif qt_module == 'PySide2':
elif qt_module == "PySide2":
from PySide2.QtGui import QImage, qRgba, QPixmap
from PySide2.QtCore import QBuffer, QIODevice
elif qt_module == 'PyQt4':
elif qt_module == "PyQt4":
from PyQt4.QtGui import QImage, qRgba, QPixmap
from PyQt4.QtCore import QBuffer, QIODevice
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.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."""
# use qRgb to pack the colors, and then turn the resulting long
# 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):
@ -81,9 +75,9 @@ def fromqimage(im):
# preserve alpha channel with png
# otherwise ppm is more friendly with Image.open
if im.hasAlphaChannel():
im.save(buffer, 'png')
im.save(buffer, "png")
else:
im.save(buffer, 'ppm')
im.save(buffer, "ppm")
b = BytesIO()
try:
@ -116,11 +110,7 @@ def align8to32(bytes, width, mode):
converts each scanline of data from 8 bit to 32 bit aligned
"""
bits_per_pixel = {
'1': 1,
'L': 8,
'P': 8,
}[mode]
bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode]
# calculate bytes per line and the extra padding if needed
bits_per_line = bits_per_pixel * width
@ -135,10 +125,12 @@ def align8to32(bytes, width, mode):
new_data = []
for i in range(len(bytes) // bytes_per_line):
new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line]
+ b'\x00' * extra_padding)
new_data.append(
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):
@ -167,7 +159,7 @@ def _toqclass_helper(im):
colortable = []
palette = im.getpalette()
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":
data = im.tobytes("raw", "BGRX")
format = QImage.Format_RGB32
@ -183,14 +175,12 @@ def _toqclass_helper(im):
raise ValueError("unsupported image mode %r" % im.mode)
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
return {
'data': __data, 'im': im, 'format': format, 'colortable': colortable
}
return {"data": __data, "im": im, "format": format, "colortable": colortable}
if qt_is_installed:
class ImageQt(QImage):
class ImageQt(QImage):
def __init__(self, im):
"""
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
# buffer, so this buffer has to hang on for the life of the image.
# Fixes https://github.com/python-pillow/Pillow/issues/1370
self.__data = im_data['data']
QImage.__init__(self,
self.__data, im_data['im'].size[0],
im_data['im'].size[1], im_data['format'])
if im_data['colortable']:
self.setColorTable(im_data['colortable'])
self.__data = im_data["data"]
QImage.__init__(
self,
self.__data,
im_data["im"].size[0],
im_data["im"].size[1],
im_data["format"],
)
if im_data["colortable"]:
self.setColorTable(im_data["colortable"])
def toqimage(im):
@ -222,8 +216,8 @@ def toqpixmap(im):
# result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
# result.loadFromData(im_data['data'])
# Fix some strange bug that causes
if im.mode == 'RGB':
im = im.convert('RGBA')
if im.mode == "RGB":
im = im.convert("RGBA")
qimage = toqimage(im)
return QPixmap.fromImage(qimage)

View File

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

View File

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

View File

@ -67,6 +67,7 @@ def _get_image_from_kw(kw):
# --------------------------------------------------------------------
# PhotoImage
class PhotoImage(object):
"""
A Tkinter-compatible photo image. This can be used
@ -183,17 +184,18 @@ class PhotoImage(object):
# activate Tkinter hook
try:
from . import _imagingtk
try:
if hasattr(tk, 'interp'):
if hasattr(tk, "interp"):
# Required for PyPy, which always has CFFI installed
from cffi import FFI
ffi = FFI()
# PyPy is using an FFI CDATA element
# (Pdb) self.tk.interp
# <cdata 'Tcl_Interp *' 0x3061b50>
_imagingtk.tkinit(
int(ffi.cast("uintptr_t", tk.interp)), 1)
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
else:
_imagingtk.tkinit(tk.interpaddr(), 1)
except AttributeError:
@ -202,6 +204,7 @@ class PhotoImage(object):
except (ImportError, AttributeError, tkinter.TclError):
raise # configuration problem; cannot attach to Tkinter
# --------------------------------------------------------------------
# BitmapImage
@ -293,8 +296,7 @@ def _show(image, title):
self.image = BitmapImage(im, foreground="white", master=master)
else:
self.image = PhotoImage(im, master=master)
tkinter.Label.__init__(self, master, image=self.image,
bg="black", bd=0)
tkinter.Label.__init__(self, master, image=self.image, bg="black", bd=0)
if not tkinter._default_root:
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
from an affine transform matrix.
"""
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
input image's coordinate system. See :ref:`coordinate-system`.
"""
method = Image.EXTENT
@ -83,6 +85,7 @@ class QuadTransform(Transform):
upper left, lower left, lower right, and upper right corner of the
source quadrilateral.
"""
method = Image.QUAD
@ -95,4 +98,5 @@ class MeshTransform(Transform):
:param data: A list of (bbox, quad) tuples.
"""
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`
methods.
"""
def __init__(self, dc):
self.dc = dc
@ -39,6 +40,7 @@ class HWND(object):
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
methods, instead of a DC.
"""
def __init__(self, wnd):
self.wnd = wnd

View File

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

View File

@ -26,10 +26,7 @@ import tempfile
# PIL.__version__ instead.
__version__ = "0.3"
COMPRESSION = {
1: "raw",
5: "jpeg"
}
COMPRESSION = {1: "raw", 5: "jpeg"}
PAD = o8(0) * 4
@ -37,13 +34,14 @@ PAD = o8(0) * 4
#
# Helpers
def i(c):
return i32((PAD + c)[-4:])
def dump(c):
for i in c:
print("%02x" % i8(i), end=' ')
print("%02x" % i8(i), end=" ")
print()
@ -51,6 +49,7 @@ def dump(c):
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
class IptcImageFile(ImageFile.ImageFile):
format = "IPTC"
@ -79,7 +78,7 @@ class IptcImageFile(ImageFile.ImageFile):
elif size == 128:
size = 0
elif size > 128:
size = i(self.fp.read(size-128))
size = i(self.fp.read(size - 128))
else:
size = i16(s[3:])
@ -109,7 +108,7 @@ class IptcImageFile(ImageFile.ImageFile):
layers = i8(self.info[(3, 60)][0])
component = i8(self.info[(3, 60)][1])
if (3, 65) in self.info:
id = i8(self.info[(3, 65)][0])-1
id = i8(self.info[(3, 65)][0]) - 1
else:
id = 0
if layers == 1 and not component:
@ -130,8 +129,9 @@ class IptcImageFile(ImageFile.ImageFile):
# tile
if tag == (8, 10):
self.tile = [("iptc", (compression, offset),
(0, 0, self.size[0], self.size[1]))]
self.tile = [
("iptc", (compression, offset), (0, 0, self.size[0], self.size[1]))
]
def load(self):
@ -216,6 +216,7 @@ def getiptcinfo(im):
# create an IptcImagePlugin object without initializing it
class FakeImage(object):
pass
im = FakeImage()
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."""
hdr = fp.read(2)
lsiz = struct.unpack('>H', hdr)[0]
lsiz = struct.unpack(">H", hdr)[0]
siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
xtosiz, ytosiz, csiz \
= struct.unpack_from('>HHIIIIIIIIH', siz)
ssiz = [None]*csiz
xrsiz = [None]*csiz
yrsiz = [None]*csiz
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
">HHIIIIIIIIH", siz
)
ssiz = [None] * csiz
xrsiz = [None] * csiz
yrsiz = [None] * csiz
for i in range(csiz):
ssiz[i], xrsiz[i], yrsiz[i] \
= struct.unpack_from('>BBB', siz, 36 + 3 * i)
ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack_from(">BBB", siz, 36 + 3 * i)
size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1:
if (yrsiz[0] & 0x7f) > 8:
mode = 'I;16'
if (yrsiz[0] & 0x7F) > 8:
mode = "I;16"
else:
mode = 'L'
mode = "L"
elif csiz == 2:
mode = 'LA'
mode = "LA"
elif csiz == 3:
mode = 'RGB'
mode = "RGB"
elif csiz == 4:
mode = 'RGBA'
mode = "RGBA"
else:
mode = None
@ -65,28 +64,28 @@ def _parse_jp2_header(fp):
header = None
mimetype = None
while True:
lbox, tbox = struct.unpack('>I4s', fp.read(8))
lbox, tbox = struct.unpack(">I4s", fp.read(8))
if lbox == 1:
lbox = struct.unpack('>Q', fp.read(8))[0]
lbox = struct.unpack(">Q", fp.read(8))[0]
hlen = 16
else:
hlen = 8
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)
break
elif tbox == b'ftyp':
if fp.read(4) == b'jpx ':
mimetype = 'image/jpx'
elif tbox == b"ftyp":
if fp.read(4) == b"jpx ":
mimetype = "image/jpx"
fp.seek(lbox - hlen - 4, os.SEEK_CUR)
else:
fp.seek(lbox - hlen, os.SEEK_CUR)
if header is None:
raise SyntaxError('could not find JP2 header')
raise SyntaxError("could not find JP2 header")
size = None
mode = None
@ -95,58 +94,57 @@ def _parse_jp2_header(fp):
hio = io.BytesIO(header)
while True:
lbox, tbox = struct.unpack('>I4s', hio.read(8))
lbox, tbox = struct.unpack(">I4s", hio.read(8))
if lbox == 1:
lbox = struct.unpack('>Q', hio.read(8))[0]
lbox = struct.unpack(">Q", hio.read(8))[0]
hlen = 16
else:
hlen = 8
content = hio.read(lbox - hlen)
if tbox == b'ihdr':
height, width, nc, bpc, c, unkc, ipr \
= struct.unpack('>IIHBBBB', content)
if tbox == b"ihdr":
height, width, nc, bpc, c, unkc, ipr = struct.unpack(">IIHBBBB", content)
size = (width, height)
if unkc:
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
if nc == 1 and (bpc & 0x7F) > 8:
mode = "I;16"
elif nc == 1:
mode = 'L'
mode = "L"
elif nc == 2:
mode = 'LA'
mode = "LA"
elif nc == 3:
mode = 'RGB'
mode = "RGB"
elif nc == 4:
mode = 'RGBA'
mode = "RGBA"
break
elif tbox == b'colr':
meth, prec, approx = struct.unpack_from('>BBB', content)
elif tbox == b"colr":
meth, prec, approx = struct.unpack_from(">BBB", content)
if meth == 1:
cs = struct.unpack_from('>I', content, 3)[0]
cs = struct.unpack_from(">I", content, 3)[0]
if cs == 16: # sRGB
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
if nc == 1 and (bpc & 0x7F) > 8:
mode = "I;16"
elif nc == 1:
mode = 'L'
mode = "L"
elif nc == 3:
mode = 'RGB'
mode = "RGB"
elif nc == 4:
mode = 'RGBA'
mode = "RGBA"
break
elif cs == 17: # grayscale
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
if nc == 1 and (bpc & 0x7F) > 8:
mode = "I;16"
elif nc == 1:
mode = 'L'
mode = "L"
elif nc == 2:
mode = 'LA'
mode = "LA"
break
elif cs == 18: # sYCC
if nc == 3:
mode = 'RGB'
mode = "RGB"
elif nc == 4:
mode = 'RGBA'
mode = "RGBA"
break
if size is None or mode is None:
@ -154,6 +152,7 @@ def _parse_jp2_header(fp):
return (size, mode, mimetype)
##
# Image plugin for JPEG2000 images.
@ -164,21 +163,21 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _open(self):
sig = self.fp.read(4)
if sig == b'\xff\x4f\xff\x51':
if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k"
self._size, self.mode = _parse_codestream(self.fp)
else:
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"
header = _parse_jp2_header(self.fp)
self._size, self.mode, self.custom_mimetype = header
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:
raise SyntaxError('unable to determine size/mode')
raise SyntaxError("unable to determine size/mode")
self.reduce = 0
self.layers = 0
@ -199,15 +198,23 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
except Exception:
length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd, length))]
self.tile = [
(
"jpeg2k",
(0, 0) + self.size,
0,
(self.codec, self.reduce, self.layers, fd, length),
)
]
def load(self):
if self.reduce:
power = 1 << self.reduce
adjust = power >> 1
self._size = (int((self.size[0] + adjust) / power),
int((self.size[1] + adjust) / power))
self._size = (
int((self.size[0] + adjust) / power),
int((self.size[1] + adjust) / power),
)
if self.tile:
# Update the reduce and layers settings
@ -219,40 +226,47 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51' or
prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
return (
prefix[:4] == b"\xff\x4f\xff\x51"
or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
)
# ------------------------------------------------------------
# Save support
def _save(im, fp, filename):
if filename.endswith('.j2k'):
kind = 'j2k'
if filename.endswith(".j2k"):
kind = "j2k"
else:
kind = 'jp2'
kind = "jp2"
# Get the keyword arguments
info = im.encoderinfo
offset = info.get('offset', None)
tile_offset = info.get('tile_offset', None)
tile_size = info.get('tile_size', None)
quality_mode = info.get('quality_mode', 'rates')
quality_layers = info.get('quality_layers', None)
offset = info.get("offset", None)
tile_offset = info.get("tile_offset", None)
tile_size = info.get("tile_size", None)
quality_mode = info.get("quality_mode", "rates")
quality_layers = info.get("quality_layers", None)
if quality_layers is not None and not (
isinstance(quality_layers, (list, tuple)) and
all([isinstance(quality_layer, (int, float))
for quality_layer in quality_layers])
isinstance(quality_layers, (list, tuple))
and all(
[
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)
cblk_size = info.get('codeblock_size', None)
precinct_size = info.get('precinct_size', None)
irreversible = info.get('irreversible', False)
progression = info.get('progression', 'LRCP')
cinema_mode = info.get('cinema_mode', 'no')
num_resolutions = info.get("num_resolutions", 0)
cblk_size = info.get("codeblock_size", None)
precinct_size = info.get("precinct_size", None)
irreversible = info.get("irreversible", False)
progression = info.get("progression", "LRCP")
cinema_mode = info.get("cinema_mode", "no")
fd = -1
if hasattr(fp, "fileno"):
@ -273,10 +287,11 @@ def _save(im, fp, filename):
irreversible,
progression,
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
@ -285,7 +300,8 @@ def _save(im, fp, filename):
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
Image.register_save(Jpeg2KImageFile.format, _save)
Image.register_extensions(Jpeg2KImageFile.format,
[".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"])
Image.register_extensions(
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
def Skip(self, marker):
n = i16(self.fp.read(2))-2
n = i16(self.fp.read(2)) - 2
ImageFile._safe_read(self.fp, n)
@ -61,7 +62,7 @@ def APP(self, marker):
# Application marker. Store these in the APP dictionary.
# 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)
app = "APP%d" % (marker & 15)
@ -110,7 +111,7 @@ def APP(self, marker):
# parse the image resource block
offset = 0
photoshop = {}
while blocks[offset:offset+4] == b"8BIM":
while blocks[offset : offset + 4] == b"8BIM":
offset += 4
# resource code
code = i16(blocks, offset)
@ -124,13 +125,13 @@ def APP(self, marker):
# resource data block
size = i32(blocks, offset)
offset += 4
data = blocks[offset:offset+size]
data = blocks[offset : offset + size]
if code == 0x03ED: # ResolutionInfo
data = {
'XResolution': i32(data[:4]) / 65536,
'DisplayedUnitsX': i16(data[4:8]),
'YResolution': i32(data[8:12]) / 65536,
'DisplayedUnitsY': i16(data[12:]),
"XResolution": i32(data[:4]) / 65536,
"DisplayedUnitsX": i16(data[4:8]),
"YResolution": i32(data[8:12]) / 65536,
"DisplayedUnitsY": i16(data[12:]),
}
photoshop[code] = data
offset = offset + size
@ -177,7 +178,7 @@ def APP(self, marker):
def COM(self, marker):
#
# 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)
self.app["COM"] = s # compatibility
@ -192,7 +193,7 @@ def SOF(self, marker):
# mode. Note that this could be made a bit brighter, by
# 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)
self._size = i16(s[3:]), i16(s[1:])
@ -227,9 +228,9 @@ def SOF(self, marker):
self.icclist = None
for i in range(6, len(s), 3):
t = s[i:i+3]
t = s[i : i + 3]
# 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):
@ -241,13 +242,13 @@ def DQT(self, marker):
# FIXME: The quantization tables can be used to estimate the
# compression quality.
n = i16(self.fp.read(2))-2
n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n)
while len(s):
if len(s) < 65:
raise SyntaxError("bad quantization table marker")
v = i8(s[0])
if v//16 == 0:
if v // 16 == 0:
self.quantization[v & 15] = array.array("B", s[1:65])
s = s[65:]
else:
@ -321,7 +322,7 @@ MARKER = {
0xFFFB: ("JPG11", "Extension 11", None),
0xFFFC: ("JPG12", "Extension 12", 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.
class JpegImageFile(ImageFile.ImageFile):
format = "JPEG"
@ -375,8 +377,7 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode
if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0, 0) + self.size, 0,
(rawmode, ""))]
self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
@ -424,8 +425,13 @@ class JpegImageFile(ImageFile.ImageFile):
for s in [8, 4, 2, 1]:
if scale >= s:
break
e = 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)
e = (
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
self.tile = [(d, e, o, a)]
@ -440,6 +446,7 @@ class JpegImageFile(ImageFile.ImageFile):
import subprocess
import tempfile
import os
f, path = tempfile.mkstemp()
os.close(f)
if os.path.exists(self.filename):
@ -505,7 +512,7 @@ def _getmp(self):
return None
file_contents = io.BytesIO(data)
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
try:
info = TiffImagePlugin.ImageFileDirectory_v2(head)
@ -525,37 +532,33 @@ def _getmp(self):
rawmpentries = mp[0xB002]
for entrynum in range(0, quant):
unpackedentry = struct.unpack_from(
'{}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
'EntryNo2')
"{}LLLHH".format(endianness), rawmpentries, entrynum * 16
)
labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2")
mpentry = dict(zip(labels, unpackedentry))
mpentryattr = {
'DependentParentImageFlag': bool(mpentry['Attribute'] &
(1 << 31)),
'DependentChildImageFlag': bool(mpentry['Attribute'] &
(1 << 30)),
'RepresentativeImageFlag': bool(mpentry['Attribute'] &
(1 << 29)),
'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
'MPType': mpentry['Attribute'] & 0x00FFFFFF
"DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)),
"DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)),
"RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)),
"Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27,
"ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24,
"MPType": mpentry["Attribute"] & 0x00FFFFFF,
}
if mpentryattr['ImageDataFormat'] == 0:
mpentryattr['ImageDataFormat'] = 'JPEG'
if mpentryattr["ImageDataFormat"] == 0:
mpentryattr["ImageDataFormat"] = "JPEG"
else:
raise SyntaxError("unsupported picture format in MPO")
mptypemap = {
0x000000: 'Undefined',
0x010001: 'Large Thumbnail (VGA Equivalent)',
0x010002: 'Large Thumbnail (Full HD Equivalent)',
0x020001: 'Multi-Frame Image (Panorama)',
0x020002: 'Multi-Frame Image: (Disparity)',
0x020003: 'Multi-Frame Image: (Multi-Angle)',
0x030000: 'Baseline MP Primary Image'
0x000000: "Undefined",
0x010001: "Large Thumbnail (VGA Equivalent)",
0x010002: "Large Thumbnail (Full HD Equivalent)",
0x020001: "Multi-Frame Image (Panorama)",
0x020002: "Multi-Frame Image: (Disparity)",
0x020003: "Multi-Frame Image: (Multi-Angle)",
0x030000: "Baseline MP Primary Image",
}
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
'Unknown')
mpentry['Attribute'] = mpentryattr
mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown")
mpentry["Attribute"] = mpentryattr
mpentries.append(mpentry)
mp[0xB002] = mpentries
except KeyError:
@ -578,19 +581,24 @@ RAWMODE = {
"YCbCr": "YCbCr",
}
zigzag_index = (0, 1, 5, 6, 14, 15, 27, 28, # noqa: E128
# fmt: off
zigzag_index = (
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63)
35, 36, 48, 49, 57, 58, 62, 63,
)
samplings = {(1, 1, 1, 1, 1, 1): 0,
samplings = {
(1, 1, 1, 1, 1, 1): 0,
(2, 1, 1, 1, 1, 1): 1,
(2, 2, 1, 1, 1, 1): 2,
}
}
# fmt: on
def convert_dict_qtables(qtables):
@ -608,7 +616,7 @@ def get_sampling(im):
# NOTE: currently Pillow can't encode JPEG to YCCK format.
# 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.
if not hasattr(im, 'layers') or im.layers in (1, 4):
if not hasattr(im, "layers") or im.layers in (1, 4):
return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1)
@ -636,15 +644,15 @@ def _save(im, fp, filename):
elif quality in presets:
preset = presets[quality]
quality = 0
subsampling = preset.get('subsampling', -1)
qtables = preset.get('quantization')
subsampling = preset.get("subsampling", -1)
qtables = preset.get("quantization")
elif not isinstance(quality, int):
raise ValueError("Invalid quality setting")
else:
if subsampling in presets:
subsampling = presets[subsampling].get('subsampling', -1)
subsampling = presets[subsampling].get("subsampling", -1)
if isStringType(qtables) and qtables in presets:
qtables = presets[qtables].get('quantization')
qtables = presets[qtables].get("quantization")
if subsampling == "4:4:4":
subsampling = 0
@ -658,8 +666,7 @@ def _save(im, fp, filename):
subsampling = 2
elif subsampling == "keep":
if im.format != "JPEG":
raise ValueError(
"Cannot use 'keep' when original image is not a JPEG")
raise ValueError("Cannot use 'keep' when original image is not a JPEG")
subsampling = get_sampling(im)
def validate_qtables(qtables):
@ -667,12 +674,15 @@ def _save(im, fp, filename):
return qtables
if isStringType(qtables):
try:
lines = [int(num) for line in qtables.splitlines()
for num in line.split('#', 1)[0].split()]
lines = [
int(num)
for line in qtables.splitlines()
for num in line.split("#", 1)[0].split()
]
except ValueError:
raise ValueError("Invalid quantization table")
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, dict):
qtables = convert_dict_qtables(qtables)
@ -684,7 +694,7 @@ def _save(im, fp, filename):
try:
if len(table) != 64:
raise TypeError
table = array.array('B', table)
table = array.array("B", table)
except TypeError:
raise ValueError("Invalid quantization table")
else:
@ -693,8 +703,7 @@ def _save(im, fp, filename):
if qtables == "keep":
if im.format != "JPEG":
raise ValueError(
"Cannot use 'keep' when original image is not a JPEG")
raise ValueError("Cannot use 'keep' when original image is not a JPEG")
qtables = getattr(im, "quantization", None)
qtables = validate_qtables(qtables)
@ -712,15 +721,20 @@ def _save(im, fp, filename):
i = 1
for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) +
o8(len(markers)) + marker)
extra += (
b"\xFF\xE2"
+ size
+ b"ICC_PROFILE\0"
+ o8(i)
+ o8(len(markers))
+ marker
)
i += 1
# "progressive" is the official name, but older documentation
# says "progression"
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
progressive = (info.get("progressive", False) or
info.get("progression", False))
progressive = info.get("progressive", False) or info.get("progression", False)
optimize = info.get("optimize", False)
@ -735,11 +749,12 @@ def _save(im, fp, filename):
info.get("smooth", 0),
optimize,
info.get("streamtype", 0),
dpi[0], dpi[1],
dpi[0],
dpi[1],
subsampling,
qtables,
extra,
exif
exif,
)
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
@ -749,7 +764,7 @@ def _save(im, fp, filename):
bufsize = 0
if optimize or progressive:
# CMYK can be bigger
if im.mode == 'CMYK':
if im.mode == "CMYK":
bufsize = 4 * im.size[0] * im.size[1]
# keep sets quality to 0, but the actual value may be high.
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.
# Ensure that our buffer is big enough. Same with the icc_profile block.
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5,
len(extra) + 1)
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, 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):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os
import subprocess
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try:
@ -786,14 +801,17 @@ def jpeg_factory(fp=None, filename=None):
if mpheader[45057] > 1:
# It's actually an MPO
from .MpoImagePlugin import MpoImageFile
# Don't reload everything, just convert it.
im = MpoImageFile.adopt(im, mpheader)
except (TypeError, IndexError):
# It is really a JPEG
pass
except SyntaxError:
warnings.warn("Image appears to be a malformed MPO file, it will be "
"interpreted as a base JPEG file")
warnings.warn(
"Image appears to be a malformed MPO file, it will be "
"interpreted as a base JPEG file"
)
return im
@ -803,7 +821,6 @@ def jpeg_factory(fp=None, filename=None):
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save)
Image.register_extensions(JpegImageFile.format,
[".jfif", ".jpe", ".jpg", ".jpeg"])
Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".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
'web_low': {'subsampling': 2, # "4:2:0"
'quantization': [
@ -240,3 +241,4 @@ presets = { # noqa: E128
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.
class McIdasImageFile(ImageFile.ImageFile):
format = "MCIDAS"
@ -64,7 +65,7 @@ class McIdasImageFile(ImageFile.ImageFile):
self._size = w[10], w[9]
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))]

View File

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

View File

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

View File

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

View File

@ -45,6 +45,7 @@ def _accept(prefix):
# Image plugin for Windows MSP images. This plugin supports both
# uncompressed (Windows 1.0).
class MspImageFile(ImageFile.ImageFile):
format = "MSP"
@ -60,7 +61,7 @@ class MspImageFile(ImageFile.ImageFile):
# Header checksum
checksum = 0
for i in range(0, 32, 2):
checksum = checksum ^ i16(s[i:i+2])
checksum = checksum ^ i16(s[i : i + 2])
if checksum != 0:
raise SyntaxError("bad MSP checksum")
@ -68,9 +69,9 @@ class MspImageFile(ImageFile.ImageFile):
self._size = i16(s[4:]), i16(s[6:])
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:
self.tile = [("MSP", (0, 0)+self.size, 32, None)]
self.tile = [("MSP", (0, 0) + self.size, 32, None)]
class MspDecoder(ImageFile.PyDecoder):
@ -113,11 +114,12 @@ class MspDecoder(ImageFile.PyDecoder):
def decode(self, buffer):
img = io.BytesIO()
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8))
blank_line = bytearray((0xFF,) * ((self.state.xsize + 7) // 8))
try:
self.fd.seek(32)
rowmap = struct.unpack_from("<%dH" % (self.state.ysize),
self.fd.read(self.state.ysize*2))
rowmap = struct.unpack_from(
"<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2)
)
except struct.error:
raise IOError("Truncated MSP file in row map")
@ -129,8 +131,8 @@ class MspDecoder(ImageFile.PyDecoder):
row = self.fd.read(rowlen)
if len(row) != rowlen:
raise IOError(
"Truncated MSP file, expected %d bytes on row %s",
(rowlen, x))
"Truncated MSP file, expected %d bytes on row %s", (rowlen, x)
)
idx = 0
while idx < rowlen:
runtype = i8(row[idx])
@ -141,7 +143,7 @@ class MspDecoder(ImageFile.PyDecoder):
idx += 2
else:
runcount = runtype
img.write(row[idx:idx+runcount])
img.write(row[idx : idx + runcount])
idx += runcount
except struct.error:
@ -152,7 +154,7 @@ class MspDecoder(ImageFile.PyDecoder):
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))
# 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:
self.fp.write(to_write)
else:
self.fp.write(bytes(to_write, 'UTF-8'))
self.fp.write(bytes(to_write, "UTF-8"))
def begin_document(self, id=None):
"""Set up printing of a document. (Write Postscript DSC header.)"""
# FIXME: incomplete
self._fp_write("%!PS-Adobe-3.0\n"
self._fp_write(
"%!PS-Adobe-3.0\n"
"save\n"
"/showpage { } def\n"
"%%EndComments\n"
"%%BeginDocument\n")
"%%BeginDocument\n"
)
# self._fp_write(ERROR_PS) # debugging!
self._fp_write(EDROFF_PS)
self._fp_write(VDI_PS)
@ -56,9 +58,7 @@ class PSDraw(object):
def end_document(self):
"""Ends printing. (Write Postscript DSC footer.)"""
self._fp_write("%%EndDocument\n"
"restore showpage\n"
"%%End\n")
self._fp_write("%%EndDocument\nrestore showpage\n%%End\n")
if hasattr(self.fp, "flush"):
self.fp.flush()
@ -71,8 +71,7 @@ class PSDraw(object):
"""
if font not in self.isofont:
# reencode font
self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
(font, font))
self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font))
self.isofont[font] = 1
# rough
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)
self._fp_write("\ngrestore\n")
# --------------------------------------------------------------------
# Postscript driver

View File

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

View File

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

View File

@ -28,6 +28,7 @@ __version__ = "0.1"
# image from the file; higher resolutions are encoded in a proprietary
# encoding.
class PcdImageFile(ImageFile.ImageFile):
format = "PCD"
@ -51,7 +52,7 @@ class PcdImageFile(ImageFile.ImageFile):
self.mode = "RGB"
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):
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_PROPERTIES = (1 << 0)
PCF_ACCELERATORS = (1 << 1)
PCF_METRICS = (1 << 2)
PCF_BITMAPS = (1 << 3)
PCF_INK_METRICS = (1 << 4)
PCF_BDF_ENCODINGS = (1 << 5)
PCF_SWIDTHS = (1 << 6)
PCF_GLYPH_NAMES = (1 << 7)
PCF_BDF_ACCELERATORS = (1 << 8)
PCF_PROPERTIES = 1 << 0
PCF_ACCELERATORS = 1 << 1
PCF_METRICS = 1 << 2
PCF_BITMAPS = 1 << 3
PCF_INK_METRICS = 1 << 4
PCF_BDF_ENCODINGS = 1 << 5
PCF_SWIDTHS = 1 << 6
PCF_GLYPH_NAMES = 1 << 7
PCF_BDF_ACCELERATORS = 1 << 8
BYTES_PER_ROW = [
lambda bits: ((bits+7) >> 3),
lambda bits: ((bits+15) >> 3) & ~1,
lambda bits: ((bits+31) >> 3) & ~3,
lambda bits: ((bits+63) >> 3) & ~7,
lambda bits: ((bits + 7) >> 3),
lambda bits: ((bits + 15) >> 3) & ~1,
lambda bits: ((bits + 31) >> 3) & ~3,
lambda bits: ((bits + 63) >> 3) & ~7,
]
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.
class PcfFontFile(FontFile.FontFile):
name = "name"
@ -83,7 +84,7 @@ class PcfFontFile(FontFile.FontFile):
ix = encoding[ch]
if ix is not None:
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
def _getformat(self, tag):
@ -141,7 +142,7 @@ class PcfFontFile(FontFile.FontFile):
append = metrics.append
if (format & 0xff00) == 0x100:
if (format & 0xFF00) == 0x100:
# "compressed" metrics
for i in range(i16(fp.read(2))):
@ -152,10 +153,7 @@ class PcfFontFile(FontFile.FontFile):
descent = i8(fp.read(1)) - 128
xsize = right - left
ysize = ascent + descent
append(
(xsize, ysize, left, right, width,
ascent, descent, 0)
)
append((xsize, ysize, left, right, width, ascent, descent, 0))
else:
@ -169,10 +167,7 @@ class PcfFontFile(FontFile.FontFile):
attributes = i16(fp.read(2))
xsize = right - left
ysize = ascent + descent
append(
(xsize, ysize, left, right, width,
ascent, descent, attributes)
)
append((xsize, ysize, left, right, width, ascent, descent, attributes))
return metrics
@ -214,10 +209,8 @@ class PcfFontFile(FontFile.FontFile):
for i in range(nbitmaps):
x, y, l, r, w, a, d, f = metrics[i]
b, e = offsets[i], offsets[i+1]
bitmaps.append(
Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x))
)
b, e = offsets[i], offsets[i + 1]
bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x)))
return bitmaps
@ -239,7 +232,7 @@ class PcfFontFile(FontFile.FontFile):
encodingOffset = i16(fp.read(2))
if encodingOffset != 0xFFFF:
try:
encoding[i+firstCol] = encodingOffset
encoding[i + firstCol] = encodingOffset
except IndexError:
break # only load ISO-8859-1 glyphs

View File

@ -44,6 +44,7 @@ def _accept(prefix):
##
# Image plugin for Paintbrush images.
class PcxImageFile(ImageFile.ImageFile):
format = "PCX"
@ -57,7 +58,7 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError("not a PCX file")
# 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]:
raise SyntaxError("bad PCX image size")
logger.debug("BBox: %s %s %s %s", *bbox)
@ -67,8 +68,13 @@ class PcxImageFile(ImageFile.ImageFile):
bits = i8(s[3])
planes = i8(s[65])
stride = i16(s, 66)
logger.debug("PCX version %s, bits %s, planes %s, stride %s",
version, bits, planes, stride)
logger.debug(
"PCX version %s, bits %s, planes %s, stride %s",
version,
bits,
planes,
stride,
)
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:
# check if the palette is linear greyscale
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"
break
if mode == "P":
@ -103,13 +109,14 @@ class PcxImageFile(ImageFile.ImageFile):
raise IOError("unknown PCX 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
logger.debug("size: %sx%s", *self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
# --------------------------------------------------------------------
# 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
# gets overwritten.
logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
im.size[0], bits, stride)
logger.debug(
"PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
im.size[0],
bits,
stride,
)
# under windows, we could determine the current screen size with
# "Image.core.display_mode()[1]", but I think that's overkill...
@ -150,17 +161,30 @@ def _save(im, fp, filename):
# PCX header
fp.write(
o8(10) + o8(version) + o8(1) + o8(bits) + 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
o8(10)
+ o8(version)
+ o8(1)
+ o8(bits)
+ 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
ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0,
(rawmode, bits*planes))])
ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))])
if im.mode == "P":
# colour palette
@ -170,7 +194,8 @@ def _save(im, fp, filename):
# greyscale palette
fp.write(o8(12))
for i in range(256):
fp.write(o8(i)*3)
fp.write(o8(i) * 3)
# --------------------------------------------------------------------
# registry

View File

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

View File

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

View File

@ -30,6 +30,7 @@ __version__ = "0.1"
#
# helpers
def _accept(prefix):
return prefix[:4] == b"\200\350\000\000"
@ -37,6 +38,7 @@ def _accept(prefix):
##
# Image plugin for PIXAR raster images.
class PixarImageFile(ImageFile.ImageFile):
format = "PIXAR"
@ -62,7 +64,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued...
# 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

@ -77,7 +77,7 @@ _MODES = {
}
_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.
# Eliminates decompression bombs where compressed chunks can expand 1000x
@ -95,14 +95,14 @@ def _safe_zlib_decompress(s):
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.
class ChunkStream(object):
class ChunkStream(object):
def __init__(self, fp):
self.fp = fp
@ -144,7 +144,7 @@ class ChunkStream(object):
"""Call the appropriate chunk handler"""
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):
"""Read and verify checksum"""
@ -160,11 +160,9 @@ class ChunkStream(object):
crc1 = _crc32(data, _crc32(cid))
crc2 = i32(self.fp.read(4))
if crc1 != crc2:
raise SyntaxError("broken PNG file (bad header checksum in %r)"
% cid)
raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid)
except struct.error:
raise SyntaxError("broken PNG file (incomplete checksum in %r)"
% cid)
raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid)
def crc_skip(self, cid, data):
"""Read checksum. Used if the C module is not present"""
@ -198,6 +196,7 @@ class iTXt(str):
keeping their extra information
"""
@staticmethod
def __new__(cls, text, lang=None, tkey=None):
"""
@ -253,11 +252,12 @@ class PngInfo(object):
tkey = tkey.encode("utf-8", "strict")
if zip:
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
zlib.compress(value))
self.add(
b"iTXt",
key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value),
)
else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" +
value)
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
def add_text(self, key, value, zip=False):
"""Appends a text chunk.
@ -274,12 +274,12 @@ class PngInfo(object):
# The tEXt chunk stores latin-1 text
if not isinstance(value, bytes):
try:
value = value.encode('latin-1', 'strict')
value = value.encode("latin-1", "strict")
except UnicodeError:
return self.add_itxt(key, value, zip=zip)
if not isinstance(key, bytes):
key = key.encode('latin-1', 'strict')
key = key.encode("latin-1", "strict")
if zip:
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
@ -290,8 +290,8 @@ class PngInfo(object):
# --------------------------------------------------------------------
# PNG image stream (IHDR/IEND)
class PngStream(ChunkStream):
class PngStream(ChunkStream):
def __init__(self, fp):
ChunkStream.__init__(self, fp)
@ -310,8 +310,10 @@ class PngStream(ChunkStream):
def check_text_memory(self, chunklen):
self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY:
raise ValueError("Too much memory used in text chunks: "
"%s>MAX_TEXT_MEMORY" % self.text_memory)
raise ValueError(
"Too much memory used in text chunks: %s>MAX_TEXT_MEMORY"
% self.text_memory
)
def chunk_iCCP(self, pos, length):
@ -327,10 +329,11 @@ class PngStream(ChunkStream):
logger.debug("Compression method %s", i8(s[i]))
comp_method = i8(s[i])
if comp_method != 0:
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
comp_method)
raise SyntaxError(
"Unknown compression method %s in iCCP chunk" % comp_method
)
try:
icc_profile = _safe_zlib_decompress(s[i+2:])
icc_profile = _safe_zlib_decompress(s[i + 2 :])
except ValueError:
if ImageFile.LOAD_TRUNCATED_IMAGES:
icc_profile = None
@ -359,7 +362,7 @@ class PngStream(ChunkStream):
def chunk_IDAT(self, pos, length):
# 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
raise EOFError
@ -408,8 +411,8 @@ class PngStream(ChunkStream):
# WP x,y, Red x,y, Green x,y Blue x,y
s = ImageFile._safe_read(self.fp, length)
raw_vals = struct.unpack('>%dI' % (len(s) // 4), s)
self.im_info['chromaticity'] = tuple(elt/100000.0 for elt in raw_vals)
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
return s
def chunk_sRGB(self, pos, length):
@ -420,7 +423,7 @@ class PngStream(ChunkStream):
# 3 absolute colorimetric
s = ImageFile._safe_read(self.fp, length)
self.im_info['srgb'] = i8(s)
self.im_info["srgb"] = i8(s)
return s
def chunk_pHYs(self, pos, length):
@ -448,8 +451,8 @@ class PngStream(ChunkStream):
v = b""
if k:
if py3:
k = k.decode('latin-1', 'strict')
v = v.decode('latin-1', 'replace')
k = k.decode("latin-1", "strict")
v = v.decode("latin-1", "replace")
self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
@ -470,8 +473,9 @@ class PngStream(ChunkStream):
else:
comp_method = 0
if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
comp_method)
raise SyntaxError(
"Unknown compression method %s in zTXt chunk" % comp_method
)
try:
v = _safe_zlib_decompress(v[1:])
except ValueError:
@ -484,8 +488,8 @@ class PngStream(ChunkStream):
if k:
if py3:
k = k.decode('latin-1', 'strict')
v = v.decode('latin-1', 'replace')
k = k.decode("latin-1", "strict")
v = v.decode("latin-1", "replace")
self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
@ -536,19 +540,20 @@ class PngStream(ChunkStream):
def chunk_eXIf(self, pos, 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
# APNG chunks
def chunk_acTL(self, pos, length):
s = ImageFile._safe_read(self.fp, length)
self.im_custom_mimetype = 'image/apng'
self.im_custom_mimetype = "image/apng"
return s
# --------------------------------------------------------------------
# PNG reader
def _accept(prefix):
return prefix[:8] == _MAGIC
@ -556,6 +561,7 @@ def _accept(prefix):
##
# Image plugin for PNG images.
class PngImageFile(ImageFile.ImageFile):
format = "PNG"
@ -711,20 +717,20 @@ class PngImageFile(ImageFile.ImageFile):
_OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'),
"L;1": ("L;1", b'\x01\x00'),
"L;2": ("L;2", b'\x02\x00'),
"L;4": ("L;4", b'\x04\x00'),
"L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'),
"I;16": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'),
"P": ("P", b'\x08\x03'),
"RGB": ("RGB", b'\x08\x02'),
"RGBA": ("RGBA", b'\x08\x06'),
"1": ("1", b"\x01\x00"),
"L;1": ("L;1", b"\x01\x00"),
"L;2": ("L;2", b"\x02\x00"),
"L;4": ("L;4", b"\x04\x00"),
"L": ("L", b"\x08\x00"),
"LA": ("LA", b"\x08\x04"),
"I": ("I;16B", b"\x10\x00"),
"I;16": ("I;16B", b"\x10\x00"),
"P;1": ("P;1", b"\x01\x03"),
"P;2": ("P;2", b"\x02\x03"),
"P;4": ("P;4", b"\x04\x03"),
"P": ("P", b"\x08\x03"),
"RGB": ("RGB", b"\x08\x02"),
"RGBA": ("RGBA", b"\x08\x06"),
}
@ -765,7 +771,7 @@ def _save(im, fp, filename, chunk=putchunk):
else:
# check palette contents
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:
colors = 256
@ -781,10 +787,12 @@ def _save(im, fp, filename, chunk=putchunk):
mode = "%s;%d" % (mode, bits)
# encoder options
im.encoderconfig = (im.encoderinfo.get("optimize", False),
im.encoderconfig = (
im.encoderinfo.get("optimize", False),
im.encoderinfo.get("compress_level", -1),
im.encoderinfo.get("compress_type", -1),
im.encoderinfo.get("dictionary", b""))
im.encoderinfo.get("dictionary", b""),
)
# get the corresponding PNG mode
try:
@ -797,12 +805,16 @@ def _save(im, fp, filename, chunk=putchunk):
fp.write(_MAGIC)
chunk(fp, b"IHDR",
o32(im.size[0]), o32(im.size[1]), # 0: size
chunk(
fp,
b"IHDR",
o32(im.size[0]), # 0: size
o32(im.size[1]),
mode, # 8: depth/type
b'\0', # 10: compression
b'\0', # 11: filter category
b'\0') # 12: interlace flag
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"]
@ -836,21 +848,20 @@ def _save(im, fp, filename, chunk=putchunk):
palette_byte_number = (2 ** bits) * 3
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
while len(palette_bytes) < palette_byte_number:
palette_bytes += b'\0'
palette_bytes += b"\0"
chunk(fp, b"PLTE", palette_bytes)
transparency = im.encoderinfo.get('transparency',
im.info.get('transparency', None))
transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None))
if transparency or transparency == 0:
if im.mode == "P":
# limit to actual palette size
alpha_bytes = 2**bits
alpha_bytes = 2 ** bits
if isinstance(transparency, bytes):
chunk(fp, b"tRNS", transparency[:alpha_bytes])
else:
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])
elif im.mode in ("1", "L", "I"):
transparency = max(0, min(65535, transparency))
@ -866,15 +877,18 @@ def _save(im, fp, filename, chunk=putchunk):
else:
if im.mode == "P" and im.im.getpalettemode() == "RGBA":
alpha = im.im.getpalette("RGBA", "A")
alpha_bytes = 2**bits
alpha_bytes = 2 ** bits
chunk(fp, b"tRNS", alpha[:alpha_bytes])
dpi = im.encoderinfo.get("dpi")
if dpi:
chunk(fp, b"pHYs",
chunk(
fp,
b"pHYs",
o32(int(dpi[0] / 0.0254 + 0.5)),
o32(int(dpi[1] / 0.0254 + 0.5)),
b'\x01')
b"\x01",
)
info = im.encoderinfo.get("pnginfo")
if info:
@ -892,8 +906,7 @@ def _save(im, fp, filename, chunk=putchunk):
exif = exif[6:]
chunk(fp, b"eXIf", exif)
ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)])
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
chunk(fp, b"IEND", b"")
@ -904,6 +917,7 @@ def _save(im, fp, filename, chunk=putchunk):
# --------------------------------------------------------------------
# PNG chunk converter
def getchunks(im, **params):
"""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 = {
# standard
@ -36,7 +36,7 @@ MODES = {
# PIL extensions (for test purposes only)
b"PyP": "P",
b"PyRGBA": "RGBA",
b"PyCMYK": "CMYK"
b"PyCMYK": "CMYK",
}
@ -47,6 +47,7 @@ def _accept(prefix):
##
# Image plugin for PBM, PGM, and PPM images.
class PpmImageFile(ImageFile.ImageFile):
format = "PPM"
@ -57,10 +58,10 @@ class PpmImageFile(ImageFile.ImageFile):
c = self.fp.read(1)
if not c or c in b_whitespace:
break
if c > b'\x79':
if c > b"\x79":
raise ValueError("Expected ASCII value, found binary")
s = s + c
if (len(s) > 9):
if len(s) > 9:
raise ValueError("Expected int, got > 9 digits")
return s
@ -92,8 +93,7 @@ class PpmImageFile(ImageFile.ImageFile):
if s not in b_whitespace:
break
if s == b"":
raise ValueError(
"File does not extend beyond magic number")
raise ValueError("File does not extend beyond magic number")
if s != b"#":
break
s = self.fp.readline()
@ -107,32 +107,30 @@ class PpmImageFile(ImageFile.ImageFile):
elif ix == 2:
# maxgrey
if s > 255:
if not mode == 'L':
if not mode == "L":
raise ValueError("Too many colors for band: %s" % s)
if s < 2**16:
self.mode = 'I'
rawmode = 'I;16B'
if s < 2 ** 16:
self.mode = "I"
rawmode = "I;16B"
else:
self.mode = 'I'
rawmode = 'I;32B'
self.mode = "I"
rawmode = "I;32B"
self._size = xsize, ysize
self.tile = [("raw",
(0, 0, xsize, ysize),
self.fp.tell(),
(rawmode, 0, 1))]
self.tile = [("raw", (0, 0, xsize, ysize), self.fp.tell(), (rawmode, 0, 1))]
#
# --------------------------------------------------------------------
def _save(im, fp, filename):
if im.mode == "1":
rawmode, head = "1;I", b"P4"
elif im.mode == "L":
rawmode, head = "L", b"P5"
elif im.mode == "I":
if im.getextrema()[1] < 2**16:
if im.getextrema()[1] < 2 ** 16:
rawmode, head = "I;16B", b"P5"
else:
rawmode, head = "I;32B", b"P5"
@ -142,7 +140,7 @@ def _save(im, fp, filename):
rawmode, head = "RGB", b"P6"
else:
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":
fp.write(b"255\n")
if head == b"P5":
@ -152,11 +150,12 @@ def _save(im, fp, filename):
fp.write(b"65535\n")
elif rawmode == "I;32B":
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
# im._dump(filename)
#
# --------------------------------------------------------------------

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ import sys
def isInt(f):
try:
i = int(f)
if f-i == 0:
if f - i == 0:
return 1
else:
return 0
@ -60,6 +60,7 @@ iforms = [1, 3, -11, -12, -21, -22]
# Returns no. of bytes in the header, if it is a valid Spider header,
# otherwise returns 0
def isSpiderHeader(t):
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
@ -81,12 +82,12 @@ def isSpiderHeader(t):
def isSpiderImage(filename):
with open(filename, 'rb') as fp:
with open(filename, "rb") as fp:
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)
if hdrlen == 0:
t = struct.unpack('<23f', f) # little-endian
t = struct.unpack("<23f", f) # little-endian
hdrlen = isSpiderHeader(t)
return hdrlen
@ -104,11 +105,11 @@ class SpiderImageFile(ImageFile.ImageFile):
try:
self.bigendian = 1
t = struct.unpack('>27f', f) # try big-endian first
t = struct.unpack(">27f", f) # try big-endian first
hdrlen = isSpiderHeader(t)
if hdrlen == 0:
self.bigendian = 0
t = struct.unpack('<27f', f) # little-endian
t = struct.unpack("<27f", f) # little-endian
hdrlen = isSpiderHeader(t)
if hdrlen == 0:
raise SyntaxError("not a valid Spider file")
@ -149,9 +150,7 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F"
self.mode = "F"
self.tile = [
("raw", (0, 0) + self.size, offset,
(self.rawmode, 0, 1))]
self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
@property
@ -184,13 +183,14 @@ class SpiderImageFile(ImageFile.ImageFile):
(minimum, maximum) = self.getextrema()
m = 1
if maximum != minimum:
m = depth / (maximum-minimum)
m = depth / (maximum - minimum)
b = -m * minimum
return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
def tkPhotoImage(self):
from PIL import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
def _close__fp(self):
@ -223,7 +223,7 @@ def loadImageSeries(filelist=None):
if not isSpiderImage(img):
print(img + " is not a Spider image file")
continue
im.info['filename'] = img
im.info["filename"] = img
imglist.append(im)
return imglist
@ -231,6 +231,7 @@ def loadImageSeries(filelist=None):
# --------------------------------------------------------------------
# For saving images in Spider format
def makeSpiderHeader(im):
nsam, nrow = im.size
lenbyt = nsam * 4 # There are labrec records in the header
@ -261,13 +262,13 @@ def makeSpiderHeader(im):
# pack binary data into a string
hdrstr = []
for v in hdr:
hdrstr.append(struct.pack('f', v))
hdrstr.append(struct.pack("f", v))
return hdrstr
def _save(im, fp, filename):
if im.mode[0] != "F":
im = im.convert('F')
im = im.convert("F")
hdr = makeSpiderHeader(im)
if len(hdr) < 256:
@ -277,7 +278,7 @@ def _save(im, fp, filename):
fp.writelines(hdr)
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):
@ -286,6 +287,7 @@ def _save_spider(im, fp, filename):
Image.register_extension(SpiderImageFile.format, ext)
_save(im, fp, filename)
# --------------------------------------------------------------------
@ -308,7 +310,7 @@ if __name__ == "__main__":
print("format: " + str(im.format))
print("size: " + str(im.size))
print("mode: " + str(im.mode))
print("max, min: ", end=' ')
print("max, min: ", end=" ")
print(im.getextrema())
if len(sys.argv) > 2:
@ -317,6 +319,7 @@ if __name__ == "__main__":
# perform some image operation
im = im.transpose(Image.FLIP_LEFT_RIGHT)
print(
"saving a flipped version of %s as %s " %
(os.path.basename(filename), outfile))
"saving a flipped version of %s as %s "
% (os.path.basename(filename), outfile)
)
im.save(outfile, SpiderImageFile.format)

View File

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

View File

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

View File

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

View File

@ -153,7 +153,6 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (1,), ()): ("1", "1"),
(II, 1, (1,), 2, (1,), ()): ("1", "1;R"),
(MM, 1, (1,), 2, (1,), ()): ("1", "1;R"),
(II, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
(MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
(II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
@ -162,7 +161,6 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (2,), ()): ("L", "L;2"),
(II, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
(MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
(II, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
(MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
(II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
@ -171,7 +169,6 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (4,), ()): ("L", "L;4"),
(II, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
(MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
(II, 0, (1,), 1, (8,), ()): ("L", "L;I"),
(MM, 0, (1,), 1, (8,), ()): ("L", "L;I"),
(II, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
@ -180,14 +177,11 @@ OPEN_INFO = {
(MM, 1, (1,), 1, (8,), ()): ("L", "L"),
(II, 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, (16,), ()): ("I;16", "I;16"),
(MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
(II, 1, (2,), 1, (16,), ()): ("I", "I;16S"),
(MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"),
(II, 0, (3,), 1, (32,), ()): ("F", "F;32F"),
(MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"),
(II, 1, (1,), 1, (32,), ()): ("I", "I;32N"),
@ -195,10 +189,8 @@ OPEN_INFO = {
(MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"),
(II, 1, (3,), 1, (32,), ()): ("F", "F;32F"),
(MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"),
(II, 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"),
(MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
(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"),
(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
(II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"),
(MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"),
(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"),
(II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"),
(MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"),
(II, 3, (1,), 1, (1,), ()): ("P", "P;1"),
(MM, 3, (1,), 1, (1,), ()): ("P", "P;1"),
(II, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
@ -255,21 +245,17 @@ OPEN_INFO = {
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(II, 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"),
(MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(II, 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"),
(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"),
# JPEG compressed images handled by LibTiff and auto-converted to RGBX
# Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
(II, 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"),
(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):
"""
@ -339,7 +325,7 @@ class IFDRational(Rational):
return
if denominator == 0:
self._val = float('nan')
self._val = float("nan")
return
elif denominator == 1:
@ -380,6 +366,7 @@ class IFDRational(Rational):
def _delegate(op):
def delegate(self, *args):
return getattr(self._val, op)(*args)
return delegate
""" 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))
"""
__add__ = _delegate('__add__')
__radd__ = _delegate('__radd__')
__sub__ = _delegate('__sub__')
__rsub__ = _delegate('__rsub__')
__div__ = _delegate('__div__')
__rdiv__ = _delegate('__rdiv__')
__mul__ = _delegate('__mul__')
__rmul__ = _delegate('__rmul__')
__truediv__ = _delegate('__truediv__')
__rtruediv__ = _delegate('__rtruediv__')
__floordiv__ = _delegate('__floordiv__')
__rfloordiv__ = _delegate('__rfloordiv__')
__mod__ = _delegate('__mod__')
__rmod__ = _delegate('__rmod__')
__pow__ = _delegate('__pow__')
__rpow__ = _delegate('__rpow__')
__pos__ = _delegate('__pos__')
__neg__ = _delegate('__neg__')
__abs__ = _delegate('__abs__')
__trunc__ = _delegate('__trunc__')
__lt__ = _delegate('__lt__')
__gt__ = _delegate('__gt__')
__le__ = _delegate('__le__')
__ge__ = _delegate('__ge__')
__nonzero__ = _delegate('__nonzero__')
__ceil__ = _delegate('__ceil__')
__floor__ = _delegate('__floor__')
__round__ = _delegate('__round__')
__add__ = _delegate("__add__")
__radd__ = _delegate("__radd__")
__sub__ = _delegate("__sub__")
__rsub__ = _delegate("__rsub__")
__div__ = _delegate("__div__")
__rdiv__ = _delegate("__rdiv__")
__mul__ = _delegate("__mul__")
__rmul__ = _delegate("__rmul__")
__truediv__ = _delegate("__truediv__")
__rtruediv__ = _delegate("__rtruediv__")
__floordiv__ = _delegate("__floordiv__")
__rfloordiv__ = _delegate("__rfloordiv__")
__mod__ = _delegate("__mod__")
__rmod__ = _delegate("__rmod__")
__pow__ = _delegate("__pow__")
__rpow__ = _delegate("__rpow__")
__pos__ = _delegate("__pos__")
__neg__ = _delegate("__neg__")
__abs__ = _delegate("__abs__")
__trunc__ = _delegate("__trunc__")
__lt__ = _delegate("__lt__")
__gt__ = _delegate("__gt__")
__le__ = _delegate("__le__")
__ge__ = _delegate("__ge__")
__nonzero__ = _delegate("__nonzero__")
__ceil__ = _delegate("__ceil__")
__floor__ = _delegate("__floor__")
__round__ = _delegate("__round__")
class ImageFileDirectory_v2(MutableMapping):
@ -451,6 +438,7 @@ class ImageFileDirectory_v2(MutableMapping):
.. versionadded:: 3.0.0
"""
"""
Documentation:
@ -536,13 +524,14 @@ class ImageFileDirectory_v2(MutableMapping):
self[tag] = handler(self, data, self.legacy_api) # check type
val = self._tags_v2[tag]
if self.legacy_api and not isinstance(val, (tuple, bytes)):
val = val,
val = (val,)
return val
def __contains__(self, tag):
return tag in self._tags_v2 or tag in self._tagdata
if not py3:
def has_key(self, tag):
return tag in self
@ -552,7 +541,7 @@ class ImageFileDirectory_v2(MutableMapping):
def _setitem(self, tag, value, legacy_api):
basetypes = (Number, bytes, str)
if not py3:
basetypes += unicode, # noqa: F821
basetypes += (unicode,) # noqa: F821
info = TiffTags.lookup(tag)
values = [value] if isinstance(value, basetypes) else value
@ -580,11 +569,11 @@ class ImageFileDirectory_v2(MutableMapping):
self.tagtype[tag] = TiffTags.ASCII
if self.tagtype[tag] == TiffTags.UNDEFINED and py3:
values = [value.encode("ascii", 'replace') if isinstance(
value, str) else value]
values = [
value.encode("ascii", "replace") if isinstance(value, str) else value
]
elif self.tagtype[tag] == TiffTags.RATIONAL:
values = [float(v) if isinstance(v, int) else v
for v in values]
values = [float(v) if isinstance(v, int) else v for v 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.
# No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
# Don't mess with the legacy api, since it's frozen.
if (info.length == 1) or \
(info.length is None and len(values) == 1 and not legacy_api):
if (info.length == 1) or (
info.length is None and len(values) == 1 and not legacy_api
):
# Don't mess with the legacy api, since it's frozen.
if legacy_api and self.tagtype[tag] in [
TiffTags.RATIONAL,
TiffTags.SIGNED_RATIONAL
TiffTags.SIGNED_RATIONAL,
]: # rationals
values = values,
values = (values,)
try:
dest[tag], = values
except ValueError:
# We've got a builtin tag with 1 expected entry
warnings.warn(
"Metadata Warning, tag %s had too many entries: "
"%s, expected 1" % (
tag, len(values)))
"Metadata Warning, tag %s had too many entries: %s, expected 1"
% (tag, len(values))
)
dest[tag] = values[0]
else:
@ -635,36 +625,51 @@ class ImageFileDirectory_v2(MutableMapping):
def _register_loader(idx, size):
def decorator(func):
from .TiffTags import TYPES
if func.__name__.startswith("load_"):
TYPES[idx] = func.__name__[5:].replace("_", " ")
_load_dispatch[idx] = size, func # noqa: F821
return func
return decorator
def _register_writer(idx):
def decorator(func):
_write_dispatch[idx] = func # noqa: F821
return func
return decorator
def _register_basic(idx_fmt_name):
from .TiffTags import TYPES
idx, fmt, name = idx_fmt_name
TYPES[idx] = name
size = struct.calcsize("=" + fmt)
_load_dispatch[idx] = size, lambda self, data, legacy_api=True: ( # noqa: F821
self._unpack("{}{}".format(len(data) // size, fmt), data))
_load_dispatch[idx] = ( # noqa: F821
size,
lambda self, data, legacy_api=True: (
self._unpack("{}{}".format(len(data) // size, fmt), data)
),
)
_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,
[(TiffTags.SHORT, "H", "short"),
list(
map(
_register_basic,
[
(TiffTags.SHORT, "H", "short"),
(TiffTags.LONG, "L", "long"),
(TiffTags.SIGNED_BYTE, "b", "signed byte"),
(TiffTags.SIGNED_SHORT, "h", "signed short"),
(TiffTags.SIGNED_LONG, "l", "signed long"),
(TiffTags.FLOAT, "f", "float"),
(TiffTags.DOUBLE, "d", "double")]))
(TiffTags.DOUBLE, "d", "double"),
],
)
)
@_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True):
@ -684,21 +689,23 @@ class ImageFileDirectory_v2(MutableMapping):
def write_string(self, value):
# remerge of https://github.com/python-pillow/Pillow/pull/1416
if sys.version_info.major == 2:
value = value.decode('ascii', 'replace')
return b"" + value.encode('ascii', 'replace') + b"\0"
value = value.decode("ascii", "replace")
return b"" + value.encode("ascii", "replace") + b"\0"
@_register_loader(5, 8)
def load_rational(self, data, legacy_api=True):
vals = self._unpack("{}L".format(len(data) // 4), data)
def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom)
for num, denom in zip(vals[::2], vals[1::2]))
def combine(a, b):
return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(5)
def write_rational(self, *values):
return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 31))
for frac in values)
return b"".join(
self._pack("2L", *_limit_rational(frac, 2 ** 31)) for frac in values
)
@_register_loader(7, 1)
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):
vals = self._unpack("{}l".format(len(data) // 4), data)
def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom)
for num, denom in zip(vals[::2], vals[1::2]))
def combine(a, b):
return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(10)
def write_signed_rational(self, *values):
return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 30))
for frac in values)
return b"".join(
self._pack("2L", *_limit_rational(frac, 2 ** 30)) for frac in values
)
def _ensure_read(self, fp, size):
ret = fp.read(size)
if len(ret) != size:
raise IOError("Corrupt EXIF data. " +
"Expecting to read %d bytes but only got %d. " %
(size, len(ret)))
raise IOError(
"Corrupt EXIF data. "
+ "Expecting to read %d bytes but only got %d. " % (size, len(ret))
)
return ret
def load(self, fp):
@ -736,13 +746,14 @@ class ImageFileDirectory_v2(MutableMapping):
try:
for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]):
tag, typ, count, data = self._unpack("HHL4s",
self._ensure_read(fp, 12))
tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12))
if DEBUG:
tagname = TiffTags.lookup(tag).name
typname = TYPES.get(typ, "unknown")
print("tag: %s (%d) - type: %s (%d)" %
(tagname, tag, typname, typ), end=" ")
print(
"tag: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ),
end=" ",
)
try:
unit_size, handler = self._load_dispatch[typ]
@ -755,8 +766,10 @@ class ImageFileDirectory_v2(MutableMapping):
here = fp.tell()
offset, = self._unpack("L", data)
if DEBUG:
print("Tag Location: %s - Data Location: %s" %
(here, offset), end=" ")
print(
"Tag Location: %s - Data Location: %s" % (here, offset),
end=" ",
)
fp.seek(offset)
data = ImageFile._safe_read(fp, size)
fp.seek(here)
@ -764,9 +777,11 @@ class ImageFileDirectory_v2(MutableMapping):
data = data[:size]
if len(data) != size:
warnings.warn("Possibly corrupt EXIF data. "
warnings.warn(
"Possibly corrupt EXIF data. "
"Expecting to read %d bytes but only got %d."
" Skipping tag %s" % (size, len(data), tag))
" Skipping tag %s" % (size, len(data), tag)
)
continue
if not data:
@ -807,8 +822,10 @@ class ImageFileDirectory_v2(MutableMapping):
if DEBUG:
tagname = TiffTags.lookup(tag).name
typname = TYPES.get(typ, "unknown")
print("save: %s (%d) - type: %s (%d)" %
(tagname, tag, typname, typ), end=" ")
print(
"save: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ),
end=" ",
)
if len(data) >= 16:
print("- value: <table: %d bytes>" % len(data))
else:
@ -823,16 +840,14 @@ class ImageFileDirectory_v2(MutableMapping):
if len(data) <= 4:
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
else:
entries.append((tag, typ, count, self._pack("L", offset),
data))
entries.append((tag, typ, count, self._pack("L", offset), data))
offset += (len(data) + 1) // 2 * 2 # pad to word
# update strip offset data to point beyond auxiliary data
if stripoffsets is not None:
tag, typ, count, value, data = entries[stripoffsets]
if data:
raise NotImplementedError(
"multistrip support not yet implemented")
raise NotImplementedError("multistrip support not yet implemented")
value = self._pack("L", self._unpack("L", value)[0] + offset)
entries[stripoffsets] = tag, typ, count, value, data
@ -893,6 +908,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
.. deprecated:: 3.0.0
"""
def __init__(self, *args, **kwargs):
ImageFileDirectory_v2.__init__(self, *args, **kwargs)
self._legacy_api = True
@ -957,7 +973,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
self._setitem(tag, handler(self, data, legacy), legacy)
val = self._tags_v1[tag]
if not isinstance(val, (tuple, bytes)):
val = val,
val = (val,)
return val
@ -968,6 +984,7 @@ ImageFileDirectory = ImageFileDirectory_v1
##
# Image plugin for TIFF files.
class TiffImageFile(ImageFile.ImageFile):
format = "TIFF"
@ -1032,9 +1049,10 @@ class TiffImageFile(ImageFile.ImageFile):
if not self.__next:
raise EOFError("no more images in TIFF file")
if DEBUG:
print("Seeking to frame %s, on frame %s, "
"__next %s, location: %s" %
(frame, self.__frame, self.__next, self.fp.tell()))
print(
"Seeking to frame %s, on frame %s, __next %s, location: %s"
% (frame, self.__frame, self.__next, self.fp.tell())
)
# reset python3 buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()
@ -1067,9 +1085,9 @@ class TiffImageFile(ImageFile.ImageFile):
@size.setter
def size(self, value):
warnings.warn(
'Setting the size of a TIFF image directly is deprecated, and will'
' be removed in a future version. Use the resize method instead.',
DeprecationWarning
"Setting the size of a TIFF image directly is deprecated, and will"
" be removed in a future version. Use the resize method instead.",
DeprecationWarning,
)
self._size = value
@ -1124,8 +1142,9 @@ class TiffImageFile(ImageFile.ImageFile):
if fp:
args[2] = fp
decoder = Image._getdecoder(self.mode, 'libtiff', tuple(args),
self.decoderconfig)
decoder = Image._getdecoder(
self.mode, "libtiff", tuple(args), self.decoderconfig
)
try:
decoder.setimage(self.im, extents)
except ValueError:
@ -1207,8 +1226,7 @@ class TiffImageFile(ImageFile.ImageFile):
print("- size:", self.size)
sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
if (len(sampleFormat) > 1
and max(sampleFormat) == min(sampleFormat) == 1):
if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:
# SAMPLEFORMAT is properly per band, so an RGB image will
# be (1,1,1). But, we don't support per band pixel types,
# 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
# mode: check photometric interpretation and bits per pixel
key = (self.tag_v2.prefix, photo, sampleFormat, fillorder,
bps_tuple, extra_tuple)
key = (
self.tag_v2.prefix,
photo,
sampleFormat,
fillorder,
bps_tuple,
extra_tuple,
)
if DEBUG:
print("format key:", key)
try:
@ -1268,7 +1292,7 @@ class TiffImageFile(ImageFile.ImageFile):
# build tile descriptors
x = y = layer = 0
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:
# Decoder expects entire file as one tile.
# 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
# contains I;16, we need to convert from native to image
# byte order.
if rawmode == 'I;16':
rawmode = 'I;16N'
if ';16B' in rawmode:
rawmode = rawmode.replace(';16B', ';16N')
if ';16L' in rawmode:
rawmode = rawmode.replace(';16L', ';16N')
if rawmode == "I;16":
rawmode = "I;16N"
if ";16B" in rawmode:
rawmode = rawmode.replace(";16B", ";16N")
if ";16L" in rawmode:
rawmode = rawmode.replace(";16L", ";16N")
# Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds
a = (rawmode, self._compression, False)
self.tile.append(
(self._compression,
(0, 0, xsize, ysize),
0, a))
self.tile.append((self._compression, (0, 0, xsize, ysize), 0, a))
elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
# striped image
@ -1337,9 +1358,13 @@ class TiffImageFile(ImageFile.ImageFile):
a = (tile_rawmode, int(stride), 1)
self.tile.append(
(self._compression,
(x, y, min(x+w, xsize), min(y+h, ysize)),
offset, a))
(
self._compression,
(x, y, min(x + w, xsize), min(y + h, ysize)),
offset,
a,
)
)
x = x + w
if x >= self.size[0]:
x, y = 0, y + h
@ -1353,7 +1378,7 @@ class TiffImageFile(ImageFile.ImageFile):
# Fix up info.
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
@ -1396,7 +1421,6 @@ SAVE_INFO = {
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (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)
compression = im.encoderinfo.get('compression', im.info.get('compression'))
compression = im.encoderinfo.get("compression", im.info.get("compression"))
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
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
ifd[PLANAR_CONFIGURATION] = getattr(im, "_planar_configuration", 1)
ifd[IMAGEWIDTH] = im.size[0]
ifd[IMAGELENGTH] = im.size[1]
@ -1440,10 +1464,16 @@ def _save(im, fp, filename):
# additions written by Greg Couch, gregc@cgl.ucsf.edu
# 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
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
for key in (
RESOLUTION_UNIT,
X_RESOLUTION,
Y_RESOLUTION,
IPTC_NAA_CHUNK,
PHOTOSHOP_CHUNK,
XMP,
):
if key in im.tag_v2:
ifd[key] = im.tag_v2[key]
ifd.tagtype[key] = im.tag_v2.tagtype[key]
@ -1453,7 +1483,8 @@ def _save(im, fp, filename):
if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"]
for key, name in [(IMAGEDESCRIPTION, "description"),
for key, name in [
(IMAGEDESCRIPTION, "description"),
(X_RESOLUTION, "resolution"),
(Y_RESOLUTION, "resolution"),
(X_RESOLUTION, "x_resolution"),
@ -1462,7 +1493,8 @@ def _save(im, fp, filename):
(SOFTWARE, "software"),
(DATE_TIME, "date_time"),
(ARTIST, "artist"),
(COPYRIGHT, "copyright")]:
(COPYRIGHT, "copyright"),
]:
if name in im.encoderinfo:
ifd[key] = im.encoderinfo[name]
@ -1487,7 +1519,7 @@ def _save(im, fp, filename):
lut = im.im.getpalette("RGB", "RGB;L")
ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut)
# 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[STRIPBYTECOUNTS] = stride * im.size[1]
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
# save(load('')) == original file.
legacy_ifd = {}
if hasattr(im, 'tag'):
if hasattr(im, "tag"):
legacy_ifd = im.tag.to_v2()
for tag, value in itertools.chain(ifd.items(),
getattr(im, 'tag_v2', {}).items(),
legacy_ifd.items()):
for tag, value in itertools.chain(
ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items()
):
# Libtiff can only process certain core items without adding
# them to the custom dictionary.
# 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 TiffTags.lookup(tag).type == TiffTags.UNDEFINED:
continue
if (distutils.version.StrictVersion(_libtiff_version()) <
distutils.version.StrictVersion("4.0")) \
or not (isinstance(value, (int, float, str, bytes)) or
(not py3 and isinstance(value, unicode))): # noqa: F821
if (
distutils.version.StrictVersion(_libtiff_version())
< distutils.version.StrictVersion("4.0")
) or not (
isinstance(value, (int, float, str, bytes))
or (not py3 and isinstance(value, unicode)) # noqa: F821
):
continue
if tag not in atts and tag not in blocklist:
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):
atts[tag] = float(value)
else:
@ -1548,15 +1583,15 @@ def _save(im, fp, filename):
# we're storing image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image
# byte order.
if im.mode in ('I;16B', 'I;16'):
rawmode = 'I;16N'
if im.mode in ("I;16B", "I;16"):
rawmode = "I;16N"
a = (rawmode, compression, _fp, filename, atts)
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
e.setimage(im.im, (0, 0)+im.size)
e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig)
e.setimage(im.im, (0, 0) + im.size)
while True:
# undone, change to self.decodermaxblock:
l, s, d = e.encode(16*1024)
l, s, d = e.encode(16 * 1024)
if not _fp:
fp.write(d)
if s:
@ -1567,9 +1602,9 @@ def _save(im, fp, filename):
else:
offset = ifd.save(fp)
ImageFile._save(im, fp, [
("raw", (0, 0)+im.size, offset, (rawmode, stride, 1))
])
ImageFile._save(
im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
)
# -- helper for multi-page save --
if "_debug_multipage" in im.encoderinfo:
@ -1603,7 +1638,7 @@ class AppendingTiffWriter:
Tags = {273, 288, 324, 519, 520, 521}
def __init__(self, fn, new=False):
if hasattr(fn, 'read'):
if hasattr(fn, "read"):
self.f = fn
self.close_fp = False
else:
@ -1654,8 +1689,7 @@ class AppendingTiffWriter:
return
if IIMM != self.IIMM:
raise RuntimeError("IIMM of new page doesn't match IIMM of "
"first page")
raise RuntimeError("IIMM of new page doesn't match IIMM of first page")
IFDoffset = self.readLong()
IFDoffset += self.offsetOfNewPage
@ -1729,34 +1763,29 @@ class AppendingTiffWriter:
self.f.seek(-2, os.SEEK_CUR)
bytesWritten = self.f.write(struct.pack(self.longFmt, value))
if bytesWritten is not None and bytesWritten != 4:
raise RuntimeError("wrote only %u bytes but wanted 4" %
bytesWritten)
raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten)
def rewriteLastShort(self, value):
self.f.seek(-2, os.SEEK_CUR)
bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
if bytesWritten is not None and bytesWritten != 2:
raise RuntimeError("wrote only %u bytes but wanted 2" %
bytesWritten)
raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten)
def rewriteLastLong(self, value):
self.f.seek(-4, os.SEEK_CUR)
bytesWritten = self.f.write(struct.pack(self.longFmt, value))
if bytesWritten is not None and bytesWritten != 4:
raise RuntimeError("wrote only %u bytes but wanted 4" %
bytesWritten)
raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten)
def writeShort(self, value):
bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
if bytesWritten is not None and bytesWritten != 2:
raise RuntimeError("wrote only %u bytes but wanted 2" %
bytesWritten)
raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten)
def writeLong(self, value):
bytesWritten = self.f.write(struct.pack(self.longFmt, value))
if bytesWritten is not None and bytesWritten != 4:
raise RuntimeError("wrote only %u bytes but wanted 4" %
bytesWritten)
raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten)
def close(self):
self.finalize()
@ -1766,12 +1795,11 @@ class AppendingTiffWriter:
numTags = self.readShort()
for i in range(numTags):
tag, fieldType, count = struct.unpack(self.tagFormat,
self.f.read(8))
tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))
fieldSize = self.fieldSizes[fieldType]
totalSize = fieldSize * count
isLocal = (totalSize <= 4)
isLocal = totalSize <= 4
if not isLocal:
offset = self.readLong()
offset += self.offsetOfNewPage
@ -1781,13 +1809,15 @@ class AppendingTiffWriter:
curPos = self.f.tell()
if isLocal:
self.fixOffsets(count, isShort=(fieldSize == 2),
isLong=(fieldSize == 4))
self.fixOffsets(
count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
)
self.f.seek(curPos + 4)
else:
self.f.seek(offset)
self.fixOffsets(count, isShort=(fieldSize == 2),
isLong=(fieldSize == 4))
self.fixOffsets(
count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
)
self.f.seek(curPos)
offset = curPos = None
@ -1830,7 +1860,7 @@ def _save_all(im, fp, filename):
cur_idx = im.tell()
try:
with AppendingTiffWriter(fp) as tf:
for ims in [im]+append_images:
for ims in [im] + append_images:
ims.encoderinfo = encoderinfo
ims.encoderconfig = encoderconfig
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")):
__slots__ = []
def __new__(cls, value=None, name="unknown",
type=None, length=None, enum=None):
return super(TagInfo, cls).__new__(
cls, value, name, type, length, enum or {})
def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
return super(TagInfo, cls).__new__(cls, value, name, type, length, enum or {})
def cvt_enum(self, value):
# 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
TAGS_V2 = {
254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1),
257: ("ImageLength", LONG, 1),
258: ("BitsPerSample", SHORT, 0),
259: ("Compression", SHORT, 1,
{"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3,
"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,
259: (
"Compression",
SHORT,
1,
{
"Uncompressed": 1,
"CCITT 1d": 2,
"Group 3 Fax": 3,
"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
"LinearRaw": 32892, # Adobe DNG
},
),
263: ("Threshholding", SHORT, 1),
264: ("CellWidth", SHORT, 1),
265: ("CellLength", SHORT, 1),
266: ("FillOrder", SHORT, 1),
269: ("DocumentName", ASCII, 1),
270: ("ImageDescription", ASCII, 1),
271: ("Make", ASCII, 1),
272: ("Model", ASCII, 1),
@ -102,7 +120,6 @@ TAGS_V2 = {
277: ("SamplesPerPixel", SHORT, 1),
278: ("RowsPerStrip", LONG, 1),
279: ("StripByteCounts", LONG, 0),
280: ("MinSampleValue", LONG, 0),
281: ("MaxSampleValue", SHORT, 0),
282: ("XResolution", RATIONAL, 1),
@ -113,31 +130,26 @@ TAGS_V2 = {
287: ("YPosition", RATIONAL, 1),
288: ("FreeOffsets", LONG, 1),
289: ("FreeByteCounts", LONG, 1),
290: ("GrayResponseUnit", SHORT, 1),
291: ("GrayResponseCurve", SHORT, 0),
292: ("T4Options", LONG, 1),
293: ("T6Options", LONG, 1),
296: ("ResolutionUnit", SHORT, 1, {"none": 1, "inch": 2, "cm": 3}),
297: ("PageNumber", SHORT, 2),
301: ("TransferFunction", SHORT, 0),
305: ("Software", ASCII, 1),
306: ("DateTime", ASCII, 1),
315: ("Artist", ASCII, 1),
316: ("HostComputer", ASCII, 1),
317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}),
318: ("WhitePoint", RATIONAL, 2),
319: ("PrimaryChromaticities", RATIONAL, 6),
320: ("ColorMap", SHORT, 0),
321: ("HalftoneHints", SHORT, 2),
322: ("TileWidth", LONG, 1),
323: ("TileLength", LONG, 1),
324: ("TileOffsets", LONG, 0),
325: ("TileByteCounts", LONG, 0),
332: ("InkSet", SHORT, 1),
333: ("InkNames", ASCII, 1),
334: ("NumberOfInks", SHORT, 1),
@ -145,13 +157,10 @@ TAGS_V2 = {
337: ("TargetPrinter", ASCII, 1),
338: ("ExtraSamples", SHORT, 0),
339: ("SampleFormat", SHORT, 0),
340: ("SMinSampleValue", DOUBLE, 0),
341: ("SMaxSampleValue", DOUBLE, 0),
342: ("TransferRange", SHORT, 6),
347: ("JPEGTables", UNDEFINED, 1),
# obsolete JPEG tags
512: ("JPEGProc", SHORT, 1),
513: ("JPEGInterchangeFormat", LONG, 1),
@ -162,22 +171,17 @@ TAGS_V2 = {
519: ("JPEGQTables", LONG, 0),
520: ("JPEGDCTables", LONG, 0),
521: ("JPEGACTables", LONG, 0),
529: ("YCbCrCoefficients", RATIONAL, 3),
530: ("YCbCrSubSampling", SHORT, 2),
531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", RATIONAL, 6),
700: ('XMP', BYTE, 1),
700: ("XMP", BYTE, 1),
33432: ("Copyright", ASCII, 1),
34377: ('PhotoshopInfo', BYTE, 1),
34377: ("PhotoshopInfo", BYTE, 1),
# FIXME add more tags here
34665: ("ExifIFD", SHORT, 1),
34675: ('ICCProfile', UNDEFINED, 1),
34853: ('GPSInfoIFD', BYTE, 1),
34675: ("ICCProfile", UNDEFINED, 1),
34853: ("GPSInfoIFD", BYTE, 1),
# MPInfo
45056: ("MPFVersion", UNDEFINED, 1),
45057: ("NumberOfImages", LONG, 1),
@ -198,159 +202,157 @@ TAGS_V2 = {
45579: ("YawAngle", SIGNED_RATIONAL, 1),
45580: ("PitchAngle", SIGNED_RATIONAL, 1),
45581: ("RollAngle", SIGNED_RATIONAL, 1),
50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
50780: ("BestQualityScale", RATIONAL, 1),
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
# these tags aren't included above, but were in the previous versions
TAGS = {347: 'JPEGTables',
700: 'XMP',
TAGS = {
347: "JPEGTables",
700: "XMP",
# Additional Exif Info
32932: 'Wang Annotation',
33434: 'ExposureTime',
33437: 'FNumber',
33445: 'MD FileTag',
33446: 'MD ScalePixel',
33447: 'MD ColorTable',
33448: 'MD LabName',
33449: 'MD SampleInfo',
33450: 'MD PrepDate',
33451: 'MD PrepTime',
33452: 'MD FileUnits',
33550: 'ModelPixelScaleTag',
33723: 'IptcNaaInfo',
33918: 'INGR Packet Data Tag',
33919: 'INGR Flag Registers',
33920: 'IrasB Transformation Matrix',
33922: 'ModelTiepointTag',
34264: 'ModelTransformationTag',
34377: 'PhotoshopInfo',
34735: 'GeoKeyDirectoryTag',
34736: 'GeoDoubleParamsTag',
34737: 'GeoAsciiParamsTag',
34850: 'ExposureProgram',
34852: 'SpectralSensitivity',
34855: 'ISOSpeedRatings',
34856: 'OECF',
34864: 'SensitivityType',
34865: 'StandardOutputSensitivity',
34866: 'RecommendedExposureIndex',
34867: 'ISOSpeed',
34868: 'ISOSpeedLatitudeyyy',
34869: 'ISOSpeedLatitudezzz',
34908: 'HylaFAX FaxRecvParams',
34909: 'HylaFAX FaxSubAddress',
34910: 'HylaFAX FaxRecvTime',
36864: 'ExifVersion',
36867: 'DateTimeOriginal',
36868: 'DateTImeDigitized',
37121: 'ComponentsConfiguration',
37122: 'CompressedBitsPerPixel',
37724: 'ImageSourceData',
37377: 'ShutterSpeedValue',
37378: 'ApertureValue',
37379: 'BrightnessValue',
37380: 'ExposureBiasValue',
37381: 'MaxApertureValue',
37382: 'SubjectDistance',
37383: 'MeteringMode',
37384: 'LightSource',
37385: 'Flash',
37386: 'FocalLength',
37396: 'SubjectArea',
37500: 'MakerNote',
37510: 'UserComment',
37520: 'SubSec',
37521: 'SubSecTimeOriginal',
37522: 'SubsecTimeDigitized',
40960: 'FlashPixVersion',
40961: 'ColorSpace',
40962: 'PixelXDimension',
40963: 'PixelYDimension',
40964: 'RelatedSoundFile',
40965: 'InteroperabilityIFD',
41483: 'FlashEnergy',
41484: 'SpatialFrequencyResponse',
41486: 'FocalPlaneXResolution',
41487: 'FocalPlaneYResolution',
41488: 'FocalPlaneResolutionUnit',
41492: 'SubjectLocation',
41493: 'ExposureIndex',
41495: 'SensingMethod',
41728: 'FileSource',
41729: 'SceneType',
41730: 'CFAPattern',
41985: 'CustomRendered',
41986: 'ExposureMode',
41987: 'WhiteBalance',
41988: 'DigitalZoomRatio',
41989: 'FocalLengthIn35mmFilm',
41990: 'SceneCaptureType',
41991: 'GainControl',
41992: 'Contrast',
41993: 'Saturation',
41994: 'Sharpness',
41995: 'DeviceSettingDescription',
41996: 'SubjectDistanceRange',
42016: 'ImageUniqueID',
42032: 'CameraOwnerName',
42033: 'BodySerialNumber',
42034: 'LensSpecification',
42035: 'LensMake',
42036: 'LensModel',
42037: 'LensSerialNumber',
42112: 'GDAL_METADATA',
42113: 'GDAL_NODATA',
42240: 'Gamma',
50215: 'Oce Scanjob Description',
50216: 'Oce Application Selector',
50217: 'Oce Identification Number',
50218: 'Oce ImageLogic Characteristics',
32932: "Wang Annotation",
33434: "ExposureTime",
33437: "FNumber",
33445: "MD FileTag",
33446: "MD ScalePixel",
33447: "MD ColorTable",
33448: "MD LabName",
33449: "MD SampleInfo",
33450: "MD PrepDate",
33451: "MD PrepTime",
33452: "MD FileUnits",
33550: "ModelPixelScaleTag",
33723: "IptcNaaInfo",
33918: "INGR Packet Data Tag",
33919: "INGR Flag Registers",
33920: "IrasB Transformation Matrix",
33922: "ModelTiepointTag",
34264: "ModelTransformationTag",
34377: "PhotoshopInfo",
34735: "GeoKeyDirectoryTag",
34736: "GeoDoubleParamsTag",
34737: "GeoAsciiParamsTag",
34850: "ExposureProgram",
34852: "SpectralSensitivity",
34855: "ISOSpeedRatings",
34856: "OECF",
34864: "SensitivityType",
34865: "StandardOutputSensitivity",
34866: "RecommendedExposureIndex",
34867: "ISOSpeed",
34868: "ISOSpeedLatitudeyyy",
34869: "ISOSpeedLatitudezzz",
34908: "HylaFAX FaxRecvParams",
34909: "HylaFAX FaxSubAddress",
34910: "HylaFAX FaxRecvTime",
36864: "ExifVersion",
36867: "DateTimeOriginal",
36868: "DateTImeDigitized",
37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel",
37724: "ImageSourceData",
37377: "ShutterSpeedValue",
37378: "ApertureValue",
37379: "BrightnessValue",
37380: "ExposureBiasValue",
37381: "MaxApertureValue",
37382: "SubjectDistance",
37383: "MeteringMode",
37384: "LightSource",
37385: "Flash",
37386: "FocalLength",
37396: "SubjectArea",
37500: "MakerNote",
37510: "UserComment",
37520: "SubSec",
37521: "SubSecTimeOriginal",
37522: "SubsecTimeDigitized",
40960: "FlashPixVersion",
40961: "ColorSpace",
40962: "PixelXDimension",
40963: "PixelYDimension",
40964: "RelatedSoundFile",
40965: "InteroperabilityIFD",
41483: "FlashEnergy",
41484: "SpatialFrequencyResponse",
41486: "FocalPlaneXResolution",
41487: "FocalPlaneYResolution",
41488: "FocalPlaneResolutionUnit",
41492: "SubjectLocation",
41493: "ExposureIndex",
41495: "SensingMethod",
41728: "FileSource",
41729: "SceneType",
41730: "CFAPattern",
41985: "CustomRendered",
41986: "ExposureMode",
41987: "WhiteBalance",
41988: "DigitalZoomRatio",
41989: "FocalLengthIn35mmFilm",
41990: "SceneCaptureType",
41991: "GainControl",
41992: "Contrast",
41993: "Saturation",
41994: "Sharpness",
41995: "DeviceSettingDescription",
41996: "SubjectDistanceRange",
42016: "ImageUniqueID",
42032: "CameraOwnerName",
42033: "BodySerialNumber",
42034: "LensSpecification",
42035: "LensMake",
42036: "LensModel",
42037: "LensSerialNumber",
42112: "GDAL_METADATA",
42113: "GDAL_NODATA",
42240: "Gamma",
50215: "Oce Scanjob Description",
50216: "Oce Application Selector",
50217: "Oce Identification Number",
50218: "Oce ImageLogic Characteristics",
# Adobe DNG
50706: 'DNGVersion',
50707: 'DNGBackwardVersion',
50708: 'UniqueCameraModel',
50709: 'LocalizedCameraModel',
50710: 'CFAPlaneColor',
50711: 'CFALayout',
50712: 'LinearizationTable',
50713: 'BlackLevelRepeatDim',
50714: 'BlackLevel',
50715: 'BlackLevelDeltaH',
50716: 'BlackLevelDeltaV',
50717: 'WhiteLevel',
50718: 'DefaultScale',
50719: 'DefaultCropOrigin',
50720: 'DefaultCropSize',
50721: 'ColorMatrix1',
50722: 'ColorMatrix2',
50723: 'CameraCalibration1',
50724: 'CameraCalibration2',
50725: 'ReductionMatrix1',
50726: 'ReductionMatrix2',
50727: 'AnalogBalance',
50728: 'AsShotNeutral',
50729: 'AsShotWhiteXY',
50730: 'BaselineExposure',
50731: 'BaselineNoise',
50732: 'BaselineSharpness',
50733: 'BayerGreenSplit',
50734: 'LinearResponseLimit',
50735: 'CameraSerialNumber',
50736: 'LensInfo',
50737: 'ChromaBlurRadius',
50738: 'AntiAliasStrength',
50740: 'DNGPrivateData',
50778: 'CalibrationIlluminant1',
50779: 'CalibrationIlluminant2',
50784: 'Alias Layer Metadata'
}
50706: "DNGVersion",
50707: "DNGBackwardVersion",
50708: "UniqueCameraModel",
50709: "LocalizedCameraModel",
50710: "CFAPlaneColor",
50711: "CFALayout",
50712: "LinearizationTable",
50713: "BlackLevelRepeatDim",
50714: "BlackLevel",
50715: "BlackLevelDeltaH",
50716: "BlackLevelDeltaV",
50717: "WhiteLevel",
50718: "DefaultScale",
50719: "DefaultCropOrigin",
50720: "DefaultCropSize",
50721: "ColorMatrix1",
50722: "ColorMatrix2",
50723: "CameraCalibration1",
50724: "CameraCalibration2",
50725: "ReductionMatrix1",
50726: "ReductionMatrix2",
50727: "AnalogBalance",
50728: "AsShotNeutral",
50729: "AsShotWhiteXY",
50730: "BaselineExposure",
50731: "BaselineNoise",
50732: "BaselineSharpness",
50733: "BayerGreenSplit",
50734: "LinearResponseLimit",
50735: "CameraSerialNumber",
50736: "LensInfo",
50737: "ChromaBlurRadius",
50738: "AntiAliasStrength",
50740: "DNGPrivateData",
50778: "CalibrationIlluminant1",
50779: "CalibrationIlluminant2",
50784: "Alias Layer Metadata",
}
def _populate():
@ -433,13 +435,48 @@ TYPES = {}
# some of these are not in our TAGS_V2 dict and were included from tiff.h
# This list also exists in encode.c
LIBTIFF_CORE = {255, 256, 257, 258, 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,
LIBTIFF_CORE = {
255,
256,
257,
258,
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
}
269, # this has been in our tests forever, and works
}
LIBTIFF_CORE.remove(320) # Array of short, crashes
LIBTIFF_CORE.remove(301) # Array of short, crashes

View File

@ -28,6 +28,7 @@ try:
import builtins
except ImportError:
import __builtin__
builtins = __builtin__
@ -46,7 +47,7 @@ def open(filename):
def imopen(fp):
# read header fields
header = fp.read(32+24+32+12)
header = fp.read(32 + 24 + 32 + 12)
size = i32(header, 32), i32(header, 36)
offset = i32(header, 40)
@ -62,7 +63,7 @@ def open(filename):
# strings are null-terminated
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:
im.info["next_name"] = next_name

View File

@ -1,28 +1,23 @@
from . import Image, ImageFile
try:
from . import _webp
SUPPORTED = True
except ImportError:
SUPPORTED = False
from io import BytesIO
_VALID_WEBP_MODES = {
"RGBX": True,
"RGBA": True,
"RGB": True,
}
_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True}
_VALID_WEBP_LEGACY_MODES = {
"RGB": True,
"RGBA": True,
}
_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True}
_VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB",
b"VP8X": "RGBA",
b"VP8L": "RGBA", # lossless
}
}
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 not SUPPORTED:
return "image file could not be identified " \
"because WEBP support not installed"
return (
"image file could not be identified because WEBP support not installed"
)
return True
@ -45,8 +41,9 @@ class WebPImageFile(ImageFile.ImageFile):
def _open(self):
if not _webp.HAVE_WEBPANIM:
# Legacy mode
data, width, height, self.mode, icc_profile, exif = \
_webp.WebPDecode(self.fp.read())
data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(
self.fp.read()
)
if icc_profile:
self.info["icc_profile"] = icc_profile
if exif:
@ -62,18 +59,18 @@ class WebPImageFile(ImageFile.ImageFile):
self._decoder = _webp.WebPAnimDecoder(self.fp.read())
# Get info from decoder
width, height, loop_count, bgcolor, frame_count, mode = \
self._decoder.get_info()
width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
self._size = width, height
self.info["loop"] = loop_count
bg_a, bg_r, bg_g, bg_b = \
(bgcolor >> 24) & 0xFF, \
(bgcolor >> 16) & 0xFF, \
(bgcolor >> 8) & 0xFF, \
bgcolor & 0xFF
bg_a, bg_r, bg_g, bg_b = (
(bgcolor >> 24) & 0xFF,
(bgcolor >> 16) & 0xFF,
(bgcolor >> 8) & 0xFF,
bgcolor & 0xFF,
)
self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
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.tile = []
@ -186,7 +183,7 @@ def _save_all(im, fp, filename):
# If total frame count is 1, then save using the legacy API, which
# will preserve non-alpha modes
total = 0
for ims in [im]+append_images:
for ims in [im] + append_images:
total += getattr(ims, "n_frames", 1)
if total == 1:
_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
palette = im.getpalette()
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)
duration = im.encoderinfo.get("duration", 0)
@ -230,10 +227,15 @@ def _save_all(im, fp, filename):
kmax = 17 if lossless else 5
# Validate background color
if (not isinstance(background, (list, tuple)) or len(background) != 4 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))
if (
not isinstance(background, (list, tuple))
or len(background) != 4
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
bg_r, bg_g, bg_b, bg_a = background
@ -241,13 +243,15 @@ def _save_all(im, fp, filename):
# Setup the WebP animation encoder
enc = _webp.WebPAnimEncoder(
im.size[0], im.size[1],
im.size[0],
im.size[1],
background,
loop,
minimize_size,
kmin, kmax,
kmin,
kmax,
allow_mixed,
verbose
verbose,
)
# Add each frame
@ -255,7 +259,7 @@ def _save_all(im, fp, filename):
timestamp = 0
cur_idx = im.tell()
try:
for ims in [im]+append_images:
for ims in [im] + append_images:
# Get # of frames in this image
nfr = getattr(ims, "n_frames", 1)
@ -267,25 +271,28 @@ def _save_all(im, fp, filename):
frame = ims
rawmode = ims.mode
if ims.mode not in _VALID_WEBP_MODES:
alpha = 'A' in ims.mode or 'a' in ims.mode \
or (ims.mode == 'P' and
'A' in ims.im.getpalettemode())
rawmode = 'RGBA' if alpha else 'RGB'
alpha = (
"A" in ims.mode
or "a" in ims.mode
or (ims.mode == "P" and "A" in ims.im.getpalettemode())
)
rawmode = "RGBA" if alpha else "RGB"
frame = ims.convert(rawmode)
if rawmode == 'RGB':
if rawmode == "RGB":
# For faster conversion, use RGBX
rawmode = 'RGBX'
rawmode = "RGBX"
# Append the frame to the animation encoder
enc.add(
frame.tobytes('raw', rawmode),
frame.tobytes("raw", rawmode),
timestamp,
frame.size[0], frame.size[1],
frame.size[0],
frame.size[1],
rawmode,
lossless,
quality,
method
method,
)
# Update timestamp and frame index
@ -299,11 +306,7 @@ def _save_all(im, fp, filename):
im.seek(cur_idx)
# Force encoder to flush frames
enc.add(
None,
timestamp,
0, 0, "", lossless, quality, 0
)
enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)
# Get the final output from the encoder
data = enc.assemble(icc_profile, exif, xmp)
@ -323,9 +326,12 @@ def _save(im, fp, filename):
xmp = im.encoderinfo.get("xmp", "")
if im.mode not in _VALID_WEBP_LEGACY_MODES:
alpha = 'A' in im.mode or 'a' in im.mode \
or (im.mode == 'P' and 'A' in im.im.getpalettemode())
im = im.convert('RGBA' if alpha else 'RGB')
alpha = (
"A" in im.mode
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(
im.tobytes(),
@ -336,7 +342,7 @@ def _save(im, fp, filename):
im.mode,
icc_profile,
exif,
xmp
xmp,
)
if data is None:
raise IOError("cannot write file as WebP (encoder returned None)")

View File

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

View File

@ -31,7 +31,9 @@ PALETTE = b""
for r in range(8):
for g in range(8):
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):
@ -41,6 +43,7 @@ def _accept(prefix):
##
# Image plugin for XV thumbnail images.
class XVThumbImageFile(ImageFile.ImageFile):
format = "XVThumb"
@ -71,10 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", PALETTE)
self.tile = [
("raw", (0, 0)+self.size,
self.fp.tell(), (self.mode, 0, 1)
)]
self.tile = [("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.
class XbmImageFile(ImageFile.ImageFile):
format = "XBM"
@ -60,14 +61,12 @@ class XbmImageFile(ImageFile.ImageFile):
ysize = int(m.group("height"))
if m.group("hotspot"):
self.info["hotspot"] = (
int(m.group("xhot")), int(m.group("yhot"))
)
self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot")))
self.mode = "1"
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):
@ -75,17 +74,17 @@ def _save(im, fp, filename):
if im.mode != "1":
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_height %d\n" % im.size[1]).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"))
hotspot = im.encoderinfo.get("hotspot")
if hotspot:
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_x_hot %d\n" % hotspot[0]).encode("ascii"))
fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode("ascii"))
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")

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ if sys.version_info.major > 2:
else:
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.
# PyPies 1.6, 2.0 do not have tkinter built in. PyPy3-2.3.1 gives an
# 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)
if py3:
def isStringType(t):
return isinstance(t, str)
@ -13,10 +14,15 @@ if py3:
def isPath(f):
return isinstance(f, (bytes, str, Path))
else:
def isPath(f):
return isinstance(f, (bytes, str))
else:
def isStringType(t):
return isinstance(t, basestring) # noqa: F821

View File

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

View File

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