Skip to main content

add my image cropping script

ID
b58203a
date
2023-05-22 02:53:23+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
4f94f9a
message
add my image cropping script
changed files
2 files, 263 additions

Changed files

images/extract_white (0) → images/extract_white (4497)

diff --git a/images/extract_white b/images/extract_white
new file mode 100755
index 0000000..61f3c88
--- /dev/null
+++ b/images/extract_white
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+
+import collections
+import io
+import itertools
+import math
+import os
+import subprocess
+import sys
+
+from PIL import Image, ImageCms
+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}")
+
+    # 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
+    }
+
+    # 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))
+
+    # 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}
+
+    # 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))
+
+    # 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"))
+
+    subprocess.check_call([
+        'retrobatch', '--workflow', '~/repos/scripts/images/overwrite_with_srgb.retrobatch', out_path
+    ], stdout=subprocess.DEVNULL)
+
+    print(out_path)
+    assert cropped_im.width == cropped_im.height * 2
+
+
+    outline_im = Image.new("RGBA", im.size)
+
+    for (x, y) in white_pixels:
+        outline_im.putpixel((x, y), (255, 0, 0, 255))
+
+    for (x, y) in corner_pixels:
+        outline_im.putpixel((x, y), (0, 255, 255, 255))
+
+    for x, y_coords in column_corners.items():
+        for y in y_coords:
+            outline_im.putpixel((x, y), (0, 0, 255, 255))
+
+    for y_coords, x_coords in rectangles.items():
+        for x in x_coords:
+            for y in y_coords:
+                # print(x, y)
+                outline_im.putpixel((x, y), (255, 0, 255, 255))
+
+
+    outline_im.save("outline5.png")

images/overwrite_with_srgb.retrobatch (0) → images/overwrite_with_srgb.retrobatch (3882)

