Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise a TypeError when Image() is called #7461

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
101 changes: 58 additions & 43 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -508,18 +503,33 @@ def size(self):
def mode(self):
return self._mode

def _new(self, im):
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.pyaccess = None
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):
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 @@ -695,7 +705,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
Expand Down Expand Up @@ -933,9 +943,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
Expand All @@ -948,8 +958,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)
Expand Down Expand Up @@ -980,7 +990,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 = new(self.mode, (1, 1))
if self.mode == "P":
trns_im.putpalette(self.palette)
if isinstance(t, tuple):
Expand Down Expand Up @@ -1021,23 +1031,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
Expand Down Expand Up @@ -2874,7 +2886,7 @@ class ImageTransformHandler:
def _wedge():
"""Create greyscale wedge (for debugging only)"""

return Image()._new(core.wedge("L"))
return Image._init(core.wedge("L"))


def _check_size(size):
Expand Down Expand Up @@ -2918,7 +2930,7 @@ def new(mode, size, color=0):

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 @@ -2927,14 +2939,17 @@ def new(mode, size, color=0):

color = ImageColor.getcolor(color, mode)

im = Image()
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to make this assumption rather than just using existing API?

We can rewrite palette if it is exist:

im = Image._init(core.fill(mode, size, color))
if palette:
     im.palette = palette

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I understand the desire to use APIs. My original thought was to try and avoid constructing a second imagePalette, but this version of the code seems simpler to me. I don't think it should ever change that a palette starts out empty, and that the first color is at the zero index.

Are you sure using getcolor() is better?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@homm did my argument persuade you, or no? I can change to your suggestion if you like, I'd rather progress this than let it sit unresolved.

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):
Expand Down Expand Up @@ -3547,7 +3562,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(core.effect_mandelbrot(size, extent, quality))


def effect_noise(size, sigma):
Expand All @@ -3558,7 +3573,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(core.effect_noise(size, sigma))


def linear_gradient(mode):
Expand All @@ -3567,7 +3582,7 @@ def linear_gradient(mode):

:param mode: Input mode.
"""
return Image()._new(core.linear_gradient(mode))
return Image._init(core.linear_gradient(mode))


def radial_gradient(mode):
Expand All @@ -3576,7 +3591,7 @@ def radial_gradient(mode):

: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 @@ -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

Expand Down