diff --git a/Tests/test_image.py b/Tests/test_image.py index 5795f6c5cb7..6ee6f2233c4 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -97,6 +97,10 @@ def test_sanity(self) -> None: # with pytest.raises(MemoryError): # Image.new("L", (1000000, 1000000)) + def test_direct(self) -> None: + with pytest.raises(TypeError): + Image.Image() + @pytest.mark.skipif(PrettyPrinter is None, reason="IPython is not installed") def test_repr_pretty(self) -> None: im = Image.new("L", (100, 100)) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 340cc47420b..57a85d26725 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] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index fbeecef0e6a..194920f4abe 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -546,15 +546,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._exif = None + msg = ( + "Images should not be instantiated directly. " + "Use the module new() function instead." + ) + raise TypeError(msg) @property def width(self) -> int: @@ -572,18 +568,32 @@ def size(self) -> tuple[int, int]: def mode(self) -> str: return self._mode - def _new(self, im: core.ImagingCore) -> Image: - new = Image() - new.im = im - new._mode = im.mode - new._size = im.size + def _prepare(self): + self.im = None + self._mode = "" + self._size = (0, 0) + self.palette = None + self.info = {} + self.readonly = 0 + self._exif = None + + @classmethod + 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"): - if self.palette: - new.palette = self.palette.copy() - else: - from . import ImagePalette + from . import ImagePalette + + self.palette = ImagePalette.ImagePalette() + return self - new.palette = ImagePalette.ImagePalette() + def _new(self, im: core.ImagingCore) -> Image: + 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 @@ -750,7 +760,7 @@ def __getstate__(self) -> list[Any]: return [self.info, self.mode, self.size, self.getpalette(), im_data] def __setstate__(self, state: list[Any]) -> None: - Image.__init__(self) + self._prepare() info, mode, size, palette, data = state self.info = info self._mode = mode @@ -3038,7 +3048,7 @@ def transform( def _wedge() -> Image: """Create grayscale wedge (for debugging only)""" - return Image()._new(core.wedge("L")) + return Image._init(core.wedge("L")) def _check_size(size: Any) -> None: @@ -3087,7 +3097,7 @@ def new( if color is None: # don't initialize - return Image()._new(core.new(mode, size)) + return Image._init(core.new(mode, size)) if isinstance(color, str): # css3-style specifier @@ -3096,7 +3106,7 @@ def new( color = ImageColor.getcolor(color, mode) - im = Image() + rgb_color = False if ( mode == "P" and isinstance(color, (list, tuple)) @@ -3105,11 +3115,14 @@ def new( color_ints: tuple[int, ...] = cast(tuple[int, ...], tuple(color)) if len(color_ints) == 3 or len(color_ints) == 4: # RGB or RGBA value for a P image - from . import ImagePalette + rgb_color = True - im.palette = ImagePalette.ImagePalette() - color = im.palette.getcolor(color_ints) - 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(color_ints) + return im def frombytes( @@ -3760,7 +3773,7 @@ def effect_mandelbrot( (x0, y0, x1, y1). :param quality: Quality. """ - return Image()._new(core.effect_mandelbrot(size, extent, quality)) + return Image._init(core.effect_mandelbrot(size, extent, quality)) def effect_noise(size: tuple[int, int], sigma: float) -> Image: @@ -3771,7 +3784,7 @@ def effect_noise(size: tuple[int, int], sigma: float) -> Image: (width, height). :param sigma: Standard deviation of noise. """ - return Image()._new(core.effect_noise(size, sigma)) + return Image._init(core.effect_noise(size, sigma)) def linear_gradient(mode: str) -> Image: @@ -3780,7 +3793,7 @@ def linear_gradient(mode: str) -> Image: :param mode: Input mode. """ - return Image()._new(core.linear_gradient(mode)) + return Image._init(core.linear_gradient(mode)) def radial_gradient(mode: str) -> Image: @@ -3789,7 +3802,7 @@ def radial_gradient(mode: str) -> Image: :param mode: Input mode. """ - return Image()._new(core.radial_gradient(mode)) + return Image._init(core.radial_gradient(mode)) # -------------------------------------------------------------------- diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index fdeb81d7a10..f7d9cca7424 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -108,7 +108,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