Private/New-GradientColorArray.ps1

function New-GradientColorArray {
    <#
    .SYNOPSIS
    Generates a gradient color array for character-by-character coloring
 
    .DESCRIPTION
    Interpolates colors evenly between 2 or more waypoint colors, one color per step,
    as RGB arrays (TrueColor) or ANSI 256-color codes (ANSI8).
 
    .PARAMETER Colors
    Array of gradient waypoints (minimum 2 colors).
    Accepts color names, hex codes, or RGB arrays.
 
    .PARAMETER Places
    Each waypoint's place from 0 to 1, or $null for one with none, as Split-GradientPlace returns
    them; $null for none placed.
 
    .PARAMETER Steps
    Number of colors to generate (typically total character count)
 
    .PARAMETER Mode
    Color mode: 'TrueColor' returns RGB arrays, 'ANSI8' returns ANSI 256-color codes
 
    .PARAMETER Space
    The space colors are blended in, as Write-ColorEX's -GradientSpace names them.
 
    .PARAMETER Hue
    The way round the hue wheel, as Write-ColorEX's -GradientHue names it.
 
    .PARAMETER Easing
    The easing curve, as Write-ColorEX's -GradientEasing names it.
 
    .PARAMETER Repeat
    What the gradient does past its first and last waypoints, as Write-ColorEX's -GradientRepeat
    names it.
 
    .PARAMETER Direction
    The way across the steps, as Write-ColorEX's -GradientDirection names it.
 
    .PARAMETER Bands
    The bands of solid color, as Write-ColorEX's -GradientSteps gives them; 0 for a smooth blend.
 
    .EXAMPLE
    New-GradientColorArray -Colors @("Red","Blue") -Steps 10 -Mode TrueColor
    Returns 10 RGB arrays interpolated from Red to Blue
 
    .EXAMPLE
    New-GradientColorArray -Colors @("#FF0000","#00FF00","#0000FF") -Steps 20 -Mode ANSI8
    Returns 20 ANSI8 codes for a red-green-blue gradient
 
    .OUTPUTS
    System.Array
    Returns an array of color values matching the Steps count:
    - TrueColor mode: Array of RGB arrays @(R,G,B)
    - ANSI8 mode: Array of ANSI 256-color codes (integers 0-255)
 
    .NOTES
    Author: Mark Newton
    License: MIT
    Requires: PowerShell 5.1 or later
 
    Write-ColorEX calls this private function for -Gradient and -BackGroundGradient, and
    Get-GradientColor for -Count. With at least as many steps as waypoints, each waypoint moves to
    the step nearest its place, a half rounded up, so every waypoint is drawn; with fewer, the
    steps are spread over the whole gradient. Each other step's color is blended between the two
    waypoints around it in the space, and in ANSI8 is the 256-color code Convert-RGBToANSI8 returns
    for it. A color name not in the color table and an invalid hex code are gray. The result of
    each set of waypoints, places, options, steps and mode is kept for the session, so a gradient
    written again costs a lookup.
 
    .LINK
    https://github.com/MarkusMcNugen/PSWriteColorEX
 
    .LINK
    Write-ColorEX
    #>

    [CmdletBinding()]
    [OutputType([object[]])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'It returns a new array of colors and changes nothing.')]
    param(
        [Parameter(Mandatory)]
        [object[]]$Colors,

        [AllowNull()]
        [object[]]$Places = $null,

        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Steps,

        [Parameter(Mandatory)]
        [ValidateSet('TrueColor', 'ANSI8')]
        [string]$Mode,

        [ValidateSet('OKLab', 'RGB', 'LinearRGB', 'OKLCH', 'HSL', 'HSV', 'Lab', 'LCh')]
        [string]$Space = 'OKLab',

        [ValidateSet('Shorter', 'Longer', 'Increasing', 'Decreasing')]
        [string]$Hue = 'Shorter',

        [ValidateSet('Linear', 'InSine', 'OutSine', 'InOutSine', 'InQuad', 'OutQuad', 'InOutQuad', 'InCubic', 'OutCubic', 'InOutCubic', 'InQuart', 'OutQuart', 'InOutQuart', 'InQuint', 'OutQuint', 'InOutQuint', 'InExpo', 'OutExpo', 'InOutExpo', 'InCirc', 'OutCirc', 'InOutCirc', 'InBack', 'OutBack', 'InOutBack', 'InElastic', 'OutElastic', 'InOutElastic', 'InBounce', 'OutBounce', 'InOutBounce')]
        [string]$Easing = 'Linear',

        [ValidateSet('None', 'Repeat', 'Mirror')]
        [string]$Repeat = 'None',

        [ValidateSet('Forward', 'Reverse', 'CenterOut', 'EdgesIn')]
        [string]$Direction = 'Forward',

        [ValidateRange(0, [int]::MaxValue)]
        [int]$Bands = 0
    )

    # Validate colors count
    if ($Colors.Count -lt 2) {
        throw "Gradient requires at least 2 colors (received $($Colors.Count))"
    }

    $rgbColors = ConvertTo-GradientStopRgb -Colors $Colors

    # The key holds the options by name, so GradientOptions runs only for a gradient not yet built
    $stopTexts = foreach ($stop in $rgbColors) { $stop -join ',' }
    $placeKey = ''
    if ($null -ne $Places) {
        $placeTexts = foreach ($place in $Places) {
            if ($null -eq $place) { '' } else { ([double]$place).ToString('R', [System.Globalization.CultureInfo]::InvariantCulture) }
        }
        $placeKey = @($placeTexts) -join ';'
    }
    $cacheKey = '{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}|{8}|{9}' -f $Mode, $Space, $Hue, $Easing, $Repeat, $Direction, $Bands, $Steps, (@($stopTexts) -join ';'), $placeKey
    $cached = $script:GradientCache[$cacheKey]
    if ($null -ne $cached) {
        return ,$cached
    }

    $options = [ColorMath]::GradientOptions($Space, $Hue, $Easing, $Repeat, $Direction, $Bands)
    $result = [ColorMath]::GradientRgb($rgbColors, $Places, $Steps, $options)
    if ($Mode -eq 'ANSI8') {
        for ($step = 0; $step -lt $result.Count; $step++) {
            $rgb = $result[$step]
            $result[$step] = [ColorMath]::NearestAnsi8($rgb[0], $rgb[1], $rgb[2])
        }
    }
    if ($script:GradientCache.Count -ge 256) {
        $script:GradientCache.Clear()
    }
    $script:GradientCache[$cacheKey] = $result

    # The comma keeps the array whole rather than unrolled into the pipeline
    return ,$result
}

function ConvertTo-GradientStopRgb {
    <#
    .SYNOPSIS
    Each of a gradient's colors as an RGB array: an RGB array clamped to 0-255, a hex code, or a
    color name. A name the color table lacks and an invalid hex code are gray.
    #>

    param([object[]]$Colors)
    if ($null -eq $script:CachedColorTable) {
        $script:CachedColorTable = Get-ColorTableWithRGB
    }
    $rgbColors = [System.Collections.Generic.List[array]]::new($Colors.Count)
    foreach ($color in $Colors) {
        if ($color -is [array] -and $color.Count -eq 3) {
            $null = $rgbColors.Add(@(
                [Math]::Max(0, [Math]::Min(255, [int]$color[0])),
                [Math]::Max(0, [Math]::Min(255, [int]$color[1])),
                [Math]::Max(0, [Math]::Min(255, [int]$color[2]))
            ))
        } elseif ($color -is [string] -and $color -match '^#|^0x') {
            $null = $rgbColors.Add((Convert-HexToRGB -Hex $color))
        } else {
            $colorEntry = $script:CachedColorTable[$color]
            if ($colorEntry) {
                $null = $rgbColors.Add($colorEntry[4])
            } else {
                $null = $rgbColors.Add(@(128, 128, 128))
            }
        }
    }
    # The comma keeps the array whole rather than unrolled into the pipeline
    return ,$rgbColors.ToArray()
}

# The gradients built so far, by mode, options, steps, waypoints and places
$script:GradientCache = @{}