Created
July 24, 2025 21:52
-
-
Save ericviana/7cfd3af162655b98dac46a0d96fad818 to your computer and use it in GitHub Desktop.
Pure bash script to convert ALL hex colors to OKLCH in CSS files
This file contains hidden or 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
| #!/bin/bash | |
| # hex-to-oklch-converter.sh | |
| # Pure bash script to convert ALL hex colors to OKLCH in CSS files | |
| set -e | |
| # Function to show usage | |
| usage() { | |
| echo "Usage: $0 <css-file>" | |
| echo "Example: $0 styles.css" | |
| echo "" | |
| echo "This script will:" | |
| echo "- Find ALL hex colors in CSS files" | |
| echo "- Convert them to OKLCH format with proper alpha handling" | |
| echo "- Handle 8-digit hex colors with alpha" | |
| echo "- Update the file in place" | |
| echo "- Create a backup with timestamp" | |
| exit 1 | |
| } | |
| # Check if bc is available | |
| if ! command -v bc &>/dev/null; then | |
| echo "Error: 'bc' calculator is required but not installed." | |
| echo "Please install bc: sudo apt-get install bc (Ubuntu/Debian) or brew install bc (macOS)" | |
| exit 1 | |
| fi | |
| # Check if file argument is provided | |
| if [ $# -eq 0 ]; then | |
| usage | |
| fi | |
| CSS_FILE="$1" | |
| # Check if file exists | |
| if [ ! -f "$CSS_FILE" ]; then | |
| echo "Error: File '$CSS_FILE' not found!" | |
| exit 1 | |
| fi | |
| # Function to convert hex to decimal | |
| hex_to_dec() { | |
| echo "ibase=16; $1" | bc | |
| } | |
| # Function for cube root using bc | |
| cbrt() { | |
| local num="$1" | |
| if (($(echo "$num <= 0" | bc -l))); then | |
| echo "0" | |
| else | |
| echo "scale=10; e(l($num)/3)" | bc -l | |
| fi | |
| } | |
| # Function for square root using bc | |
| sqrt() { | |
| echo "scale=10; sqrt($1)" | bc -l | |
| } | |
| # Function for atan2 using bc | |
| atan2() { | |
| local y="$1" | |
| local x="$2" | |
| if (($(echo "$x == 0 && $y == 0" | bc -l))); then | |
| echo "0" | |
| return | |
| fi | |
| if (($(echo "$x > 0" | bc -l))); then | |
| echo "scale=10; a($y/$x)" | bc -l | |
| elif (($(echo "$x < 0 && $y >= 0" | bc -l))); then | |
| echo "scale=10; a($y/$x) + 3.14159265359" | bc -l | |
| elif (($(echo "$x < 0 && $y < 0" | bc -l))); then | |
| echo "scale=10; a($y/$x) - 3.14159265359" | bc -l | |
| elif (($(echo "$x == 0 && $y > 0" | bc -l))); then | |
| echo "1.57079632679" # π/2 | |
| else | |
| echo "-1.57079632679" # -π/2 | |
| fi | |
| } | |
| # Function to convert radians to degrees | |
| rad_to_deg() { | |
| echo "scale=6; $1 * 180 / 3.14159265359" | bc -l | |
| } | |
| # Function to convert sRGB to linear RGB | |
| srgb_to_linear() { | |
| local val=$(echo "scale=10; $1 / 255" | bc -l) | |
| if (($(echo "$val <= 0.04045" | bc -l))); then | |
| echo "scale=10; $val / 12.92" | bc -l | |
| else | |
| echo "scale=10; e(2.4 * l(($val + 0.055) / 1.055))" | bc -l | |
| fi | |
| } | |
| # Main function to convert hex to OKLCH | |
| hex_to_oklch() { | |
| local original_hex="$1" | |
| local hex="$1" | |
| local alpha="" | |
| # Remove # if present | |
| hex=$(echo "$hex" | sed 's/^#//') | |
| # Handle 8-digit hex colors (extract alpha) | |
| if [ ${#hex} -eq 8 ]; then | |
| alpha="${hex:6:2}" | |
| hex="${hex:0:6}" | |
| fi | |
| # Ensure we have 6 characters for RGB | |
| if [ ${#hex} -ne 6 ]; then | |
| echo "Invalid hex color: $original_hex" >&2 | |
| return 1 | |
| fi | |
| # Extract RGB components (uppercase for bc) | |
| local r_hex=$(echo "${hex:0:2}" | tr '[:lower:]' '[:upper:]') | |
| local g_hex=$(echo "${hex:2:2}" | tr '[:lower:]' '[:upper:]') | |
| local b_hex=$(echo "${hex:4:2}" | tr '[:lower:]' '[:upper:]') | |
| # Convert to decimal | |
| local r_dec=$(hex_to_dec "$r_hex") | |
| local g_dec=$(hex_to_dec "$g_hex") | |
| local b_dec=$(hex_to_dec "$b_hex") | |
| # Convert to linear RGB | |
| local r_linear=$(srgb_to_linear "$r_dec") | |
| local g_linear=$(srgb_to_linear "$g_dec") | |
| local b_linear=$(srgb_to_linear "$b_dec") | |
| # Convert linear RGB to OKLab | |
| local l=$(echo "scale=10; 0.4122214708 * $r_linear + 0.5363325363 * $g_linear + 0.0514459929 * $b_linear" | bc -l) | |
| local m=$(echo "scale=10; 0.2119034982 * $r_linear + 0.6806995451 * $g_linear + 0.1073969566 * $b_linear" | bc -l) | |
| local s=$(echo "scale=10; 0.0883024619 * $r_linear + 0.2817188376 * $g_linear + 0.6299787005 * $b_linear" | bc -l) | |
| # Take cube roots | |
| local l_cbrt=$(cbrt "$l") | |
| local m_cbrt=$(cbrt "$m") | |
| local s_cbrt=$(cbrt "$s") | |
| # Convert to OKLab | |
| local L=$(echo "scale=10; 0.2104542553 * $l_cbrt + 0.7936177850 * $m_cbrt - 0.0040720468 * $s_cbrt" | bc -l) | |
| local a=$(echo "scale=10; 1.9779984951 * $l_cbrt - 2.4285922050 * $m_cbrt + 0.4505937099 * $s_cbrt" | bc -l) | |
| local b_lab=$(echo "scale=10; 0.0259040371 * $l_cbrt + 0.7827717662 * $m_cbrt - 0.8086757660 * $s_cbrt" | bc -l) | |
| # Convert OKLab to OKLCH | |
| local C=$(sqrt "$(echo "scale=10; $a * $a + $b_lab * $b_lab" | bc -l)") | |
| local H_rad=$(atan2 "$b_lab" "$a") | |
| local H=$(rad_to_deg "$H_rad") | |
| # Ensure H is positive | |
| if (($(echo "$H < 0" | bc -l))); then | |
| H=$(echo "scale=6; $H + 360" | bc -l) | |
| fi | |
| # Format output with proper precision | |
| local L_percent=$(echo "scale=1; $L * 100" | bc -l) | |
| local C_formatted=$(printf "%.4f" "$C") | |
| local H_formatted=$(printf "%.1f" "$H") | |
| # Remove trailing zeros and format | |
| L_percent=$(echo "$L_percent" | sed 's/\.0$//') | |
| H_formatted=$(echo "$H_formatted" | sed 's/\.0$//') | |
| # Handle alpha channel | |
| if [ -n "$alpha" ]; then | |
| local alpha_decimal=$(hex_to_dec "$(echo "$alpha" | tr '[:lower:]' '[:upper:]')") | |
| local alpha_percent=$(echo "scale=1; $alpha_decimal / 255 * 100" | bc -l) | |
| # Format alpha percentage properly | |
| alpha_percent=$(printf "%.1f" "$alpha_percent") | |
| alpha_percent=$(echo "$alpha_percent" | sed 's/\.0$//') | |
| echo "oklch(${L_percent}% ${C_formatted} ${H_formatted} / ${alpha_percent}%)" | |
| else | |
| echo "oklch(${L_percent}% ${C_formatted} ${H_formatted})" | |
| fi | |
| } | |
| # Create backup with timestamp and proper extension | |
| TIMESTAMP=$(date +"%Y%m%d_%H%M%S") | |
| BACKUP_FILE="${CSS_FILE%.css}_backup_${TIMESTAMP}.css" | |
| cp "$CSS_FILE" "$BACKUP_FILE" | |
| echo "Created backup: $BACKUP_FILE" | |
| # Process the file | |
| echo "Converting hex colors to OKLCH..." | |
| # Create a temporary file for processing | |
| temp_file=$(mktemp) | |
| # Process line by line with better hex color detection | |
| while IFS= read -r line || [ -n "$line" ]; do | |
| processed_line="$line" | |
| # Find hex colors using a more precise regex | |
| # Match #rrggbb or #rrggbbaa patterns | |
| if echo "$line" | grep -qE '#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?'; then | |
| # Extract all hex colors from the line | |
| hex_matches=$(echo "$line" | grep -oE '#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?' || true) | |
| for hex_color in $hex_matches; do | |
| if [ -n "$hex_color" ]; then | |
| oklch_value=$(hex_to_oklch "$hex_color" 2>/dev/null) | |
| if [ $? -eq 0 ] && [ -n "$oklch_value" ]; then | |
| # Use a more precise replacement to avoid partial matches | |
| processed_line=$(echo "$processed_line" | sed "s|${hex_color}|${oklch_value}|g") | |
| echo " Converted: $hex_color → $oklch_value" >&2 | |
| fi | |
| fi | |
| done | |
| fi | |
| echo "$processed_line" | |
| done <"$CSS_FILE" >"$temp_file" | |
| # Replace original file with processed version | |
| mv "$temp_file" "$CSS_FILE" | |
| echo "" | |
| echo "Conversion complete!" | |
| echo "Original file backed up as: $BACKUP_FILE" | |
| echo "Hex colors have been converted to OKLCH format in: $CSS_FILE" | |
| # Count how many conversions were made | |
| conversion_count=$(diff "$BACKUP_FILE" "$CSS_FILE" | grep -c "oklch(" || echo "0") | |
| echo "Total conversions made: $conversion_count" | |
| # Show a summary of changes | |
| echo "" | |
| echo "Summary of changes (first 10 lines):" | |
| if command -v diff &>/dev/null; then | |
| diff "$BACKUP_FILE" "$CSS_FILE" | grep "oklch(" | head -10 || echo "No changes detected" | |
| fi | |
| echo "" | |
| echo "To verify alpha conversion:" | |
| echo "3D hex = $(hex_to_dec 3D) decimal = $(echo "scale=1; $(hex_to_dec 3D) / 255 * 100" | bc -l)% opacity" | |
| echo "" | |
| echo "To view the backup file, use:" | |
| echo " cat $BACKUP_FILE" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment