Skip to content
4 changes: 4 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 5 additions & 4 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
75 changes: 44 additions & 31 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -3096,7 +3106,7 @@ def new(

color = ImageColor.getcolor(color, mode)

im = Image()
rgb_color = False
if (
mode == "P"
and isinstance(color, (list, tuple))
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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))


# --------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down