Skip to content

Commit

Permalink
Merge pull request #3203 from radarhere/size
Browse files Browse the repository at this point in the history
Changed Image size property to be read-only by default
  • Loading branch information
hugovk committed Sep 30, 2018
2 parents 8344aec + 05f2169 commit 2fa5440
Show file tree
Hide file tree
Showing 50 changed files with 142 additions and 62 deletions.
4 changes: 4 additions & 0 deletions Tests/test_file_icns.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def test_sizes(self):
self.assertEqual(im2.mode, 'RGBA')
self.assertEqual(im2.size, (wr, hr))

# Check that we cannot load an incorrect size
with self.assertRaises(ValueError):
im.size = (1, 1)

def test_older_icon(self):
# This icon was made with Icon Composer rather than iconutil; it still
# uses PNG rather than JP2, however (since it was made on 10.9).
Expand Down
5 changes: 5 additions & 0 deletions Tests/test_file_ico.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ def test_save_to_bytes(self):
self.assert_image_equal(reloaded,
hopper().resize((32, 32), Image.LANCZOS))

def test_incorrect_size(self):
im = Image.open(TEST_ICO_FILE)
with self.assertRaises(ValueError):
im.size = (1, 1)

def test_save_256x256(self):
"""Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
# Arrange
Expand Down
8 changes: 8 additions & 0 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ def test_set_legacy_api(self):
self.assertEqual(str(e.exception),
"Not allowing setting of legacy api")

def test_size(self):
filename = "Tests/images/pil168.tif"
im = Image.open(filename)

def set_size():
im.size = (256, 256)
self.assert_warning(DeprecationWarning, set_size)

def test_xyres_tiff(self):
filename = "Tests/images/pil168.tif"
im = Image.open(filename)
Expand Down
5 changes: 2 additions & 3 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ def test_width_height(self):
self.assertEqual(im.width, 1)
self.assertEqual(im.height, 2)

im.size = (3, 4)
self.assertEqual(im.width, 3)
self.assertEqual(im.height, 4)
with self.assertRaises(AttributeError) as e:
im.size = (3, 4)

def test_invalid_image(self):
if py3:
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class MockImageFile(ImageFile.ImageFile):
def _open(self):
self.rawmode = 'RGBA'
self.mode = 'RGBA'
self.size = (200, 200)
self._size = (200, 200)
self.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize), 32, None)]


Expand Down
2 changes: 1 addition & 1 deletion docs/example/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def _open(self):
header = BytesIO(header_bytes)

flags, height, width = struct.unpack("<3I", header.read(12))
self.size = (width, height)
self._size = (width, height)
self.mode = "RGBA"

pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
Expand Down
2 changes: 1 addition & 1 deletion docs/handbook/writing-your-own-file-decoder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ true color.
header = string.split(header)

# size in pixels (width, height)
self.size = int(header[1]), int(header[2])
self._size = int(header[1]), int(header[2])

# mode setting
bits = int(header[3])
Expand Down
16 changes: 16 additions & 0 deletions docs/releasenotes/5.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ and size, new method ``ImageOps.pad`` pads images to fill a requested aspect
ratio and size, filling new space with a provided ``color`` and positioning the
image within the new area through a ``centering`` argument.

Image Size
==========

If you attempt to set the size of an image directly, e.g.
``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is
not about removing existing functionality, but instead about raising an
explicit error to prevent later consequences. The ``resize`` method is the
correct way to change an image's size.

The exceptions to this are:
* The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select
a subimage.
* The TIFF image format, which now has a ``DeprecationWarning`` for this
action, as direct image size setting was previously necessary to work around an
issue with tile extents.

Other Changes
=============

Expand Down
2 changes: 1 addition & 1 deletion src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def _read_blp_header(self):
self._blp_alpha_encoding, = struct.unpack("<b", self.fp.read(1))
self._blp_mips, = struct.unpack("<b", self.fp.read(1))

self.size = struct.unpack("<II", self.fp.read(8))
self._size = struct.unpack("<II", self.fp.read(8))

if self.magic == b"BLP1":
# Only present for BLP1
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def _bitmap(self, header=0, offset=0):
file_info['header_size'])
# ------------------ Special case : header is reported 40, which
# ---------------------- is shorter than real size for bpp >= 16
self.size = file_info['width'], file_info['height']
self._size = file_info['width'], file_info['height']
# -------- If color count was not found in the header, compute from bits
file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits'])
# -------------------------------- Check abnormal values for DOS attacks
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/BufrStubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _open(self):

# make something up
self.mode = "F"
self.size = 1, 1
self._size = 1, 1

loader = self._load()
if loader:
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/CurImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _open(self):
self._bitmap(i32(m[12:]) + offset)

# patch up the bitmap height
self.size = self.size[0], self.size[1]//2
self._size = self.size[0], self.size[1]//2
d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0)+self.size, o, a

Expand Down
2 changes: 1 addition & 1 deletion src/PIL/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def _open(self):
header = BytesIO(header_bytes)

flags, height, width = struct.unpack("<3I", header.read(12))
self.size = (width, height)
self._size = (width, height)
self.mode = "RGBA"

pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
Expand Down
8 changes: 4 additions & 4 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def _open(self):
box = None

self.mode = "RGB"
self.size = 1, 1 # FIXME: huh?
self._size = 1, 1 # FIXME: huh?

#
# Load EPS header
Expand All @@ -244,7 +244,7 @@ def _open(self):
# fields should be integers, but some drivers
# put floating point values there anyway.
box = [int(float(i)) for i in v.split()]
self.size = box[2] - box[0], box[3] - box[1]
self._size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0, 0) + self.size, offset,
(length, box))]
except Exception:
Expand Down Expand Up @@ -293,7 +293,7 @@ def _open(self):
except ValueError:
break

self.size = int(x), int(y)
self._size = int(x), int(y)
return

s = fp.readline().strip('\r\n')
Expand Down Expand Up @@ -331,7 +331,7 @@ def load(self, scale=1):
return
self.im = Ghostscript(self.tile, self.size, self.fp, scale)
self.mode = self.im.mode
self.size = self.im.size
self._size = self.im.size
self.tile = []

def load_seek(self, *args, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FitsStubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _open(self):

# make something up
self.mode = "F"
self.size = 1, 1
self._size = 1, 1

loader = self._load()
if loader:
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FliImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def _open(self):

# image characteristics
self.mode = "P"
self.size = i16(s[8:10]), i16(s[10:12])
self._size = i16(s[8:10]), i16(s[10:12])

# animation speed
duration = i32(s[16:20])
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FpxImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _open_index(self, index=1):

# size (highest resolution)

self.size = prop[0x1000002], prop[0x1000003]
self._size = prop[0x1000002], prop[0x1000003]

size = max(self.size)
i = 1
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FtexImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class FtexImageFile(ImageFile.ImageFile):
def _open(self):
magic = struct.unpack("<I", self.fp.read(4))
version = struct.unpack("<i", self.fp.read(4))
self.size = struct.unpack("<2i", self.fp.read(8))
self._size = struct.unpack("<2i", self.fp.read(8))
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))

self.mode = "RGB"
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GbrImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _open(self):
else:
self.mode = 'RGBA'

self.size = width, height
self._size = width, height

self.info["comment"] = comment

Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GdImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _open(self):
raise SyntaxError("Not a valid GD 2.x .gd file")

self.mode = "L" # FIXME: "P"
self.size = i16(s[2:4]), i16(s[4:6])
self._size = i16(s[2:4]), i16(s[4:6])

trueColor = i8(s[6])
trueColorOffset = 2 if trueColor else 0
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _open(self):
raise SyntaxError("not a GIF file")

self.info["version"] = s[:6]
self.size = i16(s[6:]), i16(s[8:])
self._size = i16(s[6:]), i16(s[8:])
self.tile = []
flags = i8(s[10])
bits = (flags & 7) + 1
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GribStubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _open(self):

# make something up
self.mode = "F"
self.size = 1, 1
self._size = 1, 1

loader = self._load()
if loader:
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/Hdf5StubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _open(self):

# make something up
self.mode = "F"
self.size = 1, 1
self._size = 1, 1

loader = self._load()
if loader:
Expand Down
22 changes: 21 additions & 1 deletion src/PIL/IcnsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,33 @@ class IcnsImageFile(ImageFile.ImageFile):
def _open(self):
self.icns = IcnsFile(self.fp)
self.mode = 'RGBA'
self.info['sizes'] = self.icns.itersizes()
self.best_size = self.icns.bestsize()
self.size = (self.best_size[0] * self.best_size[2],
self.best_size[1] * self.best_size[2])
self.info['sizes'] = self.icns.itersizes()
# Just use this to see if it's loaded or not yet.
self.tile = ('',)

@property
def size(self):
return self._size

@size.setter
def size(self, value):
info_size = value
if info_size not in self.info['sizes'] and len(info_size) == 2:
info_size = (info_size[0], info_size[1], 1)
if info_size not in self.info['sizes'] and len(info_size) == 3 and \
info_size[2] == 1:
simple_sizes = [(size[0] * size[2], size[1] * size[2])
for size in self.info['sizes']]
if value in simple_sizes:
info_size = self.info['sizes'][simple_sizes.index(value)]
if info_size not in self.info['sizes']:
raise ValueError(
"This is not one of the allowed sizes of this image")
self._size = value

def load(self):
if len(self.size) == 3:
self.best_size = self.size
Expand Down
13 changes: 12 additions & 1 deletion src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def frame(self, idx):
im = BmpImagePlugin.DibImageFile(self.buf)

# change tile dimension to only encompass XOR image
im.size = (im.size[0], int(im.size[1] / 2))
im._size = (im.size[0], int(im.size[1] / 2))
d, e, o, a = im.tile[0]
im.tile[0] = d, (0, 0) + im.size, o, a

Expand Down Expand Up @@ -263,6 +263,17 @@ def _open(self):
self.size = self.ico.entry[0]['dim']
self.load()

@property
def size(self):
return self._size

@size.setter
def size(self, value):
if value not in self.info['sizes']:
raise ValueError(
"This is not one of the allowed sizes of this image")
self._size = value

def load(self):
im = self.ico.getimage(self.size)
# if tile is PNG, it won't really be loaded yet
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/ImImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def _open(self):
raise SyntaxError("Not an IM file")

# Basic attributes
self.size = self.info[SIZE]
self._size = self.info[SIZE]
self.mode = self.info[MODE]

# Skip forward to start of image data
Expand Down
12 changes: 8 additions & 4 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ def __init__(self):
# FIXME: turn mode and size into delegating properties?
self.im = None
self.mode = ""
self.size = (0, 0)
self._size = (0, 0)
self.palette = None
self.info = {}
self.category = NORMAL
Expand All @@ -546,11 +546,15 @@ def width(self):
def height(self):
return self.size[1]

@property
def size(self):
return self._size

def _new(self, im):
new = Image()
new.im = im
new.mode = im.mode
new.size = im.size
new._size = im.size
if im.mode in ('P', 'PA'):
if self.palette:
new.palette = self.palette.copy()
Expand Down Expand Up @@ -698,7 +702,7 @@ def __setstate__(self, state):
info, mode, size, palette, data = state
self.info = info
self.mode = mode
self.size = size
self._size = size
self.im = core.new(mode, size)
if mode in ("L", "P") and palette:
self.putpalette(palette)
Expand Down Expand Up @@ -2104,7 +2108,7 @@ def thumbnail(self, size, resample=BICUBIC):

self.im = im.im
self.mode = im.mode
self.size = size
self._size = size

self.readonly = 0
self.pyaccess = None
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/ImtImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ def _open(self):
k, v = m.group(1, 2)
if k == "width":
xsize = int(v)
self.size = xsize, ysize
self._size = xsize, ysize
elif k == "height":
ysize = int(v)
self.size = xsize, ysize
self._size = xsize, ysize
elif k == "pixel" and v == "n8":
self.mode = "L"

Expand Down
2 changes: 1 addition & 1 deletion src/PIL/IptcImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def _open(self):
self.mode = "CMYK"[id]

# size
self.size = self.getint((3, 20)), self.getint((3, 30))
self._size = self.getint((3, 20)), self.getint((3, 30))

# compression
try:
Expand Down
Loading

0 comments on commit 2fa5440

Please sign in to comment.