From 9979a822c731833fcf52e12a20e9109f84dc1aae Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Jul 2023 09:28:18 +1000 Subject: [PATCH 1/2] Changed Image mode property to be read-only by default --- Tests/test_image.py | 6 +++++ Tests/test_imagefile.py | 4 ++-- Tests/test_pickle.py | 4 ++-- docs/example/DdsImagePlugin.py | 2 +- .../writing-your-own-image-plugin.rst | 6 ++--- src/PIL/BlpImagePlugin.py | 2 +- src/PIL/BmpImagePlugin.py | 10 ++++----- src/PIL/BufrStubImagePlugin.py | 2 +- src/PIL/DdsImagePlugin.py | 22 +++++++++---------- src/PIL/EpsImagePlugin.py | 8 +++---- src/PIL/FitsImagePlugin.py | 8 +++---- src/PIL/FliImagePlugin.py | 2 +- src/PIL/FpxImagePlugin.py | 2 +- src/PIL/FtexImagePlugin.py | 4 ++-- src/PIL/GbrImagePlugin.py | 4 ++-- src/PIL/GdImageFile.py | 2 +- src/PIL/GifImagePlugin.py | 18 +++++++-------- src/PIL/GribStubImagePlugin.py | 2 +- src/PIL/Hdf5StubImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 4 ++-- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/ImImagePlugin.py | 6 ++--- src/PIL/Image.py | 18 +++++++++------ src/PIL/ImtImagePlugin.py | 2 +- src/PIL/IptcImagePlugin.py | 6 ++--- src/PIL/Jpeg2KImagePlugin.py | 4 ++-- src/PIL/JpegImagePlugin.py | 10 ++++----- src/PIL/McIdasImagePlugin.py | 2 +- src/PIL/MpegImagePlugin.py | 2 +- src/PIL/MspImagePlugin.py | 2 +- src/PIL/PcdImagePlugin.py | 2 +- src/PIL/PcxImagePlugin.py | 2 +- src/PIL/PixarImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 2 +- src/PIL/PpmImagePlugin.py | 6 ++--- src/PIL/PsdImagePlugin.py | 4 ++-- src/PIL/QoiImagePlugin.py | 2 +- src/PIL/SgiImagePlugin.py | 2 +- src/PIL/SpiderImagePlugin.py | 2 +- src/PIL/SunImagePlugin.py | 16 +++++++------- src/PIL/TgaImagePlugin.py | 12 +++++----- src/PIL/TiffImagePlugin.py | 4 ++-- src/PIL/WalImageFile.py | 2 +- src/PIL/WebPImagePlugin.py | 4 ++-- src/PIL/WmfImagePlugin.py | 4 ++-- src/PIL/XVThumbImagePlugin.py | 2 +- src/PIL/XbmImagePlugin.py | 2 +- src/PIL/XpmImagePlugin.py | 2 +- 48 files changed, 125 insertions(+), 115 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 85f9f7d0231..36f24379a60 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -135,6 +135,12 @@ def test_width_height(self): with pytest.raises(AttributeError): im.size = (3, 4) + def test_set_mode(self): + im = Image.new("RGB", (1, 1)) + + with pytest.raises(AttributeError): + im.mode = "P" + def test_invalid_image(self): im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 412bc10d9a8..ff75b8c2a69 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -136,7 +136,7 @@ def test_no_format(self): class DummyImageFile(ImageFile.ImageFile): def _open(self): - self.mode = "RGB" + self._mode = "RGB" self._size = (1, 1) im = DummyImageFile(buf) @@ -217,7 +217,7 @@ def cleanup(self): class MockImageFile(ImageFile.ImageFile): def _open(self): self.rawmode = "RGBA" - self.mode = "RGBA" + self._mode = "RGBA" self._size = (200, 200) self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)] diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 2f6d0588853..531936f8f19 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -75,13 +75,13 @@ def test_pickle_la_mode_with_palette(tmp_path): # Act / Assert for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - im.mode = "LA" + im._mode = "LA" with open(filename, "wb") as f: pickle.dump(im, f, protocol) with open(filename, "rb") as f: loaded_im = pickle.load(f) - im.mode = "PA" + im._mode = "PA" assert im == loaded_im diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 26451533eee..61690410b6c 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -225,7 +225,7 @@ def _open(self): flags, height, width = struct.unpack("<3I", header.read(12)) self._size = (width, height) - self.mode = "RGBA" + self._mode = "RGBA" pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved diff --git a/docs/handbook/writing-your-own-image-plugin.rst b/docs/handbook/writing-your-own-image-plugin.rst index 75604e17a96..ca16fccda6a 100644 --- a/docs/handbook/writing-your-own-image-plugin.rst +++ b/docs/handbook/writing-your-own-image-plugin.rst @@ -72,11 +72,11 @@ true color. # mode setting bits = int(header[3]) if bits == 1: - self.mode = "1" + self._mode = "1" elif bits == 8: - self.mode = "L" + self._mode = "L" elif bits == 24: - self.mode = "RGB" + self._mode = "RGB" else: msg = "unknown number of bits" raise SyntaxError(msg) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 0ca60ff2471..a5cfad5f4ee 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -266,7 +266,7 @@ def _open(self): msg = f"Bad BLP magic {repr(self.magic)}" raise BLPFormatError(msg) - self.mode = "RGBA" if self._blp_alpha_depth else "RGB" + self._mode = "RGBA" if self._blp_alpha_depth else "RGB" self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 5bda0a5b05d..9abfd0b5b8d 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -163,7 +163,7 @@ def _bitmap(self, header=0, offset=0): offset += 4 * file_info["colors"] # ---------------------- Check bit depth for unusual unsupported values - self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) + self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) if self.mode is None: msg = f"Unsupported BMP pixel depth ({file_info['bits']})" raise OSError(msg) @@ -200,7 +200,7 @@ def _bitmap(self, header=0, offset=0): and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] ): raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] - self.mode = "RGBA" if "A" in raw_mode else self.mode + self._mode = "RGBA" if "A" in raw_mode else self.mode elif ( file_info["bits"] in (24, 16) and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] @@ -214,7 +214,7 @@ def _bitmap(self, header=0, offset=0): raise OSError(msg) elif file_info["compression"] == self.RAW: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset - raw_mode, self.mode = "BGRA", "RGBA" + raw_mode, self._mode = "BGRA", "RGBA" elif file_info["compression"] in (self.RLE8, self.RLE4): decoder_name = "bmp_rle" else: @@ -245,10 +245,10 @@ def _bitmap(self, header=0, offset=0): # ------- If all colors are grey, white or black, ditch palette if greyscale: - self.mode = "1" if file_info["colors"] == 2 else "L" + self._mode = "1" if file_info["colors"] == 2 else "L" raw_mode = self.mode else: - self.mode = "P" + self._mode = "P" self.palette = ImagePalette.raw( "BGRX" if padding == 4 else "BGR", palette ) diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 0425bbd750e..eef25aa14cf 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -46,7 +46,7 @@ def _open(self): self.fp.seek(offset) # make something up - self.mode = "F" + self._mode = "F" self._size = 1, 1 loader = self._load() diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index a946daeaa6b..1368ae24e59 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -128,7 +128,7 @@ def _open(self): flags, height, width = struct.unpack("<3I", header.read(12)) self._size = (width, height) - self.mode = "RGBA" + self._mode = "RGBA" pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved @@ -141,9 +141,9 @@ def _open(self): if pfflags & DDPF_LUMINANCE: # Texture contains uncompressed L or LA data if pfflags & DDPF_ALPHAPIXELS: - self.mode = "LA" + self._mode = "LA" else: - self.mode = "L" + self._mode = "L" self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))] elif pfflags & DDPF_RGB: @@ -153,7 +153,7 @@ def _open(self): if pfflags & DDPF_ALPHAPIXELS: rawmode += masks[0xFF000000] else: - self.mode = "RGB" + self._mode = "RGB" rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF] self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] @@ -172,15 +172,15 @@ def _open(self): elif fourcc == b"ATI1": self.pixel_format = "BC4" n = 4 - self.mode = "L" + self._mode = "L" elif fourcc == b"ATI2": self.pixel_format = "BC5" n = 5 - self.mode = "RGB" + self._mode = "RGB" elif fourcc == b"BC5S": self.pixel_format = "BC5S" n = 5 - self.mode = "RGB" + self._mode = "RGB" elif fourcc == b"DX10": data_start += 20 # ignoring flags which pertain to volume textures and cubemaps @@ -189,19 +189,19 @@ def _open(self): if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM): self.pixel_format = "BC5" n = 5 - self.mode = "RGB" + self._mode = "RGB" elif dxgi_format == DXGI_FORMAT_BC5_SNORM: self.pixel_format = "BC5S" n = 5 - self.mode = "RGB" + self._mode = "RGB" elif dxgi_format == DXGI_FORMAT_BC6H_UF16: self.pixel_format = "BC6H" n = 6 - self.mode = "RGB" + self._mode = "RGB" elif dxgi_format == DXGI_FORMAT_BC6H_SF16: self.pixel_format = "BC6HS" n = 6 - self.mode = "RGB" + self._mode = "RGB" elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): self.pixel_format = "BC7" n = 7 diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 6b1b5947ec0..b96ce9603a3 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -227,7 +227,7 @@ def _open(self): # go to offset - start of "%!PS" self.fp.seek(offset) - self.mode = "RGB" + self._mode = "RGB" self._size = None byte_arr = bytearray(255) @@ -344,10 +344,10 @@ def check_required_header_comments(): ] if bit_depth == 1: - self.mode = "1" + self._mode = "1" elif bit_depth == 8: try: - self.mode = self.mode_map[mode_id] + self._mode = self.mode_map[mode_id] except ValueError: break else: @@ -391,7 +391,7 @@ def load(self, scale=1, transparency=False): # Load EPS via Ghostscript if self.tile: self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) - self.mode = self.im.mode + self._mode = self.im.mode self._size = self.im.size self.tile = [] return Image.Image.load(self) diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py index 1359aeb1282..e0e51aaac73 100644 --- a/src/PIL/FitsImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -51,14 +51,14 @@ def _open(self): number_of_bits = int(headers[b"BITPIX"]) if number_of_bits == 8: - self.mode = "L" + self._mode = "L" elif number_of_bits == 16: - self.mode = "I" + self._mode = "I" # rawmode = "I;16S" elif number_of_bits == 32: - self.mode = "I" + self._mode = "I" elif number_of_bits in (-32, -64): - self.mode = "F" + self._mode = "F" # rawmode = "F" if number_of_bits == -32 else "F;64F" offset = math.ceil(self.fp.tell() / 2880) * 2880 diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index f4e89a03e02..8f641ece998 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -56,7 +56,7 @@ def _open(self): self.is_animated = self.n_frames > 1 # image characteristics - self.mode = "P" + self._mode = "P" self._size = i16(s, 8), i16(s, 10) # animation speed diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 2450c67e9a6..a878cbfd200 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -106,7 +106,7 @@ def _open_index(self, index=1): # note: for now, we ignore the "uncalibrated" flag colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF) - self.mode, self.rawmode = MODES[tuple(colors)] + self._mode, self.rawmode = MODES[tuple(colors)] # load JPEG tables, if any self.jpeg = {} diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index c46b2f28ba6..c2e4ead7174 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -77,7 +77,7 @@ def _open(self): self._size = struct.unpack("<2i", self.fp.read(8)) mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8)) - self.mode = "RGB" + self._mode = "RGB" # Only support single-format files. # I don't know of any multi-format file. @@ -90,7 +90,7 @@ def _open(self): data = self.fp.read(mipmap_size) if format == Format.DXT1: - self.mode = "RGBA" + self._mode = "RGBA" self.tile = [("bcn", (0, 0) + self.size, 0, 1)] elif format == Format.UNCOMPRESSED: self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))] diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 994a6e8ebb2..ec6e9de6e7b 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -73,9 +73,9 @@ def _open(self): comment = self.fp.read(comment_length)[:-1] if color_depth == 1: - self.mode = "L" + self._mode = "L" else: - self.mode = "RGBA" + self._mode = "RGBA" self._size = width, height diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index bafc43a19d4..3599994a8e3 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -51,7 +51,7 @@ def _open(self): msg = "Not a valid GD 2.x .gd file" raise SyntaxError(msg) - self.mode = "L" # FIXME: "P" + self._mode = "L" # FIXME: "P" self._size = i16(s, 2), i16(s, 4) true_color = s[6] diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 255643de678..943842f770d 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -304,11 +304,11 @@ def _seek(self, frame, update_image=True): if frame == 0: if self._frame_palette: if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: - self.mode = "RGBA" if frame_transparency is not None else "RGB" + self._mode = "RGBA" if frame_transparency is not None else "RGB" else: - self.mode = "P" + self._mode = "P" else: - self.mode = "L" + self._mode = "L" if not palette and self.global_palette: from copy import copy @@ -325,10 +325,10 @@ def _seek(self, frame, update_image=True): if "transparency" in self.info: self.im.putpalettealpha(self.info["transparency"], 0) self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG) - self.mode = "RGBA" + self._mode = "RGBA" del self.info["transparency"] else: - self.mode = "RGB" + self._mode = "RGB" self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) def _rgb(color): @@ -424,7 +424,7 @@ def load_prepare(self): self.im.putpalette(*self._frame_palette.getdata()) else: self.im = None - self.mode = temp_mode + self._mode = temp_mode self._frame_palette = None super().load_prepare() @@ -434,9 +434,9 @@ def load_end(self): if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: if self._frame_transparency is not None: self.im.putpalettealpha(self._frame_transparency, 0) - self.mode = "RGBA" + self._mode = "RGBA" else: - self.mode = "RGB" + self._mode = "RGB" self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG) return if not self._prev_im: @@ -449,7 +449,7 @@ def load_end(self): frame_im = self._crop(frame_im, self.dispose_extent) self.im = self._prev_im - self.mode = self.im.mode + self._mode = self.im.mode if frame_im.mode == "RGBA": self.im.paste(frame_im, self.dispose_extent, frame_im) else: diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 8a799f19caa..c1c71da08c9 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -46,7 +46,7 @@ def _open(self): self.fp.seek(offset) # make something up - self.mode = "F" + self._mode = "F" self._size = 1, 1 loader = self._load() diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index bba05ed65a7..c26b480acf2 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -46,7 +46,7 @@ def _open(self): self.fp.seek(offset) # make something up - self.mode = "F" + self._mode = "F" self._size = 1, 1 loader = self._load() diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 27cb89f735e..0aa4f7a8458 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -253,7 +253,7 @@ class IcnsImageFile(ImageFile.ImageFile): def _open(self): self.icns = IcnsFile(self.fp) - self.mode = "RGBA" + self._mode = "RGBA" self.info["sizes"] = self.icns.itersizes() self.best_size = self.icns.bestsize() self.size = ( @@ -305,7 +305,7 @@ def load(self): px = im.load() self.im = im.im - self.mode = im.mode + self._mode = im.mode self.size = im.size return px diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index a188f8fdcea..0445a2ab22f 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -330,7 +330,7 @@ def load(self): im.load() self.im = im.im self.pyaccess = None - self.mode = im.mode + self._mode = im.mode if im.size != self.size: warnings.warn("Image was not the expected size") diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 746743f658c..b42ba7cac70 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -205,7 +205,7 @@ def _open(self): # Basic attributes self._size = self.info[SIZE] - self.mode = self.info[MODE] + self._mode = self.info[MODE] # Skip forward to start of image data while s and s[:1] != b"\x1A": @@ -231,9 +231,9 @@ def _open(self): self.lut = list(palette[:256]) else: if self.mode in ["L", "P"]: - self.mode = self.rawmode = "P" + self._mode = self.rawmode = "P" elif self.mode in ["LA", "PA"]: - self.mode = "PA" + self._mode = "PA" self.rawmode = "PA;L" self.palette = ImagePalette.raw("RGB;L", palette) elif self.mode == "RGB": diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a519a28af36..6a9ac5a2adb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -482,7 +482,7 @@ def __init__(self): # FIXME: take "new" parameters / other image? # FIXME: turn mode and size into delegating properties? self.im = None - self.mode = "" + self._mode = "" self._size = (0, 0) self.palette = None self.info = {} @@ -502,10 +502,14 @@ def height(self): def size(self): return self._size + @property + def mode(self): + return self._mode + def _new(self, im): new = Image() new.im = im - new.mode = im.mode + new._mode = im.mode new._size = im.size if im.mode in ("P", "PA"): if self.palette: @@ -693,7 +697,7 @@ def __setstate__(self, state): Image.__init__(self) info, mode, size, palette, data = state self.info = info - self.mode = mode + self._mode = mode self._size = size self.im = core.new(mode, size) if mode in ("L", "LA", "P", "PA") and palette: @@ -1840,7 +1844,7 @@ def putalpha(self, alpha): raise ValueError from e # sanity check self.im = im self.pyaccess = None - self.mode = self.im.mode + self._mode = self.im.mode except KeyError as e: msg = "illegal image mode" raise ValueError(msg) from e @@ -1918,7 +1922,7 @@ def putpalette(self, data, rawmode="RGB"): if not isinstance(data, bytes): data = bytes(data) palette = ImagePalette.raw(rawmode, data) - self.mode = "PA" if "A" in self.mode else "P" + self._mode = "PA" if "A" in self.mode else "P" self.palette = palette self.palette.mode = "RGB" self.load() # install new palette @@ -2026,7 +2030,7 @@ def remap_palette(self, dest_map, source_palette=None): mapping_palette = bytearray(new_positions) m_im = self.copy() - m_im.mode = "P" + m_im._mode = "P" m_im.palette = ImagePalette.ImagePalette( palette_mode, palette=mapping_palette * bands @@ -2601,7 +2605,7 @@ def round_aspect(number, key): self.im = im.im self._size = size - self.mode = self.im.mode + self._mode = self.im.mode self.readonly = 0 self.pyaccess = None diff --git a/src/PIL/ImtImagePlugin.py b/src/PIL/ImtImagePlugin.py index ac267457b06..d409fcd59de 100644 --- a/src/PIL/ImtImagePlugin.py +++ b/src/PIL/ImtImagePlugin.py @@ -89,7 +89,7 @@ def _open(self): ysize = int(v) self._size = xsize, ysize elif k == b"pixel" and v == b"n8": - self.mode = "L" + self._mode = "L" # diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 4c47b55c1a5..6ce4975c09d 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -109,11 +109,11 @@ def _open(self): else: id = 0 if layers == 1 and not component: - self.mode = "L" + self._mode = "L" elif layers == 3 and component: - self.mode = "RGB"[id] + self._mode = "RGB"[id] elif layers == 4 and component: - self.mode = "CMYK"[id] + self._mode = "CMYK"[id] # size self._size = self.getint((3, 20)), self.getint((3, 30)) diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 9309768bacf..963d6c1a31c 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -208,14 +208,14 @@ def _open(self): sig = self.fp.read(4) if sig == b"\xff\x4f\xff\x51": self.codec = "j2k" - self._size, self.mode = _parse_codestream(self.fp) + self._size, self._mode = _parse_codestream(self.fp) else: sig = sig + self.fp.read(8) if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a": self.codec = "jp2" header = _parse_jp2_header(self.fp) - self._size, self.mode, self.custom_mimetype, dpi = header + self._size, self._mode, self.custom_mimetype, dpi = header if dpi is not None: self.info["dpi"] = dpi if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index dfc7e6e9f56..475e54c2b47 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -208,11 +208,11 @@ def SOF(self, marker): self.layers = s[5] if self.layers == 1: - self.mode = "L" + self._mode = "L" elif self.layers == 3: - self.mode = "RGB" + self._mode = "RGB" elif self.layers == 4: - self.mode = "CMYK" + self._mode = "CMYK" else: msg = f"cannot handle {self.layers}-layer images" raise SyntaxError(msg) @@ -426,7 +426,7 @@ def draft(self, mode, size): original_size = self.size if a[0] == "RGB" and mode in ["L", "YCbCr"]: - self.mode = mode + self._mode = mode a = mode, "" if size: @@ -475,7 +475,7 @@ def load_djpeg(self): except OSError: pass - self.mode = self.im.mode + self._mode = self.im.mode self._size = self.im.size self.tile = [] diff --git a/src/PIL/McIdasImagePlugin.py b/src/PIL/McIdasImagePlugin.py index 17c008b9a6a..bb79e71de5d 100644 --- a/src/PIL/McIdasImagePlugin.py +++ b/src/PIL/McIdasImagePlugin.py @@ -58,7 +58,7 @@ def _open(self): msg = "unsupported McIdas format" raise SyntaxError(msg) - self.mode = mode + self._mode = mode self._size = w[10], w[9] offset = w[34] + w[15] diff --git a/src/PIL/MpegImagePlugin.py b/src/PIL/MpegImagePlugin.py index d96d3a11c49..bfa88fe99c4 100644 --- a/src/PIL/MpegImagePlugin.py +++ b/src/PIL/MpegImagePlugin.py @@ -68,7 +68,7 @@ def _open(self): msg = "not an MPEG file" raise SyntaxError(msg) - self.mode = "RGB" + self._mode = "RGB" self._size = s.read(12), s.read(12) diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index c6567b2ae62..3f3609f1c20 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -62,7 +62,7 @@ def _open(self): msg = "bad MSP checksum" raise SyntaxError(msg) - self.mode = "1" + self._mode = "1" self._size = i16(s, 4), i16(s, 6) if s[:4] == b"DanM": diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index e390f3fe51d..c7cbca8c5d7 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -43,7 +43,7 @@ def _open(self): elif orientation == 3: self.tile_post_rotate = -90 - self.mode = "RGB" + self._mode = "RGB" self._size = 768, 512 # FIXME: not correct for rotated images! self.tile = [("pcd", (0, 0) + self.size, 96 * 2048, None)] diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index f42c2456b4b..854d9e83ee7 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -108,7 +108,7 @@ def _open(self): msg = "unknown PCX mode" raise OSError(msg) - self.mode = mode + self._mode = mode self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] # Don't trust the passed in stride. diff --git a/src/PIL/PixarImagePlugin.py b/src/PIL/PixarImagePlugin.py index 7eb82228a99..850272311de 100644 --- a/src/PIL/PixarImagePlugin.py +++ b/src/PIL/PixarImagePlugin.py @@ -54,7 +54,7 @@ def _open(self): mode = i16(s, 424), i16(s, 426) if mode == (14, 2): - self.mode = "RGB" + self._mode = "RGB" # FIXME: to be continued... # create tile descriptor (assuming "dumped") diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index bfa8cb7ac66..2ed182d32de 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -737,7 +737,7 @@ def _open(self): # difficult to break if things go wrong in the decoder... # (believe me, I've tried ;-) - self.mode = self.png.im_mode + self._mode = self.png.im_mode self._size = self.png.im_size self.info = self.png.im_info self._text = None diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 2cb1e56365d..e480ab05581 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -116,18 +116,18 @@ def _open(self): elif ix == 1: # token is the y size ysize = token if mode == "1": - self.mode = "1" + self._mode = "1" rawmode = "1;I" break else: - self.mode = rawmode = mode + self._mode = rawmode = mode elif ix == 2: # token is maxval maxval = token if not 0 < maxval < 65536: msg = "maxval must be greater than 0 and less than 65536" raise ValueError(msg) if maxval > 255 and mode == "L": - self.mode = "I" + self._mode = "I" if decoder_name != "ppm_plain": # If maxval matches a bit depth, use the raw decoder directly diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 5a5d60d568c..2f019bb8c34 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -79,7 +79,7 @@ def _open(self): mode = "RGBA" channels = 4 - self.mode = mode + self._mode = mode self._size = i32(s, 18), i32(s, 14) # @@ -146,7 +146,7 @@ def seek(self, layer): # seek to given layer (1..max) try: name, mode, bbox, tile = self.layers[layer - 1] - self.mode = mode + self._mode = mode self.tile = tile self.frame = layer self.fp = self._fp diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index ef91b90abca..5c34075038f 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -29,7 +29,7 @@ def _open(self): self._size = tuple(i32(self.fp.read(4)) for i in range(2)) channels = self.fp.read(1)[0] - self.mode = "RGB" if channels == 3 else "RGBA" + self._mode = "RGB" if channels == 3 else "RGBA" self.fp.seek(1, os.SEEK_CUR) # colorspace self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)] diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 3662ffd1571..acb9ce5a38c 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -94,7 +94,7 @@ def _open(self): raise ValueError(msg) self._size = xsize, ysize - self.mode = rawmode.split(";")[0] + self._mode = rawmode.split(";")[0] if self.mode == "RGB": self.custom_mimetype = "image/rgb" diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 5614957c176..408b982b515 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -149,7 +149,7 @@ def _open(self): self.rawmode = "F;32BF" else: self.rawmode = "F;32F" - self.mode = "F" + self._mode = "F" self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))] self._fp = self.fp # FIXME: hack diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py index 6712583d71c..6a8d5d86b73 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -66,21 +66,21 @@ def _open(self): palette_length = i32(s, 28) if depth == 1: - self.mode, rawmode = "1", "1;I" + self._mode, rawmode = "1", "1;I" elif depth == 4: - self.mode, rawmode = "L", "L;4" + self._mode, rawmode = "L", "L;4" elif depth == 8: - self.mode = rawmode = "L" + self._mode = rawmode = "L" elif depth == 24: if file_type == 3: - self.mode, rawmode = "RGB", "RGB" + self._mode, rawmode = "RGB", "RGB" else: - self.mode, rawmode = "RGB", "BGR" + self._mode, rawmode = "RGB", "BGR" elif depth == 32: if file_type == 3: - self.mode, rawmode = "RGB", "RGBX" + self._mode, rawmode = "RGB", "RGBX" else: - self.mode, rawmode = "RGB", "BGRX" + self._mode, rawmode = "RGB", "BGRX" else: msg = "Unsupported Mode/Bit Depth" raise SyntaxError(msg) @@ -97,7 +97,7 @@ def _open(self): offset = offset + palette_length self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) if self.mode == "L": - self.mode = "P" + self._mode = "P" rawmode = rawmode.replace("L", "P") # 16 bit boundaries on stride diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 67dfc3d3c8e..f24ee4f5c31 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -76,17 +76,17 @@ def _open(self): # image mode if imagetype in (3, 11): - self.mode = "L" + self._mode = "L" if depth == 1: - self.mode = "1" # ??? + self._mode = "1" # ??? elif depth == 16: - self.mode = "LA" + self._mode = "LA" elif imagetype in (1, 9): - self.mode = "P" + self._mode = "P" elif imagetype in (2, 10): - self.mode = "RGB" + self._mode = "RGB" if depth == 32: - self.mode = "RGBA" + self._mode = "RGBA" else: msg = "unknown TGA mode" raise SyntaxError(msg) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index d5148828506..99c23ad4b28 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1409,7 +1409,7 @@ def _setup(self): ) logger.debug(f"format key: {key}") try: - self.mode, rawmode = OPEN_INFO[key] + self._mode, rawmode = OPEN_INFO[key] except KeyError as e: logger.debug("- unsupported format") msg = "unknown pixel mode" @@ -1461,7 +1461,7 @@ def _setup(self): # this should always work, since all the # fillorder==2 modes have a corresponding # fillorder=1 mode - self.mode, rawmode = OPEN_INFO[key] + self._mode, rawmode = OPEN_INFO[key] # libtiff always returns the bytes in native order. # we're expecting image byte order. So, if the rawmode # contains I;16, we need to convert from native to image diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index e4f47aa04bc..3d9f97f8485 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -32,7 +32,7 @@ class WalImageFile(ImageFile.ImageFile): format_description = "Quake2 Texture" def _open(self): - self.mode = "P" + self._mode = "P" # read header fields header = self.fp.read(32 + 24 + 32 + 12) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index ce8e05fcbb1..028e5d2bdd9 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -43,7 +43,7 @@ class WebPImageFile(ImageFile.ImageFile): def _open(self): if not _webp.HAVE_WEBPANIM: # Legacy mode - data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode( + data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode( self.fp.read() ) if icc_profile: @@ -74,7 +74,7 @@ def _open(self): self.info["background"] = (bg_r, bg_g, bg_b, bg_a) self.n_frames = frame_count self.is_animated = self.n_frames > 1 - self.mode = "RGB" if mode == "RGBX" else mode + self._mode = "RGB" if mode == "RGBX" else mode self.rawmode = mode self.tile = [] diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 0ecab56a824..3e5fb01512d 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -42,7 +42,7 @@ def register_handler(handler): class WmfHandler: def open(self, im): - im.mode = "RGB" + im._mode = "RGB" self.bbox = im.info["wmf_bbox"] def load(self, im): @@ -139,7 +139,7 @@ def _open(self): msg = "Unsupported file format" raise SyntaxError(msg) - self.mode = "RGB" + self._mode = "RGB" self._size = size loader = self._load() diff --git a/src/PIL/XVThumbImagePlugin.py b/src/PIL/XVThumbImagePlugin.py index aa4a01f4e5a..eda60c5c5ca 100644 --- a/src/PIL/XVThumbImagePlugin.py +++ b/src/PIL/XVThumbImagePlugin.py @@ -65,7 +65,7 @@ def _open(self): # parse header line (already read) s = s.strip().split() - self.mode = "P" + self._mode = "P" self._size = int(s[0]), int(s[1]) self.palette = ImagePalette.raw("RGB", PALETTE) diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index 3c12564c963..71cd57d74da 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -60,7 +60,7 @@ def _open(self): if m.group("hotspot"): self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot"))) - self.mode = "1" + self._mode = "1" self._size = xsize, ysize self.tile = [("xbm", (0, 0) + self.size, m.end(), None)] diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py index 5d5bdc3edfa..8491d3b7e92 100644 --- a/src/PIL/XpmImagePlugin.py +++ b/src/PIL/XpmImagePlugin.py @@ -98,7 +98,7 @@ def _open(self): msg = "cannot read this XPM file" raise ValueError(msg) - self.mode = "P" + self._mode = "P" self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))] From 8eee4577255123ed9f7d2d984a4fa32af181cd62 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 31 Jul 2023 21:06:16 +1000 Subject: [PATCH 2/2] Added release notes --- docs/releasenotes/10.1.0.rst | 54 ++++++++++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 55 insertions(+) create mode 100644 docs/releasenotes/10.1.0.rst diff --git a/docs/releasenotes/10.1.0.rst b/docs/releasenotes/10.1.0.rst new file mode 100644 index 00000000000..da5153cceef --- /dev/null +++ b/docs/releasenotes/10.1.0.rst @@ -0,0 +1,54 @@ +10.1.0 +------ + +Backwards Incompatible Changes +============================== + +Setting image mode +^^^^^^^^^^^^^^^^^^ + +If you attempt to set the mode of an image directly, e.g. +``im.mode = "RGBA"``, 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 ``convert`` method is the +correct way to change an image's mode. + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO +^^^^ + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 9bca9854152..a843ddd7263 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 10.1.0 10.0.0 9.5.0 9.4.0