459 lines
13 KiB
Bash
Executable File
459 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
# 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
|
||
|
||
# This is the named color map. Just add new entries and they'll be supported...
|
||
|
||
declare -A map
|
||
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
|
||
|
||
function dump_colors()
|
||
{
|
||
for key in ${!map[@]}
|
||
do
|
||
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 [[ ! -v CSHENV_TERMINAL_COLORS ]] || (( ${CSHENV_TERMINAL_COLORS} >= 256 ))
|
||
then
|
||
printf "`env CSHENV_TERMINAL_COLORS=256 $0 reset reverse bg:${text_color} fg:${key}`8-bit sample`$0 reset`|"
|
||
fi
|
||
if [[ ! -v CSHENV_TERMINAL_COLORS ]] || (( ${CSHENV_TERMINAL_COLORS} >= 16 ))
|
||
then
|
||
printf "`env CSHENV_TERMINAL_COLORS=16 $0 reset reverse bg:${text_color} fg:${key}`4-bit sample`$0 reset`|"
|
||
fi
|
||
if [[ ! -v CSHENV_TERMINAL_COLORS ]] || (( ${CSHENV_TERMINAL_COLORS} >= 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
|
||
}
|
||
|
||
function 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 [[ -v CSHENV_TERMINAL_COLORS ]]
|
||
then
|
||
if (( ${CSHENV_TERMINAL_COLORS} == 256 ))
|
||
then
|
||
use_8_bit=1
|
||
elif (( ${CSHENV_TERMINAL_COLORS} == 16 ))
|
||
then
|
||
use_4_bit=1
|
||
elif (( ${CSHENV_TERMINAL_COLORS} == 8 ))
|
||
then
|
||
use_3_bit=1
|
||
else
|
||
echo "ERROR!" 1>&2 ; exit -1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
function render_color()
|
||
{
|
||
command_color=30
|
||
if (( ${use_3_bit} ))
|
||
then
|
||
# Underline colours need 8-bit or more
|
||
if (( ${background} > 1 )) ; then return; fi
|
||
basecolor=$(( ${command_color} + ${background}*10))
|
||
next="$(( ${basecolor} + ${legacy_3_bit} ))"
|
||
elif (( ${use_4_bit} ))
|
||
then
|
||
# Underline colours need 8-bit or more
|
||
if (( ${background} > 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} ))
|
||
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
|
||
}
|
||
|
||
|
||
|
||
function ansi_color()
|
||
{
|
||
if (( ${1} > 15 ))
|
||
then
|
||
echo "ERROR!" 1>&2 ; exit -1
|
||
elif (( ${1} >= 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
|
||
}
|
||
|
||
function ext_grey()
|
||
{
|
||
use_8_bit=1
|
||
grey_val=${1}
|
||
if (( ${grey_val} < 0 || ${grey_val} > 23 ))
|
||
then
|
||
echo "ERROR!" 1>&2 ; exit -1
|
||
fi
|
||
ext_8_bit=$(( 232 + ${grey_val} ))
|
||
}
|
||
|
||
function ext_rgb()
|
||
{
|
||
use_8_bit=1
|
||
red_ext_val=$((6#${1:0:1}))
|
||
green_ext_val=$((6#${1:1:1}))
|
||
blue_ext_val=$((6#${1:2:1}))
|
||
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
|
||
}
|
||
|
||
function ext_color()
|
||
{
|
||
use_8_bit=1
|
||
if (( ${1} > 255 ))
|
||
then
|
||
echo "ERROR!" 1>&2 ; exit -1
|
||
else
|
||
ext_8_bit=${1}
|
||
fi
|
||
}
|
||
|
||
function rgb_color()
|
||
{
|
||
color=$1
|
||
case $1 in
|
||
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) color=${1:0:1}${1:0:1}${1:1:1}${1:1:1}${1:2:1}${1:2:1} ;;
|
||
esac
|
||
# First split off the red, green, and blue components...
|
||
red_hex=${color:0:2}
|
||
green_hex=${color:2:2}
|
||
blue_hex=${color:4:2}
|
||
|
||
# Convert to decimal...
|
||
red_dec=$((16#${red_hex}))
|
||
green_dec=$((16#${green_hex}))
|
||
blue_dec=$((16#${blue_hex}))
|
||
|
||
if (( ${debug} != 0 ))
|
||
then
|
||
echo "Red: $red_dec"
|
||
echo "Green: $green_dec"
|
||
echo "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} >= 192 || ${green_dec} >= 192 || ${blue_dec} >= 192 ))
|
||
then
|
||
intensity_1_bit=1
|
||
fi
|
||
|
||
if (( ${debug} != 0 ))
|
||
then
|
||
echo "Red bit: " $red_1_bit
|
||
echo "Green bit: " $green_1_bit
|
||
echo "Blue bit: " $blue_1_bit
|
||
echo "Intensity bit: " $intensity_1_bit
|
||
fi
|
||
|
||
|
||
# Because grey is tricky, I'm going to special case it:
|
||
if (( 1 && ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} && ${red_dec} < 128 && ${red_dec} >= 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} > 128 && ${green_dec} > 80 && ${blue_dec} < 50 ))
|
||
then
|
||
red_1_bit=1
|
||
green_1_bit=1
|
||
blue_1_bit=0
|
||
fi
|
||
|
||
if (( ${debug} != 0 ))
|
||
then
|
||
echo "Red bit: " $red_1_bit
|
||
echo "Green bit: " $green_1_bit
|
||
echo "Blue bit: " $blue_1_bit
|
||
echo "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} != 0 ))
|
||
then
|
||
echo "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} != 0 ))
|
||
then
|
||
echo "Red ext: " ${red_ext_val}
|
||
echo "Green ext: " ${green_ext_val}
|
||
echo "Blue ext: " ${blue_ext_val}
|
||
fi
|
||
|
||
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
|
||
|
||
if (( ${debug} ))
|
||
then
|
||
echo "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} == ${green_dec} && ${green_dec} == ${blue_dec} ))
|
||
then
|
||
ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 ))
|
||
fi
|
||
|
||
if (( ${debug} ))
|
||
then
|
||
echo "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit}
|
||
fi
|
||
|
||
if (( ${enable_greyscale_cheat} ))
|
||
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} == ${green_dec} && ${green_dec} == ${blue_dec} ))
|
||
then
|
||
if (( ${red_dec} == 0 ))
|
||
then
|
||
ext_8_bit=0
|
||
elif (( ${red_dec} == 255 ))
|
||
then
|
||
ext_8_bit=15
|
||
elif (( ${red_dec} == 128 ))
|
||
then
|
||
ext_8_bit=8
|
||
elif (( ${red_dec} == 192 ))
|
||
then
|
||
ext_8_bit=7
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if (( ${debug} ))
|
||
then
|
||
echo "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.
|
||
}
|
||
|
||
function make_color()
|
||
{
|
||
setup_color_depth
|
||
background=$1 # $1 is the background bit
|
||
$2 $3 # $2 is the color function
|
||
render_color
|
||
}
|
||
|
||
function named_color()
|
||
{
|
||
name=${1,,}
|
||
if [[ ! -v map[$name] ]]
|
||
then
|
||
echo "ERROR!" 1>&2 ; exit -1
|
||
fi
|
||
|
||
mapped=${map[$name]}
|
||
rgb_color ${mapped}
|
||
}
|
||
|
||
function build_sgr_code()
|
||
{
|
||
case $1 in
|
||
dump-colors) dump_colors ;;
|
||
|
||
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:5} ;;
|
||
ext:grey*) make_color 0 ext_grey ${1:8} ;;
|
||
ext:rgb*) make_color 0 ext_rgb ${1:7} ;;
|
||
ext:*) make_color 0 ext_color ${1:4} ;;
|
||
# 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:8} ;;
|
||
fg:ext:grey*) make_color 0 ext_grey ${1:11} ;;
|
||
fg:ext:rgb*) make_color 0 ext_rgb ${1:10} ;;
|
||
fg:ext:*) make_color 0 ext_color ${1:7} ;;
|
||
# 12-bit color also supported
|
||
fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:3} ;;
|
||
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:3} ;;
|
||
|
||
bg:ansi:*) make_color 1 ansi_color ${1:8} ;;
|
||
bg:ext:grey*) make_color 1 ext_grey ${1:11} ;;
|
||
bg:ext:rgb*) make_color 1 ext_rgb ${1:10} ;;
|
||
bg:ext:*) make_color 1 ext_color ${1:7} ;;
|
||
# 12-bit color also supported
|
||
bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1:3} ;;
|
||
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:3} ;;
|
||
|
||
ul:ext:*) make_color 2 ext_color ${1:7} ;;
|
||
# 12-bit color also supported
|
||
ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1:3} ;;
|
||
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:3} ;;
|
||
|
||
fg:*) make_color 0 named_color ${1:3} ;;
|
||
bg:*) make_color 1 named_color ${1:3} ;;
|
||
ul:*) make_color 2 named_color ${1:3} ;;
|
||
*) 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} ))
|
||
then
|
||
output="[${output}m"
|
||
fi
|
||
if [[ ! -z ${output} ]]
|
||
then
|
||
printf ${output}
|
||
fi
|
||
|
||
|
||
exit 0
|