Skip to main content

Merge pull request #19 from alexwlchan/better-srgbify

ID
9ad1652
date
2024-03-18 07:43:33+00:00
author
Alex Chan <alex@alexwlchan.net>
parents
6116a53, 87e69ed
message
Merge pull request #19 from alexwlchan/better-srgbify

Add tests for `srgbify`; fix a bug with greyscale images
changed files
8 files, 110 additions, 26 deletions

Changed files

.github/workflows/test.yml (1214) → .github/workflows/test.yml (1018)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index bbe8dae..b72bfaa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -38,9 +38,4 @@ jobs:
 
     - name: Run tests
       run: |
-        py.test aws/test_s3tree.py
-        py.test flickr/test_fluser_lookup.py
-        py.test text/test_fix_twitter_thread.py
-        py.test textexpander/test_get_mastodon_text.py
-        py.test web/test_save_ao3_links.py
-        py.test web/test_save_youtube_videos.py
+        find . -name 'test_*.py' -not -path './.venv/*' | sort | xargs pytest

images/examples/3925727501_6aa7c94c10_w.jpg (0) → images/examples/3925727501_6aa7c94c10_w.jpg (36732)

diff --git a/images/examples/3925727501_6aa7c94c10_w.jpg b/images/examples/3925727501_6aa7c94c10_w.jpg
new file mode 100644
index 0000000..232f760
Binary files /dev/null and b/images/examples/3925727501_6aa7c94c10_w.jpg differ

images/examples/Iceland-P3.jpg (0) → images/examples/Iceland-P3.jpg (102415)

diff --git a/images/examples/Iceland-P3.jpg b/images/examples/Iceland-P3.jpg
new file mode 100644
index 0000000..fe9ebea
Binary files /dev/null and b/images/examples/Iceland-P3.jpg differ

images/examples/Iceland-sRGB-expected.jpg (0) → images/examples/Iceland-sRGB-expected.jpg (30481)

diff --git a/images/examples/Iceland-sRGB-expected.jpg b/images/examples/Iceland-sRGB-expected.jpg
new file mode 100644
index 0000000..ea0e17c
Binary files /dev/null and b/images/examples/Iceland-sRGB-expected.jpg differ

images/examples/Shoes-sRGB.jpg (0) → images/examples/Shoes-sRGB.jpg (451785)

diff --git a/images/examples/Shoes-sRGB.jpg b/images/examples/Shoes-sRGB.jpg
new file mode 100644
index 0000000..9a6515f
Binary files /dev/null and b/images/examples/Shoes-sRGB.jpg differ

images/examples/screenshot-with-transparency.png (0) → images/examples/screenshot-with-transparency.png (361453)

diff --git a/images/examples/screenshot-with-transparency.png b/images/examples/screenshot-with-transparency.png
new file mode 100644
index 0000000..afb6a57
Binary files /dev/null and b/images/examples/screenshot-with-transparency.png differ

images/srgbify.py (1264) → images/srgbify.py (1846)

