#!/bin/sh # This script will take an HTML compatible hex colour string and turn it into # a terminal sequence for true colour and other formats which best approximate # its colour value. # # After that, we can map a few other special named symbols. The idea is to # permit shell variables such as `aaffcc bold italic` to indicate what you # want a format to look like. # # The terminal colour and effect language herein implemented is named # "SGR Name". debug=0 debug_print() { if [ ${debug} -ne 0 ] then echo ${*} >&2 fi } # This is the named color map. Just add new entries and they'll be supported... COLOR_TABLE="" map() { if [ $# -eq 2 ] then COLOR_TABLE="${COLOR_TABLE}:${1}=${2}" else ___right="${COLOR_TABLE##*:${1}=}" echo ${___right%%:*} fi } map white "FFFFFF" map silver "C0C0C0" map gray "808080" map grey "808080" map black "000000" map red "FF0000" map maroon "800000" map yellow "FFFF00" map olive "808000" map lime "00FF00" map green "008000" map aqua "00FFFF" map teal "008080" map blue "0000FF" map navy "000080" map fuchsia "FF00FF" map purple "800080" # Some VGA color names can be promoted: map brown "aa5500" map cyan "00aaaa" # The VGA names can be prefixed with "vga-" to explicitly avoid the collisions with HTML names, above... # Based upon wikipedia's article on ANSI. See the table on that page for details. map vga-black "000000" map vga-red "aa0000" map vga-green "00aa00" map vga-brown "aa5500" map vga-yellow "aa5500" map vga-blue "0000aa" map vga-magenta "aa00aa" map vga-cyan "00aaaa" map vga-white "aaaaaa" map vga-brightblack "555555" map vga-grey "555555" map vga-gray "555555" map vga-brightred "ff5555" map vga-brightgreen "55ff55" map vga-brightyellow "ffff55" map vga-brightblue "5555ff" map vga-brightmagenta "ff55ff" map vga-brightcyan "55ffff" map vga-brightwhite "ffffff" enable_greyscale_cheat=0 nocsi=0 if [ "$1" = "no-csi" ] then nocsi=1 shift 1 fi dump_colors() { all_maps=$(echo ${COLOR_TABLE} | sed -e 's/:/ /g' -e 's/=[0-9a-fA-F]\{6\}//g') debug_print "Going through all these colors ${all_maps} (${COLOR_TABLE})" for key in ${all_maps} do debug_print "Processing key ${key}" case $key in *bright*) text_color="000000" ;; *black*) text_color="ffffff" ;; *) text_color="000000" ;; esac printf "%-24s$(map ${key} ) |" ${key} printf "`$0 reset reverse bg:${text_color} fg:${key}`native-sample`$0 reset`|" if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 256 ] then printf "`env CSHENV_TERMINAL_COLORS=256 $0 reset reverse bg:${text_color} fg:${key}`8-bit sample`$0 reset`|" fi if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 16 ] then printf "`env CSHENV_TERMINAL_COLORS=16 $0 reset reverse bg:${text_color} fg:${key}`4-bit sample`$0 reset`|" fi if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 8 ] then printf "`env CSHENV_TERMINAL_COLORS=8 $0 reset reverse bg:${text_color} fg:${key}`3-bit sample`$0 reset`|" fi printf "\n" done exit 0 } cube_row() { r_base=${1} g=0 while [ ${g} -lt 6 ] do u=0 while [ ${u} -lt 3 ] do r=$(( r_base + u )) b=0 while [ ${b} -lt 6 ] do i=$(( 16 + ( r * 36 ) + ( g * 6 ) + ( b ) )) txt="" && [ ${g} -lt 3 ] && txt=';37;1' printf "[30${txt}m[48;5;${i}m%3i " ${i} b=$(( b + 1 )) done printf ' ' u=$(( u + 1 )) done printf '\n' g=$(( g + 1 )) done printf '\n' } greyscale_chart() { [ ${i} -ge 244 ] && txt=";30" local k=$(( i + 12 )) while [ ${i} -lt ${k} ] do printf "[48;5;${i};37;1${txt}m%3i " ${i} i=$(( i + 1 )) done printf '\n' } color_chart() { i=0 # Base 16 while [ ${i} -lt 16 ] do [ ${i} -gt 0 ] && txt=";30" printf "[37;1${txt};48;5;${i}m%3i " ${i} i=$(( i + 1 )) done printf '\n\n' r=0;g=0;b=0 cube_row 0 cube_row 3 i=$(( 6 * 6 * 6 + 16 )) greyscale_chart greyscale_chart } setup_color_depth() { use_8_bit=0 use_4_bit=0 use_3_bit=0 # Must only be set if terminal colors are reduced... if [ "${CSHENV_TERMINAL_COLORS+x}" ] then if [ ${CSHENV_TERMINAL_COLORS} -eq 256 ] then use_8_bit=1 elif [ ${CSHENV_TERMINAL_COLORS} -eq 16 ] then use_4_bit=1 elif [ ${CSHENV_TERMINAL_COLORS} -eq 8 ] then use_3_bit=1 else echo "ERROR!" 1>&2 ; exit 255 fi fi } render_color() { command_color=30 if [ ${use_3_bit} -ne 0 ] then # Underline colours need 8-bit or more if [ ${background} -gt 1 ] ; then return; fi basecolor=$(( ${command_color} + ${background}*10)) next="$(( ${basecolor} + ${legacy_3_bit} ))" elif [ ${use_4_bit} -ne 0 ] then # Underline colours need 8-bit or more if [ ${background} -gt 1 ] ; then return; fi command_color=$(( ${command_color} + ${intensity_1_bit} * 60 )) basecolor=$(( ${command_color} + ${background}*10 )) next="$(( ${basecolor} + ${legacy_3_bit} ))" elif [ ${use_8_bit} -ne 0 ] then basecolor=$(( 8 + ${command_color} + ${background}*10 )) next="${basecolor};5;${ext_8_bit}" else basecolor=$(( 8 + ${command_color} + ${background}*10 )) next="${basecolor};2;${red_dec};${green_dec};${blue_dec}" fi } ansi_color() { if [ ${1} -gt 15 ] then echo "ERROR!" 1>&2 ; exit 255 elif [ ${1} -ge 8 ] then use_4_bit=1 intensity_1_bit=1 legacy_3_bit=$(( ${1} - 8 )) else use_3_bit=1 intensity_1_bit=0 legacy_3_bit=$(( ${1} )) fi } ext_grey() { use_8_bit=1 grey_val=${1} if [ ${grey_val} -le 0 - ${grey_val} -gt 23 ] then echo "ERROR!" 1>&2 ; exit 255 fi ext_8_bit=$(( 232 + ${grey_val} )) } ext_rgb() { use_8_bit=1 red_ext_val="${1%??*}" tmp="${1#?}" green_ext_val=${tmp%?*} blue_ext_val=${tmp#?*} ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} )) } ext_color() { use_8_bit=1 if [ ${1} -gt 255 ] then echo "ERROR!" 1>&2 ; exit 255 else ext_8_bit=${1} fi } rgb_color() { color=$1 case $1 in [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) tmp="${color}" color="${tmp%??*}${tmp%??*}" tmp="${tmp#?}" color="${color}${tmp%?*}${tmp%?*}${tmp#?}${tmp#?}" ;; esac # First split off the red, green, and blue components... red_hex="0x${color%????}" color="${color#??}" green_hex="0x${color%??}" blue_hex="0x${color#??}" # Convert to decimal... red_dec=$((${red_hex})) green_dec=$((${green_hex})) blue_dec=$((${blue_hex})) if [ ${debug} -ne 0 ] then debug_print "Red: $red_dec" debug_print "Green: $green_dec" debug_print "Blue: $blue_dec" fi # Now compute the rest of the stuff... # We probably don't have to compute all the unused color states. We only have to compute # the color state we're signed up for by CSHENV_TERMINAL_COLORS... but whatever... # We round up by 128 points of colour, so that if we're above a certain intensity, we get the top # bit set. red_1_bit=$(( ( ( ${red_dec} + 128 ) >> 8 ) & 1 )) green_1_bit=$(( ( ( ${green_dec} + 128 ) >> 8 ) & 1 )) blue_1_bit=$(( ( ( ${blue_dec} + 128 ) >> 8 ) & 1 )) intensity_1_bit=0 # If we can support an intensity bit, we'll turn that on too... # But we're going to stop using bold to set the colour "intense" # We'll use the 9x and 10x forms... if [ ${red_dec} -ge 192 -o ${green_dec} -ge 192 -o ${blue_dec} -ge 192 ] then intensity_1_bit=1 fi if [ ${debug} -ne 0 ] then debug_print "Red bit: " $red_1_bit debug_print "Green bit: " $green_1_bit debug_print "Blue bit: " $blue_1_bit debug_print "Intensity bit: " $intensity_1_bit fi # Because grey is tricky, I'm going to special case it: if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} -a ${red_dec} -lt 128 -a ${red_dec} -ge 80 ] then red_1_bit=0 green_1_bit=0 blue_1_bit=0 intensity_1_bit=1 fi # Because brown is tricky, I'm going to special case it: if [ ${red_dec} -gt 128 -a ${green_dec} -gt 80 -a ${blue_dec} -lt 50 ] then red_1_bit=1 green_1_bit=1 blue_1_bit=0 fi if [ ${debug} -ne 0 ] then debug_print "Red bit: " $red_1_bit debug_print "Green bit: " $green_1_bit debug_print "Blue bit: " $blue_1_bit debug_print "Intensity bit: " $intensity_1_bit fi # This lets us combine them for a legacy colour value in the legacy colour space... legacy_3_bit=$(( ( ${blue_1_bit} << 2 ) + ( ${green_1_bit} << 1 ) + ( ${red_1_bit} ) )) if [ ${debug} -ne 0 ] then debug_print "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit} fi # Now compute an extended colour cube placement (216 colours): red_ext_val=$(( ${red_dec} * 6 / 256 )) green_ext_val=$(( ${green_dec} * 6 / 256 )) blue_ext_val=$(( ${blue_dec} * 6 / 256 )) if [ ${debug} -ne 0 ] then debug_print "Red ext: " ${red_ext_val} debug_print "Green ext: " ${green_ext_val} debug_print "Blue ext: " ${blue_ext_val} fi ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} )) if [ ${debug} -ne 0 ] then debug_print "Computed 216 color cube: " ${ext_8_bit} fi # Check for precise greyscale, which would replace the above: # (Note that precise greyscale is implied by a user explicitly making ALL of RGB the # same value, thus demanding greyscale, rather than a subtle tinting...) if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ] then ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 )) fi if [ ${debug} -ne 0 ] then debug_print "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit} fi if [ ${enable_greyscale_cheat} -ne 0 ] then # Check for SPECIFIC white and black and grey: # The half-saturation, no saturation, and full saturation values are mapped # to specific points in the legacy set. (This may be ill-advised as a manually # built reverse video terminal setting through colour mapping may make it so that # we wind up in la-la land. Perhaps the better way is to find the head and tail # points in the 6x6x6 colour cube.) # # TODO: Only map the head and tail of the colour cube. if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ] then if [ ${red_dec} -eq 0 ] then ext_8_bit=0 elif [ ${red_dec} -eq 255 ] then ext_8_bit=15 elif [ ${red_dec} -eq 128 ] then ext_8_bit=8 elif [ ${red_dec} -eq 192 ] then ext_8_bit=7 fi fi fi if [ ${debug} -ne 0 ] then debug_print "Computed 256 color choice, after applying white and black scan: " ${ext_8_bit} fi # We do NOT check for the base colours precisely. Since those 16 can be custom mapped by your terminal emulator, you just pass `ansi:0` thru # `ansi:15` to select them by ID. Or use `ext:0` thru `ext:255` to select a specific extended colour. } make_color() { setup_color_depth background=$1 # $1 is the background bit $2 $3 # $2 is the color function render_color } named_color() { name=$( echo ${1} | awk '{print tolower($1)}' ) mapped=$(map ${name}) debug_print "${name} -> ${mapped}" if [ -z "${mapped}" ] then echo "ERROR!" 1>&2 ; exit 255 fi rgb_color ${mapped} } build_sgr_code() { case $1 in dump-colors) dump_colors ;; color-chart) color_chart ;; reset) next=0 ;; bold) next=1 ;; dim) next=2 ;; italic) next=3 ;; underline|under) next=4 ;; blink) next=5 ;; fastblink) next=6 ;; reverse) next=7 ;; hide|conceal) next=8 ;; strike|strikethrough|strikethru) next=9 ;; doubleunder) next=21 ;; reveal) next=28 ;; ansi:*) make_color 0 ansi_color ${1#?????} ;; ext:grey*) make_color 0 ext_grey ${1#????????} ;; ext:rgb*) make_color 0 ext_rgb ${1#???????} ;; ext:*) make_color 0 ext_color ${1#????} ;; # 12-bit color also supported [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;; [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;; fg:ansi:*) make_color 0 ansi_color ${1#????????} ;; fg:ext:grey*) make_color 0 ext_grey ${1#???????????} ;; fg:ext:rgb*) make_color 0 ext_rgb ${1#??????????} ;; fg:ext:*) make_color 0 ext_color ${1#???????} ;; # 12-bit color also supported fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1#???} ;; fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1#???} ;; bg:ansi:*) make_color 1 ansi_color ${1#????????} ;; bg:ext:grey*) make_color 1 ext_grey ${1#???????????} ;; bg:ext:rgb*) make_color 1 ext_rgb ${1#??????????} ;; bg:ext:*) make_color 1 ext_color ${1#???????} ;; # 12-bit color also supported bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;; bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;; ul:ext:grey*) make_color 2 ext_grey ${1#???????????} ;; ul:ext:rgb*) make_color 2 ext_rgb ${1#??????????} ;; ul:ext:*) make_color 2 ext_color ${1#???????} ;; # 12-bit color also supported ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;; ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;; fg:*) make_color 0 named_color ${1#???} ;; bg:*) make_color 1 named_color ${1#???} ;; ul:*) make_color 2 named_color ${1#???} ;; *) make_color 0 named_color $1 ;; esac if [ ! -z ${output} ] then output="${output};" fi output="${output}${next}" } output="" while [ ! -z $1 ] do build_sgr_code $1 shift 1 done if [ ! -z "${output}" ] && [ ${nocsi} -ne 1 ] then #echo "I AM PRINTING WITH CSI! (${nocsi} is nocsi, \`${output}\` is output)">&2 output="[${output}m" fi if [ ! -z "${output}" ] then printf ${output} fi exit 0