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: MarkusMcNugen
    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')]
    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 closest ANSI 8-bit color
     
    .DESCRIPTION
    Finds the closest ANSI 256-color palette match for RGB values
     
    .PARAMETER RGB
    Array of RGB values [R, G, B]
     
    .EXAMPLE
    Convert-RGBToANSI8 @(255, 128, 0)
    Returns: 208 (closest orange in 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: MarkusMcNugen
    License: MIT
    Requires: PowerShell 5.1 or later
 
    A color whose channels are within 10 of each other maps to the grays, or to 16 (black) and
    231 (white) at the ends. Any other color maps to the color cube. Values outside 0-255 are
    clamped.
 
    .LINK
    https://github.com/MarkusMcNugen/PSWriteColorEX
 
    .LINK
    Convert-HexToRGB
 
    .LINK
    Write-ColorEX
    #>

    [CmdletBinding()]
    [Alias('CRA8', 'RGB2ANSI8')]
    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]))

    if ([Math]::Abs($r - $g) -lt 10 -and [Math]::Abs($g - $b) -lt 10) {
        # The 24 grays, 232-255
        $gray = [Math]::Round(($r + $g + $b) / 3)
        if ($gray -lt 8) {
            return 16  # Black
        } elseif ($gray -gt 248) {
            return 231  # White
        } else {
            # Map to 24 grayscale colors (232-255)
            $grayIndex = [Math]::Round(($gray - 8) / 10)
            return 232 + [Math]::Min(23, $grayIndex)
        }
    }
    
    # The 6x6x6 color cube, 16-231: each channel's step, 0 to 5
    if ($null -eq $script:RGB6LevelLookup) {
        # The module fills the lookup table at import; this runs without it
        $r6 = if ($r -lt 48) { 0 } elseif ($r -lt 115) { 1 } elseif ($r -lt 155) { 2 } elseif ($r -lt 195) { 3 } elseif ($r -lt 235) { 4 } else { 5 }
        $g6 = if ($g -lt 48) { 0 } elseif ($g -lt 115) { 1 } elseif ($g -lt 155) { 2 } elseif ($g -lt 195) { 3 } elseif ($g -lt 235) { 4 } else { 5 }
        $b6 = if ($b -lt 48) { 0 } elseif ($b -lt 115) { 1 } elseif ($b -lt 155) { 2 } elseif ($b -lt 195) { 3 } elseif ($b -lt 235) { 4 } else { 5 }
    } else {
        $r6 = $script:RGB6LevelLookup[$r]
        $g6 = $script:RGB6LevelLookup[$g]
        $b6 = $script:RGB6LevelLookup[$b]
    }

    return 16 + (36 * $r6) + (6 * $g6) + $b6
}

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: MarkusMcNugen
    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')]
    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. 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] = 208 (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: MarkusMcNugen
    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')]
    param()
    
    return @{
        # Neutral family
        Black = @('Black', 30, 40, 0, @(0, 0, 0))
        LightBlack = @('DarkGray', 90, 100, 238, @(118, 118, 118))
        DarkGray = @('DarkGray', 90, 100, 8, @(128, 128, 128))
        Gray = @('Gray', 37, 47, 7, @(192, 192, 192))
        LightGray = @('Gray', 37, 47, 253, @(238, 238, 238))
        White = @('White', 97, 107, 15, @(255, 255, 255))
        
        # Red family
        DarkRed = @('DarkRed', 31, 41, 52, @(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, 28, @(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, 136, @(204, 204, 0))
        Yellow = @('Yellow', 33, 43, 220, @(255, 255, 0))
        LightYellow = @('Yellow', 93, 103, 11, @(255, 255, 85))
        
        # Blue family
        DarkBlue = @('DarkBlue', 34, 44, 19, @(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, 53, @(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, 166, @(255, 140, 0))
        Orange = @('DarkYellow', 33, 43, 208, @(255, 165, 0))
        LightOrange = @('Yellow', 33, 43, 215, @(255, 195, 0))
        
        # Purple family
        DarkPurple = @('DarkMagenta', 35, 45, 54, @(75, 0, 130))
        Purple = @('DarkMagenta', 35, 45, 93, @(128, 0, 128))
        LightPurple = @('Magenta', 35, 45, 135, @(147, 112, 219))
        
        # Pink family
        DarkPink = @('DarkMagenta', 35, 45, 163, @(199, 21, 133))
        Pink = @('Magenta', 35, 45, 205, @(255, 192, 203))
        LightPink = @('Magenta', 95, 105, 218, @(255, 182, 193))
        
        # Brown family
        DarkBrown = @('DarkRed', 31, 41, 88, @(101, 67, 33))
        Brown = @('DarkRed', 31, 41, 130, @(150, 75, 0))
        LightBrown = @('DarkYellow', 33, 43, 173, @(205, 133, 63))
        
        # Teal family
        DarkTeal = @('DarkCyan', 36, 46, 23, @(0, 128, 128))
        Teal = @('DarkCyan', 36, 46, 30, @(0, 150, 150))
        LightTeal = @('Cyan', 36, 46, 80, @(64, 224, 208))
        
        # Violet family
        DarkViolet = @('DarkMagenta', 35, 45, 128, @(148, 0, 211))
        Violet = @('Magenta', 35, 45, 134, @(238, 130, 238))
        LightViolet = @('Magenta', 95, 105, 177, @(200, 162, 200))
        
        # Lime family
        DarkLime = @('DarkGreen', 32, 42, 34, @(50, 205, 50))
        Lime = @('Green', 32, 42, 118, @(0, 255, 0))
        LightLime = @('Green', 92, 102, 119, @(50, 255, 50))
        
        # Slate family
        DarkSlate = @('DarkGray', 90, 100, 238, @(47, 79, 79))
        Slate = @('Gray', 37, 47, 102, @(112, 128, 144))
        LightSlate = @('Gray', 37, 47, 103, @(119, 136, 153))
        
        # Gold family
        DarkGold = @('DarkYellow', 33, 43, 136, @(184, 134, 11))
        Gold = @('Yellow', 33, 43, 178, @(255, 215, 0))
        LightGold = @('Yellow', 93, 103, 185, @(255, 223, 0))
        
        # Sky family
        DarkSky = @('DarkBlue', 34, 44, 24, @(0, 191, 255))
        Sky = @('Blue', 34, 44, 111, @(135, 206, 235))
        LightSky = @('Cyan', 36, 46, 152, @(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, 107, @(170, 170, 0))
        
        # Lavender family
        DarkLavender = @('DarkMagenta', 35, 45, 97, @(100, 100, 150))
        Lavender = @('Magenta', 35, 45, 183, @(230, 230, 250))
        LightLavender = @('Magenta', 95, 105, 189, @(240, 240, 255))
        
        # Mint family
        DarkMint = @('DarkGreen', 32, 42, 29, @(60, 179, 113))
        Mint = @('Green', 32, 42, 121, @(152, 251, 152))
        LightMint = @('Green', 92, 102, 157, @(189, 252, 201))
        
        # Salmon family
        DarkSalmon = @('DarkRed', 31, 41, 173, @(233, 150, 122))
        Salmon = @('Red', 31, 41, 174, @(250, 128, 114))
        LightSalmon = @('Red', 91, 101, 175, @(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, 61, @(102, 102, 153))
        
        # Turquoise family
        DarkTurquoise = @('DarkCyan', 36, 46, 31, @(0, 206, 209))
        Turquoise = @('Cyan', 36, 46, 43, @(64, 224, 208))
        LightTurquoise = @('Cyan', 96, 106, 86, @(175, 238, 238))
        
        # Ruby family
        DarkRuby = @('DarkRed', 31, 41, 52, @(155, 17, 30))
        Ruby = @('Red', 31, 41, 124, @(224, 17, 95))
        LightRuby = @('Red', 91, 101, 161, @(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, 79, @(64, 216, 143))
        
        # Amber family
        DarkAmber = @('DarkYellow', 33, 43, 130, @(255, 160, 0))
        Amber = @('Yellow', 33, 43, 214, @(255, 191, 0))
        LightAmber = @('Yellow', 93, 103, 221, @(255, 204, 0))
        
        # Steel family
        DarkSteel = @('DarkGray', 90, 100, 60, @(70, 70, 70))
        Steel = @('Gray', 37, 47, 66, @(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, 161, @(248, 48, 88))
        
        # Emerald family
        DarkEmerald = @('DarkGreen', 32, 42, 22, @(0, 100, 0))
        Emerald = @('Green', 32, 42, 36, @(80, 200, 120))
        LightEmerald = @('Green', 92, 102, 85, @(128, 255, 170))
        
        # Sapphire family
        DarkSapphire = @('DarkBlue', 34, 44, 18, @(8, 37, 103))
        Sapphire = @('Blue', 34, 44, 25, @(15, 82, 186))
        LightSapphire = @('Blue', 94, 104, 69, @(100, 149, 237))
        
        # Wine family
        DarkWine = @('DarkRed', 31, 41, 52, @(72, 0, 25))
        Wine = @('DarkRed', 31, 41, 88, @(114, 47, 55))
        LightWine = @('Red', 31, 41, 125, @(179, 97, 115))
        
        # Additional color families
        # Peach family
        DarkPeach = @('DarkYellow', 33, 43, 172, @(255, 164, 96))
        Peach = @('Yellow', 33, 43, 216, @(255, 218, 185))
        LightPeach = @('Yellow', 93, 103, 223, @(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, 24, @(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, 34, @(50, 205, 50))
        
        # Rose family
        DarkRose = @('DarkMagenta', 35, 45, 125, @(128, 0, 64))
        Rose = @('Magenta', 35, 45, 168, @(255, 0, 127))
        LightRose = @('Magenta', 95, 105, 211, @(255, 182, 193))
        
        # Plum family
        DarkPlum = @('DarkMagenta', 35, 45, 89, @(102, 51, 153))
        Plum = @('Magenta', 35, 45, 133, @(221, 160, 221))
        LightPlum = @('Magenta', 95, 105, 176, @(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, 187, @(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, 124, @(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, 154, @(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))
    }
}

function Get-LighterRGBColor {
    <#
    .SYNOPSIS
    Answers 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: MarkusMcNugen
    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')]
    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
    Answers 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: MarkusMcNugen
    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')]
    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
    Answers 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: MarkusMcNugen
    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')]
    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
}