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>