diff --git a/images/srgbify.py b/images/srgbify.py
index 1bbcb31..503d016 100755
--- a/images/srgbify.py
+++ b/images/srgbify.py
@@ -9,41 +9,63 @@ which I want to convert to sRGB for converting on the web.
 Based on https://github.com/python-pillow/Pillow/issues/1662
 """
 
+import io
 import sys
-import tempfile
 
 from PIL import Image, ImageCms
+from PIL.ImageCms import PyCMSError
 from pillow_heif import register_heif_opener
 
 
 register_heif_opener()
 
 
-if __name__ == "__main__":
-    if len(sys.argv) == 1:
-        sys.exit(f"Usage: {__file__} <PATH...>")
+def convert_image_to_srgb(im: Image) -> Image:
+    """
+    Convert an image to sRGB and return a new Image instance.
+    """
+    icc_profile = im.info.get("icc_profile")
 
-    for path in sys.argv[1:]:
-        in_img = Image.open(path)
+    # If this image doesn't have a colour profile, we're done.
+    if icc_profile is None:
+        return im
 
-        srgb_profile = ImageCms.createProfile("sRGB")
+    # Otherwise, convert the image to an sRGB colour profile and return that.
 
-        icc_profile = in_img.info.get("icc_profile")
+    try:
+        return ImageCms.profileToProfile(
+            im,
+            inputProfile=io.BytesIO(icc_profile),
+            outputProfile=ImageCms.createProfile("sRGB"),
+        )
+    except PyCMSError as err:
+        print(err.args[0].args)
+        if (
+            im.mode == "L"
+            and b"GRAYXYZ" in icc_profile
+            and err.args[0].args == ("cannot build transform",)
+        ):
+            return ImageCms.profileToProfile(
+                im,
+                inputProfile=io.BytesIO(icc_profile),
+                outputProfile=ImageCms.createProfile("sRGB"),
+                outputMode="RGB",
+            )
+        else:
+            raise
+
+    return out_im
 
-        # Handle the case where this image doesn't have
-        # a colour profile
-        if icc_profile is None:
-            print(path)
-            continue
 
-        _, icc_profile_path = tempfile.mkstemp(suffix=".icc")
+if __name__ == "__main__":
+    if len(sys.argv) == 1:
+        sys.exit(f"Usage: {__file__} <PATH...>")
 
-        with open(icc_profile_path, "wb") as out_file:
-            out_file.write(icc_profile)
+    for path in sys.argv[1:]:
+        im = Image.open(path)
+        out_im = convert_image_to_srgb(im)
 
-        out_img = ImageCms.profileToProfile(
-            in_img, inputProfile=icc_profile_path, outputProfile=srgb_profile
-        )
+        if out_im != im:
+            out_im.save(path)
 
-        out_img.save(path)
         print(path)

images/test_srgbify.py (0) → images/test_srgbify.py (1762)

diff --git a/images/test_srgbify.py b/images/test_srgbify.py
new file mode 100644
index 0000000..aed3d10
--- /dev/null
+++ b/images/test_srgbify.py
@@ -0,0 +1,67 @@
+import filecmp
+
+from PIL import Image
+
+from srgbify import convert_image_to_srgb
+
+
+def test_it_ignores_an_image_which_is_already_srgb():
+    """
+    If an image is already in sRGB, we don't need to do anything.
+
+    This image was downloaded from https://webkit.org/blog-files/color-gamut/
+    """
+    im = Image.open("images/examples/Shoes-sRGB.jpg")
+
+    new_im = convert_image_to_srgb(im)
+
+    assert new_im is im
+
+
+def test_it_converts_a_display_p3_image_to_srgb(tmp_path):
+    """
+    If an image is in Display P3, it gets converted to sRGB.
+
+    This image was downloaded from https://webkit.org/blog-files/color-gamut/
+    """
+    im = Image.open("images/examples/Iceland-P3.jpg")
+
+    new_im = convert_image_to_srgb(im)
+
+    new_im.save(tmp_path / "Iceland-sRGB-actual.jpg")
+
+    assert filecmp.cmp(
+        tmp_path / "Iceland-sRGB-actual.jpg",
+        "images/examples/Iceland-sRGB-expected.jpg",
+        shallow=False,
+    )
+
+
+def test_it_preserves_transparency_in_png_images():
+    """
+    If an image is a PNG with transparency, that gets preserved when converting
+    to sRGB.
+
+    This image is a screenshot from my own computer.
+    """
+    im = Image.open("images/examples/screenshot-with-transparency.png")
+
+    new_im = convert_image_to_srgb(im)
+
+    assert new_im.mode == "RGBA"
+
+    # The top left-hand corner should be transparent
+    assert new_im.getpixel((0, 0)) == (0, 0, 0, 0)
+
+
+def test_it_converts_images_with_a_grey_profile():
+    """
+    If an image has a grey colour profile, that gets converted to sRGB.
+
+    This image is from https://www.flickr.com/photos/lselibrary/3925727501/
+    """
+    im = Image.open("images/examples/3925727501_6aa7c94c10_w.jpg")
+
+    new_im = convert_image_to_srgb(im)
+
+    assert new_im.mode == "RGB"