From f69168523e531f27e034a828835990c6f9b73b19 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 Apr 2024 15:24:10 +1100 Subject: [PATCH 1/3] Do not calculate destination length each time --- src/PIL/BmpImagePlugin.py | 3 ++- src/PIL/DdsImagePlugin.py | 3 ++- src/PIL/PpmImagePlugin.py | 3 ++- src/PIL/QoiImagePlugin.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 6f730cfef16..9947f439b07 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -291,7 +291,8 @@ def decode(self, buffer): rle4 = self.args[1] data = bytearray() x = 0 - while len(data) < self.state.xsize * self.state.ysize: + dest_length = self.state.xsize * self.state.ysize + while len(data) < dest_length: pixels = self.fd.read(1) byte = self.fd.read(1) if not pixels or not byte: diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index be17f4223aa..b89502d1fb6 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -497,7 +497,8 @@ def decode(self, buffer): data = bytearray() bytecount = bitcount // 8 - while len(data) < self.state.xsize * self.state.ysize * len(masks): + dest_length = self.state.xsize * self.state.ysize * len(masks) + while len(data) < dest_length: value = int.from_bytes(self.fd.read(bytecount), "little") for i, mask in enumerate(masks): masked_value = value & mask diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index bca3018c36f..94bf430b825 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -307,7 +307,8 @@ def decode(self, buffer: bytes) -> tuple[int, int]: out_byte_count = 4 if self.mode == "I" else 1 out_max = 65535 if self.mode == "I" else 255 bands = Image.getmodebands(self.mode) - while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count: + dest_length = self.state.xsize * self.state.ysize * bands * out_byte_count + while len(data) < dest_length: pixels = self.fd.read(in_byte_count * bands) if len(pixels) < in_byte_count * bands: # eof diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index a7b9d4a9e3a..1a2a4d388c9 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -53,7 +53,8 @@ def decode(self, buffer): data = bytearray() bands = Image.getmodebands(self.mode) - while len(data) < self.state.xsize * self.state.ysize * bands: + dest_length = self.state.xsize * self.state.ysize * bands + while len(data) < dest_length: byte = self.fd.read(1)[0] if byte == 0b11111110: # QOI_OP_RGB value = self.fd.read(3) + self._previous_pixel[3:] From badc92079d40ae74e3a343ababdaca442e43b2dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 Apr 2024 15:24:40 +1100 Subject: [PATCH 2/3] Do not cast to bytes for set_as_raw() --- src/PIL/BlpImagePlugin.py | 4 ++-- src/PIL/DdsImagePlugin.py | 2 +- src/PIL/QoiImagePlugin.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index f0fbc8cc28a..95e8077811d 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -341,7 +341,7 @@ def _load(self): if self._blp_encoding in (4, 5): palette = self._read_palette() data = self._read_bgra(palette) - self.set_as_raw(bytes(data)) + self.set_as_raw(data) else: msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}" raise BLPFormatError(msg) @@ -412,7 +412,7 @@ def _load(self): msg = f"Unknown BLP compression {repr(self._blp_compression)}" raise BLPFormatError(msg) - self.set_as_raw(bytes(data)) + self.set_as_raw(data) class BLPEncoder(ImageFile.PyEncoder): diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index b89502d1fb6..93c8e341d44 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -506,7 +506,7 @@ def decode(self, buffer): data += o8( int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) ) - self.set_as_raw(bytes(data)) + self.set_as_raw(data) return -1, 0 diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index 1a2a4d388c9..e9fef37a936 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -98,7 +98,7 @@ def decode(self, buffer): if bands == 3: value = value[:3] data += value - self.set_as_raw(bytes(data)) + self.set_as_raw(data) return -1, 0 From b7d0908dc5a175ecc097c152199ee62695fb9efe Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 Apr 2024 15:31:44 +1100 Subject: [PATCH 3/3] Increase use of bytearrays to improve loading speed --- src/PIL/QoiImagePlugin.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index e9fef37a936..f8aa720c134 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -11,7 +11,6 @@ from . import Image, ImageFile from ._binary import i32be as i32 -from ._binary import o8 def _accept(prefix): @@ -49,7 +48,7 @@ def _add_to_previous_pixels(self, value): def decode(self, buffer): self._previously_seen_pixels = {} self._previous_pixel = None - self._add_to_previous_pixels(b"".join(o8(i) for i in (0, 0, 0, 255))) + self._add_to_previous_pixels(bytearray((0, 0, 0, 255))) data = bytearray() bands = Image.getmodebands(self.mode) @@ -57,34 +56,40 @@ def decode(self, buffer): while len(data) < dest_length: byte = self.fd.read(1)[0] if byte == 0b11111110: # QOI_OP_RGB - value = self.fd.read(3) + self._previous_pixel[3:] + value = bytearray(self.fd.read(3)) + self._previous_pixel[3:] elif byte == 0b11111111: # QOI_OP_RGBA value = self.fd.read(4) else: op = byte >> 6 if op == 0: # QOI_OP_INDEX op_index = byte & 0b00111111 - value = self._previously_seen_pixels.get(op_index, (0, 0, 0, 0)) + value = self._previously_seen_pixels.get( + op_index, bytearray((0, 0, 0, 0)) + ) elif op == 1: # QOI_OP_DIFF - value = ( - (self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2) - % 256, - (self._previous_pixel[1] + ((byte & 0b00001100) >> 2) - 2) - % 256, - (self._previous_pixel[2] + (byte & 0b00000011) - 2) % 256, + value = bytearray( + ( + (self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2) + % 256, + (self._previous_pixel[1] + ((byte & 0b00001100) >> 2) - 2) + % 256, + (self._previous_pixel[2] + (byte & 0b00000011) - 2) % 256, + self._previous_pixel[3], + ) ) - value += (self._previous_pixel[3],) elif op == 2: # QOI_OP_LUMA second_byte = self.fd.read(1)[0] diff_green = (byte & 0b00111111) - 32 diff_red = ((second_byte & 0b11110000) >> 4) - 8 diff_blue = (second_byte & 0b00001111) - 8 - value = tuple( - (self._previous_pixel[i] + diff_green + diff) % 256 - for i, diff in enumerate((diff_red, 0, diff_blue)) + value = bytearray( + tuple( + (self._previous_pixel[i] + diff_green + diff) % 256 + for i, diff in enumerate((diff_red, 0, diff_blue)) + ) ) - value += (self._previous_pixel[3],) + value += self._previous_pixel[3:] elif op == 3: # QOI_OP_RUN run_length = (byte & 0b00111111) + 1 value = self._previous_pixel @@ -92,7 +97,6 @@ def decode(self, buffer): value = value[:3] data += value * run_length continue - value = b"".join(o8(i) for i in value) self._add_to_previous_pixels(value) if bands == 3: