Private/New-GradientColorArray.ps1
|
function New-GradientColorArray { <# .SYNOPSIS Generates a gradient color array for character-by-character coloring .DESCRIPTION Creates an array of interpolated colors between waypoints for smooth gradients. Supports 2+ color waypoints and both TrueColor (RGB) and ANSI8 (256-color) modes. .PARAMETER Colors Array of gradient waypoints (minimum 2 colors). Accepts color names, hex codes, or RGB arrays. .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 .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: MarkusMcNugen License: MIT Requires: PowerShell 5.1 or later This is a private function used internally by Write-ColorEX for gradient processing. It performs linear interpolation between color waypoints to create smooth gradients. Performance: Uses List[object] for array building (18000x faster than += operator). .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Write-ColorEX #> [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Colors, [Parameter(Mandatory)] [ValidateRange(1, [int]::MaxValue)] [int]$Steps, [Parameter(Mandatory)] [ValidateSet('TrueColor', 'ANSI8')] [string]$Mode ) # Validate colors count if ($Colors.Count -lt 2) { throw "Gradient requires at least 2 colors (received $($Colors.Count))" } # Performance: Use List instead of array += (18,000x faster for large arrays) $gradientColors = [System.Collections.Generic.List[object]]::new($Steps) # Convert all input colors to RGB first (performance: do once, reuse many times) $rgbColors = [System.Collections.Generic.List[array]]::new($Colors.Count) # Cache command availability check (avoid repeated Get-Command calls) $hasConvertHex = $null -ne (Get-Command Convert-HexToRGB -ErrorAction SilentlyContinue) $hasConvertANSI8 = $null -ne (Get-Command Convert-RGBToANSI8 -ErrorAction SilentlyContinue) $hasColorTable = $null -ne (Get-Command Get-ColorTableWithRGB -ErrorAction SilentlyContinue) foreach ($color in $Colors) { if ($color -is [array] -and $color.Count -eq 3) { # Already RGB - clamp to 0-255 range $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') { # Hex color - convert to RGB if ($hasConvertHex) { $rgb = Convert-HexToRGB -Hex $color $null = $rgbColors.Add($rgb) } else { # Fallback to gray if conversion not available $null = $rgbColors.Add(@(128, 128, 128)) } } else { # Named color - get RGB from color table if ($null -eq $script:CachedColorTable -and $hasColorTable) { $script:CachedColorTable = Get-ColorTableWithRGB } if ($null -ne $script:CachedColorTable) { # Direct hashtable access (2x faster than ContainsKey + lookup) $colorEntry = $script:CachedColorTable[$color] if ($colorEntry) { $null = $rgbColors.Add($colorEntry[4]) } else { # Color not found - fallback to gray $null = $rgbColors.Add(@(128, 128, 128)) } } else { # No color table - fallback to gray $null = $rgbColors.Add(@(128, 128, 128)) } } } # Edge case: single step (return first color) if ($Steps -eq 1) { $rgb = $rgbColors[0] if ($Mode -eq 'ANSI8') { if ($hasConvertANSI8) { $null = $gradientColors.Add((Convert-RGBToANSI8 -RGB $rgb)) } else { $null = $gradientColors.Add(7) # Gray fallback } } else { $null = $gradientColors.Add($rgb) } return ,$gradientColors.ToArray() } # Calculate gradient colors if ($rgbColors.Count -eq 2) { # Two-color gradient (optimized fast path) $startRGB = $rgbColors[0] $endRGB = $rgbColors[1] for ($i = 0; $i -lt $Steps; $i++) { # Linear interpolation $ratio = $i / ($Steps - 1) $r = [int]($startRGB[0] + ($endRGB[0] - $startRGB[0]) * $ratio) $g = [int]($startRGB[1] + ($endRGB[1] - $startRGB[1]) * $ratio) $b = [int]($startRGB[2] + ($endRGB[2] - $startRGB[2]) * $ratio) if ($Mode -eq 'ANSI8') { if ($hasConvertANSI8) { $null = $gradientColors.Add((Convert-RGBToANSI8 -RGB @($r, $g, $b))) } else { $null = $gradientColors.Add(7) # Gray fallback } } else { $null = $gradientColors.Add(@($r, $g, $b)) } } } else { # Multi-stop gradient (3+ colors) $segmentCount = $rgbColors.Count - 1 for ($step = 0; $step -lt $Steps; $step++) { # Determine which color segment this step falls into $position = $step / ($Steps - 1) * $segmentCount $segmentIndex = [Math]::Min([int][Math]::Floor($position), $segmentCount - 1) $segmentProgress = $position - $segmentIndex $startRGB = $rgbColors[$segmentIndex] $endRGB = $rgbColors[$segmentIndex + 1] # Linear interpolation within segment $r = [int]($startRGB[0] + ($endRGB[0] - $startRGB[0]) * $segmentProgress) $g = [int]($startRGB[1] + ($endRGB[1] - $startRGB[1]) * $segmentProgress) $b = [int]($startRGB[2] + ($endRGB[2] - $startRGB[2]) * $segmentProgress) if ($Mode -eq 'ANSI8') { if ($hasConvertANSI8) { $null = $gradientColors.Add((Convert-RGBToANSI8 -RGB @($r, $g, $b))) } else { $null = $gradientColors.Add(7) # Gray fallback } } else { $null = $gradientColors.Add(@($r, $g, $b)) } } } # Return as array (comma operator prevents unwrapping) return ,$gradientColors.ToArray() } |