2010-07-31 06:52:47 +04:00
|
|
|
#
|
|
|
|
# The Python Imaging Library.
|
|
|
|
# $Id$
|
|
|
|
#
|
|
|
|
# PPM support for PIL
|
|
|
|
#
|
|
|
|
# History:
|
|
|
|
# 96-03-24 fl Created
|
|
|
|
# 98-03-06 fl Write RGBA images (as RGB, that is)
|
|
|
|
#
|
|
|
|
# Copyright (c) Secret Labs AB 1997-98.
|
|
|
|
# Copyright (c) Fredrik Lundh 1996.
|
|
|
|
#
|
|
|
|
# See the README file for information on usage and redistribution.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
2017-01-17 16:22:18 +03:00
|
|
|
from . import Image, ImageFile
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
#
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
2022-03-04 02:36:34 +03:00
|
|
|
b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d"
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
MODES = {
|
2022-03-12 09:32:15 +03:00
|
|
|
# standard
|
|
|
|
b"P1": "1",
|
|
|
|
b"P2": "L",
|
|
|
|
b"P3": "RGB",
|
|
|
|
b"P4": "1",
|
|
|
|
b"P5": "L",
|
|
|
|
b"P6": "RGB",
|
2010-07-31 06:52:47 +04:00
|
|
|
# extensions
|
2022-03-12 09:32:15 +03:00
|
|
|
b"P0CMYK": "CMYK",
|
2010-07-31 06:52:47 +04:00
|
|
|
# PIL extensions (for test purposes only)
|
2022-03-12 09:32:15 +03:00
|
|
|
b"PyP": "P",
|
|
|
|
b"PyRGBA": "RGBA",
|
|
|
|
b"PyCMYK": "CMYK",
|
2010-07-31 06:52:47 +04:00
|
|
|
}
|
|
|
|
|
2014-08-26 17:47:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def _accept(prefix):
|
2021-01-09 07:33:29 +03:00
|
|
|
return prefix[0:1] == b"P" and prefix[1] in b"0123456y"
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2014-08-26 17:47:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
##
|
|
|
|
# Image plugin for PBM, PGM, and PPM images.
|
|
|
|
|
2019-03-21 16:28:20 +03:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
class PpmImageFile(ImageFile.ImageFile):
|
|
|
|
|
|
|
|
format = "PPM"
|
|
|
|
format_description = "Pbmplus image"
|
|
|
|
|
2022-03-04 06:58:05 +03:00
|
|
|
def _read_magic(self):
|
|
|
|
magic = b""
|
|
|
|
# read until whitespace or longest available magic number
|
|
|
|
for _ in range(6):
|
2021-01-04 07:49:19 +03:00
|
|
|
c = self.fp.read(1)
|
2022-03-04 03:17:25 +03:00
|
|
|
if not c or c in b_whitespace:
|
2021-01-04 07:49:19 +03:00
|
|
|
break
|
2021-01-11 00:45:46 +03:00
|
|
|
magic += c
|
|
|
|
return magic
|
2021-01-04 07:49:19 +03:00
|
|
|
|
2022-03-04 06:58:05 +03:00
|
|
|
def _read_token(self):
|
|
|
|
token = b""
|
|
|
|
while len(token) <= 10: # read until next whitespace or limit of 10 characters
|
2020-12-21 06:56:30 +03:00
|
|
|
c = self.fp.read(1)
|
2022-03-04 03:17:25 +03:00
|
|
|
if not c:
|
2010-07-31 06:52:47 +04:00
|
|
|
break
|
2022-03-04 06:58:05 +03:00
|
|
|
elif c in b_whitespace: # token ended
|
|
|
|
if not token:
|
|
|
|
# skip whitespace at start
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
elif c == b"#":
|
|
|
|
# ignores rest of the line; stops at CR, LF or EOF
|
|
|
|
while self.fp.read(1) not in b"\r\n":
|
|
|
|
pass
|
|
|
|
continue
|
2020-12-21 06:56:30 +03:00
|
|
|
token += c
|
2022-03-04 06:58:05 +03:00
|
|
|
if not token:
|
|
|
|
# Token was not even 1 byte
|
|
|
|
raise ValueError("Reached EOF while reading header")
|
|
|
|
elif len(token) > 10:
|
|
|
|
raise ValueError(f"Token too long in file header: {token}")
|
2020-12-21 06:56:30 +03:00
|
|
|
return token
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
def _open(self):
|
2021-01-04 07:49:19 +03:00
|
|
|
magic_number = self._read_magic()
|
|
|
|
try:
|
2022-03-12 09:32:15 +03:00
|
|
|
mode = MODES[magic_number]
|
2021-01-04 07:49:19 +03:00
|
|
|
except KeyError:
|
2022-03-04 03:01:37 +03:00
|
|
|
raise SyntaxError("not a PPM file")
|
2019-03-04 10:17:12 +03:00
|
|
|
|
2022-03-12 09:32:15 +03:00
|
|
|
if magic_number in (b"P1", b"P4"):
|
|
|
|
self.custom_mimetype = "image/x-portable-bitmap"
|
|
|
|
elif magic_number in (b"P2", b"P5"):
|
|
|
|
self.custom_mimetype = "image/x-portable-graymap"
|
|
|
|
elif magic_number in (b"P3", b"P6"):
|
|
|
|
self.custom_mimetype = "image/x-portable-pixmap"
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
for ix in range(3):
|
2022-03-04 04:13:08 +03:00
|
|
|
token = int(self._read_token())
|
2020-12-21 06:56:30 +03:00
|
|
|
if ix == 0: # token is the x size
|
|
|
|
xsize = token
|
|
|
|
elif ix == 1: # token is the y size
|
|
|
|
ysize = token
|
2010-07-31 06:52:47 +04:00
|
|
|
if mode == "1":
|
2021-01-09 07:33:29 +03:00
|
|
|
self.mode = "1"
|
|
|
|
rawmode = "1;I"
|
2010-07-31 06:52:47 +04:00
|
|
|
break
|
2021-01-09 07:33:29 +03:00
|
|
|
else:
|
|
|
|
self.mode = rawmode = mode
|
2020-12-21 06:56:30 +03:00
|
|
|
elif ix == 2: # token is maxval
|
2021-01-31 06:51:39 +03:00
|
|
|
maxval = token
|
|
|
|
if maxval > 255:
|
2019-03-21 16:28:20 +03:00
|
|
|
if not mode == "L":
|
2021-01-09 07:33:29 +03:00
|
|
|
raise ValueError(f"Too many colors for band: {maxval}")
|
2022-03-04 08:42:24 +03:00
|
|
|
if maxval < 2**16:
|
2019-03-21 16:28:20 +03:00
|
|
|
self.mode = "I"
|
|
|
|
rawmode = "I;16B"
|
2014-04-08 09:12:33 +04:00
|
|
|
else:
|
2019-03-21 16:28:20 +03:00
|
|
|
self.mode = "I"
|
|
|
|
rawmode = "I;32B"
|
2014-08-26 17:47:10 +04:00
|
|
|
|
2022-03-12 09:32:15 +03:00
|
|
|
decoder_name = "raw"
|
|
|
|
if magic_number in (b"P1", b"P2", b"P3"):
|
|
|
|
decoder_name = "ppm_plain"
|
2018-09-30 05:58:02 +03:00
|
|
|
self._size = xsize, ysize
|
2021-01-09 07:33:29 +03:00
|
|
|
self.tile = [
|
2022-03-12 09:32:15 +03:00
|
|
|
(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), (rawmode, 0, 1))
|
2021-01-09 07:33:29 +03:00
|
|
|
]
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2014-08-26 17:47:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
#
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
2019-03-21 16:28:20 +03:00
|
|
|
|
2021-01-16 22:52:40 +03:00
|
|
|
class PpmPlainDecoder(ImageFile.PyDecoder):
|
|
|
|
_pulls_fd = True
|
|
|
|
|
2022-03-12 09:32:15 +03:00
|
|
|
def _read_block(self):
|
|
|
|
return self.fd.read(10**6)
|
2021-01-16 22:52:40 +03:00
|
|
|
|
|
|
|
def _find_comment_end(self, block, start=0):
|
|
|
|
a = block.find(b"\n", start)
|
|
|
|
b = block.find(b"\r", start)
|
|
|
|
return min(a, b) if a * b > 0 else max(a, b) # lowest nonnegative index (or -1)
|
|
|
|
|
|
|
|
def _ignore_comments(self, block):
|
|
|
|
"""
|
2022-03-12 09:32:15 +03:00
|
|
|
Deletes comments from block.
|
|
|
|
If comment does not end in this block, raises a flag.
|
2021-01-16 22:52:40 +03:00
|
|
|
"""
|
|
|
|
comment_spans = False
|
|
|
|
while True:
|
|
|
|
comment_start = block.find(b"#") # look for next comment
|
|
|
|
if comment_start == -1: # no comment found
|
|
|
|
break
|
|
|
|
comment_end = self._find_comment_end(block, comment_start)
|
|
|
|
if comment_end != -1: # comment ends in this block
|
2022-03-12 09:32:15 +03:00
|
|
|
# delete comment
|
|
|
|
block = block[:comment_start] + block[comment_end + 1 :]
|
2021-01-16 22:52:40 +03:00
|
|
|
else: # last comment continues to next block(s)
|
|
|
|
block = block[:comment_start]
|
|
|
|
comment_spans = True
|
|
|
|
break
|
|
|
|
return block, comment_spans
|
|
|
|
|
|
|
|
def _decode_bitonal(self):
|
|
|
|
"""
|
2022-03-12 09:32:15 +03:00
|
|
|
This is a separate method because the plain PBM format all data tokens
|
|
|
|
are exactly one byte, and so the inter-token whitespace is optional.
|
2021-01-16 22:52:40 +03:00
|
|
|
"""
|
|
|
|
decoded_data = bytearray()
|
|
|
|
total_tokens = self.size
|
|
|
|
|
|
|
|
comment_spans = False
|
|
|
|
tokens_read = 0
|
|
|
|
while True:
|
|
|
|
block = self._read_block() # read next block
|
|
|
|
if not block:
|
|
|
|
raise ValueError("Reached EOF while reading data")
|
|
|
|
|
|
|
|
while block and comment_spans:
|
|
|
|
comment_end = self._find_comment_end(block)
|
|
|
|
if comment_end != -1: # comment ends in this block
|
|
|
|
comment_spans = False
|
|
|
|
block = block[comment_end + 1 :] # delete tail of previous comment
|
|
|
|
else: # comment spans whole block
|
|
|
|
block = self._read_block()
|
|
|
|
|
|
|
|
block, comment_spans = self._ignore_comments(block)
|
|
|
|
|
|
|
|
tokens = b"".join(block.split())
|
|
|
|
|
|
|
|
for token in tokens:
|
2022-03-12 09:40:08 +03:00
|
|
|
if token not in (48, 49):
|
2021-01-16 22:52:40 +03:00
|
|
|
raise ValueError(f"Invalid token for this mode: {bytes([token])}")
|
2022-03-12 09:40:08 +03:00
|
|
|
tokens_read += 1
|
2021-01-16 22:52:40 +03:00
|
|
|
decoded_data.append(token)
|
|
|
|
if tokens_read == total_tokens: # finished!
|
|
|
|
invert = bytes.maketrans(b"01", b"\xFF\x00")
|
2022-03-12 09:40:08 +03:00
|
|
|
return decoded_data.translate(invert)
|
2021-01-16 22:52:40 +03:00
|
|
|
|
|
|
|
def _decode_blocks(self, channels=1, depth=8):
|
2021-01-16 22:53:24 +03:00
|
|
|
decoded_data = bytearray()
|
2022-03-12 09:32:15 +03:00
|
|
|
# HACK: 32-bit grayscale uses signed int
|
|
|
|
maxval = 2 ** (31 if depth == 32 else depth) - 1
|
2021-01-16 22:53:24 +03:00
|
|
|
max_len = 10
|
|
|
|
bytes_per_sample = depth // 8
|
|
|
|
total_tokens = self.size * channels
|
|
|
|
|
|
|
|
comment_spans = False
|
|
|
|
half_token = False
|
|
|
|
tokens_read = 0
|
|
|
|
while True:
|
|
|
|
block = self._read_block() # read next block
|
|
|
|
if not block:
|
2022-03-12 09:40:08 +03:00
|
|
|
if half_token:
|
2021-01-16 22:53:24 +03:00
|
|
|
block = bytearray(b" ") # flush half_token
|
|
|
|
else:
|
|
|
|
raise ValueError("Reached EOF while reading data")
|
|
|
|
|
|
|
|
while block and comment_spans:
|
|
|
|
comment_end = self._find_comment_end(block)
|
|
|
|
if comment_end != -1: # comment ends in this block
|
|
|
|
block = block[comment_end + 1 :] # delete tail of previous comment
|
|
|
|
break
|
|
|
|
else: # comment spans whole block
|
|
|
|
block = self._read_block()
|
|
|
|
|
|
|
|
block, comment_spans = self._ignore_comments(block)
|
|
|
|
|
2022-03-12 09:40:08 +03:00
|
|
|
if half_token:
|
2021-01-16 22:53:24 +03:00
|
|
|
block = half_token + block # stitch half_token to new block
|
|
|
|
|
|
|
|
tokens = block.split()
|
|
|
|
|
|
|
|
if block and not block[-1:].isspace(): # block might split token
|
|
|
|
half_token = tokens.pop() # save half token for later
|
|
|
|
if len(half_token) > max_len: # prevent buildup of half_token
|
|
|
|
raise ValueError(
|
|
|
|
f"Token too long found in data: {half_token[:max_len + 1]}"
|
|
|
|
)
|
|
|
|
|
|
|
|
for token in tokens:
|
|
|
|
if len(token) > max_len:
|
|
|
|
raise ValueError(
|
|
|
|
f"Token too long found in data: {token[:max_len + 1]}"
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
token = int(token)
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError(
|
|
|
|
f"Non-decimal-ASCII found in data: {token}"
|
|
|
|
) from None
|
|
|
|
tokens_read += 1
|
|
|
|
if token > maxval:
|
|
|
|
raise ValueError(f"Channel value too large for this mode: {token}")
|
|
|
|
decoded_data += token.to_bytes(bytes_per_sample, "big")
|
|
|
|
if tokens_read == total_tokens: # finished!
|
|
|
|
return decoded_data
|
2021-01-16 22:52:40 +03:00
|
|
|
|
|
|
|
def decode(self, buffer):
|
|
|
|
self.size = self.state.xsize * self.state.ysize
|
|
|
|
rawmode = self.args[0]
|
|
|
|
|
|
|
|
if self.mode == "1":
|
|
|
|
decoded_data = self._decode_bitonal()
|
|
|
|
rawmode = "1;8"
|
|
|
|
elif self.mode == "L":
|
|
|
|
decoded_data = self._decode_blocks(channels=1, depth=8)
|
|
|
|
elif self.mode == "I":
|
|
|
|
if rawmode == "I;16B":
|
|
|
|
decoded_data = self._decode_blocks(channels=1, depth=16)
|
|
|
|
elif rawmode == "I;32B":
|
|
|
|
decoded_data = self._decode_blocks(channels=1, depth=32)
|
|
|
|
elif self.mode == "RGB":
|
|
|
|
decoded_data = self._decode_blocks(channels=3, depth=8)
|
|
|
|
|
|
|
|
self.set_as_raw(bytes(decoded_data), rawmode)
|
|
|
|
return -1, 0
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def _save(im, fp, filename):
|
|
|
|
if im.mode == "1":
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
rawmode, head = "1;I", b"P4"
|
2010-07-31 06:52:47 +04:00
|
|
|
elif im.mode == "L":
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
rawmode, head = "L", b"P5"
|
2014-04-08 09:23:04 +04:00
|
|
|
elif im.mode == "I":
|
2022-03-04 08:42:24 +03:00
|
|
|
if im.getextrema()[1] < 2**16:
|
2014-04-08 09:23:04 +04:00
|
|
|
rawmode, head = "I;16B", b"P5"
|
|
|
|
else:
|
|
|
|
rawmode, head = "I;32B", b"P5"
|
2010-07-31 06:52:47 +04:00
|
|
|
elif im.mode == "RGB":
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
rawmode, head = "RGB", b"P6"
|
2010-07-31 06:52:47 +04:00
|
|
|
elif im.mode == "RGBA":
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
rawmode, head = "RGB", b"P6"
|
2010-07-31 06:52:47 +04:00
|
|
|
else:
|
2021-03-22 19:06:16 +03:00
|
|
|
raise OSError(f"cannot write mode {im.mode} as PPM")
|
2019-03-21 16:28:20 +03:00
|
|
|
fp.write(head + ("\n%d %d\n" % im.size).encode("ascii"))
|
2014-04-08 09:23:04 +04:00
|
|
|
if head == b"P6":
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
fp.write(b"255\n")
|
2014-04-08 09:23:04 +04:00
|
|
|
if head == b"P5":
|
2014-04-09 09:44:24 +04:00
|
|
|
if rawmode == "L":
|
2014-04-08 09:23:04 +04:00
|
|
|
fp.write(b"255\n")
|
|
|
|
elif rawmode == "I;16B":
|
|
|
|
fp.write(b"65535\n")
|
|
|
|
elif rawmode == "I;32B":
|
|
|
|
fp.write(b"2147483648\n")
|
2019-03-21 16:28:20 +03:00
|
|
|
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
# ALTERNATIVE: save via builtin debug function
|
|
|
|
# im._dump(filename)
|
|
|
|
|
2019-03-21 16:28:20 +03:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
#
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
2021-03-21 06:33:05 +03:00
|
|
|
Image.register_decoder("ppm_plain", PpmPlainDecoder)
|
2015-07-04 16:29:58 +03:00
|
|
|
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
|
|
|
|
Image.register_save(PpmImageFile.format, _save)
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2018-12-31 09:41:41 +03:00
|
|
|
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])
|
2019-03-04 10:17:12 +03:00
|
|
|
|
2019-03-06 00:06:19 +03:00
|
|
|
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
|