#!/bin/bash # hsv-rbg-browser.sh -- bash script for browsing RGB <-> HSV Colors. # Version: 0.11 -- Last update: Fri Oct 25 07:12:06 2024 # Read colors from screen, clipboard and/or command line. # (C) 2018-2024 Felix Hauri - felix@f-hauri.ch # Licensed under terms of GPL v3. www.gnu.org # Color browser using hsv<->rgb functions: hsvrgb.sh in same directory. # Require: tput, xclip, colorpicker SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # shellcheck source=./hsvrgb.sh if [ -e "$SRCDIR/hsvrgb.sh" ]; then . "$SRCDIR/hsvrgb.sh" else echo >&2 "Functions file 'hsvrgb.sh' not found."; exit 1 fi pasteLoop=false # shellcheck disable=SC2064 # I want to store stty setting in trap command trap "stty '$(stty -g)'" 0 1 2 3 6 15 stty -echo declare -i cols readGrSizes() { cols=$( tput 'cols' ) printf -v ColFmt '%*s' "$cols" '' ColFmt=${ColFmt// /'\e[48;2;%d;%d;%d;38;2;%d;%d;%dm\U2590'} ColFmt+='\e[0m' } refresh() { readGrSizes start=${EPOCHREALTIME/.} printf "\e8\e[J%s" "$clearPanel" drawLines showValues } trap refresh WINCH readGrSizes declare -ai xtR xtG xtB getTermColors() { # Don't understand why this take time under mate or gnome term local _i local _chr _r _g _b for _i in {0..255}; do printf '\e]4;%d;?;\e\134' "$_i" >/dev/tty IFS='' read -sd \\ -r _chr _chr=${_chr#*:} IFS=/ read -r _r _g _b <<<"${_chr%$'\e'}" # shellcheck disable=SC2034 # Bad handling of integer xtR[_i]="16#$_r / 257" xtG[_i]="16#$_g / 257" xtB[_i]="16#$_b / 257" printf '\r\e[48;2;%d;%d;%dm %3d \e[0m' \ "${xtR[_i]}" "${xtR[_i]}" "${xtR[_i]}" "$_i" done } declare -A namedColors getNamedColors() { local -i _r _g _b local cname rgbfile tries=(/{lib{64,},etc,usr/{lib,share}}/X1*1/rgb.txt) read -ra tries <<<"${tries[*]//*X1?1?rgb*}" if ! [[ -f ${tries[0]} ]]; then echo >&2 "WARNING: Color file 'X11/rgb.txt' not found!" return fi rgbfile=${tries[0]} while read -r cname ; do case $cname in [0-9]* ) read -r _r _g _b cname <<<"$cname" namedColors[$cname]="$_r $_g $_b" namedColors[${cname,,}]="$_r $_g $_b" esac done <"$rgbfile" } oldClipBoard='' parseClipBoard() { local string read -r string < <(xclip -o 2>/dev/null) string=${string//[^0-9a-zA-Z),(# ]} [[ "$string" == "$oldClipBoard" ]] && return oldClipBoard="$string" crtColorFromString "$string"; } crtColorFromString() { local string="$*" local -i arcol case $string in '#'???????????? ) arcol=( "16#${string:1:4}/257" "16#${string:5:4}/16" "16#${string:9:4}/257" ) ;; '#'????????? ) arcol=( "16#${string:1:3}/16" "16#${string:4:3}/16" "16#${string:7:3}/16" ) ;; '#'?????? ) arcol=( "16#${string:1:2}" "16#${string:3:2}" "16#${string:5:2}" ) ;; '#'??? ) arcol=( "16#${string:1:1}*17" "16#${string:2:1}*17" "16#${string:3:1}*17" ) ;; 'rgb('*')' | 'rgba('*')' ) read -ra arcol <<<"${string//[rgba),(]/ }" ;; * ) [[ -v namedColors["$string"] ]] || return 1 read -ra arcol <<<"${namedColors["$string"]}" ;; esac if [ "${arcol[2]}" ]; then r=${arcol[0]} g=${arcol[1]} b=${arcol[2]} rehsv fi } peekFromScreen() { local peek read -r peek < <(colorpicker --one-shot --short) r=$((16#${peek:1:2})) g=$((16#${peek:3:2})) b=$((16#${peek:5:2})) rehsv } closestColor() { local -i _r=$1 _g=$2 _b=$3 _idx _crt _closest=292742550 local -n _res=$4 for _idx in {0..255}; do [[ ${xtR[_idx]}.${xtG[_idx]}.${xtB[_idx]} == $_r.$_g.$_b ]] && _res=$_idx && return _crt='((xtR[_idx]-_r )*3)**2+((xtG[_idx]-_g)*6)**2+(xtB[_idx]-_b)**2' (( _crt < _closest )) && { _closest=$_crt # shellcheck disable=SC2034 # Bad handling of namref _res=$_idx } done return 1 } closestNamedColor() { local _idx local -i _r=$1 _g=$2 _b=$3 _crt _closest=292742550 _nr _ng _nb local -n _res=$4 for _idx in "${!namedColors[@]}"; do read -r _nr _ng _nb <<<"${namedColors[$_idx]}" [[ $_nr.$_ng.$_nb == $_r.$_g.$_b ]] && _res=$_idx && return _crt='((_nr-_r )*3)**2+((_ng-_g)*6)**2+(_nb-_b)**2' (( _crt < _closest )) && { _closest=$_crt # shellcheck disable=SC2034 # Bad handling of namref _res=$_idx } done return 1 } # shellcheck disable=SC2059 # Format string was built drawLines(){ local -ia sl=() vl=() hl=() rl=() gl=() bl=() colors local -i i local sint # {r,g,b,h,s,v}_blank for ((i=0 ; 2*cols > i ; i++ )) do rgb $((17950000*i/cols/100000)) "$s" "$v" colors hl+=("${colors[@]}") sint=0000$((i*100000/cols)) rgb "$h" ${sint::-5}.${sint: -5} "$v" colors sl+=("${colors[@]}") rgb "$h" "$s" $((25600000*i/cols/100000)) colors vl+=("${colors[@]}") rl+=("$((25600000*i/cols/100000))" "$g" "$b") gl+=("$r" "$((25600000*i/cols/100000))" "$b") bl+=("$r" "$g" "$((25600000*i/cols/100000))") done printf -v Hline "$ColFmt" "${hl[@]}" printf -v Sline "$ColFmt" "${sl[@]}" printf -v Vline "$ColFmt" "${vl[@]}" printf -v Rline "$ColFmt" "${rl[@]}" printf -v Gline "$ColFmt" "${gl[@]}" printf -v Bline "$ColFmt" "${bl[@]}" } printf -v retsym[0] %b \\U2BC8 showValues() { local sv sn ccret cnret h_blank=00$((1000*cols*h/360)) printf -v h_blank "%.0f" ${h_blank::-3}.${h_blank: -3} printf -v h_blank '%*s' $((h_blank>(cols-1)?(cols-1):h_blank)) '' printf -v s_blank %.6f "$s" s_blank=00$((1000*cols*10#${s_blank/.}/1000000)) printf -v s_blank "%.0f" ${s_blank::-3}.${s_blank: -3} printf -v s_blank '%*s' $((s_blank>(cols-1)?(cols-1):s_blank)) '' v_blank=00$((1000*cols*v/255)) printf -v v_blank "%.0f" ${v_blank::-3}.${v_blank: -3} printf -v v_blank '%*s' $((v_blank>(cols-1)?(cols-1):v_blank)) '' r_blank=00$((1000*cols*r/255)) printf -v r_blank "%.0f" ${r_blank::-3}.${r_blank: -3} printf -v r_blank '%*s' $((r_blank>(cols-1)?(cols-1):r_blank)) '' g_blank=00$((1000*cols*g/255)) printf -v g_blank "%.0f" ${g_blank::-3}.${g_blank: -3} printf -v g_blank '%*s' $((g_blank>(cols-1)?(cols-1):g_blank)) '' b_blank=00$((1000*cols*b/255)) printf -v b_blank "%.0f" ${b_blank::-3}.${b_blank: -3} printf -v b_blank '%*s' $((b_blank>(cols-1)?(cols-1):b_blank)) '' closestColor "$r" "$g" "$b" sv ccret=$? closestNamedColor "$r" "$g" "$b" sn cnret=$? # shellcheck disable=SC2059 # Format string was built # shellcheck disable=SC2154 # sv was assigned by nameref # shellcheck disable=SC2086 # namedColors are groups of 3 values printf "$format" \ "$Hline" "$h_blank" "$Sline" "$s_blank" "$Vline" "$v_blank" \ "$Rline" "$r_blank" "$Gline" "$g_blank" "$Bline" "$b_blank" \ "$r" "$g" "$b" "$r" "$g" "$b" "$r" "$g" "$b" "$h" "$s" "$v" \ ${namedColors[$sn]} "$r" "$g" "$b" "$sn" "${retsym[cnret]}" \ ${namedColors[$sn]} "$sv" "$step" "$sv" "${retsym[ccret]}" "$sv" printf $'\e[21C%\04714d\U00B5s\e[K\e[37D' $(( ${EPOCHREALTIME/.} -start )) } # shellcheck disable=SC2154 # variable out assigned by nameref rehsv() { hsv "$r" "$g" "$b" out;h=${out[0]} s=${out[1]} v=${out[2]};} rergb() { rgb "$h" "$s" "$v" out;r=${out[0]} g=${out[1]} b=${out[2]};} showUsage() { printf '\e8\e[K' printf '\n\e[K %-12b %b' '\e8\e[KHSV-RGB Color browser' \ '\n\e[K---------------------' '' '' '\e[2DKeyboard interaction:' ''{,,}\ '[RrGgBbVvLl]' \ "Incrase/decrase [R,G,B,Val|Lum] by step ($step), from 0 to 255." \ '[HhTt]' 'Incrase/decrase Hue (Tint) loop over 0 - 359.' \ '[Ss]' "Increase/decrase Saturation by .002x step ($step)." \ '' '' '[Cc]' 'Toggle Color bar rendering (upper C fix HSV)' \ '' '' '[p]' 'Paste color from clipboard' '[P]' \ 'Toggle loop mode \e[3mPaste Color from clipboard\e[23m every .3sec'\ '[k]' 'peeK color on screen' \ '' '' '[+-]' "Incrase/decrase step. ($step)" \ '[fF]' 'Refresh display (or ^L)' \ '' '' '[uU]' 'show Usage (this)' \ '[qQ]' 'Quit.' \ '' '' "\\e[2D${0##*/} - $(sed -ne '2s/^# //p' "$0")" '' read -rsn 1 -t 20 _ start=${EPOCHREALTIME/.} printf "\e8%s" "$clearPanel" showValues } printf " Initialisation: 1 read TERM color map...\r" getTermColors getNamedColors printf " Initialisation: 2 open console space, build output format...\r" start=${EPOCHREALTIME/.} step=3 format='\e8Hue\e[K\n%s\n\e[K%s\U25B2\nSaturation\e[K\n%s\n\e[K%s\U25B2\n' format+='Value\e[K\n%s\n\e[K%s\U25B2\nRed\e[K\n%s\n\e[K%s\U25B2\n' format+='Green\e[K\n%s\n\e[K%s\U25B2\nBlue\e[K\n%s\n\e[K%s\U25B2\n' format+=' R: %3d, G: %3d, B: %3d => #%02X%02X%02X \e[0;48;2;%d;%d;%dm \e[0' format+='m\n H: %3d, S: %06.5f, V: %3d \e[0;48;2;%d;%d;%dm ' format+='\e[0m\e[0;48;2;%d;%d;%dm \e[0m\n Name: %-25s%1s\e[0;48;2;%d;%d;%dm' format+=' \e[0m\e[48;5;%dm \e[0m\e[11C%3d\n' format+=' 1 byte ANSI -> [%3d] %1s\e[48;5;%dm \e[0m | \e[2D' # shellcheck disable=SC2015 # Set crtColor from $* or default to hsv(204,.5,128) [[ $1 ]] && crtColorFromString "$*" || { h=204 s=.5 v=128 rergb } drawLines renderColor=$(( ( ${EPOCHREALTIME/.} - start ) < 100000 )) printf -v clearPanel \ '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n%43s%s\n%43s%s\n%43s%s\n%43s%s\r\e[21A\e7'\ \| ' Use keys [RGBHSV] to change values,' \| \ ' Lower case to lower, upper to higher' \| \ ' by step: [+-] (in/de)crase step.' \| \ $' or [\e[1mu\e[0m] for \47\e[1mUsage\e[0m\47.' echo -n "$clearPanel" tput civis while :; do [[ "$r $g $b" != "$oldcolors" ]] && { start=${EPOCHREALTIME/.} ((renderColor)) && drawLines oldcolors="$r $g $b" showValues } $pasteLoop && parseClipBoard if IFS= read -rsn 1 -t .02 keypress; then case $keypress in q|Q ) break ;; u|U ) showUsage ;; c|C ) if ((renderColor=1-renderColor)) ; then drawLines else or=$r og=$g ob=$b h=204 s=.8 v=204 rergb drawLines r=$or g=$og b=$ob rehsv fi ;; - ) step=$(( step-1 > 1 ? step-1 : 1 ));showValues ;; + ) step=$(( step+1 < 128 ? step+1 : 128 ));showValues ;; r ) r=$(( r-step > 0 ? r-step : 0 )) ;rehsv;; R ) r=$(( r+step < 255 ? r+step : 255));rehsv;; g ) g=$(( g-step > 0 ? g-step : 0 )) ;rehsv;; G ) g=$(( g+step < 255 ? g+step : 255));rehsv;; b ) b=$(( b-step > 0 ? b-step : 0 )) ;rehsv;; B ) b=$(( b+step < 255 ? b+step : 255));rehsv;; t|h ) h=$(( ( h+360 - step ) % 360 )) ;rergb;; T|H ) h=$(( ( h+step ) % 360 )) ;rergb;; v|l ) v=$(( v-step > 0 ? v-step : 0 )) ;rergb;; V|L ) v=$(( v+step < 255 ? v+step : 255));rergb;; s ) printf -v s %.6f "$s" s=00000$(( s=10#${s/.}, s-step*2000 > 0 ? s-step * 2000 : 0 )) printf -v s %.5f ${s::-6}.${s: -6} ;rergb;; S ) printf -v s %.6f "$s" s=00000$(( s=10#${s/.}, s+step*2000 < 1000000 ? s+step*2000:1000000)) printf -v s %.5f ${s::-6}.${s: -6} ;rergb;; f|$'\f' ) printf "\e8\e[J%s" "$clearPanel"; drawLines;showValues;; p ) parseClipBoard;; P ) if $pasteLoop; then pasteLoop=false; else pasteLoop=true; fi ;; k ) peekFromScreen;; * ) printf '\e[32C[%03d]\e[37D' \'"$keypress" esac unset keypress fi done echo $'\e[0m' tput cnorm