diff --git a/images/overwrite_with_srgb.retrobatch b/images/overwrite_with_srgb.retrobatch
new file mode 100644
index 0000000..d8bb96f
--- /dev/null
+++ b/images/overwrite_with_srgb.retrobatch
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>previewBackgroundColorState</key>
+	<integer>0</integer>
+	<key>processes</key>
+	<array>
+		<dict>
+			<key>canvasOrigin</key>
+			<string>{560, 160}</string>
+			<key>class</key>
+			<string>OTWriteNode</string>
+			<key>inputFileNameTokens</key>
+			<data>
+			BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0FycmF5AISECE5TT2Jq
+			ZWN0AIWEAWkBkoSEhBFPVFRva2VuRmllbGRUb2tlbgCUkoSEhAhO
+			U1N0cmluZwGUhAErCiRGaWxlTmFtZSSGhoY=
+			</data>
+			<key>inputLossyCompressionQuality</key>
+			<real>0.5</real>
+			<key>inputOpenFolderOnFinish</key>
+			<false/>
+			<key>inputOverwriteExistingFiles</key>
+			<integer>1</integer>
+			<key>inputProgressive</key>
+			<false/>
+			<key>inputRemoveAlphaChannelIfOpaque</key>
+			<true/>
+			<key>inputReplaceOriginalImage</key>
+			<integer>1</integer>
+			<key>inputTIFFCompressionAlgorithm</key>
+			<integer>5</integer>
+			<key>outputFolderURLBookmarkData</key>
+			<data>
+			Ym9vazAEAAAAAAQQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+			AAAAAAAAAAAAIAMAAAQAAAADAwAAAAIAAAUAAAABAQAAVXNlcnMA
+			AAAKAAAAAQEAAGFsZXh3bGNoYW4AAAUAAAABAQAAcmVwb3MAAAAO
+			AAAAAQEAAGFsZXh3bGNoYW4ubmV0AAADAAAAAQEAAHNyYwAHAAAA
+			AQEAAF9pbWFnZXMABAAAAAEBAAAyMDIzHAAAAAEGAAAQAAAAIAAA
+			ADQAAABEAAAAXAAAAGgAAAB4AAAACAAAAAQDAAAVXQAAAAAAAAgA
+			AAAEAwAAQeMDAAAAAAAIAAAABAMAADfuCgAAAAAACAAAAAQDAACV
+			jhYAAAAAAAgAAAAEAwAAUaYWAAAAAAAIAAAABAMAAE2nFgAAAAAA
+			CAAAAAQDAADPlDMAAAAAABwAAAABBgAAqAAAALgAAADIAAAA2AAA
+			AOgAAAD4AAAACAEAAAgAAAAABAAAQcS6Xb1mzIwYAAAAAQIAAAIA
+			AAAAAAAADwAAAAAAAAAAAAAAAAAAAAgAAAAEAwAABQAAAAAAAAAE
+			AAAAAwMAAPUBAAAIAAAAAQkAAGZpbGU6Ly8vDAAAAAEBAABNYWNp
+			bnRvc2ggSEQIAAAABAMAAAAAhxE5AAAACAAAAAAEAABBxEAH4AAA
+			ACQAAAABAQAARkJENjhFN0MtRjYyNy00MDVCLTgzREUtOEZCNzBF
+			M0MyQzU1GAAAAAECAACBAAAAAQAAAO8TAAABAAAAAAAAAAAAAAAB
+			AAAAAQEAAC8AAAAAAAAAAQUAAOkAAAABAgAAZDBhZjA1YWUxNmY1
+			MWViZDI3Yjk3ZTY0N2MzOGFhYTIxMWZkYjNkN2M0M2Y5OGQ2NTEz
+			MjNhYWMxNmQ2YzljZjswMDswMDAwMDAwMDswMDAwMDAwMDswMDAw
+			MDAwMDswMDAwMDAwMDAwMDAwMDIwO2NvbS5hcHBsZS5hcHAtc2Fu
+			ZGJveC5yZWFkLXdyaXRlOzAxOzAxMDAwMDEyOzAwMDAwMDAwMDAz
+			Mzk0Y2Y7MDE7L3VzZXJzL2FsZXh3bGNoYW4vcmVwb3MvYWxleHds
+			Y2hhbi5uZXQvc3JjL19pbWFnZXMvMjAyMwAAAADYAAAA/v///wEA
+			AAAAAAAAEQAAAAQQAACEAAAAAAAAAAUQAAAYAQAAAAAAABAQAABM
+			AQAAAAAAAEAQAAA8AQAAAAAAAAIgAAAYAgAAAAAAAAUgAACIAQAA
+			AAAAABAgAACYAQAAAAAAABEgAADMAQAAAAAAABIgAACsAQAAAAAA
+			ABMgAAC8AQAAAAAAACAgAAD4AQAAAAAAADAgAAAkAgAAAAAAAAHA
+			AABsAQAAAAAAABHAAAAgAAAAAAAAABLAAAB8AQAAAAAAABDQAAAE
+			AAAAAAAAAIDwAAAsAgAAAAAAAA==
+			</data>
+			<key>outputOriginalFolderPath</key>
+			<string>/Users/alexwlchan/repos/alexwlchan.net/src/_images/2023</string>
+			<key>outputUTI</key>
+			<string>original</string>
+			<key>outputsIds</key>
+			<array/>
+			<key>proccessId</key>
+			<string>8e9b92f0-c105-41bd-bfa0-1f5717fe871d</string>
+		</dict>
+		<dict>
+			<key>canvasOrigin</key>
+			<string>{320, 160}</string>
+			<key>class</key>
+			<string>OTColorProfileNode</string>
+			<key>inputMatchToProfile</key>
+			<integer>1</integer>
+			<key>inputProfileURLBookmark</key>
+			<data>
+			</data>
+			<key>inputRenderingIntent</key>
+			<integer>0</integer>
+			<key>inputStripColorProfile</key>
+			<integer>0</integer>
+			<key>outputsIds</key>
+			<array>
+				<string>8e9b92f0-c105-41bd-bfa0-1f5717fe871d</string>
+			</array>
+			<key>proccessId</key>
+			<string>8d5aa9bc-0e45-4780-afb3-328d0b3e237a</string>
+		</dict>
+		<dict>
+			<key>canvasOrigin</key>
+			<string>{80, 160}</string>
+			<key>class</key>
+			<string>OTFilesNode</string>
+			<key>outputsIds</key>
+			<array>
+				<string>8d5aa9bc-0e45-4780-afb3-328d0b3e237a</string>
+			</array>
+			<key>proccessId</key>
+			<string>1f92c379-3eb8-450d-a3df-a90d5eb1f024</string>
+			<key>urls</key>
+			<array/>
+		</dict>
+	</array>
+	<key>version</key>
+	<integer>0</integer>
+</dict>
+</plist>