async function decompressZlib( data: Uint8Array ): Promise> { const stream = new DecompressionStream('deflate') const writer = stream.writable.getWriter() try { await writer.write(data) await writer.close() } finally { writer.releaseLock() } const reader = stream.readable.getReader() const chunks: Uint8Array[] = [] let totalLength = 0 try { while (true) { const { done, value } = await reader.read() if (done) break chunks.push(value) totalLength += value.length } } finally { reader.releaseLock() } const result = new Uint8Array(totalLength) let offset = 0 for (const chunk of chunks) { result.set(chunk, offset) offset += chunk.length } return result } export async function getFromPngBuffer( buffer: ArrayBuffer ): Promise> { const pngData = new Uint8Array(buffer) const dataView = new DataView(pngData.buffer) if (dataView.getUint32(0) !== 0x89504e47) { console.error('Not a valid PNG file') return {} } let offset = 8 const txt_chunks: Record = {} while (offset < pngData.length) { const length = dataView.getUint32(offset) const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8)) if (type === 'tEXt' || type === 'comf' || type === 'iTXt') { let keyword_end = offset + 8 while (pngData[keyword_end] !== 0) { keyword_end++ } const keyword = String.fromCharCode( ...pngData.slice(offset + 8, keyword_end) ) let textStart = keyword_end + 1 let isCompressed = false let compressionMethod = 0 if (type === 'iTXt') { const chunkEnd = offset + 8 + length isCompressed = pngData[textStart] === 1 compressionMethod = pngData[textStart + 1] textStart += 2 while (pngData[textStart] !== 0 && textStart < chunkEnd) { textStart++ } if (textStart < chunkEnd) textStart++ while (pngData[textStart] !== 0 && textStart < chunkEnd) { textStart++ } if (textStart < chunkEnd) textStart++ } let contentArraySegment = pngData.slice(textStart, offset + 8 + length) if (isCompressed) { if (compressionMethod === 0) { try { contentArraySegment = await decompressZlib(contentArraySegment) } catch (e) { console.error(`Failed to decompress iTXt chunk "${keyword}":`, e) offset += 12 + length continue } } else { console.warn( `Unsupported compression method ${compressionMethod} for iTXt chunk "${keyword}"` ) offset += 12 + length continue } } const contentJson = new TextDecoder('utf-8').decode(contentArraySegment) txt_chunks[keyword] = contentJson } offset += 12 + length } return txt_chunks } export async function getFromPngFile( file: File ): Promise> { return new Promise>((resolve, reject) => { const reader = new FileReader() reader.onload = async (event) => { const buffer = event.target?.result if (!(buffer instanceof ArrayBuffer)) { reject(new Error('Failed to read file as ArrayBuffer')) return } const result = await getFromPngBuffer(buffer) resolve(result) } reader.onerror = () => reject(reader.error) reader.readAsArrayBuffer(file) }) }