Pillow/src/PIL/ImageFile.py

749 lines
22 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
# $Id$
#
# base class for image file handlers
#
# history:
# 1995-09-09 fl Created
# 1996-03-11 fl Fixed load mechanism.
# 1996-04-15 fl Added pcx/xbm decoders.
# 1996-04-30 fl Added encoders.
# 1996-12-14 fl Added load helpers
# 1997-01-11 fl Use encode_to_file where possible
# 1997-08-27 fl Flush output in _save
# 1998-03-05 fl Use memory mapping for some modes
# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B"
# 1999-05-31 fl Added image parser
# 2000-10-12 fl Set readonly flag on memory-mapped images
# 2002-03-20 fl Use better messages for common decoder errors
# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available
# 2003-10-30 fl Added StubImageFile class
# 2004-02-25 fl Made incremental parser more robust
#
# Copyright (c) 1997-2004 by Secret Labs AB
# Copyright (c) 1995-2004 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
import io
import itertools
2015-07-31 01:20:51 +03:00
import struct
import sys
from . import Image
from ._util import isPath
2019-03-12 02:27:43 +03:00
2010-07-31 06:52:47 +04:00
MAXBLOCK = 65536
2019-03-21 16:28:20 +03:00
SAFEBLOCK = 1024 * 1024
2010-07-31 06:52:47 +04:00
LOAD_TRUNCATED_IMAGES = False
2020-09-01 20:16:46 +03:00
"""Whether or not to load truncated image files. User code may change this."""
2010-07-31 06:52:47 +04:00
ERRORS = {
-1: "image buffer overrun error",
-2: "decoding error",
-3: "unknown error",
-8: "bad configuration",
2019-03-21 16:28:20 +03:00
-9: "out of memory error",
2010-07-31 06:52:47 +04:00
}
2022-02-25 08:07:01 +03:00
"""
Dict of known error codes returned from :meth:`.PyDecoder.decode`,
:meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and
:meth:`.PyEncoder.encode_to_file`.
"""
2010-07-31 06:52:47 +04:00
2014-08-26 17:47:10 +04:00
#
# --------------------------------------------------------------------
# Helpers
def raise_oserror(error):
2010-07-31 06:52:47 +04:00
try:
message = Image.core.getcodecstatus(error)
except AttributeError:
message = ERRORS.get(error)
if not message:
message = f"decoder error {error}"
raise OSError(message + " when reading image file")
2010-07-31 06:52:47 +04:00
2014-08-26 17:47:10 +04:00
def _tilesort(t):
2010-07-31 06:52:47 +04:00
# sort on offset
return t[2]
2010-07-31 06:52:47 +04:00
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
#
# --------------------------------------------------------------------
# ImageFile base class
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
class ImageFile(Image.Image):
"""Base class for image file format handlers."""
2010-07-31 06:52:47 +04:00
def __init__(self, fp=None, filename=None):
super().__init__()
2010-07-31 06:52:47 +04:00
2017-09-06 06:23:50 +03:00
self._min_frame = 0
self.custom_mimetype = None
2010-07-31 06:52:47 +04:00
self.tile = None
2020-07-23 15:40:02 +03:00
""" A list of tile descriptors, or ``None`` """
2014-08-26 17:47:10 +04:00
self.readonly = 1 # until we know better
2010-07-31 06:52:47 +04:00
self.decoderconfig = ()
self.decodermaxblock = MAXBLOCK
2013-06-30 15:04:42 +04:00
if isPath(fp):
2010-07-31 06:52:47 +04:00
# filename
self.fp = open(fp, "rb")
self.filename = fp
2016-07-17 06:29:36 +03:00
self._exclusive_fp = True
2010-07-31 06:52:47 +04:00
else:
# stream
self.fp = fp
self.filename = filename
# can be overridden
self._exclusive_fp = None
2010-07-31 06:52:47 +04:00
try:
Improve handling of file resources Follow Python's file object semantics. User code is responsible for closing resources (usually through a context manager) in a deterministic way. To achieve this, remove __del__ functions. These functions used to closed open file handlers in an attempt to silence Python ResourceWarnings. However, using __del__ has the following drawbacks: - __del__ isn't called until the object's reference count reaches 0. Therefore, resource handlers remain open or in use longer than necessary. - The __del__ method isn't guaranteed to execute on system exit. See the Python documentation: https://docs.python.org/3/reference/datamodel.html#object.__del__ > It is not guaranteed that __del__() methods are called for objects > that still exist when the interpreter exits. - Exceptions that occur inside __del__ are ignored instead of raised. This has the potential of hiding bugs. This is also in the Python documentation: > Warning: Due to the precarious circumstances under which __del__() > methods are invoked, exceptions that occur during their execution > are ignored, and a warning is printed to sys.stderr instead. Instead, always close resource handlers when they are no longer in use. This will close the file handler at a specified point in the user's code and not wait until the interpreter chooses to. It is always guaranteed to run. And, if an exception occurs while closing the file handler, the bug will not be ignored. Now, when code receives a ResourceWarning, it will highlight an area that is mishandling resources. It should not simply be silenced, but fixed by closing resources with a context manager. All warnings that were emitted during tests have been cleaned up. To enable warnings, I passed the `-Wa` CLI option to Python. This exposed some mishandling of resources in ImageFile.__init__() and SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
try:
self._open()
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:
raise SyntaxError(v) from v
Improve handling of file resources Follow Python's file object semantics. User code is responsible for closing resources (usually through a context manager) in a deterministic way. To achieve this, remove __del__ functions. These functions used to closed open file handlers in an attempt to silence Python ResourceWarnings. However, using __del__ has the following drawbacks: - __del__ isn't called until the object's reference count reaches 0. Therefore, resource handlers remain open or in use longer than necessary. - The __del__ method isn't guaranteed to execute on system exit. See the Python documentation: https://docs.python.org/3/reference/datamodel.html#object.__del__ > It is not guaranteed that __del__() methods are called for objects > that still exist when the interpreter exits. - Exceptions that occur inside __del__ are ignored instead of raised. This has the potential of hiding bugs. This is also in the Python documentation: > Warning: Due to the precarious circumstances under which __del__() > methods are invoked, exceptions that occur during their execution > are ignored, and a warning is printed to sys.stderr instead. Instead, always close resource handlers when they are no longer in use. This will close the file handler at a specified point in the user's code and not wait until the interpreter chooses to. It is always guaranteed to run. And, if an exception occurs while closing the file handler, the bug will not be ignored. Now, when code receives a ResourceWarning, it will highlight an area that is mishandling resources. It should not simply be silenced, but fixed by closing resources with a context manager. All warnings that were emitted during tests have been cleaned up. To enable warnings, I passed the `-Wa` CLI option to Python. This exposed some mishandling of resources in ImageFile.__init__() and SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
if not self.mode or self.size[0] <= 0:
raise SyntaxError("not identified by this driver")
except BaseException:
2016-07-17 06:29:36 +03:00
# close the file only if we have opened it this constructor
if self._exclusive_fp:
self.fp.close()
Improve handling of file resources Follow Python's file object semantics. User code is responsible for closing resources (usually through a context manager) in a deterministic way. To achieve this, remove __del__ functions. These functions used to closed open file handlers in an attempt to silence Python ResourceWarnings. However, using __del__ has the following drawbacks: - __del__ isn't called until the object's reference count reaches 0. Therefore, resource handlers remain open or in use longer than necessary. - The __del__ method isn't guaranteed to execute on system exit. See the Python documentation: https://docs.python.org/3/reference/datamodel.html#object.__del__ > It is not guaranteed that __del__() methods are called for objects > that still exist when the interpreter exits. - Exceptions that occur inside __del__ are ignored instead of raised. This has the potential of hiding bugs. This is also in the Python documentation: > Warning: Due to the precarious circumstances under which __del__() > methods are invoked, exceptions that occur during their execution > are ignored, and a warning is printed to sys.stderr instead. Instead, always close resource handlers when they are no longer in use. This will close the file handler at a specified point in the user's code and not wait until the interpreter chooses to. It is always guaranteed to run. And, if an exception occurs while closing the file handler, the bug will not be ignored. Now, when code receives a ResourceWarning, it will highlight an area that is mishandling resources. It should not simply be silenced, but fixed by closing resources with a context manager. All warnings that were emitted during tests have been cleaned up. To enable warnings, I passed the `-Wa` CLI option to Python. This exposed some mishandling of resources in ImageFile.__init__() and SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
raise
2010-07-31 06:52:47 +04:00
def get_format_mimetype(self):
2019-01-05 23:00:00 +03:00
if self.custom_mimetype:
return self.custom_mimetype
if self.format is not None:
return Image.MIME.get(self.format.upper())
2010-07-31 06:52:47 +04:00
def verify(self):
2019-02-03 07:58:24 +03:00
"""Check file integrity"""
2010-07-31 06:52:47 +04:00
# raise exception if something's wrong. must be called
# directly after open, and closes file when finished.
if self._exclusive_fp:
self.fp.close()
2010-07-31 06:52:47 +04:00
self.fp = None
def load(self):
2019-02-03 07:58:24 +03:00
"""Load image data based on tile list"""
2010-07-31 06:52:47 +04:00
if self.tile is None:
raise OSError("cannot load this image")
2020-04-17 11:29:45 +03:00
pixel = Image.Image.load(self)
2010-07-31 06:52:47 +04:00
if not self.tile:
return pixel
self.map = None
use_mmap = self.filename and len(self.tile) == 1
# As of pypy 2.1.0, memory mapping was failing here.
2019-03-21 16:28:20 +03:00
use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
readonly = 0
# look for read/seek overrides
try:
read = self.load_read
# don't use mmap if there are custom read/seek functions
use_mmap = False
except AttributeError:
read = self.fp.read
try:
seek = self.load_seek
use_mmap = False
except AttributeError:
seek = self.fp.seek
if use_mmap:
2010-07-31 06:52:47 +04:00
# try memory mapping
decoder_name, extents, offset, args = self.tile[0]
2019-03-21 16:28:20 +03:00
if (
decoder_name == "raw"
and len(args) >= 3
and args[0] == self.mode
and args[0] in Image._MAPMODES
):
2010-07-31 06:52:47 +04:00
try:
# use mmap, if possible
import mmap
with open(self.filename) as fp:
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, offset, args
)
2010-07-31 06:52:47 +04:00
readonly = 1
2018-06-24 15:32:25 +03:00
# After trashing self.im,
# we might need to reload the palette data.
if self.palette:
self.palette.dirty = 1
except (AttributeError, OSError, ImportError):
2010-07-31 06:52:47 +04:00
self.map = None
self.load_prepare()
err_code = -3 # initialize to unknown error
2010-07-31 06:52:47 +04:00
if not self.map:
# sort tiles in file order
self.tile.sort(key=_tilesort)
2010-07-31 06:52:47 +04:00
try:
# FIXME: This is a hack to handle TIFF's JpegTables tag.
prefix = self.tile_prefix
except AttributeError:
prefix = b""
2010-07-31 06:52:47 +04:00
# Remove consecutive duplicates that only differ by their offset
self.tile = [
list(tiles)[-1]
for _, tiles in itertools.groupby(
self.tile, lambda tile: (tile[0], tile[1], tile[3])
)
]
for decoder_name, extents, offset, args in self.tile:
seek(offset)
2019-03-21 16:28:20 +03:00
decoder = Image._getdecoder(
self.mode, decoder_name, args, self.decoderconfig
)
try:
decoder.setimage(self.im, extents)
if decoder.pulls_fd:
decoder.setfd(self.fp)
2022-03-08 11:51:24 +03:00
err_code = decoder.decode(b"")[1]
else:
b = prefix
while True:
try:
s = read(self.decodermaxblock)
except (IndexError, struct.error) as e:
2018-06-24 15:32:25 +03:00
# truncated png/gif
if LOAD_TRUNCATED_IMAGES:
break
else:
raise OSError("image file is truncated") from e
if not s: # truncated jpeg
if LOAD_TRUNCATED_IMAGES:
break
else:
raise OSError(
2019-03-21 16:28:20 +03:00
"image file is truncated "
f"({len(b)} bytes not processed)"
2019-03-21 16:28:20 +03:00
)
b = b + s
n, err_code = decoder.decode(b)
if n < 0:
break
b = b[n:]
finally:
# Need to cleanup here to prevent leaks
decoder.cleanup()
2010-07-31 06:52:47 +04:00
self.tile = []
self.readonly = readonly
self.load_end()
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
self.fp.close()
self.fp = None
2010-07-31 06:52:47 +04:00
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
2013-01-11 17:40:02 +04:00
# still raised if decoder fails to return anything
raise_oserror(err_code)
2010-07-31 06:52:47 +04:00
return Image.Image.load(self)
def load_prepare(self):
# create image memory if necessary
2019-03-21 16:28:20 +03:00
if not self.im or self.im.mode != self.mode or self.im.size != self.size:
2010-07-31 06:52:47 +04:00
self.im = Image.core.new(self.mode, self.size)
# create palette (optional)
if self.mode == "P":
Image.Image.load(self)
def load_end(self):
# may be overridden
pass
# may be defined for contained formats
# def load_seek(self, pos):
# pass
# may be defined for blocked formats (e.g. PNG)
# def load_read(self, bytes):
# pass
def _seek_check(self, frame):
2019-03-21 16:28:20 +03:00
if (
frame < self._min_frame
# Only check upper limit on frames if additional seek operations
# are not required to do so
2019-03-21 16:28:20 +03:00
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
2010-07-31 06:52:47 +04:00
class StubImageFile(ImageFile):
"""
Base class for stub image loaders.
A stub loader is an image loader that can identify files of a
certain format, but relies on external code to load the file.
"""
2010-07-31 06:52:47 +04:00
def _open(self):
2019-03-21 16:28:20 +03:00
raise NotImplementedError("StubImageFile subclass must implement _open")
2010-07-31 06:52:47 +04:00
def load(self):
loader = self._load()
if loader is None:
raise OSError(f"cannot find loader for this {self.format} file")
2010-07-31 06:52:47 +04:00
image = loader.load(self)
assert image is not None
# become the other object (!)
self.__class__ = image.__class__
self.__dict__ = image.__dict__
return image.load()
2010-07-31 06:52:47 +04:00
def _load(self):
2019-02-03 07:58:24 +03:00
"""(Hook) Find actual image loader."""
2019-03-21 16:28:20 +03:00
raise NotImplementedError("StubImageFile subclass must implement _load")
2010-07-31 06:52:47 +04:00
class Parser:
"""
Incremental image parser. This class implements the standard
feed/close consumer interface.
"""
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
incremental = None
image = None
data = None
decoder = None
2015-04-24 11:24:52 +03:00
offset = 0
2010-07-31 06:52:47 +04:00
finished = 0
def reset(self):
"""
(Consumer) Reset the parser. Note that you can only call this
method immediately after you've created a parser; parser
instances cannot be reused.
"""
2010-07-31 06:52:47 +04:00
assert self.data is None, "cannot reuse parsers"
def feed(self, data):
"""
(Consumer) Feed data to the parser.
2013-10-13 00:57:27 +04:00
:param data: A string buffer.
:exception OSError: If the parser failed to parse the image file.
"""
2010-07-31 06:52:47 +04:00
# collect data
if self.finished:
return
if self.data is None:
self.data = data
else:
self.data = self.data + data
# parse what we have
if self.decoder:
if self.offset > 0:
# skip header
skip = min(len(self.data), self.offset)
self.data = self.data[skip:]
self.offset = self.offset - skip
if self.offset > 0 or not self.data:
return
n, e = self.decoder.decode(self.data)
if n < 0:
# end of stream
self.data = None
self.finished = 1
if e < 0:
# decoding error
self.image = None
raise_oserror(e)
2010-07-31 06:52:47 +04:00
else:
# end of image
return
self.data = self.data[n:]
elif self.image:
# if we end up here with no decoder, this file cannot
# be incrementally parsed. wait until we've gotten all
# available data
pass
else:
# attempt to open this file
try:
2017-05-13 01:48:03 +03:00
with io.BytesIO(self.data) as fp:
2010-07-31 06:52:47 +04:00
im = Image.open(fp)
except OSError:
# traceback.print_exc()
2014-08-26 17:47:10 +04:00
pass # not enough data
2010-07-31 06:52:47 +04:00
else:
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
if flag or len(im.tile) != 1:
# custom load code, or multiple tiles
self.decode = None
else:
# initialize decoder
im.load_prepare()
d, e, o, a = im.tile[0]
im.tile = []
2019-03-21 16:28:20 +03:00
self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig)
2010-07-31 06:52:47 +04:00
self.decoder.setimage(im.im, e)
# calculate decoder offset
self.offset = o
if self.offset <= len(self.data):
2019-03-21 16:28:20 +03:00
self.data = self.data[self.offset :]
2010-07-31 06:52:47 +04:00
self.offset = 0
self.image = im
2017-10-07 15:18:23 +03:00
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
2010-07-31 06:52:47 +04:00
def close(self):
"""
(Consumer) Close the stream.
:returns: An image object.
:exception OSError: If the parser failed to parse the image file either
2013-10-13 00:57:27 +04:00
because it cannot be identified or cannot be
decoded.
"""
2010-07-31 06:52:47 +04:00
# finish decoding
if self.decoder:
# get rid of what's left in the buffers
self.feed(b"")
2010-07-31 06:52:47 +04:00
self.data = self.decoder = None
if not self.finished:
raise OSError("image was incomplete")
2010-07-31 06:52:47 +04:00
if not self.image:
raise OSError("cannot parse this image")
2010-07-31 06:52:47 +04:00
if self.data:
# incremental parsing not possible; reopen the file
# not that we have all data
2017-05-13 01:48:03 +03:00
with io.BytesIO(self.data) as fp:
try:
self.image = Image.open(fp)
finally:
self.image.load()
2010-07-31 06:52:47 +04:00
return self.image
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
# --------------------------------------------------------------------
2019-03-21 16:28:20 +03:00
def _save(im, fp, tile, bufsize=0):
"""Helper to save image based on tile list
:param im: Image object.
:param fp: File object.
:param tile: Tile list.
:param bufsize: Optional buffer size
"""
2010-07-31 06:52:47 +04:00
im.load()
if not hasattr(im, "encoderconfig"):
im.encoderconfig = ()
tile.sort(key=_tilesort)
2010-07-31 06:52:47 +04:00
# FIXME: make MAXBLOCK a configuration parameter
2014-08-26 17:47:10 +04:00
# It would be great if we could have the encoder specify what it needs
# But, it would need at least the image size in most cases. RawEncode is
# a tricky case.
2014-08-26 17:47:10 +04:00
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
2010-07-31 06:52:47 +04:00
try:
fh = fp.fileno()
fp.flush()
2022-02-28 05:59:52 +03:00
exc = None
except (AttributeError, io.UnsupportedOperation) as e:
exc = e
for e, b, o, a in tile:
if o > 0:
fp.seek(o)
encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
try:
encoder.setimage(im.im, b)
if encoder.pushes_fd:
encoder.setfd(fp)
l, s = encoder.encode_to_pyfd()
else:
2022-02-28 05:59:52 +03:00
if exc:
# compress to Python file-compatible object
while True:
2022-02-28 05:59:52 +03:00
l, s, d = encoder.encode(bufsize)
fp.write(d)
if s:
break
else:
2022-02-28 05:59:52 +03:00
# slight speedup: compress to real file object
s = encoder.encode_to_file(fh, bufsize)
2010-07-31 06:52:47 +04:00
if s < 0:
2020-09-01 20:16:46 +03:00
raise OSError(f"encoder error {s} when writing image file") from exc
2022-02-28 05:59:52 +03:00
finally:
encoder.cleanup()
if hasattr(fp, "flush"):
2010-07-31 06:52:47 +04:00
fp.flush()
def _safe_read(fp, size):
"""
Reads large blocks in a safe way. Unlike fp.read(n), this function
doesn't trust the user. If the requested size is larger than
SAFEBLOCK, the file is read block by block.
:param fp: File handle. Must implement a <b>read</b> method.
:param size: Number of bytes to read.
:returns: A string containing <i>size</i> bytes of data.
2021-04-01 17:41:46 +03:00
Raises an OSError if the file is truncated and the read cannot be completed
"""
2010-07-31 06:52:47 +04:00
if size <= 0:
return b""
2010-07-31 06:52:47 +04:00
if size <= SAFEBLOCK:
data = fp.read(size)
if len(data) < size:
raise OSError("Truncated File Read")
return data
2010-07-31 06:52:47 +04:00
data = []
remaining_size = size
while remaining_size > 0:
block = fp.read(min(remaining_size, SAFEBLOCK))
2010-07-31 06:52:47 +04:00
if not block:
break
data.append(block)
remaining_size -= len(block)
if sum(len(d) for d in data) < size:
raise OSError("Truncated File Read")
return b"".join(data)
2016-05-31 11:10:01 +03:00
class PyCodecState:
2016-05-31 11:10:01 +03:00
def __init__(self):
self.xsize = 0
self.ysize = 0
self.xoff = 0
self.yoff = 0
def extents(self):
2019-03-21 16:28:20 +03:00
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
2016-05-31 11:10:01 +03:00
2017-04-20 14:14:23 +03:00
2022-02-25 08:07:01 +03:00
class PyCodec:
2016-05-31 11:10:01 +03:00
def __init__(self, mode, *args):
self.im = None
self.state = PyCodecState()
self.fd = None
self.mode = mode
self.init(args)
def init(self, args):
2016-07-08 12:52:30 +03:00
"""
2022-02-25 08:07:01 +03:00
Override to perform codec specific initialization
2016-07-08 12:52:30 +03:00
:param args: Array of args items from the tile entry
:returns: None
"""
2016-05-31 11:10:01 +03:00
self.args = args
2017-04-20 14:14:23 +03:00
2016-05-31 11:10:01 +03:00
def cleanup(self):
2016-07-08 12:52:30 +03:00
"""
2022-02-25 08:07:01 +03:00
Override to perform codec specific cleanup
2016-07-08 12:52:30 +03:00
:returns: None
"""
2016-05-31 11:10:01 +03:00
pass
def setfd(self, fd):
2016-07-08 12:52:30 +03:00
"""
2022-02-25 08:07:01 +03:00
Called from ImageFile to set the Python file-like object
2016-07-08 12:52:30 +03:00
2022-02-25 08:07:01 +03:00
:param fd: A Python file-like object
2016-07-08 12:52:30 +03:00
:returns: None
"""
2016-05-31 11:10:01 +03:00
self.fd = fd
2017-04-20 14:14:23 +03:00
2016-05-31 11:10:01 +03:00
def setimage(self, im, extents=None):
2016-07-08 12:52:30 +03:00
"""
2022-02-25 08:07:01 +03:00
Called from ImageFile to set the core output image for the codec
2016-07-08 12:52:30 +03:00
:param im: A core image object
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
for this tile
:returns: None
"""
2017-04-20 14:14:23 +03:00
2016-05-31 11:10:01 +03:00
# following c code
self.im = im
if extents:
(x0, y0, x1, y1) = extents
else:
(x0, y0, x1, y1) = (0, 0, 0, 0)
2017-02-23 13:20:25 +03:00
if x0 == 0 and x1 == 0:
2016-05-31 11:10:01 +03:00
self.state.xsize, self.state.ysize = self.im.size
else:
self.state.xoff = x0
self.state.yoff = y0
self.state.xsize = x1 - x0
self.state.ysize = y1 - y0
if self.state.xsize <= 0 or self.state.ysize <= 0:
2017-02-23 13:20:25 +03:00
raise ValueError("Size cannot be negative")
2017-04-20 14:14:23 +03:00
2019-03-21 16:28:20 +03:00
if (
self.state.xsize + self.state.xoff > self.im.size[0]
or self.state.ysize + self.state.yoff > self.im.size[1]
):
2016-05-31 11:10:01 +03:00
raise ValueError("Tile cannot extend outside image")
2017-04-20 14:14:23 +03:00
2022-02-25 08:07:01 +03:00
class PyDecoder(PyCodec):
"""
Python implementation of a format decoder. Override this class and
add the decoding logic in the :meth:`decode` method.
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
"""
_pulls_fd = False
@property
def pulls_fd(self):
return self._pulls_fd
def decode(self, buffer):
"""
Override to perform the decoding process.
:param buffer: A bytes object with the data to be decoded.
:returns: A tuple of ``(bytes consumed, errcode)``.
2022-03-08 11:48:58 +03:00
If finished with decoding return -1 for the bytes consumed.
2022-02-25 08:07:01 +03:00
Err codes are from :data:`.ImageFile.ERRORS`.
"""
raise NotImplementedError()
2016-05-31 11:10:01 +03:00
def set_as_raw(self, data, rawmode=None):
2016-07-08 12:52:30 +03:00
"""
Convenience method to set the internal image from a stream of raw data
:param data: Bytes to be set
2018-06-24 15:32:25 +03:00
:param rawmode: The rawmode to be used for the decoder.
If not specified, it will default to the mode of the image
2016-07-08 12:52:30 +03:00
:returns: None
"""
2017-04-20 14:14:23 +03:00
2016-05-31 11:10:01 +03:00
if not rawmode:
rawmode = self.mode
2019-03-21 16:28:20 +03:00
d = Image._getdecoder(self.mode, "raw", (rawmode))
2016-05-31 11:10:01 +03:00
d.setimage(self.im, self.state.extents())
s = d.decode(data)
2017-04-20 14:14:23 +03:00
2016-05-31 11:10:01 +03:00
if s[0] >= 0:
raise ValueError("not enough image data")
if s[1] != 0:
raise ValueError("cannot decode image data")
2022-02-25 08:07:01 +03:00
class PyEncoder(PyCodec):
"""
Python implementation of a format encoder. Override this class and
add the decoding logic in the :meth:`encode` method.
2022-02-28 00:27:39 +03:00
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
2022-02-25 08:07:01 +03:00
"""
_pushes_fd = False
@property
def pushes_fd(self):
return self._pushes_fd
def encode(self, bufsize):
"""
Override to perform the encoding process.
:param bufsize: Buffer size.
:returns: A tuple of ``(bytes encoded, errcode, bytes)``.
If finished with encoding return 1 for the error code.
Err codes are from :data:`.ImageFile.ERRORS`.
"""
raise NotImplementedError()
def encode_to_pyfd(self):
"""
If ``pushes_fd`` is ``True``, then this method will be used,
and ``encode()`` will only be called once.
2022-02-25 08:07:01 +03:00
:returns: A tuple of ``(bytes consumed, errcode)``.
Err codes are from :data:`.ImageFile.ERRORS`.
"""
if not self.pushes_fd:
return 0, -8 # bad configuration
bytes_consumed, errcode, data = self.encode(0)
if data:
self.fd.write(data)
return bytes_consumed, errcode
def encode_to_file(self, fh, bufsize):
"""
:param fh: File handle.
:param bufsize: Buffer size.
:returns: If finished successfully, return 0.
Otherwise, return an error code. Err codes are from
:data:`.ImageFile.ERRORS`.
"""
errcode = 0
while errcode == 0:
status, errcode, buf = self.encode(bufsize)
if status > 0:
fh.write(buf[status:])
return errcode