Private/Read-AseLayerData.ps1
|
function Read-AseLayerData { <# .SYNOPSIS Reads and decompresses layer data from an Aseprite .ase file. .DESCRIPTION Parses Aseprite frame data to extract cel chunks (0x2005) with compressed image data. Only processes the first frame. Each cel's zlib-compressed RGBA data is decompressed and returned as a byte array. .PARAMETER Data Byte array containing .ase file data. .PARAMETER Width Expected image width in pixels. .PARAMETER Height Expected image height in pixels. .OUTPUTS System.Array of byte arrays, one per valid layer. .NOTES Aseprite frame layout: - Frame header: 16 bytes (size, magic 0xF1FA, chunk count, etc.) - Chunks: layer (0x2004), cel (0x2005), etc. - Cel chunk with type 2 = compressed image: has width, height, then zlib data. #> [CmdletBinding()] [OutputType([byte[][]])] param( [Parameter(Mandatory)] [byte[]]$Data, [Parameter(Mandatory)] [int]$Width, [Parameter(Mandatory)] [int]$Height ) $expectedBytes = $Width * $Height * 4 $layers = [System.Collections.Generic.List[byte[]]]::new() if ($Data.Length -lt 128) { return , $layers.ToArray() } $frameCount = [BitConverter]::ToUInt16($Data, 6) if ($frameCount -eq 0) { return , $layers.ToArray() } # only process the first frame $offset = 128 if ($offset + 16 -gt $Data.Length) { return , $layers.ToArray() } $frameSize = [BitConverter]::ToUInt32($Data, $offset) $frameMagic = [BitConverter]::ToUInt16($Data, $offset + 4) if ($frameMagic -ne 0xF1FA) { Write-Warning "Invalid frame magic: 0x$($frameMagic.ToString('X4'))" return , $layers.ToArray() } $oldChunkCount = [BitConverter]::ToUInt16($Data, $offset + 6) $newChunkCount = [BitConverter]::ToUInt32($Data, $offset + 12) $chunkCount = if ($newChunkCount -ne 0) { $newChunkCount } else { $oldChunkCount } $chunkOffset = $offset + 16 for ($ci = 0; $ci -lt $chunkCount; $ci++) { if ($chunkOffset + 6 -gt $Data.Length) { break } $chunkSize = [BitConverter]::ToUInt32($Data, $chunkOffset) $chunkType = [BitConverter]::ToUInt16($Data, $chunkOffset + 4) if ($chunkType -eq 0x2005) { # cel chunk $celDataOffset = $chunkOffset + 6 # layer index: WORD at +0 # x position: SHORT at +2 # y position: SHORT at +4 # opacity: BYTE at +6 # cel type: WORD at +7 # z-index: SHORT at +9 # future: 5 bytes at +11 $celType = [BitConverter]::ToUInt16($Data, $celDataOffset + 7) if ($celType -eq 2) { # compressed image # cel width: WORD at +16 # cel height: WORD at +18 # compressed data starts at +20 $celWidth = [BitConverter]::ToUInt16($Data, $celDataOffset + 16) $celHeight = [BitConverter]::ToUInt16($Data, $celDataOffset + 18) $compressedStart = $celDataOffset + 20 if ($celWidth -eq $Width -and $celHeight -eq $Height) { try { $layerData = Expand-ZlibData -Data $Data -Offset $compressedStart if ($layerData.Length -eq $expectedBytes) { $layers.Add($layerData) } else { Write-Verbose "Cel data length mismatch: expected $expectedBytes, got $($layerData.Length)" } } catch { Write-Verbose "Failed to decompress cel at offset $compressedStart`: $($_.Exception.Message)" } } } } $chunkOffset += $chunkSize } return , $layers.ToArray() } |