You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将PNG的IDAT块数据解码为原始像素?附4x4示例解析

Got it, let's walk through decoding your 4x4 PNG's IDAT block into raw pixel data—since you know Swift, I’ll structure this with pseudocode and steps that map directly to Swift’s capabilities.

Step 1: Extract and Inflate the IDAT Compressed Data

First, let's pull the compressed data from your hex dump. Your IDAT block starts at offset 0000003F:

  • Block length: 0000003F (63 bytes)
  • Block type: 49444152 (IDAT)
  • Compressed data: 081D013400CBFF01CC96B1134FE120C0CECDF15101FFA56000000000000E0403201DFE59286DF6D0000000000004EDB11F002E007A2193EDB11F3063136F4733525A
  • CRC: (we can ignore this since you already validated it)

PNG uses the DEFLATE compression algorithm (part of zlib). In Swift, you can use the zlib library (via Foundation or a lightweight wrapper) to inflate this data. Here’s pseudocode for this step:

// 1. Convert the hex string of compressed IDAT data to Data
let compressedIDATHex = "081D013400CBFF01CC96B1134FE120C0CECDF15101FFA56000000000000E0403201DFE59286DF6D0000000000004EDB11F002E007A2193EDB11F3063136F4733525A"
guard let compressedIDATData = Data(hexString: compressedIDATHex) else {
    fatalError("Failed to parse hex string to Data")
}

// 2. Inflate using zlib (Swift implementation with zlib bindings)
func inflate(data: Data) -> Data? {
    guard !data.isEmpty else { return nil }
    // Add zlib header (0x78 0x9C) if your raw IDAT data doesn't include it
    let zlibHeader = Data([0x78, 0x9C])
    let fullData = zlibHeader + data
    
    var buffer = [UInt8](repeating: 0, count: 1024)
    var stream = z_stream()
    stream.zalloc = nil
    stream.zfree = nil
    stream.opaque = nil
    stream.avail_in = UInt32(fullData.count)
    stream.next_in = UnsafeMutablePointer(mutating: (fullData as NSData).bytes.bindMemory(to: UInt8.self, capacity: fullData.count))
    stream.avail_out = UInt32(buffer.count)
    stream.next_out = UnsafeMutablePointer(mutating: buffer)
    
    guard inflateInit2(&stream, MAX_WBITS + 16) == Z_OK else { return nil }
    var result = Data()
    
    repeat {
        if stream.avail_out == 0 {
            result.append(buffer)
            buffer = [UInt8](repeating: 0, count: 1024)
            stream.avail_out = UInt32(buffer.count)
            stream.next_out = UnsafeMutablePointer(mutating: buffer)
        }
        let status = inflate(&stream, Z_NO_FLUSH)
        guard status != Z_STREAM_ERROR else {
            inflateEnd(&stream)
            return nil
        }
        switch status {
        case Z_NEED_DICT, Z_DATA_ERROR, Z_MEM_ERROR:
            inflateEnd(&stream)
            return nil
        default: break
        }
    } while stream.avail_in > 0 || stream.avail_out == 0
    
    let remainingBytes = buffer.count - Int(stream.avail_out)
    if remainingBytes > 0 {
        result.append(buffer[0..<remainingBytes])
    }
    inflateEnd(&stream)
    return result
}

guard let inflatedData = inflate(data: compressedIDATData) else {
    fatalError("Failed to inflate IDAT data")
}

Note: Raw IDAT DEFLATE data often lacks the zlib header, so adding 0x78 0x9C (standard zlib wrapper) is usually required for the inflate function to work correctly.

Step 2: Reverse the Scanline Filters

Once inflated, you’ll get a sequence of scanlines. Each scanline starts with a filter byte (1 byte) followed by pixel data. For your 4x4 RGB image:

  • Each pixel uses 3 bytes (R, G, B)
  • 4 pixels per scanline = 12 bytes of pixel data
  • Total per scanline: 1 (filter byte) + 12 = 13 bytes
  • 4 scanlines total: 4 * 13 = 52 bytes of inflated data

You need to reverse the filter for each scanline using PNG’s official filter rules. Here’s Swift-style pseudocode for this step:

// Define PNG filter types as an enum for clarity
enum PNGFilterType: UInt8 {
    case none = 0
    case sub = 1
    case up = 2
    case average = 3
    case paeth = 4
}

// 1. Split inflated data into individual scanlines
let bytesPerPixel = 3 // RGB, 8 bits per channel
let pixelsPerScanline = 4
let bytesPerScanline = 1 + (pixelsPerScanline * bytesPerPixel) // 13 bytes per line
var scanlines = [[UInt8]]()
var currentIndex = 0

while currentIndex < inflatedData.count {
    let endIndex = currentIndex + bytesPerScanline
    guard endIndex <= inflatedData.count else { break }
    let scanline = Array(inflatedData[currentIndex..<endIndex])
    scanlines.append(scanline)
    currentIndex = endIndex
}

// 2. De-filter each scanline to get raw pixel data
var rawPixels = [UInt8]()
var previousScanlinePixelData = [UInt8]() // Empty for the first scanline

for scanline in scanlines {
    let filterType = PNGFilterType(rawValue: scanline[0]) ?? .none
    let filteredPixelData = scanline[1...] // Skip the leading filter byte
    
    var deFilteredData = [UInt8]()
    for (i, byte) in filteredPixelData.enumerated() {
        // Get neighboring bytes based on filter rules
        let leftByte: UInt8 = i >= bytesPerPixel ? deFilteredData[i - bytesPerPixel] : 0
        let upByte: UInt8 = i < previousScanlinePixelData.count ? previousScanlinePixelData[i] : 0
        let upLeftByte: UInt8 = (i >= bytesPerPixel && i - bytesPerPixel < previousScanlinePixelData.count) ? previousScanlinePixelData[i - bytesPerPixel] : 0
        
        let deFilteredByte: UInt8
        switch filterType {
        case .none:
            deFilteredByte = byte
        case .sub:
            deFilteredByte = byte &+ leftByte // Use wrapping addition
        case .up:
            deFilteredByte = byte &+ upByte
        case .average:
            deFilteredByte = byte &+ UInt8((Int(leftByte) + Int(upByte)) / 2)
        case .paeth:
            // Paeth predictor: pick the closest of left/up/up-left
            let p = Int(leftByte) + Int(upByte) - Int(upLeftByte)
            let pa = abs(p - Int(leftByte))
            let pb = abs(p - Int(upByte))
            let pc = abs(p - Int(upLeftByte))
            
            let predictor: UInt8
            if pa <= pb && pa <= pc {
                predictor = leftByte
            } else if pb <= pc {
                predictor = upByte
            } else {
                predictor = upLeftByte
            }
            deFilteredByte = byte &+ predictor
        }
        deFilteredData.append(deFilteredByte)
    }
    
    rawPixels.append(contentsOf: deFilteredData)
    previousScanlinePixelData = deFilteredData
}

// Now rawPixels contains 48 bytes of raw RGB data (4x4 pixels × 3 bytes each)
// Pixels are ordered left-to-right, top-to-bottom, each as (R, G, B)
Verifying Your Results

Once you run this code, you can cross-check the raw pixel values against the original image you created in GIMP. For example, if your image had a solid color or simple pattern, the raw RGB bytes should match exactly what you designed.


内容的提问来源于stack exchange,提问作者ANoobSwiftly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:04:58