diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index b96ce9603a3..195dde86367 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -360,7 +360,7 @@ def check_required_header_comments(): check_required_header_comments() - if not self._size: + if not self.size: msg = "cannot determine EPS bounding box" raise OSError(msg) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 92074b0d49e..d2e33271d18 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -262,7 +262,7 @@ def _seek(self, frame, update_image=True): x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) if (x1 > self.size[0] or y1 > self.size[1]) and update_image: self._size = max(x1, self.size[0]), max(y1, self.size[1]) - Image._decompression_bomb_check(self._size) + Image._decompression_bomb_check(self.size) frame_dispose_extent = x0, y0, x1, y1 flags = s[8] @@ -328,8 +328,8 @@ def _seek(self, frame, update_image=True): self._mode = "RGBA" del self.info["transparency"] else: - self._mode = "RGB" self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) + self._mode = "RGB" def _rgb(color): if self._frame_palette: diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 0aa4f7a8458..5df3449b1c2 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -261,11 +261,7 @@ def _open(self): self.best_size[1] * self.best_size[2], ) - @property - def size(self): - return self._size - - @size.setter + @Image.Image.size.setter def size(self, value): info_size = value if info_size not in self.info["sizes"] and len(info_size) == 2: @@ -283,7 +279,10 @@ def size(self, value): if info_size not in self.info["sizes"]: msg = "This is not one of the allowed sizes of this image" raise ValueError(msg) - self._size = value + if value != self.size: + self.im = None + self.pyaccess = None + self._size = value def load(self): if len(self.size) == 3: @@ -306,7 +305,7 @@ def load(self): self.im = im.im self._mode = im.mode - self.size = im.size + self._size = im.size return px diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 0445a2ab22f..3d75afb627a 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -310,36 +310,36 @@ def _open(self): self.size = self.ico.entry[0]["dim"] self.load() - @property - def size(self): - return self._size - - @size.setter + @Image.Image.size.setter def size(self, value): if value not in self.info["sizes"]: msg = "This is not one of the allowed sizes of this image" raise ValueError(msg) - self._size = value + if value != self.size: + self.im = None + self.pyaccess = None + self._size = value def load(self): if self.im is not None and self.im.size == self.size: # Already loaded return Image.Image.load(self) - im = self.ico.getimage(self.size) + size_to_load = self.size + im = self.ico.getimage(size_to_load) # if tile is PNG, it won't really be loaded yet im.load() self.im = im.im self.pyaccess = None self._mode = im.mode - if im.size != self.size: + if im.size != size_to_load: warnings.warn("Image was not the expected size") - index = self.ico.getentryindex(self.size) + index = self.ico.getentryindex(size_to_load) sizes = list(self.info["sizes"]) sizes[index] = im.size self.info["sizes"] = set(sizes) - self.size = im.size + self._size = im.size def load_seek(self): # Flag the ImageFile.Parser so that it diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 476ed012278..f09429875df 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -480,16 +480,24 @@ class Image: def __init__(self): # FIXME: take "new" parameters / other image? - # FIXME: turn mode and size into delegating properties? self.im = None - self._mode = "" - self._size = (0, 0) + # do not directly change __mode; use _mode instead + self.__mode = "" + # do not directly change __size; use _size instead + self.__size = (0, 0) self.palette = None self.info = {} self.readonly = 0 self.pyaccess = None self._exif = None + def _use_im_values(self): + """ + Whether or not to try using values from self.im + in addition to the values in this class. + """ + return self.im is not None + @property def width(self): return self.size[0] @@ -500,11 +508,31 @@ def height(self): @property def size(self): - return self._size + if self._use_im_values(): + return self.im.size + return self.__size + + def _size(self, value): + # set im.size first in case it raises an exception + if self._use_im_values(): + self.im.size = value + self.__size = value + + _size = property(fset=_size) @property def mode(self): - return self._mode + if self._use_im_values(): + return self.im.mode + return self.__mode + + def _mode(self, value): + # set im.mode first in case it raises an exception + if self._use_im_values(): + self.im.mode = value + self.__mode = value + + _mode = property(fset=_mode) def _new(self, im): new = Image() diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 8e4f7dfb2c8..39187624971 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -139,6 +139,9 @@ def get_format_mimetype(self): if self.format is not None: return Image.MIME.get(self.format.upper()) + def _use_im_values(self): + return self.tile is None and self.im is not None + def __setstate__(self, state): self.tile = [] super().__setstate__(state) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 17702778c13..92561860282 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -604,7 +604,7 @@ def exif_transpose(image, *, in_place=False): if in_place: image.im = transposed_image.im image.pyaccess = None - image._size = transposed_image._size + image._size = transposed_image.size exif_image = image if in_place else transposed_image exif = exif_image.getexif() diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 854d9e83ee7..f4a636098a4 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -114,7 +114,7 @@ def _open(self): # Don't trust the passed in stride. # Calculate the approximate position for ourselves. # CVE-2020-35653 - stride = (self._size[0] * bits + 7) // 8 + stride = (self.size[0] * bits + 7) // 8 # While the specification states that this must be even, # not all images follow this diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index 5c34075038f..e51e739af2e 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -32,7 +32,7 @@ def _open(self): self._mode = "RGB" if channels == 3 else "RGBA" self.fp.seek(1, os.SEEK_CUR) # colorspace - self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)] + self.tile = [("qoi", (0, 0) + self.size, self.fp.tell(), None)] class QoiDecoder(ImageFile.PyDecoder): diff --git a/src/_imaging.c b/src/_imaging.c index e15cb89fcea..5c911d81a2e 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3646,11 +3646,49 @@ _getattr_mode(ImagingObject *self, void *closure) { return PyUnicode_FromString(self->image->mode); } +static int +_setattr_mode(ImagingObject *self, PyObject *value, void *closure) { + if (value == NULL) { + self->image->mode[0] = '\0'; + return 0; + } + + const char *mode = PyUnicode_AsUTF8(value); + if (mode == NULL) { + return -1; + } + if (strlen(mode) >= IMAGING_MODE_LENGTH) { + PyErr_SetString(PyExc_ValueError, "given mode name is too long"); + return -1; + } + + strcpy(self->image->mode, mode); + return 0; +} + static PyObject * _getattr_size(ImagingObject *self, void *closure) { return Py_BuildValue("ii", self->image->xsize, self->image->ysize); } +static int +_setattr_size(ImagingObject *self, PyObject *value, void *closure) { + if (value == NULL) { + self->image->xsize = 0; + self->image->ysize = 0; + return 0; + } + + int xsize, ysize; + if (!PyArg_ParseTuple(value, "ii", &xsize, &ysize)) { + return -1; + } + + self->image->xsize = xsize; + self->image->ysize = ysize; + return 0; +} + static PyObject * _getattr_bands(ImagingObject *self, void *closure) { return PyLong_FromLong(self->image->bands); @@ -3679,13 +3717,14 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) { }; static struct PyGetSetDef getsetters[] = { - {"mode", (getter)_getattr_mode}, - {"size", (getter)_getattr_size}, + {"mode", (getter)_getattr_mode, (setter)_setattr_mode}, + {"size", (getter)_getattr_size, (setter)_setattr_size}, {"bands", (getter)_getattr_bands}, {"id", (getter)_getattr_id}, {"ptr", (getter)_getattr_ptr}, {"unsafe_ptrs", (getter)_getattr_unsafe_ptrs}, - {NULL}}; + {NULL} +}; /* basic sequence semantics */