import { applyUnfilter } from './applyUnfilter'; import type { DecodeInterlaceNullParams } from './decodeInterlaceNull'; const uint16 = new Uint16Array([0x00ff]); const uint8 = new Uint8Array(uint16.buffer); const osIsLittleEndian = uint8[0] === 0xff; /** * Decodes the Adam7 interlaced PNG data. * * @param params - DecodeInterlaceNullParams * @returns - array of pixel data. */ export function decodeInterlaceAdam7(params: DecodeInterlaceNullParams) { const { data, width, height, channels, depth } = params; // Adam7 interlacing pattern const passes = [ { x: 0, y: 0, xStep: 8, yStep: 8 }, // Pass 1 { x: 4, y: 0, xStep: 8, yStep: 8 }, // Pass 2 { x: 0, y: 4, xStep: 4, yStep: 8 }, // Pass 3 { x: 2, y: 0, xStep: 4, yStep: 4 }, // Pass 4 { x: 0, y: 2, xStep: 2, yStep: 4 }, // Pass 5 { x: 1, y: 0, xStep: 2, yStep: 2 }, // Pass 6 { x: 0, y: 1, xStep: 1, yStep: 2 }, // Pass 7 ]; const bytesPerPixel = Math.ceil(depth / 8) * channels; const resultData = new Uint8Array(height * width * bytesPerPixel); let offset = 0; // Process each pass for (let passIndex = 0; passIndex < 7; passIndex++) { const pass = passes[passIndex]; // Calculate pass dimensions const passWidth = Math.ceil((width - pass.x) / pass.xStep); const passHeight = Math.ceil((height - pass.y) / pass.yStep); if (passWidth <= 0 || passHeight <= 0) continue; const passLineBytes = passWidth * bytesPerPixel; const prevLine = new Uint8Array(passLineBytes); // Process each scanline in this pass for (let y = 0; y < passHeight; y++) { // First byte is the filter type const filterType = data[offset++]; const currentLine = data.subarray(offset, offset + passLineBytes); offset += passLineBytes; // Create a new line for the unfiltered data const newLine = new Uint8Array(passLineBytes); // Apply the appropriate unfilter applyUnfilter( filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel, ); prevLine.set(newLine); for (let x = 0; x < passWidth; x++) { const outputX = pass.x + x * pass.xStep; const outputY = pass.y + y * pass.yStep; if (outputX >= width || outputY >= height) continue; for (let i = 0; i < bytesPerPixel; i++) { resultData[(outputY * width + outputX) * bytesPerPixel + i] = newLine[x * bytesPerPixel + i]; } } } } if (depth === 16) { const uint16Data = new Uint16Array(resultData.buffer); if (osIsLittleEndian) { for (let k = 0; k < uint16Data.length; k++) { // PNG is always big endian. Swap the bytes. uint16Data[k] = swap16(uint16Data[k]); } } return uint16Data; } else { return resultData; } } function swap16(val: number): number { return ((val & 0xff) << 8) | ((val >> 8) & 0xff); }