#!/bin/bash # ██ ███ ███ ███ ██ ███ █ █ ███ ██ ███ █ # █ █ █ █ ██ █ █ ███ ██ █ █ ███ # ██ ███ █ █ ███ ██ █ █ █ ███ ██ ███ █ █ # Colorize some string based on its hash # 2025: Mostly written by chatgpt, # but with a lot of brontosaurusrex input and corrections # ------------------------------------------------------- # usage: # sinesthesia "woot woot woot" # cat some.txt | sinesthesia # sinesthesia --theme=ocean "woot woot woot" # (autumn, ocean, forest, glacier, herbal, icefire, tealgray, sunset) # zsh: # repeat 9 sinesthesia # ------------------------------------------------------- # Global color controls sat="25" # Some themes may override it light="93" theme="" # Avoid certain hues avoidPink="0" avoidYellow="0" debug="0" # --- Helpers --- hsv_to_rgb() { awk -v h="$1" -v s="$2" -v v="$3" ' BEGIN { h = (h % 360 + 360) % 360 s /= 100 v /= 100 c = v * s h_ = h / 60 x = c * (1 - ((h_ % 2) - 1)^2) m = v - c if (h_ < 1) { r = c; g = x; b = 0 } else if (h_ < 2) { r = x; g = c; b = 0 } else if (h_ < 3) { r = 0; g = c; b = x } else if (h_ < 4) { r = 0; g = x; b = c } else if (h_ < 5) { r = x; g = 0; b = c } else { r = c; g = 0; b = x } r = int((r + m) * 255 + 0.5) g = int((g + m) * 255 + 0.5) b = int((b + m) * 255 + 0.5) printf "%d %d %d\n", r, g, b }' } gamma_correct() { awk -v targetY="$1" ' function to_linear(v) { v = v / 255 return (v <= 0.04045) ? v / 12.92 : ((v + 0.055) / 1.055)^2.4 } function to_srgb(v) { return (v <= 0.0031308) ? v * 12.92 : 1.055 * (v ^ (1/2.4)) - 0.055 } { r = to_linear($1) g = to_linear($2) b = to_linear($3) Y = 0.2126 * r + 0.7152 * g + 0.0722 * b if (Y > 0) { scale = targetY / Y r *= scale; g *= scale; b *= scale } r = int(to_srgb(r) * 255 + 0.5) g = int(to_srgb(g) * 255 + 0.5) b = int(to_srgb(b) * 255 + 0.5) printf "%d %d %d\n", (r > 255 ? 255 : (r < 0 ? 0 : r)), (g > 255 ? 255 : (g < 0 ? 0 : g)), (b > 255 ? 255 : (b < 0 ? 0 : b)) }' } # --- Theme remapping --- remap_hue() { local hue="$1" local new_hue="$hue" local new_sat="$sat" case "$theme" in autumn) new_hue=$(( (hue % 50) + 20 )) ;; ocean) new_hue=$(( (hue % 100) + 140 )) ;; forest) new_hue=$(( (hue % 60) + 90 )) ;; glacier) new_hue=$(( (hue % 50) + 170 )) ;; herbal) new_hue=$(( (hue % 60) + 120 )) ;; icefire) case $((hue % 4)) in 0) new_hue=20 ;; 1) new_hue=200 ;; 2) new_hue=40 ;; 3) new_hue=260 ;; esac ;; tealgray) case $((hue % 4)) in 0) new_hue=176; new_sat=57 ;; # #66D2CE 1) new_hue=172; new_sat=58 ;; # #2DAA9E 2) new_hue=0; new_sat=0 ;; # #EAEAEA 3) new_hue=30; new_sat=17 ;; # #E3D2C3 esac ;; sunset) case $((hue % 7)) in 0) new_hue=163; new_sat=42 ;; # #84E3C8 1) new_hue=158; new_sat=27 ;; # #A8E6CF 2) new_hue=83; new_sat=19 ;; # #DCEDC1 3) new_hue=24; new_sat=29 ;; # #FFD3B6 4) new_hue=3; new_sat=35 ;; # #FFAAA5 5) new_hue=355; new_sat=45 ;; # #FF8B94 6) new_hue=355; new_sat=55 ;; # #FF7480 esac ;; esac echo "$new_hue $new_sat" } # --- Core function --- colorize_string() { local string="$1" # fallback string if [ -z "$string" ]; then string="███████████████████████ ███████████████████████ $RANDOM$$ ███████████████████████" fi local hash hue red grn blu hash="$(echo "$string" | cksum | cut -d ' ' -f 1)" hue="$((hash % 360))" # avoid unwanted ranges (( avoidPink )) && (( hue >= 290 && hue <= 340 )) && hue=$(( (hue + 50) % 360 )) (( avoidYellow )) && (( hue >= 40 && hue <= 80 )) && hue=$(( (hue + 41) % 360 )) # apply theme mapping (outputs hue sat) read -r hue sat < <(remap_hue "$hue") # convert HSV to RGB read -r red grn blu < <(hsv_to_rgb "$hue" "$sat" "$light" | gamma_correct "$(awk "BEGIN { print $light / 100 }")") (( debug )) && echo "hue=$hue sat=$sat rgb=$red $grn $blu" # output colored string while IFS= read -r line; do echo -e "\e[38;2;${red};${grn};${blu}m${line}\e[0m" done <<< "$string" } # --- Entry point --- # parse --theme=NAME from args if [[ "$1" == --theme=* ]]; then theme="${1#--theme=}" shift fi if [ -t 0 ]; then if [ $# -gt 0 ]; then colorize_string "$*" else colorize_string echo fi else input="$(cat)" colorize_string "$input" fi