From ae96f81450353c983df2d8d3c6f54491a2b0305b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 13 Oct 2023 19:10:08 +1100 Subject: [PATCH 1/4] Raise a TypeError when Image() is called --- Tests/test_image.py | 4 ++++ src/PIL/Image.py | 51 +++++++++++++++++++++++++++----------------- src/PIL/ImageFile.py | 2 +- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 83dac70802f..25c7739415b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -94,6 +94,10 @@ def test_sanity(self): # with pytest.raises(MemoryError): # Image.new("L", (1000000, 1000000)) + def test_direct(self): + with pytest.raises(TypeError): + Image.Image() + def test_repr_pretty(self): class Pretty: def text(self, text): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a79666d7afe..1272c0f375c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -481,16 +481,11 @@ class Image: _close_exclusive_fp_after_loading = True 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) - self.palette = None - self.info = {} - self.readonly = 0 - self.pyaccess = None - self._exif = None + msg = ( + "Images should not be instantiated directly. " + "Use the module new() function instead." + ) + raise TypeError(msg) @property def width(self): @@ -508,8 +503,24 @@ def size(self): def mode(self): return self._mode + def _prepare(self): + self.im = None + self._mode = "" + self._size = (0, 0) + self.palette = None + self.info = {} + self.readonly = 0 + self.pyaccess = None + self._exif = None + + @classmethod + def _init(cls): + self = cls.__new__(cls) + self._prepare() + return self + def _new(self, im): - new = Image() + new = Image._init() new.im = im new._mode = im.mode new._size = im.size @@ -695,7 +706,7 @@ def __getstate__(self): return [self.info, self.mode, self.size, self.getpalette(), im_data] def __setstate__(self, state): - Image.__init__(self) + self._prepare() info, mode, size, palette, data = state self.info = info self._mode = mode @@ -980,7 +991,7 @@ def convert_transparency(m, v): else: # get the new transparency color. # use existing conversions - trns_im = Image()._new(core.new(self.mode, (1, 1))) + trns_im = Image._init()._new(core.new(self.mode, (1, 1))) if self.mode == "P": trns_im.putpalette(self.palette) if isinstance(t, tuple): @@ -2874,7 +2885,7 @@ class ImageTransformHandler: def _wedge(): """Create greyscale wedge (for debugging only)""" - return Image()._new(core.wedge("L")) + return Image._init()._new(core.wedge("L")) def _check_size(size): @@ -2918,7 +2929,7 @@ def new(mode, size, color=0): if color is None: # don't initialize - return Image()._new(core.new(mode, size)) + return Image._init()._new(core.new(mode, size)) if isinstance(color, str): # css3-style specifier @@ -2927,7 +2938,7 @@ def new(mode, size, color=0): color = ImageColor.getcolor(color, mode) - im = Image() + im = Image._init() if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]: # RGB or RGBA value for a P image from . import ImagePalette @@ -3547,7 +3558,7 @@ def effect_mandelbrot(size, extent, quality): (x0, y0, x1, y1). :param quality: Quality. """ - return Image()._new(core.effect_mandelbrot(size, extent, quality)) + return Image._init()._new(core.effect_mandelbrot(size, extent, quality)) def effect_noise(size, sigma): @@ -3558,7 +3569,7 @@ def effect_noise(size, sigma): (width, height). :param sigma: Standard deviation of noise. """ - return Image()._new(core.effect_noise(size, sigma)) + return Image._init()._new(core.effect_noise(size, sigma)) def linear_gradient(mode): @@ -3567,7 +3578,7 @@ def linear_gradient(mode): :param mode: Input mode. """ - return Image()._new(core.linear_gradient(mode)) + return Image._init()._new(core.linear_gradient(mode)) def radial_gradient(mode): @@ -3576,7 +3587,7 @@ def radial_gradient(mode): :param mode: Input mode. """ - return Image()._new(core.radial_gradient(mode)) + return Image._init()._new(core.radial_gradient(mode)) # -------------------------------------------------------------------- diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 8e4f7dfb2c8..7dd33a4dbe1 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -86,7 +86,7 @@ class ImageFile(Image.Image): """Base class for image file format handlers.""" def __init__(self, fp=None, filename=None): - super().__init__() + super()._prepare() self._min_frame = 0 From 54792a48f11bd0a0c536dbc8cc5bdcb232723af4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 13 Oct 2023 15:41:26 +1100 Subject: [PATCH 2/4] Use new() instead of Image()._new() --- src/PIL/Image.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1272c0f375c..22e2feeb4d8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -944,9 +944,9 @@ def convert( msg = "illegal conversion" raise ValueError(msg) im = self.im.convert_matrix(mode, matrix) - new = self._new(im) + new_im = self._new(im) if has_transparency and self.im.bands == 3: - transparency = new.info["transparency"] + transparency = new_im.info["transparency"] def convert_transparency(m, v): v = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5 @@ -959,8 +959,8 @@ def convert_transparency(m, v): convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) for i in range(0, len(transparency)) ) - new.info["transparency"] = transparency - return new + new_im.info["transparency"] = transparency + return new_im if mode == "P" and self.mode == "RGBA": return self.quantize(colors) @@ -991,7 +991,7 @@ def convert_transparency(m, v): else: # get the new transparency color. # use existing conversions - trns_im = Image._init()._new(core.new(self.mode, (1, 1))) + trns_im = new(self.mode, (1, 1)) if self.mode == "P": trns_im.putpalette(self.palette) if isinstance(t, tuple): @@ -1032,23 +1032,25 @@ def convert_transparency(m, v): if mode == "P" and palette == Palette.ADAPTIVE: im = self.im.quantize(colors) - new = self._new(im) + new_im = self._new(im) from . import ImagePalette - new.palette = ImagePalette.ImagePalette("RGB", new.im.getpalette("RGB")) + new_im.palette = ImagePalette.ImagePalette( + "RGB", new_im.im.getpalette("RGB") + ) if delete_trns: # This could possibly happen if we requantize to fewer colors. # The transparency would be totally off in that case. - del new.info["transparency"] + del new_im.info["transparency"] if trns is not None: try: - new.info["transparency"] = new.palette.getcolor(trns, new) + new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im) except Exception: # if we can't make a transparent color, don't leave the old # transparency hanging around to mess us up. - del new.info["transparency"] + del new_im.info["transparency"] warnings.warn("Couldn't allocate palette entry for transparency") - return new + return new_im if "LAB" in (self.mode, mode): other_mode = mode if self.mode == "LAB" else self.mode From 50a5bc8f9e3705bba1409d3c093b83453ed36dba Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 13 Oct 2023 21:53:24 +1100 Subject: [PATCH 3/4] Pass core image object to _init() --- src/PIL/Image.py | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 22e2feeb4d8..cbec03bfcc1 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -514,23 +514,22 @@ def _prepare(self): self._exif = None @classmethod - def _init(cls): + def _init(cls, im): self = cls.__new__(cls) self._prepare() + self.im = im + self._mode = im.mode + self._size = im.size + if im.mode in ("P", "PA"): + from . import ImagePalette + + self.palette = ImagePalette.ImagePalette() return self def _new(self, im): - new = Image._init() - new.im = im - new._mode = im.mode - new._size = im.size - if im.mode in ("P", "PA"): - if self.palette: - new.palette = self.palette.copy() - else: - from . import ImagePalette - - new.palette = ImagePalette.ImagePalette() + new = Image._init(im) + if im.mode in ("P", "PA") and self.palette: + new.palette = self.palette.copy() new.info = self.info.copy() return new @@ -2887,7 +2886,7 @@ class ImageTransformHandler: def _wedge(): """Create greyscale wedge (for debugging only)""" - return Image._init()._new(core.wedge("L")) + return Image._init(core.wedge("L")) def _check_size(size): @@ -2931,7 +2930,7 @@ def new(mode, size, color=0): if color is None: # don't initialize - return Image._init()._new(core.new(mode, size)) + return Image._init(core.new(mode, size)) if isinstance(color, str): # css3-style specifier @@ -2940,14 +2939,17 @@ def new(mode, size, color=0): color = ImageColor.getcolor(color, mode) - im = Image._init() + rgb_color = None if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]: # RGB or RGBA value for a P image - from . import ImagePalette + rgb_color = color - im.palette = ImagePalette.ImagePalette() - color = im.palette.getcolor(color) - return im._new(core.fill(mode, size, color)) + # This will be the first color allocated to the palette + color = 0 + im = Image._init(core.fill(mode, size, color)) + if rgb_color: + im.palette.getcolor(rgb_color) + return im def frombytes(mode, size, data, decoder_name="raw", *args): @@ -3560,7 +3562,7 @@ def effect_mandelbrot(size, extent, quality): (x0, y0, x1, y1). :param quality: Quality. """ - return Image._init()._new(core.effect_mandelbrot(size, extent, quality)) + return Image._init(core.effect_mandelbrot(size, extent, quality)) def effect_noise(size, sigma): @@ -3571,7 +3573,7 @@ def effect_noise(size, sigma): (width, height). :param sigma: Standard deviation of noise. """ - return Image._init()._new(core.effect_noise(size, sigma)) + return Image._init(core.effect_noise(size, sigma)) def linear_gradient(mode): @@ -3580,7 +3582,7 @@ def linear_gradient(mode): :param mode: Input mode. """ - return Image._init()._new(core.linear_gradient(mode)) + return Image._init(core.linear_gradient(mode)) def radial_gradient(mode): @@ -3589,7 +3591,7 @@ def radial_gradient(mode): :param mode: Input mode. """ - return Image._init()._new(core.radial_gradient(mode)) + return Image._init(core.radial_gradient(mode)) # -------------------------------------------------------------------- From cab66d7a5431841924e48f4816f96aabb6324478 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jul 2024 06:06:16 +1000 Subject: [PATCH 4/4] Do not call Image() --- Tests/test_imagefont.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 9cb42037106..7d6e2e31dd6 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1102,14 +1102,15 @@ def test_bytes(font: ImageFont.FreeTypeFont) -> None: assert font.getbbox(b"test") == font.getbbox("test") + im = Image.new("L", (1, 1)) assert_image_equal( - Image.Image()._new(font.getmask(b"test")), - Image.Image()._new(font.getmask("test")), + im._new(font.getmask(b"test")), + im._new(font.getmask("test")), ) assert_image_equal( - Image.Image()._new(font.getmask2(b"test")[0]), - Image.Image()._new(font.getmask2("test")[0]), + im._new(font.getmask2(b"test")[0]), + im._new(font.getmask2("test")[0]), ) assert font.getmask2(b"test")[1] == font.getmask2("test")[1]