Public/Convert-ColorValue.ps1
|
# Color conversion utility functions function Convert-HexToRGB { <# .SYNOPSIS Converts hex color codes to RGB values .DESCRIPTION Converts hex color codes (#RRGGBB or 0xRRGGBB) to RGB array .PARAMETER Hex The hex color code to convert .EXAMPLE Convert-HexToRGB "#FF8000" Returns: @(255, 128, 0) .OUTPUTS System.Array Returns a 3-element integer array @(R, G, B) with values 0-255. Invalid input returns gray @(128, 128, 128). .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later Accepts #RRGGBB, 0xRRGGBB and RRGGBB. An invalid code gives a warning and gray. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Write-ColorEX #> [CmdletBinding()] [Alias('CHR', 'Hex2RGB')] [OutputType([int])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'The array it returns reaches the pipeline as three Int32 values; the analyzer reads the array''s own type.')] param( [Parameter(Mandatory)] [string]$Hex ) # Remove # or 0x prefix $Hex = $Hex -replace '^#|^0x', '' # Validate hex string if ($Hex -notmatch '^[0-9A-Fa-f]{6}$') { Write-Warning "Invalid hex color format: $Hex. Expected format: #RRGGBB or RRGGBB" return @(128, 128, 128) # Default to gray } # Convert to RGB $r = [Convert]::ToInt32($Hex.Substring(0, 2), 16) $g = [Convert]::ToInt32($Hex.Substring(2, 2), 16) $b = [Convert]::ToInt32($Hex.Substring(4, 2), 16) return @($r, $g, $b) } function Convert-RGBToANSI8 { <# .SYNOPSIS Converts RGB values to the nearest ANSI 256-color code .DESCRIPTION Finds the 256-color code that looks nearest to an RGB color: the code at the least distance in OKLab, a color space whose distances follow how different two colors look. The codes are the 6x6x6 color cube (16-231) and the 24 grays (232-255); codes 0-15 are left out, since the terminal's scheme sets their colors. Of two codes as near, it returns the lower. .PARAMETER RGB Array of RGB values [R, G, B] .EXAMPLE Convert-RGBToANSI8 @(255, 128, 0) Returns: 208 (#FF8700, the nearest orange in the 256-color palette) .OUTPUTS System.Int32 Returns the nearest ANSI 256-color code, from the 6x6x6 color cube (16-231) or the 24 grays (232-255). .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later Values outside 0-255 are clamped. Windows PowerShell 5.1, PowerShell 7 and PWRSWriteColorEX return the same code for every color. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Convert-HexToRGB .LINK Write-ColorEX #> [CmdletBinding()] [Alias('CRA8', 'RGB2ANSI8')] [OutputType([int])] param( [Parameter(Mandatory)] [int[]]$RGB ) $r = [Math]::Max(0, [Math]::Min(255, $RGB[0])) $g = [Math]::Max(0, [Math]::Min(255, $RGB[1])) $b = [Math]::Max(0, [Math]::Min(255, $RGB[2])) return [ColorMath]::NearestAnsi8($r, $g, $b) } function Convert-RGBToANSI4 { <# .SYNOPSIS Converts RGB values to closest ANSI 4-bit color .DESCRIPTION Finds the closest ANSI 16-color match for RGB values .PARAMETER RGB Array of RGB values [R, G, B] .EXAMPLE Convert-RGBToANSI4 @(255, 128, 0) Returns: 93 (bright yellow in the 16-color palette) .OUTPUTS System.Int32 Returns an ANSI 4-bit foreground color code: - Normal colors: 30-37 (Black, Red, Green, Yellow, Blue, Magenta, Cyan, White) - Bright colors: 90-97 (Bright versions of above) .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later A color whose channels are within 30 of each other maps to black, dark gray, gray or white by its brightness. Any other color maps by its strongest channels, and to the bright variant when the strongest channel is 200 or more. Returns foreground codes; a background code is 10 more (40-47, 100-107). .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Convert-HexToRGB .LINK Write-ColorEX #> [CmdletBinding()] [Alias('CRA4', 'RGB2ANSI4')] [OutputType([int])] param( [Parameter(Mandatory)] [int[]]$RGB ) $r = $RGB[0] $g = $RGB[1] $b = $RGB[2] # Find dominant color channel $max = [Math]::Max($r, [Math]::Max($g, $b)) $min = [Math]::Min($r, [Math]::Min($g, $b)) # Calculate brightness (for grayscale detection) $brightness = ($r + $g + $b) / 3 # Determine if it's a bright or dark color based on max channel value # For saturated colors like (255, 0, 0), we want to detect as "bright" $isBright = $max -ge 200 # Check for grayscale if ($max - $min -lt 30) { if ($brightness -lt 64) { return 30 # Black } elseif ($brightness -lt 128) { return 90 # Dark Gray } elseif ($brightness -lt 192) { return 37 # Gray } else { return 97 # White } } # Determine color based on dominant channels if ($r -eq $max) { if ($g -gt $b + 30) { # Yellow range if ($isBright) { return 93 } else { return 33 } } elseif ($b -gt $g + 30) { # Magenta range if ($isBright) { return 95 } else { return 35 } } else { # Red if ($isBright) { return 91 } else { return 31 } } } elseif ($g -eq $max) { if ($r -gt $b + 30) { # Yellow range if ($isBright) { return 93 } else { return 33 } } elseif ($b -gt $r + 30) { # Cyan range if ($isBright) { return 96 } else { return 36 } } else { # Green if ($isBright) { return 92 } else { return 32 } } } else { # Blue is max if ($r -gt $g + 30) { # Magenta range if ($isBright) { return 95 } else { return 35 } } elseif ($g -gt $r + 30) { # Cyan range if ($isBright) { return 96 } else { return 36 } } else { # Blue if ($isBright) { return 94 } else { return 34 } } } } function Get-ColorTableWithRGB { <# .SYNOPSIS Returns the complete color table with RGB values .DESCRIPTION Returns a hashtable of 129 color names in 44 families, most with Dark, normal and Light variants, and the names Register-ColorName added. Each entry holds the color in every color mode. ENTRY FORMAT: @(Native, ANSI4FG, ANSI4BG, ANSI8, @(R,G,B)) - Native: PowerShell ConsoleColor name - ANSI4FG: 4-bit foreground code (30-37, 90-97) - ANSI4BG: 4-bit background code (40-47, 100-107) - ANSI8: 8-bit color code (0-255) - RGB: RGB array @(R, G, B) COLOR FAMILIES INCLUDED: Neutrals, Red, Green, Blue, Yellow, Cyan, Magenta, Orange, Purple, Pink, Brown, Teal, Violet, Lime, Slate, Gold, Sky, Coral, Olive, Lavender, Mint, Salmon, Indigo, Turquoise, Ruby, Jade, Amber, Steel, Crimson, Emerald, Sapphire, and more. .EXAMPLE $colors = Get-ColorTableWithRGB $orange = $colors['Orange'] # $orange[0] = 'DarkYellow' (Native PowerShell) # $orange[1] = 33 (ANSI 4-bit FG) # $orange[2] = 43 (ANSI 4-bit BG) # $orange[3] = 214 (ANSI 8-bit) # $orange[4] = @(255,165,0) (RGB) .EXAMPLE $table = Get-ColorTableWithRGB $table.Keys | Sort-Object # Lists the 129 color names .OUTPUTS System.Collections.Hashtable Returns a hashtable where: - Keys: Color names (strings) - Values: 5-element arrays with Native, ANSI4FG, ANSI4BG, ANSI8, RGB data .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later Write-ColorEX builds this table on first use and keeps it for the session. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Write-ColorEX .LINK Convert-RGBToANSI8 .LINK Convert-RGBToANSI4 #> [CmdletBinding()] [Alias('GCT', 'Get-ColorTable', 'Get-ColourTable')] [OutputType([hashtable])] param() $table = @{ # Neutral family Black = @('Black', 30, 40, 0, @(0, 0, 0)) LightBlack = @('DarkGray', 90, 100, 243, @(118, 118, 118)) DarkGray = @('DarkGray', 90, 100, 8, @(128, 128, 128)) Gray = @('Gray', 37, 47, 7, @(192, 192, 192)) LightGray = @('Gray', 37, 47, 255, @(238, 238, 238)) White = @('White', 97, 107, 15, @(255, 255, 255)) # Red family DarkRed = @('DarkRed', 31, 41, 88, @(139, 0, 0)) Red = @('Red', 31, 41, 1, @(255, 0, 0)) LightRed = @('Red', 91, 101, 9, @(255, 85, 85)) # Green family DarkGreen = @('DarkGreen', 32, 42, 22, @(0, 100, 0)) Green = @('Green', 32, 42, 2, @(0, 255, 0)) LightGreen = @('Green', 92, 102, 10, @(85, 255, 85)) # Yellow family DarkYellow = @('DarkYellow', 33, 43, 184, @(204, 204, 0)) Yellow = @('Yellow', 33, 43, 226, @(255, 255, 0)) LightYellow = @('Yellow', 93, 103, 11, @(255, 255, 85)) # Blue family DarkBlue = @('DarkBlue', 34, 44, 18, @(0, 0, 139)) Blue = @('Blue', 34, 44, 4, @(0, 0, 255)) LightBlue = @('Blue', 94, 104, 12, @(85, 85, 255)) # Magenta family DarkMagenta = @('DarkMagenta', 35, 45, 90, @(139, 0, 139)) Magenta = @('Magenta', 35, 45, 5, @(255, 0, 255)) LightMagenta = @('Magenta', 95, 105, 13, @(255, 85, 255)) # Cyan family DarkCyan = @('DarkCyan', 36, 46, 30, @(0, 139, 139)) Cyan = @('Cyan', 36, 46, 6, @(0, 255, 255)) LightCyan = @('Cyan', 96, 106, 14, @(85, 255, 255)) # Orange family DarkOrange = @('DarkYellow', 33, 43, 208, @(255, 140, 0)) Orange = @('DarkYellow', 33, 43, 214, @(255, 165, 0)) LightOrange = @('Yellow', 33, 43, 214, @(255, 195, 0)) # Purple family DarkPurple = @('DarkMagenta', 35, 45, 54, @(75, 0, 130)) Purple = @('DarkMagenta', 35, 45, 90, @(128, 0, 128)) LightPurple = @('Magenta', 35, 45, 98, @(147, 112, 219)) # Pink family DarkPink = @('DarkMagenta', 35, 45, 162, @(199, 21, 133)) Pink = @('Magenta', 35, 45, 217, @(255, 192, 203)) LightPink = @('Magenta', 95, 105, 217, @(255, 182, 193)) # Brown family DarkBrown = @('DarkRed', 31, 41, 239, @(101, 67, 33)) Brown = @('DarkRed', 31, 41, 130, @(150, 75, 0)) LightBrown = @('DarkYellow', 33, 43, 172, @(205, 133, 63)) # Teal family DarkTeal = @('DarkCyan', 36, 46, 30, @(0, 128, 128)) Teal = @('DarkCyan', 36, 46, 30, @(0, 150, 150)) LightTeal = @('Cyan', 36, 46, 44, @(64, 224, 208)) # Violet family DarkViolet = @('DarkMagenta', 35, 45, 92, @(148, 0, 211)) Violet = @('Magenta', 35, 45, 213, @(238, 130, 238)) LightViolet = @('Magenta', 95, 105, 182, @(200, 162, 200)) # Lime family DarkLime = @('DarkGreen', 32, 42, 41, @(50, 205, 50)) Lime = @('Green', 32, 42, 46, @(0, 255, 0)) LightLime = @('Green', 92, 102, 46, @(50, 255, 50)) # Slate family DarkSlate = @('DarkGray', 90, 100, 238, @(47, 79, 79)) Slate = @('Gray', 37, 47, 244, @(112, 128, 144)) LightSlate = @('Gray', 37, 47, 102, @(119, 136, 153)) # Gold family DarkGold = @('DarkYellow', 33, 43, 136, @(184, 134, 11)) Gold = @('Yellow', 33, 43, 220, @(255, 215, 0)) LightGold = @('Yellow', 93, 103, 220, @(255, 223, 0)) # Sky family DarkSky = @('DarkBlue', 34, 44, 39, @(0, 191, 255)) Sky = @('Blue', 34, 44, 117, @(135, 206, 235)) LightSky = @('Cyan', 36, 46, 117, @(135, 206, 250)) # Coral family DarkCoral = @('DarkRed', 31, 41, 167, @(205, 91, 69)) Coral = @('Red', 31, 41, 209, @(255, 127, 80)) LightCoral = @('Red', 91, 101, 210, @(240, 128, 128)) # Olive family DarkOlive = @('DarkGreen', 32, 42, 58, @(85, 107, 47)) Olive = @('DarkYellow', 33, 43, 100, @(128, 128, 0)) LightOlive = @('DarkYellow', 33, 43, 142, @(170, 170, 0)) # Lavender family DarkLavender = @('DarkMagenta', 35, 45, 60, @(100, 100, 150)) Lavender = @('Magenta', 35, 45, 254, @(230, 230, 250)) LightLavender = @('Magenta', 95, 105, 255, @(240, 240, 255)) # Mint family DarkMint = @('DarkGreen', 32, 42, 71, @(60, 179, 113)) Mint = @('Green', 32, 42, 121, @(152, 251, 152)) LightMint = @('Green', 92, 102, 158, @(189, 252, 201)) # Salmon family DarkSalmon = @('DarkRed', 31, 41, 173, @(233, 150, 122)) Salmon = @('Red', 31, 41, 210, @(250, 128, 114)) LightSalmon = @('Red', 91, 101, 216, @(255, 160, 122)) # Indigo family DarkIndigo = @('DarkBlue', 34, 44, 17, @(25, 25, 112)) Indigo = @('DarkMagenta', 35, 45, 54, @(75, 0, 130)) LightIndigo = @('Blue', 34, 44, 60, @(102, 102, 153)) # Turquoise family DarkTurquoise = @('DarkCyan', 36, 46, 44, @(0, 206, 209)) Turquoise = @('Cyan', 36, 46, 44, @(64, 224, 208)) LightTurquoise = @('Cyan', 96, 106, 159, @(175, 238, 238)) # Ruby family DarkRuby = @('DarkRed', 31, 41, 88, @(155, 17, 30)) Ruby = @('Red', 31, 41, 161, @(224, 17, 95)) LightRuby = @('Red', 91, 101, 204, @(255, 102, 153)) # Jade family DarkJade = @('DarkGreen', 32, 42, 22, @(0, 100, 50)) Jade = @('DarkGreen', 32, 42, 35, @(0, 168, 107)) LightJade = @('Green', 32, 42, 78, @(64, 216, 143)) # Amber family DarkAmber = @('DarkYellow', 33, 43, 214, @(255, 160, 0)) Amber = @('Yellow', 33, 43, 214, @(255, 191, 0)) LightAmber = @('Yellow', 93, 103, 220, @(255, 204, 0)) # Steel family DarkSteel = @('DarkGray', 90, 100, 238, @(70, 70, 70)) Steel = @('Gray', 37, 47, 243, @(113, 121, 126)) LightSteel = @('White', 97, 47, 146, @(176, 196, 222)) # Crimson family DarkCrimson = @('DarkRed', 31, 41, 88, @(139, 0, 0)) Crimson = @('Red', 31, 41, 160, @(220, 20, 60)) LightCrimson = @('Red', 91, 101, 197, @(248, 48, 88)) # Emerald family DarkEmerald = @('DarkGreen', 32, 42, 22, @(0, 100, 0)) Emerald = @('Green', 32, 42, 78, @(80, 200, 120)) LightEmerald = @('Green', 92, 102, 121, @(128, 255, 170)) # Sapphire family DarkSapphire = @('DarkBlue', 34, 44, 17, @(8, 37, 103)) Sapphire = @('Blue', 34, 44, 25, @(15, 82, 186)) LightSapphire = @('Blue', 94, 104, 68, @(100, 149, 237)) # Wine family DarkWine = @('DarkRed', 31, 41, 52, @(72, 0, 25)) Wine = @('DarkRed', 31, 41, 52, @(114, 47, 55)) LightWine = @('Red', 31, 41, 131, @(179, 97, 115)) # Additional color families # Peach family DarkPeach = @('DarkYellow', 33, 43, 215, @(255, 164, 96)) Peach = @('Yellow', 33, 43, 223, @(255, 218, 185)) LightPeach = @('Yellow', 93, 103, 230, @(255, 239, 213)) # Navy family DarkNavy = @('DarkBlue', 34, 44, 17, @(0, 0, 80)) Navy = @('DarkBlue', 34, 44, 18, @(0, 0, 128)) LightNavy = @('Blue', 34, 44, 20, @(0, 0, 205)) # Forest family DarkForest = @('DarkGreen', 32, 42, 22, @(34, 75, 34)) Forest = @('DarkGreen', 32, 42, 28, @(34, 139, 34)) LightForest = @('Green', 32, 42, 41, @(50, 205, 50)) # Rose family DarkRose = @('DarkMagenta', 35, 45, 89, @(128, 0, 64)) Rose = @('Magenta', 35, 45, 198, @(255, 0, 127)) LightRose = @('Magenta', 95, 105, 217, @(255, 182, 193)) # Plum family DarkPlum = @('DarkMagenta', 35, 45, 54, @(102, 51, 153)) Plum = @('Magenta', 35, 45, 182, @(221, 160, 221)) LightPlum = @('Magenta', 95, 105, 219, @(238, 174, 238)) # Tan family DarkTan = @('DarkYellow', 33, 43, 94, @(139, 90, 43)) Tan = @('Yellow', 33, 43, 180, @(210, 180, 140)) LightTan = @('Yellow', 93, 103, 223, @(245, 222, 179)) # Maroon family DarkMaroon = @('DarkRed', 31, 41, 52, @(69, 0, 0)) Maroon = @('DarkRed', 31, 41, 88, @(128, 0, 0)) LightMaroon = @('Red', 31, 41, 125, @(176, 48, 96)) # Aqua family (alternative cyan) DarkAqua = @('DarkCyan', 36, 46, 30, @(0, 128, 128)) Aqua = @('Cyan', 36, 46, 6, @(0, 255, 255)) LightAqua = @('Cyan', 96, 106, 14, @(127, 255, 255)) # Chartreuse family DarkChartreuse = @('DarkGreen', 32, 42, 64, @(69, 139, 0)) Chartreuse = @('Green', 32, 42, 118, @(127, 255, 0)) LightChartreuse = @('Green', 92, 102, 156, @(191, 255, 127)) # Brick family DarkBrick = @('DarkRed', 31, 41, 88, @(139, 26, 26)) Brick = @('DarkRed', 31, 41, 124, @(178, 34, 34)) LightBrick = @('Red', 31, 41, 167, @(205, 92, 92)) } # The names Register-ColorName added, over a built-in name of the same spelling foreach ($name in $script:CustomColors.Keys) { $table[$name] = $script:CustomColors[$name] } return $table } function Get-LighterRGBColor { <# .SYNOPSIS Returns a lighter version of an RGB color .DESCRIPTION Multiplies each channel by a factor, with a floor so black becomes dark gray. Write-ColorEX uses it for -Bold in terminals that show bold as brighter colors rather than a bold font. .PARAMETER RGB Array of RGB values [R, G, B] .PARAMETER Factor Lightening factor (default 1.4 for 40% lighter) .EXAMPLE Get-LighterRGBColor @(139, 0, 0) Returns: @(195, 102, 102) .EXAMPLE Get-LighterRGBColor @(50, 50, 50) -Factor 2.0 Returns: @(255, 255, 255) .OUTPUTS System.Array Returns a 3-element integer array @(R, G, B) with lightened values, clamped to 0-255 range. .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later Each channel becomes the channel times the factor, at least 255 times (factor - 1) and at most 255. Lighten-RGBColor is an alias of this function. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Write-ColorEX .LINK Test-AnsiSupport #> [CmdletBinding()] [Alias('Lighten-RGBColor')] [OutputType([int])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'The array it returns reaches the pipeline as three Int32 values; the analyzer reads the array''s own type.')] param( [Parameter(Mandatory)] [int[]]$RGB, [double]$Factor = 1.4 ) # The floor each channel is raised to, so black becomes dark gray rather than staying black $minLighten = [Math]::Round(255 * ($Factor - 1.0)) $r = [Math]::Min(255, [Math]::Max($minLighten, [Math]::Round($RGB[0] * $Factor))) $g = [Math]::Min(255, [Math]::Max($minLighten, [Math]::Round($RGB[1] * $Factor))) $b = [Math]::Min(255, [Math]::Max($minLighten, [Math]::Round($RGB[2] * $Factor))) return @([int]$r, [int]$g, [int]$b) } function Get-LighterANSI8Color { <# .SYNOPSIS Returns a lighter ANSI 8-bit (256-color) code .DESCRIPTION Write-ColorEX uses it for -Bold in ANSI8 mode in terminals that show bold as brighter colors rather than a bold font. It handles each part of the 256-color table: the standard colors (0-15), the RGB cube (16-231) and the gray ramp (232-255). .PARAMETER ANSI8Code ANSI 256-color code to lighten (0-255) .PARAMETER Factor Lightening factor (default 1.4 for 40% lighter, matching TrueColor behavior) .EXAMPLE Get-LighterANSI8Color 196 Returns: 203 # 196 is red in the RGB cube; its RGB value is lightened and mapped back to the cube .EXAMPLE Get-LighterANSI8Color 240 -Factor 1.4 Returns: 244 # The gray ramp moves up four steps .EXAMPLE Get-LighterANSI8Color 4 Returns: 12 # A dark standard color (0-7) becomes its bright form (8-15) .OUTPUTS System.Int32 Returns an integer between 0-255 representing the lightened ANSI 256-color code. Processing varies by input range: - Standard colors (0-15): Maps dark (0-7) to bright (8-15), or converts to RGB cube - RGB cube (16-231): Converts to RGB, lightens mathematically, converts back - Grayscale ramp (232-255): Increments by ~25% brightness steps .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later The RGB cube and the bright standard colors are lightened as Get-LighterRGBColor lightens RGB, with the same factor, and mapped back to the nearest 256-color code. Lighten-ANSI8Color, Lighten-ANSI8 and LA8 are aliases of this function. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Lighten-RGBColor .LINK Convert-RGBToANSI8 .LINK Write-ColorEX #> [CmdletBinding()] [Alias('LA8', 'Lighten-ANSI8', 'Lighten-ANSI8Color')] [OutputType([int])] param( [Parameter(Mandatory)] [ValidateRange(0, 255)] [int]$ANSI8Code, [double]$Factor = 1.4 ) # Standard colors (0-15): Map to brighter variants or RGB cube if ($ANSI8Code -le 15) { # Map dark colors (0-7) to bright variants (8-15) if ($ANSI8Code -le 7) { return $ANSI8Code + 8 } # Already bright (8-15), convert to RGB, lighten, and convert back # Standard ANSI color RGB mappings $standardRGB = @( @(0, 0, 0), # 0: Black @(128, 0, 0), # 1: Dark Red (Maroon) @(0, 128, 0), # 2: Dark Green @(128, 128, 0), # 3: Dark Yellow (Olive) @(0, 0, 128), # 4: Dark Blue (Navy) @(128, 0, 128), # 5: Dark Magenta (Purple) @(0, 128, 128), # 6: Dark Cyan (Teal) @(192, 192, 192), # 7: Light Gray @(128, 128, 128), # 8: Dark Gray @(255, 0, 0), # 9: Red @(0, 255, 0), # 10: Green @(255, 255, 0), # 11: Yellow @(0, 0, 255), # 12: Blue @(255, 0, 255), # 13: Magenta @(0, 255, 255), # 14: Cyan @(255, 255, 255) # 15: White ) $rgb = $standardRGB[$ANSI8Code] $lightenedRGB = Get-LighterRGBColor -RGB $rgb -Factor $Factor return Convert-RGBToANSI8 -RGB $lightenedRGB } # Grayscale ramp (232-255): Increment by steps if ($ANSI8Code -ge 232) { $grayLevel = $ANSI8Code - 232 # 0-23 # Add approximately 25% brightness (~6 steps out of 24) $increment = [Math]::Max(1, [Math]::Round(($Factor - 1.0) * 10)) $newLevel = [Math]::Min(23, $grayLevel + $increment) return 232 + $newLevel } # RGB cube (16-231): Convert to RGB, lighten, convert back # ANSI8 color = 16 + (36 × r) + (6 × g) + b # where r,g,b are 0-5 (mapped to 0,95,135,175,215,255 in RGB) $index = $ANSI8Code - 16 $r6 = [Math]::Floor($index / 36) $g6 = [Math]::Floor(($index % 36) / 6) $b6 = $index % 6 # Convert 0-5 to RGB 0-255 $rgbLevels = @(0, 95, 135, 175, 215, 255) $rgb = @( $rgbLevels[$r6], $rgbLevels[$g6], $rgbLevels[$b6] ) $lightenedRGB = Get-LighterRGBColor -RGB $rgb -Factor $Factor # Convert back to ANSI8 return Convert-RGBToANSI8 -RGB $lightenedRGB } function Get-LighterColorName { <# .SYNOPSIS Returns the next lighter color name in the color table .DESCRIPTION Shifts a color name one step lighter within its family: DarkRed to Red, Red to LightRed. A name with no lighter name in the color table, such as White or LightRed, is returned unchanged. Write-ColorEX uses it for -Bold in terminals that show bold as brighter colors rather than a bold font. .PARAMETER ColorName Color name to lighten (e.g., "DarkRed", "Red") .EXAMPLE Get-LighterColorName "DarkRed" Returns: "Red" .EXAMPLE Get-LighterColorName "Red" Returns: "LightRed" .EXAMPLE Get-LighterColorName "White" Returns: "White" (the table has no LightWhite) .OUTPUTS System.String Returns the lighter color name: - Dark* without "Dark", when that name is in the table (DarkRed to Red) - Light* in front of a name, when that name is in the table (Red to LightRed) - The name unchanged otherwise .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later Lighten-ColorName is an alias of this function. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Get-LighterRGBColor .LINK Write-ColorEX .LINK Get-ColorTableWithRGB #> [CmdletBinding()] [Alias('Lighten-ColorName')] [OutputType([string])] param( [Parameter(Mandatory)] [string]$ColorName ) if ($null -eq $script:CachedColorTable) { $script:CachedColorTable = Get-ColorTableWithRGB } $table = $script:CachedColorTable if ($ColorName -like 'Light*') { return $ColorName } if ($ColorName -like 'Dark*') { $lighter = $ColorName -replace '^Dark', '' } else { $lighter = "Light$ColorName" } if ($table.ContainsKey($lighter)) { return $lighter } return $ColorName } function Get-ColorContrast { <# .SYNOPSIS Returns the contrast ratio of two colors .DESCRIPTION Returns the contrast ratio WCAG 2 defines for text in one color on a background in another: (L1 + 0.05) / (L2 + 0.05), L1 being the relative luminance of the lighter color and L2 of the darker. It runs from 1, for two colors of the same luminance, to 21 for black and white, and is the same either way round. WCAG asks for at least 4.5 for body text and 3 for large text. .PARAMETER Color The text color: a hex code ('#FF8000', '#F80'), 'rgb(255, 128, 0)', 'hsl(30, 100%, 50%)', an RGB array @(255, 128, 0), or a name in the color table. .PARAMETER BackGroundColor The background color, in the same forms as -Color. .EXAMPLE Get-ColorContrast -Color White -BackGroundColor '#1E1E2E' The contrast of white text on a dark blue-gray background, about 16.4. .EXAMPLE if ((Get-ColorContrast Gold $background) -lt 4.5) { $text = 'Black' } Picks black text where gold would be too faint on the background. .OUTPUTS System.Double The contrast ratio, from 1 to 21. .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later Windows PowerShell 5.1, PowerShell 7 and PWRSWriteColorEX return the same ratio to the last bit. A value that is not a color gives an error and no ratio. Get-ColourContrast is an alias of this function. .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Register-ColorName .LINK Write-ColorEX #> [CmdletBinding()] [Alias('Get-ColourContrast')] [OutputType([double])] param( [Parameter(Mandatory, Position = 0)] [Alias('C', 'ForegroundColor', 'FGC')] [object]$Color, [Parameter(Mandatory, Position = 1)] [Alias('B', 'BGC')] [object]$BackGroundColor ) $table = Get-ColorTableWithRGB $foreground = Get-ColorRegisterRgb -Value $Color -Table $table $background = Get-ColorRegisterRgb -Value $BackGroundColor -Table $table $readable = $true foreach ($given in @(@{ Value = $Color; Rgb = $foreground }, @{ Value = $BackGroundColor; Rgb = $background })) { if ($null -eq $given.Rgb) { $shown = if ($given.Value -is [array]) { "@($($given.Value -join ', '))" } else { "$($given.Value)" } $PSCmdlet.WriteError([System.Management.Automation.ErrorRecord]::new( [System.ArgumentException]::new("'$shown' is not a color. A color is a hex code, rgb(), hsl(), an RGB array of three numbers 0-255, or a name in the color table."), 'InvalidColor', [System.Management.Automation.ErrorCategory]::InvalidArgument, $given.Value)) $readable = $false } } if ($readable) { [ColorMath]::ContrastRatio($foreground, $background) } } |