Skip to main content

images: remove kn_cover_image.py

ID
361d9f3
date
2026-04-15 21:02:50+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
c6a963b
message
images: remove kn_cover_image.py

I realised it's easier to create a 2:1 slide in Keynote and just fill
the whole screen, so I don't need this any more.
changed files
4 files, 1 addition, 222 deletions

Changed files

images/README.md (10850) → images/README.md (9325)

diff --git a/images/README.md b/images/README.md
index 541a019..49b0159 100644
--- a/images/README.md
+++ b/images/README.md
@@ -89,25 +89,6 @@ scripts = [
         """
     },
     {
-        "usage": "kn_cover_image.py [PATH]",
-        "description": """
-        prepare a cover image for an article on my website.
-        <p>
-          I use Keynote to compose a lot of my promo images, then I export the slide to an image.
-          The slide includes a white rectangle that marks the rough boundary of the image; this script extracts the selected region, adjusts the crop so it's an exact 2:1 ratio, and converts the colour profile to sRGB.
-        </p>
-        <p>
-          <table>
-            <tr>
-              <td><img src="examples/kn_example.jpeg"></td>
-              <td>&rarr;</td>
-              <td><img src="examples/kn_example.cropped.jpg"></td>
-            </tr>
-          </table>
-        </p>
-        """
-    },
-    {
         "usage": "pdfthumb.py [PATH] --page=[PAGE]",
         "description": """
         get a PNG thumbnail of a specific page of a PDF, for example <code>pdfthumb pattern.pdf --page=3</code> will create a thumbnail of the third page.
@@ -250,28 +231,6 @@ cog_helpers.create_description_table(folder_name=folder_name, scripts=scripts)
   </dd>
 
   <dt>
-    <a href="https://github.com/alexwlchan/scripts/blob/main/images/kn_cover_image.py">
-      <code>kn_cover_image.py [PATH]</code>
-    </a>
-  </dt>
-  <dd>
-    prepare a cover image for an article on my website.
-    <p>
-      I use Keynote to compose a lot of my promo images, then I export the slide to an image.
-      The slide includes a white rectangle that marks the rough boundary of the image; this script extracts the selected region, adjusts the crop so it's an exact 2:1 ratio, and converts the colour profile to sRGB.
-    </p>
-    <p>
-      <table>
-        <tr>
-          <td><img src="examples/kn_example.jpeg"></td>
-          <td>&rarr;</td>
-          <td><img src="examples/kn_example.cropped.jpg"></td>
-        </tr>
-      </table>
-    </p>
-  </dd>
-
-  <dt>
     <a href="https://github.com/alexwlchan/scripts/blob/main/images/pdfthumb.py">
       <code>pdfthumb.py [PATH] --page=[PAGE]</code>
     </a>
@@ -337,4 +296,4 @@ cog_helpers.create_description_table(folder_name=folder_name, scripts=scripts)
     I don’t use this script very often, but I checked it in because I thought it was a neat trick I didn’t want to forget.
   </dd>
 </dl>
-<!-- [[[end]]] (sum: 6PJa9TAkgu) -->
+<!-- [[[end]]] (sum: 9yBrlmL4gH) -->

images/examples/kn_example.cropped.jpg (49580) → images/examples/kn_example.cropped.jpg (0)

diff --git a/images/examples/kn_example.cropped.jpg b/images/examples/kn_example.cropped.jpg
deleted file mode 100644
index 5dccdff..0000000
Binary files a/images/examples/kn_example.cropped.jpg and /dev/null differ

images/examples/kn_example.jpeg (515368) → images/examples/kn_example.jpeg (0)

diff --git a/images/examples/kn_example.jpeg b/images/examples/kn_example.jpeg
deleted file mode 100644
index 04a325c..0000000
Binary files a/images/examples/kn_example.jpeg and /dev/null differ

images/kn_cover_image.py (5277) → images/kn_cover_image.py (0)

diff --git a/images/kn_cover_image.py b/images/kn_cover_image.py
deleted file mode 100755
index 7153399..0000000
--- a/images/kn_cover_image.py
+++ /dev/null
@@ -1,180 +0,0 @@
-#!/usr/bin/env python3
-"""
-Prepare a cover image for my website, based on an image I've created in Keynote.
-
-You can add the --debug flag to get the intermediate stages saved
-as images, so you can see how the script is working.
-
-"""
-
-import collections
-import math
-import os
-import subprocess
-import sys
-
-from PIL import Image
-import termcolor
-
-
-def hilight(info):
-    return termcolor.colored(info, "blue")
-
-
-if __name__ == "__main__":
-    try:
-        path = sys.argv[1]
-    except IndexError:
-        sys.exit(f"Usage: {__file__} [PATH]")
-
-    im = Image.open(path)
-
-    if im.mode != "RGB":
-        sys.exit(f"Unsupported image mode: {im.mode}")
-
-    name, ext = os.path.splitext(path)
-    ext = ext.replace(".jpeg", ".jpg")
-
-    # Find all the positions with white pixels in the image.
-    #
-    # This assumes that Keynote may not always get perfectly white, so
-    # anything which is almost white is sufficient for these purposes.
-    white_pixels = {
-        (x, y)
-        for x in range(im.width)
-        for y in range(im.height)
-        if min(im.getpixel((x, y))) >= 250
-    }
-
-    if "--debug" in sys.argv:
-        im_white = Image.new("RGB", size=im.size)
-
-        for p in white_pixels:
-            im_white.putpixel(p, (255, 0, 0))
-
-        im_white.save(f"{name}.debug-1-white_pixels{ext}")
-
-    # Now find all the white "inner corner" pixels -- that is, pixels
-    # surrounded on three corners by white pixels, but not the fourth.
-    #
-    #       wwwwwwwwwww
-    #       wCwwwwwwwCw
-    #       www......ww
-    #       www......ww
-    #       www......ww
-    #       wCwwwwwwwCw
-    #       wwwwwwwwwww
-    #
-    corner_pixels = set()
-
-    for x, y in white_pixels:
-        diagonal = {(x - 1, y - 1), (x - 1, y + 1), (x + 1, y - 1), (x + 1, y + 1)}
-        orthogonal = {(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)}
-
-        if (
-            len(white_pixels.intersection(diagonal)) == 3
-            and len(white_pixels.intersection(orthogonal)) == 4
-        ):
-            corner_pixels.add((x, y))
-
-    if "--debug" in sys.argv:
-        im_corner = Image.new("RGB", size=im.size)
-
-        for p in corner_pixels:
-            im_corner.putpixel(p, (255, 0, 0))
-
-        im_corner.save(f"{name}.debug-2-corner_pixels{ext}")
-
-    # Group the corners by row.
-    #
-    # The `corners_by_col` is [x: {y-coords of corners with this y-coord}]
-    #
-    column_corners = collections.defaultdict(list)
-
-    for x, y in corner_pixels:
-        column_corners[x].append(y)
-
-    # Now look for columns with:
-    #
-    #   1. Exactly two pixels which are corners
-    #   2. Another column which has exactly two pixels at the same y-coordinates
-    #
-    # `rectangles` is [{y-coords}: {x-coords of columns with these y-corners}].
-    #
-    # This gives us a list of candidate rectangles.
-    #
-    rectangles = collections.defaultdict(list)
-
-    for x, y_coords in column_corners.items():
-        if len(y_coords) == 2:
-            rectangles[tuple(sorted(y_coords))].append(x)
-
-    rectangles = {
-        y_coords: sorted(x_coords)
-        for y_coords, x_coords in rectangles.items()
-        if len(x_coords) == 2
-    }
-
-    if "--debug" in sys.argv:
-        im_corner = Image.new("RGB", size=im.size, color=(255, 255, 255))
-
-        for (y0, y1), (x0, x1) in rectangles.items():
-            im_corner.putpixel((x0, y0), (0, 0, 0))
-            im_corner.putpixel((x0, y1), (0, 0, 0))
-            im_corner.putpixel((x1, y0), (0, 0, 0))
-            im_corner.putpixel((x1, y1), (0, 0, 0))
-
-        im_corner.save(f"{name}.debug-3-rectangles{ext}")
-
-    # The outline rectangle is the biggest rectangle; sort by area.
-    (y0, y1), (x0, x1) = max(
-        rectangles.items(), key=lambda xy: (xy[1][0] - xy[1][1]) * (xy[0][0] - xy[0][1])
-    )
-
-    # There may be a little slop here, so slice an extra two pixels off
-    # either side to account for light grey pixels that have slipped in.
-    # This cropping is only approximate, so this is fine.
-    x0 += 2
-    y0 += 2
-    x1 -= 2
-    y1 -= 2
-
-    # Now adjust the pixels so we get a 2:1 aspect ratio in the final image.
-    # Again, we don't care about exactness here, slicing a few pixels off
-    # either edge is fine.
-    width = x1 - x0
-    height = y1 - y0
-
-    if height * 2 < width:  # too wide
-        diff = width - height * 2
-        x0 += int(math.ceil(diff / 2))
-        x1 -= int(math.floor(diff / 2))
-    elif height * 2 > width:  # too tall
-        diff = height * 2 - width
-        y0 += int(math.ceil(diff / 2))
-        y1 -= int(math.floor(diff / 2))
-
-    # Account for the case where odd/even offsets mean we're actually
-    # one away, e.g. 1601 × 800
-    if x1 - x0 == (y1 - y0) * 2 + 1:
-        x0 += 1
-
-    assert (x1 - x0) == (y1 - y0) * 2
-
-    # Now save the image to an appropriate filename, and tell the user
-    cropped_im = im.crop((x0, y0, x1, y1))
-
-    name, ext = os.path.splitext(path)
-    ext = ext.replace(".jpeg", ".jpg")
-    out_path = f"{name}.cropped{ext}"
-
-    cropped_im.save(out_path, icc_profile=im.info.get("icc_profile"))
-
-    if im.info.get("icc_profile") is not None:
-        subprocess.check_call(
-            ["srgbify.py", out_path],
-            stdout=subprocess.DEVNULL,
-        )
-
-    print(out_path)
-    assert cropped_im.width == cropped_im.height * 2