mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Added QOI reading
This commit is contained in:
parent
cdf5fd439c
commit
2d01875e7c
BIN
Tests/images/hopper.qoi
Normal file
BIN
Tests/images/hopper.qoi
Normal file
Binary file not shown.
BIN
Tests/images/pil123rgba.qoi
Normal file
BIN
Tests/images/pil123rgba.qoi
Normal file
Binary file not shown.
28
Tests/test_file_qoi.py
Normal file
28
Tests/test_file_qoi.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, QoiImagePlugin
|
||||||
|
|
||||||
|
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileQOI:
|
||||||
|
def test_sanity(self):
|
||||||
|
with Image.open("Tests/images/hopper.qoi") as im:
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
assert im.format == "QOI"
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/pil123rgba.qoi") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
assert im.size == (162, 150)
|
||||||
|
assert im.format == "QOI"
|
||||||
|
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
|
||||||
|
|
||||||
|
def test_invalid_file(self):
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
QoiImagePlugin.QoiImageFile(invalid_file)
|
|
@ -1544,6 +1544,13 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
|
|
||||||
.. versionadded:: 5.3.0
|
.. versionadded:: 5.3.0
|
||||||
|
|
||||||
|
QOI
|
||||||
|
^^^
|
||||||
|
|
||||||
|
.. versionadded:: 9.5.0
|
||||||
|
|
||||||
|
Pillow identifies and reads QOI images.
|
||||||
|
|
||||||
XV Thumbnails
|
XV Thumbnails
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,11 @@ TODO
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
QOI file format
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow can now read QOI images.
|
||||||
|
|
||||||
Added ``dpi`` argument when saving PDFs
|
Added ``dpi`` argument when saving PDFs
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
106
src/PIL/QoiImagePlugin.py
Normal file
106
src/PIL/QoiImagePlugin.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#
|
||||||
|
# The Python Imaging Library.
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# QOI support for PIL
|
||||||
|
#
|
||||||
|
# See the README file for information on usage and redistribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i32be as i32
|
||||||
|
from ._binary import o8
|
||||||
|
|
||||||
|
|
||||||
|
def _accept(prefix):
|
||||||
|
return prefix[:4] == b"qoif"
|
||||||
|
|
||||||
|
|
||||||
|
class QoiImageFile(ImageFile.ImageFile):
|
||||||
|
format = "QOI"
|
||||||
|
format_description = "Quite OK Image"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
if not _accept(self.fp.read(4)):
|
||||||
|
msg = "not a QOI file"
|
||||||
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
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.fp.seek(1, os.SEEK_CUR) # colorspace
|
||||||
|
self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)]
|
||||||
|
|
||||||
|
|
||||||
|
class QoiDecoder(ImageFile.PyDecoder):
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def _add_to_previous_pixels(self, value):
|
||||||
|
self._previous_pixel = value
|
||||||
|
|
||||||
|
r, g, b, a = value
|
||||||
|
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
|
||||||
|
self._previously_seen_pixels[hash_value] = 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)))
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
bands = Image.getmodebands(self.mode)
|
||||||
|
while len(data) < self.state.xsize * self.state.ysize * bands:
|
||||||
|
byte = self.fd.read(1)[0]
|
||||||
|
if byte == 0b11111110: # QOI_OP_RGB
|
||||||
|
value = self.fd.read(3) + o8(255)
|
||||||
|
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))
|
||||||
|
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 += (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 += (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))
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
|
||||||
|
Image.register_decoder("qoi", QoiDecoder)
|
||||||
|
Image.register_extension(QoiImageFile.format, ".qoi")
|
|
@ -59,6 +59,7 @@ _plugins = [
|
||||||
"PngImagePlugin",
|
"PngImagePlugin",
|
||||||
"PpmImagePlugin",
|
"PpmImagePlugin",
|
||||||
"PsdImagePlugin",
|
"PsdImagePlugin",
|
||||||
|
"QoiImagePlugin",
|
||||||
"SgiImagePlugin",
|
"SgiImagePlugin",
|
||||||
"SpiderImagePlugin",
|
"SpiderImagePlugin",
|
||||||
"SunImagePlugin",
|
"SunImagePlugin",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user