Last active
December 12, 2025 19:00
-
-
Save Winterhuman/c03a892f131c17f2ace4a1c42e9de06f to your computer and use it in GitHub Desktop.
A script which finds the minimum PNG dimensions for a given aspect ratio and pixel count, creates a colour-cycling PPM file, and then converts it to PNG.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S unshare --mount --map-root-user /usr/bin/bash | |
| # Licensed under the Zero-Clause BSD terms: https://opensource.org/license/0bsd | |
| # Requires: imagemagick & [sisyphus](https://gist.github.com/Winterhuman/21d7b148db40ff041f397b07a7aafb83) | |
| # | |
| # Argument 1: Number of pixels to fill, required | |
| # Argument 2: Height ratio, default 23 | |
| # Argument 3: Width ratio, default 5 | |
| set -Ceuf | |
| pstat() { | |
| printf "%b\033[1m%b\033[0;39m" "${2:-\033[1m\033[34m::\033[0;39m }" "$1" | |
| } | |
| pquit() { | |
| pstat "$1" "\033[1m\033[31m!!\033[0;39m " | |
| exit 1 | |
| } | |
| # Using '$RANDOM' is the only non-POSIX part of this script | |
| _="${RANDOM:?"$(pquit "The 'RANDOM' environment variable is not available!\n")"}" | |
| # Check if all arguments are valid | |
| fill="${1:?"$(pquit "No arguments given!\n")"}" | |
| if ! printf "%d" "$fill" >/dev/null 2>&1 || [ "$fill" -le 0 ]; then | |
| pquit "'$fill' is not a positive integer!\n"; fi | |
| pstat "Fill: $fill\n" | |
| hratio="${2:-"23"}" | |
| wratio="${3:-"5"}" | |
| if ! printf "%d" "$hratio" "$wratio" >/dev/null 2>&1 || | |
| [ "$hratio" -le 0 ] || [ "$wratio" -le 0 ]; then | |
| pquit "Aspect ratio values must be positive integers!\n"; fi | |
| # Calculate the optimal/minimal image dimensions for the given fill value | |
| calc_w() { | |
| ## '+ wratio - 1' yields a rounded up answer, instead of rounded down | |
| printf "%d\n" "$(( (("$1" * hratio) + wratio - 1 ) / wratio ))" | |
| } | |
| calc_h() { | |
| printf "%d\n" "$(( ( ("$1" * wratio) + hratio - 1 ) / hratio ))" | |
| } | |
| height="1" | |
| width="$(calc_w "$height")" | |
| bad="0" | |
| ## 'input + 1' is required to make this round up, since '$wratio' is in | |
| ## both the numerator and denominator (manual calculations verify this). | |
| ### '-1' is omitted from 'hratio + wratio' to handle '2:1' ratios. | |
| good="$(( | |
| ( (fill + 1) * wratio + hratio + wratio ) / | |
| (hratio + wratio) | |
| ))" | |
| good="$(( "$(calc_h "$good")" + 2 ))" | |
| if [ "$good" -gt 1 ]; then | |
| while [ "$bad" -le "$(( good - 1 ))" ]; do | |
| if [ "$(( width * height ))" -le "$fill" ]; then | |
| bad="$(( height + 1 ))" | |
| else | |
| good="$height" | |
| fi | |
| height="$(( (bad + good) / 2 ))" | |
| width="$(calc_w "$height")" | |
| done | |
| fi | |
| height="$good" | |
| width="$(calc_w "$height")" | |
| blank="$(( (height * width) - fill ))" | |
| groups="$(( fill / 3 ))" | |
| remainofgroups="$(( fill % 3 ))" | |
| pstat "Image dimensions: $width x $height\n" | |
| pstat "Pixel fill: ${fill}px (${blank}px left)\n" | |
| # Setup a private tmpfs for this script to use, which is what 'unshare' is for. | |
| ## 'nr_inodes' must be >= to 'max number of files + 1' | |
| mount -t tmpfs -o nosuid,nodev,noexec,size=256M,nr_inodes=7 ppm2png /tmp/ || | |
| pquit "Failed to overmount '/tmp/'!\n" | |
| # Create the PPM file. | |
| ## Create the P3 PPM header | |
| cat <<-PPM >/tmp/header & | |
| P3 | |
| $width $height | |
| 255 | |
| PPM | |
| ## Create the blank lines. | |
| ### This method is faster than: `printf "%*s" "$blank" | sed "s/ /0 0 0\n/g"` | |
| yes "0 0 0" | head --lines="$blank" >>/tmp/blank & | |
| ## Create the filled-in lines. | |
| ### This method is faster than the usual `[ "$count" -gt 0 ]` method. The `yes` | |
| ### method shown above is faster, but it would repeat the same "random" value | |
| rand_fill() { | |
| while :; do | |
| printf "%d\n" "$(( RANDOM % 6 + 1 ))" | |
| done | head --lines="$groups" | |
| } | |
| ### Replace the colour indexes with their hex values | |
| #### `[] ||` corrects for unclean divisions (e.g. 5 fill = 1 group + 2 remainder) | |
| { | |
| rand_fill | |
| [ "$remainofgroups" -lt 1 ] || printf "%d\n" "$(( RANDOM % 6 + 1 ))" | |
| } | sed -e "s/^1$/0 255 179/" \ | |
| -e "s/^2$/33 219 155/" \ | |
| -e "s/^3$/43 185 132/" \ | |
| -e "s/^4$/46 151 110/" \ | |
| -e "s/^5$/45 119 88/" \ | |
| -e "s/^6$/41 89 67/" \ | |
| -e "s/^7$/34 60 48/" >>/tmp/1 & | |
| { | |
| rand_fill | |
| [ "$remainofgroups" -ne 2 ] || printf "%d\n" "$(( RANDOM % 6 + 1 ))" | |
| } | sed -e "s/^1$/255 185 0/" \ | |
| -e "s/^2$/219 160 26/" \ | |
| -e "s/^3$/185 137 34/" \ | |
| -e "s/^4$/151 113 37/" \ | |
| -e "s/^5$/119 91 37/" \ | |
| -e "s/^6$/89 69 35/" \ | |
| -e "s/^7$/60 49 30/" >>/tmp/2 & | |
| rand_fill | sed \ | |
| -e "s/^1$/255 47 120/" \ | |
| -e "s/^2$/220 48 105/" \ | |
| -e "s/^3$/186 48 91/" \ | |
| -e "s/^4$/153 46 77/" \ | |
| -e "s/^5$/122 42 64/" \ | |
| -e "s/^6$/91 37 50/" \ | |
| -e "s/^7$/62 31 38/" >>/tmp/3 & | |
| wait | |
| ## Combine the PPM file parts | |
| ppm="/tmp/img.ppm" | |
| { | |
| cat /tmp/header | |
| ## `head` removes the final empty line when '$fill' isn't cleanly | |
| ## divisible by 3 | |
| paste --delimiters="\n" /tmp/1 /tmp/2 /tmp/3 | head --lines="$fill" | |
| cat /tmp/blank | |
| } >"$ppm" | |
| ## Save some space, just in case '$tmp_img' would bring it over the size limit | |
| rm /tmp/header /tmp/1 /tmp/2 /tmp/3 /tmp/blank >/dev/null 2>&1 ||: | |
| # Convert the PPM file to PNG | |
| tmp_img="/tmp/tmp.png" | |
| magick "$ppm" -transparent "#000" "$tmp_img" || | |
| pquit "Failed to create '$tmp_img'!\n" | |
| # Move any existing output to a new '.old' path | |
| mv_exist() { | |
| [ -f "$1" ] || return 0 | |
| mv "$1" "$1.old" | |
| } | |
| mv_exist "header.png" | |
| mv_exist "header.webp" | |
| ## Optimise PNG with 'sisyphus' | |
| pstat "Optimising with Sisyphus...\n" | |
| ## '$tmp_img' exists outside Sisyphus's private tmpfs, meaning it can't see it. | |
| ## So, instead of copying '$tmp_img' somewhere, just give it a file descriptor | |
| exec 3<"$tmp_img" | |
| sisyphus --max-procs 24 --all-oxi true --results 16 /proc/self/fd/3 "header" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment