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

Improve speed of loading QOI images #7925

Merged
merged 3 commits into from Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 2 deletions src/PIL/BlpImagePlugin.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/BmpImagePlugin.py
Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions src/PIL/DdsImagePlugin.py
Expand Up @@ -497,15 +497,16 @@ 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
# Remove the zero padding, and scale it to 8 bits
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


Expand Down
3 changes: 2 additions & 1 deletion src/PIL/PpmImagePlugin.py
Expand Up @@ -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
Expand Down
41 changes: 23 additions & 18 deletions src/PIL/QoiImagePlugin.py
Expand Up @@ -11,7 +11,6 @@

from . import Image, ImageFile
from ._binary import i32be as i32
from ._binary import o8


def _accept(prefix):
Expand Down Expand Up @@ -49,55 +48,61 @@ 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)
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:]
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
if bands == 3:
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:
value = value[:3]
data += value
self.set_as_raw(bytes(data))
self.set_as_raw(data)
return -1, 0


Expand Down