Last active
January 6, 2023 17:27
-
-
Save leagris/59e1b7e72462024b278652696f375e71 to your computer and use it in GitHub Desktop.
Mandelbrot for POSIX shell
This file contains 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 sh | |
# | |
# Modified version of bash mandelbrot from Mecki stackoverflow.com/users/15809 | |
# found at https://stackoverflow.com/a/63749612/1765658 | |
# Avoiding forks and running bc as background task, reducing | |
# execution time, from many hours to less than 10' (on my desktop) | |
# | |
# Modified version for POSIX shell by Léa Gris: | |
# https://gist.github.com/leagris/59e1b7e72462024b278652696f375e71 | |
# Improved to addressing fifos by FDs. | |
# Spawn 4 instances of bc to parallel compute 2 characters of 2 pixels | |
BAILOUT=16 | |
MAX_ITERATIONS=1000 | |
# Cleanup resources and state at end of execution | |
unset tempdir | |
trap 'rm -fr -- "$tempdir";stty "$old_settings";printf "\\e(B\\e[m\\n";exit' EXIT INT | |
if [ -t 0 ] && [ -t 1 ]; then | |
old_settings=$(stty -g) || exit | |
stty -icanon -echo min 0 time 3 || exit | |
# CSI CUP 1000 1000 move cursor to 1000 1000 | |
# CSI CSR Get status report cursor position y;xR | |
printf '\e[1000;1000H\e[6n' | |
pos="$(dd count=1 2> /dev/null)" | |
pos="${pos%R*}" | |
pos="${pos##*\[}" | |
x="${pos##*;}" | |
y="${pos%%;*}" | |
else | |
x=80 | |
y=24 | |
fi | |
ALPHWIDTH=$((x / 2)) | |
AWIDE=$((ALPHWIDTH - 1)) | |
tempdir=$(mktemp -d) || exit 1 | |
bc1in="$tempdir/bc1in" | |
bc1out="$tempdir/bc1out" | |
bc2in="$tempdir/bc2in" | |
bc2out="$tempdir/bc2out" | |
bc3in="$tempdir/bc3in" | |
bc3out="$tempdir/bc3out" | |
bc4in="$tempdir/bc4in" | |
bc4out="$tempdir/bc4out" | |
mkfifo \ | |
"$bc1in" "$bc1out" \ | |
"$bc2in" "$bc2out" \ | |
"$bc3in" "$bc3out" \ | |
"$bc4in" "$bc4out" | |
# Spawn bc instances on their own FD | |
exec 2<&-; | |
exec 2<>"$bc1in"; | |
exec 3<>"$bc1out" | |
bc -l <&2 >&3 & | |
exec 4<>"$bc2in" | |
exec 5<>"$bc2out" | |
bc -l <&4 >&5 & | |
exec 6<>"$bc3in" | |
exec 7<>"$bc3out" | |
bc -l <&6 >&7 & | |
exec 8<>"$bc4in" | |
exec 9<>"$bc4out" | |
bc -l <&8 >&9 & | |
# bc initialization code shared by all instances | |
init_bc=" | |
scale=16 | |
bailout=$BAILOUT | |
alphwidth=$ALPHWIDTH | |
maxiter=$MAX_ITERATIONS | |
# Integer division reminder | |
define reminder(x,y) { | |
os=scale # Save scale setting | |
scale=0 # Integer scale | |
x/=1 # x to integer | |
y/=1 # y to integer | |
x%=y # Integer division reminder | |
scale=os # Restore scale setting | |
return x | |
} | |
# Index to ANSI CSI x-term 256-clours code to form a gradient | |
define color(i) { | |
c=reminder(i+23,26) # Repeat 26-colours gradient | |
if(c<6)return c+16 # Black to Blue | |
if(c<12)return (c-6)*42+21 # Blue to White | |
if(c<16)return 243-c # White to Yellow | |
if(c<21)return 220-(c-16)*6 # Yellow to Red | |
return 196-(c-21)*36 # Red to Black | |
} | |
define iterate (x,y) { | |
zi=0; zr=0; cr=x-.5 | |
for ( i=1 ; i < maxiter ; i++ ) { | |
zr2=zr^2-zi^2+cr | |
zi2=2*zr*zi+y | |
if ( (zi^2+zr^2) > bailout ) return color(i) | |
zr=zr2;zi=zi2 | |
} | |
return 0 | |
} | |
0 | |
" | |
# Initialise all bc instances | |
printf %s "$init_bc" >&2 # init bc1 | |
printf %s "$init_bc" >&4 # Init bc2 | |
printf %s "$init_bc" >&6 # init bc3 | |
printf %s "$init_bc" >&8 # Init bc4 | |
# Wait for all bc instances to be ready | |
read -r _ <&3 | |
read -r _ <&5 | |
read -r _ <&7 | |
read -r _ <&9 | |
paintchar() { | |
fg=$1 bg=$2 | |
if [ "$obg" -ne "$bg" ]; then | |
obg=$bg | |
printf '\e[48;5;%dm' "$bg" #tput setab "$bg" | |
fi | |
if [ "$fg" -ne "$bg" ]; then | |
if [ "$ofg" -ne "$fg" ]; then | |
ofg=$fg | |
printf '\e[38;5;%dm' "$fg" #tput setaf "$fg" | |
fi | |
printf '\342\226\200' # UTF-8 U+2580 UPPER HALF BLOCK | |
else | |
printf ' ' | |
fi | |
} | |
mandelbrot() { | |
printf \\n | |
y=$((0 - AWIDE)) | |
while [ "$y" -lt "$AWIDE" ]; do | |
x=$((0 - AWIDE)) | |
y2=$((y + 1)) | |
ofg=-1 obg=-1 | |
while [ "$x" -lt "$AWIDE" ]; do | |
printf 'iterate ( %d / alphwidth, %d / alphwidth )\n' $x $y >&2 | |
printf 'iterate ( %d / alphwidth, %d / alphwidth )\n' $x $y2 >&4 | |
x=$((x + 1)) | |
printf 'iterate ( %d / alphwidth, %d / alphwidth )\n' $x $y >&6 | |
printf 'iterate ( %d / alphwidth, %d / alphwidth )\n' $x $y2 >&8 | |
read -r fg1 <&3 | |
read -r bg1 <&5 | |
read -r fg2 <&7 | |
read -r bg2 <&9 | |
paintchar "$fg1" "$bg1" | |
paintchar "$fg2" "$bg2" | |
x=$((x + 1)) | |
done | |
y=$((y + 2)) | |
printf '\e(B\e[m\n' #tput sgr0; printf \\n | |
done | |
printf \\n | |
} | |
mandelbrot |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment