Help us understand the problem. What is going on with this article?

PNG の IDAT のみを弄る

Swift の練習を兼ねて PNG ファイルを弄るプログラムを作ってみる。

処理内容は

  1. PNG ファイルをチャンク単位にして読み込む
  2. IDAT 以外は変更しない
  3. IDAT を zlib で展開 (uncompress)
  4. フィルタ タイプを弄る
  5. IDAT を zlib で圧縮 (compress)
  6. PNG ファイルを出力する

で、IDAT 内のフィルタ タイプの処理をテストする。

テスト プログラム(Xcode のコンソールアプリ)
main.swift
import Foundation
import Compression

// ############

var flag_update_image = true
var flag_image_filter = true
var flag_overwrite = false
var flag_verbose = false

// ############

enum PNGError : Error {
    case system
    case open
    case exists
    case create
    case signature
    case unsupported
    case data
    case io
    func errorPrint(_ msg: String) {
        switch (self)
        {
        case .system:
            print("内部エラー: \(msg)")
        case .open:
            print("ファイルが開けません: \(msg)")
        case .exists:
            print("ファイルが既にあります: \(msg)")
        case .create:
            print("ファイルが作れません: \(msg)")
        case .signature:
            print("PNG ではありません: \(msg)")
        case .unsupported:
            print("非対応の形式です: \(msg)")
        case .data:
            print("データが破損しています: \(msg)")
        case .io:
            print("読み込みに失敗しました: \(msg)")
        }
    }
}

// ############

func UInt32EB(_ byteArray: ArraySlice<UInt8>) -> UInt32 {
    var value: UInt32 = 0
    for b in byteArray {
        value <<= 8
        value |= UInt32(b)
    }
    return value
}

extension Data {
    func ToArrayU8() -> [UInt8] {
        return [UInt8](unsafeUninitializedCapacity: count)
        { buffer, initializedCount in
            copyBytes(to: buffer, from: 0..<count)
            initializedCount = count
        }
    }
}

// ############

extension FileHandle {
    func readDataU8(ofLength count: Int) -> [UInt8] {
        return readData(ofLength: count).ToArrayU8()
    }
    func readU32EB() -> UInt32? {
        let data = readDataU8(ofLength: 4)
        if data.count != 4 {
            return nil
        }
        return UInt32EB(data[0..<4])
    }
    func writeU32EB(_ value: UInt32) {
        write(Data([
            UInt8((value >> 24) & 0xff),
            UInt8((value >> 16) & 0xff),
            UInt8((value >>  8) & 0xff),
            UInt8((value >>  0) & 0xff),
        ]))
    }
}

// ############

class CRC32Table {
    private static var dictionary: [UInt32: [UInt32]] = [:]
    private let table: [UInt32]
    subscript(index: Int) -> UInt32 {
        return table[index]
    }
    init(_ polynomial: UInt32) {
        if let dictTable = CRC32Table.dictionary[polynomial] {
            table = dictTable
            return
        }
        let newTable = [UInt32](unsafeUninitializedCapacity: 256)
        { buffer, initializedCount in
            for n in 0..<256 {
                var v = UInt32(n)
                for _ in 0..<8 {
                    let f = v & 1
                    v >>= 1
                    if f != 0 {
                        v ^= polynomial
                    }
                }
                buffer[n] = v
            }
            initializedCount = 256
        }
        CRC32Table.dictionary[polynomial] = newTable
        table = newTable
    }
}

class CRC32 {
    private let table: CRC32Table
    private var value_: UInt32 = ~0
    var value : UInt32 {
        get {
            return ~value_
        }
    }
    init(_ polynomial: UInt32 = 0xEDB88320) {
        table = CRC32Table(polynomial)
    }
    func update(_ data: UInt8) {
        let index = Int(UInt8(value_ & 0xff) ^ data)
        value_ = (value_ >> 8) ^ table[index]
    }
    func update(_ data: Data)  {
        update(data.ToArrayU8())
    }
    func updateEB(_ data: UInt32) {
        update(UInt8((data >> 24) & 0xff))
        update(UInt8((data >> 16) & 0xff))
        update(UInt8((data >>  8) & 0xff))
        update(UInt8((data >>  0) & 0xff))
    }
    func update(_ data: [UInt8]) {
        for v in data {
            update(v)
        }
    }
}

// ############

// Compression Module (ZLIB)

func CompressM(_ src: [UInt8]) -> [UInt8]? {
    var buffer: [UInt8] = [0x78, 0x5E]
    do {
        let out = try OutputFilter(.compress, using: .zlib)
        { (data: Data?) -> Void in
            buffer.append(contentsOf: data?.ToArrayU8() ?? [])
        }
        try out.write(src)
        try out.finalize()
    } catch let error {
        print(error.localizedDescription)
        return nil
    }
    return buffer
}

func ZLibUncompress(_ size: UInt32 , _ src: [UInt8]) -> [UInt8]? {
    var buffer: [UInt8] = []
    do {
        let out = try OutputFilter(.decompress, using: .zlib)
        { (data: Data?) -> Void in
            buffer.append(contentsOf: data?.ToArrayU8() ?? [])
        }
        try out.write(src[2...])
        try out.finalize()
    } catch let error {
        print(error.localizedDescription)
        return nil
    }
    return buffer
}

// C Library

func zlibCompress(_ src: [UInt8]) -> [UInt8]? {
    var buffer = [UInt8](repeating: 0, count: Int(src.count * 2))
    let status = CompressC(&buffer, UInt(buffer.count), src, UInt(src.count), 9)
    if status < 0 {
        return nil
    }
    return [UInt8](buffer[0..<status])
}

// https://developer.apple.com/documentation/compression/compression_zlib
// Compression モジュールは圧縮レベル 5 のみだから高圧縮を求めるため C ライブラリを使う.
var ZLibCompress = zlibCompress // C ライブラリ (libz) を使用する.

// ############

class Chunk
{
    var length: UInt32 = 0
    var idData = Data()
    var id = ""
    var data: [UInt8] = []
    var crc: UInt32 = 0
    init() { /*NOP*/ }
    init(_ handle: FileHandle) throws {
        try read(handle)
    }
    func read(_ handle: FileHandle) throws {
        guard let rLen = handle.readU32EB() else {
            throw PNGError.io
        }
        length = rLen

        idData = handle.readData(ofLength: 4)
        if idData.count != 4 {
            throw PNGError.io
        }
        guard let cId = String(data: idData, encoding: String.Encoding.utf8) else {
            throw PNGError.data
        }
        id = cId

        let rData = handle.readDataU8(ofLength: Int(rLen))
        if rData.count != rLen {
            throw PNGError.data
        }
        data = rData

        guard let rCrc = handle.readU32EB() else {
            throw PNGError.io
        }
        crc = rCrc

        let crc32 = CRC32()
        crc32.update(idData)
        crc32.update(data)
        if crc32.value != rCrc {
            throw PNGError.data
        }
        VerbosePrint(String(format: "\(id): len=%-6d  crc=%#010x", length, crc))
    }
    func write(_ handle: FileHandle) {
        handle.writeU32EB(length)
        handle.write(idData)
        handle.write(Data(data))

        let crc32 = CRC32()
        crc32.update(idData)
        crc32.update(data)
        handle.writeU32EB(crc32.value)

        VerbosePrint(String(format: "\(id): len=%-6d  crc=%#010x", length, crc))
    }
}

class IHDRChunk
{
    static let colorTypeTable: [UInt8] = [1,0,3,1,2,0,4]
    var width: UInt32 = 0
    var height: UInt32 = 0
    var colorDepth: UInt8 = 0
    var colorType: UInt8 = 0
    var compression: UInt8 = 0
    var filter: UInt8 = 0
    var interlace: UInt8 = 0
    var bitsPerPixel: UInt8 = 0
    var bytesPerPixel : UInt8 = 0
    init() { /*NO-OP*/ }
    init(_ chunk: Chunk) {
        let ihdr = chunk.data
        width  = UInt32EB(ihdr[0..<4])
        height = UInt32EB(ihdr[4..<8])
        colorDepth = ihdr[8]
        colorType = ihdr[9]
        compression = ihdr[10]
        filter = ihdr[11]
        interlace = ihdr[12]
        let numDepth = IHDRChunk.colorTypeTable[Int(colorType)]
        bitsPerPixel = colorDepth * numDepth
        bytesPerPixel = (bitsPerPixel + 7) >> 3
    }
}

struct ADAM7Param {
    static let xOffsetTable: [UInt8] = [0, 4, 0, 2, 0, 1, 0, 0]
    static let yOffsetTable: [UInt8] = [0, 0, 4, 0, 2, 0, 1, 0]
    static let xStepTable  : [UInt8] = [8, 8, 4, 4, 2, 2, 1, 1]
    static let yStepTable  : [UInt8] = [8, 8, 8, 4, 4, 2, 2, 1]
    let xOffs: UInt32
    let yOffs: UInt32
    let xStep: UInt32
    let yStep: UInt32
    let width: UInt32
    let height: UInt32
    let rawBytes: UInt32
    let stride: UInt32
    let imageSize: UInt32
    let imageOffset: UInt32
    init(_ ihdr: IHDRChunk, _ level:Int, _ offs:UInt32) {
        xOffs = UInt32(ADAM7Param.xOffsetTable[level])
        yOffs = UInt32(ADAM7Param.yOffsetTable[level])
        xStep = UInt32(ADAM7Param.xStepTable[level])
        yStep = UInt32(ADAM7Param.yStepTable[level])
        //
        width    = (ihdr.width  >= xOffs) ? ((ihdr.width  - xOffs + xStep - 1) / xStep) : 0
        height   = (ihdr.height >= yOffs) ? ((ihdr.height - yOffs + yStep - 1) / yStep) : 0
        rawBytes = (width * UInt32(ihdr.bitsPerPixel) + 7) >> 3
        stride   = (rawBytes != 0) ? (rawBytes + 1) : 0
        //
        imageSize   = stride * height
        imageOffset = offs
    }
}

class ADAM7 {
    let interlace: [ADAM7Param]
    let interlaceSize: UInt32
    let imageSize: UInt32
    var progressive: ADAM7Param {
        get {
            return interlace[7]
        }
    }
    subscript(index: Int) -> ADAM7Param {
        return interlace[index]
    }
    init(_ ihdr: IHDRChunk) {
        interlace = [ADAM7Param](unsafeUninitializedCapacity: 8)
        { buffer, initializedCount in
            var offs: UInt32 = 0
            for n in 0..<7 {
                buffer[n] = ADAM7Param(ihdr, n, offs)
                offs += buffer[n].imageSize
            }
            buffer[7] = ADAM7Param(ihdr, 7, 0)
            initializedCount = 8
        }
        interlaceSize = interlace[6].imageOffset + interlace[6].imageSize
        imageSize = (ihdr.interlace != 0) ? interlaceSize: interlace[7].imageSize
    }
}

class PNGFile {
    static let signature: [UInt8] = [137, 80, 78, 71, 13, 10, 26, 10]
    var chunk: [Chunk] = []
    var idat_length: [UInt32] = []
    var idat_average: UInt32 = 0
    var ihdr : IHDRChunk = IHDRChunk()
    var image: [UInt8] = []
    subscript(id: String) -> Chunk? {
        let n = find(id)
        if n < 0 {
            return nil
        }
        return chunk[n]
    }
    init(_ path: String) throws {
        try self.read(path)
    }
    func find(_ id: String) -> Int {
        for n in 0..<chunk.count {
            if id == chunk[n].id {
                return n
            }
        }
        return -1
    }
    func setImageData(_ image: [UInt8]) {
        let n = find("IDAT")
        chunk[n].length = UInt32(image.count)
        chunk[n].data = image
    }
    func read(_ handle: FileHandle) throws {
        let signature: [UInt8] = handle.readDataU8(ofLength: 8)
        if signature != PNGFile.signature {
            throw PNGError.signature
        }
        var rChunk = try Chunk(handle)
        ihdr = IHDRChunk(rChunk)
        var chunk_id = rChunk.id
        if chunk_id != "IHDR" {
            throw PNGError.data
        }
        chunk.append(rChunk)
        while chunk_id != "IEND" {
            let last_chunk = chunk_id
            rChunk = try Chunk(handle)
            chunk_id = rChunk.id
            switch (chunk_id) {
            case "IHDR":
                throw PNGError.data
            case "IDAT":
                idat_length.append(rChunk.length)
                if last_chunk == "IDAT" {
                    let idatIdx = chunk.count - 1
                    chunk[idatIdx].data.append(contentsOf: rChunk.data)
                    chunk[idatIdx].length = UInt32(chunk[idatIdx].data.count)
                    continue
                }
                if idat_length.count != 1 {
                    throw PNGError.data
                }
            default: break
            }
            chunk.append(rChunk)
        }
        if ![1,2,4,8,16].contains(ihdr.colorDepth) {
            throw PNGError.unsupported
        }
        if ![0,2,3,4,6].contains(ihdr.colorType) {
            throw PNGError.unsupported
        }
        if ihdr.compression != 0 {
            throw PNGError.unsupported
        }
        if ihdr.filter != 0 {
            throw PNGError.unsupported
        }
        if ihdr.interlace >= 2 {
            throw PNGError.unsupported
        }

        idat_length.removeLast()
        idat_average = 0
        if idat_length.count != 0 {
            let split1 = idat_length.reduce(0, +)
            idat_average = split1 / UInt32(idat_length.count)
        }
    }
    func read(_ path: String) throws {
        guard let handle = FileHandle(forReadingAtPath: path) else {
            throw PNGError.open
        }
        try! self.read(handle)
        handle.closeFile()
    }
    func write(_ handle: FileHandle) {
        handle.write(Data(PNGFile.signature))
        for ch in chunk {
            if ch.id != "IDAT" {
                ch.write(handle)
                continue
            }
            if idat_average == 0 {
                ch.write(handle)
                continue
            }
            var idat_spIdx = 0
            var idat_offset = 0
            var idat_total = ch.data.count
            while idat_total > 0 {
                var idat_split = Int(idat_average)
                if idat_spIdx < idat_length.count {
                    idat_split = Int(idat_length[idat_spIdx])
                    idat_spIdx += 1
                }
                if idat_split > idat_total {
                    idat_split = idat_total
                }
                let idat_next = idat_offset + idat_split
                let split_data = Data(ch.data[idat_offset..<idat_next])

                handle.writeU32EB(UInt32(idat_split))
                handle.write(ch.idData)
                handle.write(split_data)

                let crc32 = CRC32()
                crc32.update(ch.idData)
                crc32.update(split_data)
                let crc = crc32.value
                handle.writeU32EB(crc)

                idat_offset = idat_next
                idat_total -= idat_split

                VerbosePrint(String(format: "\(ch.id): len=%-6d  crc=%#010x", split_data.count, crc))
            }
        }
    }
    func write(_ path: String, _ overwrite: Bool = false) throws {
        if FileManager.default.fileExists(atPath: path) {
            if !overwrite {
                throw PNGError.exists
            }
            try FileManager.default.removeItem(atPath: path)
        }
        if !FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) {
            throw PNGError.system
        }
        guard let handle = FileHandle(forWritingAtPath: path) else {
            throw PNGError.open
        }
        self.write(handle)
        handle.closeFile()
    }
}

// ############

func ImageReconstruct(_ ihdr: IHDRChunk, _ adam7: ADAM7, _ image: inout [UInt8]) throws {
    VerbosePrint("Reconstruct:")
    for adam in adam7.interlace[(ihdr.filter != 0) ? (0..<7) : (7..<8)] {
        VerbosePrint(String(format: "  (W:%04d, H:%04d)", adam.width, adam.height))
        VerbosePrint(String(format: "  (X:%04d, Y:%04d)", adam.xOffs, adam.yOffs))
        VerbosePrint(String(format: "  (R:%04d, B:%04d)", adam.xStep, adam.yStep))
        let pixelBytes = Int(ihdr.bytesPerPixel)
        let stride = Int(adam.stride)
        let rawBytes = Int(adam.rawBytes)
        let lineBytes = pixelBytes + stride - 1
        var lineOffs = Int(adam.imageOffset)
        var imgOffs = lineOffs + 1
        if stride == 0 {
            continue
        }
        var lastLine = [UInt8](repeating: 0, count: pixelBytes + lineBytes)
        for y in 0..<adam.height {
            var currentLine = [UInt8](repeating: 0, count: pixelBytes)
            currentLine.append(contentsOf: image[imgOffs..<(imgOffs+rawBytes)])
            switch (image[lineOffs])
            {
            case 0: // NONE
                VerbosePrint(String(format: "  Line %4d: NONE", y))
                break
            case 1: // SUB
                VerbosePrint(String(format: "  Line %4d: SUB", y))
                image[lineOffs] = 0
                for x in 0..<rawBytes {
                    let a = UInt16(currentLine[x])
                    let p = UInt16(currentLine[x+pixelBytes])
                    let q = UInt8((p + a) & 0xff)
                    image[imgOffs+x] = q
                    currentLine[x+pixelBytes] = q
                }
            case 2: // UP
                VerbosePrint(String(format: "  Line %4d: UP", y))
                image[lineOffs] = 0
                for x in 0..<rawBytes {
                    let b = UInt16(lastLine[x+pixelBytes])
                    let p = UInt16(currentLine[x+pixelBytes])
                    let q = UInt8((p + b) & 0xff)
                    image[imgOffs+x] = q
                    currentLine[x+pixelBytes] = q
                }
            case 3: // AVE
                VerbosePrint(String(format: "  Line %4d: AVE", y))
                image[lineOffs] = 0
                for x in 0..<rawBytes {
                    let a = UInt16(currentLine[x])
                    let b = UInt16(lastLine[x+pixelBytes])
                    let p = UInt16(currentLine[x+pixelBytes])
                    let q = UInt8((p + ((a + b) >> 1)) & 0xff)
                    image[imgOffs+x] = q
                    currentLine[x+pixelBytes] = q
                }
            case 4: // PAETH
                VerbosePrint(String(format: "  Line %4d: PAETH", y))
                image[lineOffs] = 0
                for x in 0..<rawBytes {
                    let a = Int16(currentLine[x])
                    let b = Int16(lastLine[x+pixelBytes])
                    let c = Int16(lastLine[x])
                    let p = Int16(currentLine[x+pixelBytes])
                    let d = a + b - c
                    let pa = (d > a) ? (d - a) : (a - d)
                    var pb = (d > b) ? (d - b) : (b - d)
                    let pc = (d > c) ? (d - c) : (c - d)
                    var qa = a
                    var qb = b
                    if pb > pc { qb = c; pb = pc }
                    if pa > pb { qa = qb }
                    let q = UInt8((p + qa) & 0xff)
                    image[imgOffs+x] = q
                    currentLine[x+pixelBytes] = q
                }
            default:
                throw PNGError.data
            }
            lastLine = currentLine
            lineOffs += stride
            imgOffs += stride
        }
    }
}

func ImageFilterHint(_ data: [UInt8]) -> UInt32 {
    var hist = [UInt32](repeating: 0, count: 256)
    for n in data {
        hist[Int(n)] += 1
    }
    return hist.reduce(0) { ($0 > $1) ? $0 : $1 }
}

let filterName = ["NONE", "SUB", "UP", "AVE", "PAETH"]
func ImageFilter(_ ihdr: IHDRChunk, _ adam7: ADAM7, _ image: inout [UInt8]) {
    VerbosePrint("Filter:")
    for adam in adam7.interlace[(ihdr.filter != 0) ? (0..<7) : (7..<8)] {
        VerbosePrint(String(format: "  (W:%04d, H:%04d)", adam.width, adam.height))
        VerbosePrint(String(format: "  (X:%04d, Y:%04d)", adam.xOffs, adam.yOffs))
        VerbosePrint(String(format: "  (R:%04d, B:%04d)", adam.xStep, adam.yStep))
        let pixelBytes = Int(ihdr.bytesPerPixel)
        let stride = Int(adam.stride)
        let rawBytes = Int(adam.rawBytes)
        let lineBytes = pixelBytes + stride - 1
        var lineOffs = Int(adam.imageOffset)
        var imgOffs = lineOffs + 1
        if stride == 0 {
            continue
        }
        var lastLine = [UInt8](repeating: 0, count: pixelBytes + lineBytes)
        for y in 0..<adam.height {
            let pixelData = image[imgOffs..<(imgOffs+rawBytes)]
            var currentLine = [UInt8](repeating: 0, count: pixelBytes)
            currentLine.append(contentsOf: pixelData)

            var f0: [UInt8] = [0]
            var f1: [UInt8] = [1]
            var f2: [UInt8] = [2]
            var f3: [UInt8] = [3]
            var f4: [UInt8] = [4]

            // NONE
            f0.append(contentsOf: pixelData)
            // SUB
            for x in 0..<rawBytes {
                let a = Int16(currentLine[x])
                let p = Int16(currentLine[x+pixelBytes])
                let q = UInt8((p - a) & 0xff)
                f1.append(q)
            }
            // UP
            for x in 0..<rawBytes {
                let b = Int16(lastLine[x+pixelBytes])
                let p = Int16(currentLine[x+pixelBytes])
                let q = UInt8((p - b) & 0xff)
                f2.append(q)
            }
            // AVE
            for x in 0..<rawBytes {
                let a = Int16(currentLine[x])
                let b = Int16(lastLine[x+pixelBytes])
                let p = Int16(currentLine[x+pixelBytes])
                let q = UInt8((p - ((a + b) >> 1)) & 0xff)
                f3.append(q)
            }
            // PAETH
            for x in 0..<rawBytes {
                let a = Int16(currentLine[x])
                let b = Int16(lastLine[x+pixelBytes])
                let c = Int16(lastLine[x])
                let p = Int16(currentLine[x+pixelBytes])
                let d = a + b - c
                let pa = (d > a) ? (d - a) : (a - d)
                var pb = (d > b) ? (d - b) : (b - d)
                let pc = (d > c) ? (d - c) : (c - d)
                var qa = a
                var qb = b
                if pb > pc { qb = c; pb = pc }
                if pa > pb { qa = qb }
                let q = UInt8((p - qa) & 0xff)
                f4.append(q)
            }

            let filters = [f0,f1,f2,f3,f4]
            var hist: UInt32 = 0
            var filter: Int = 0
            for n in 0..<5 {
                let fh = ImageFilterHint(filters[n])
                if fh > hist {
                    hist = fh
                    filter = n
                }
            }

            VerbosePrint(String(format: "  Line %4d: \(filterName[filter])", y))
            let filterData = filters[filter]
            for n in 0..<stride {
                image[lineOffs+n] = filterData[n]
            }

            lastLine = currentLine
            lineOffs += stride
            imgOffs += stride
        }
    }
}

func UpdateImage(_ png: inout PNGFile) throws {
    guard let idat = png["IDAT"] else {
        throw PNGError.data
    }
    let ihdr = png.ihdr
    let adam7 = ADAM7(ihdr)
    guard var image_buffer = ZLibUncompress(adam7.imageSize, idat.data) else {
        throw PNGError.data
    }
    VerbosePrint(String(format: "Uncompress: %d(%d)", image_buffer.count, adam7.imageSize))
    if flag_update_image {
        try ImageReconstruct(ihdr, adam7, &image_buffer)
        if flag_image_filter {
            ImageFilter(ihdr, adam7, &image_buffer)
        }
    }
    guard let deflate_buffer = ZLibCompress(image_buffer) else {
        throw PNGError.system
    }
    VerbosePrint(String(format: "Compress: %d -> %d", idat.data.count, deflate_buffer.count))
    if flag_update_image {
        png.setImageData(deflate_buffer)
    }
}

// ############

func VerbosePrint(_ msg: String) {
    if flag_verbose {
        print(msg)
    }
}

// ############

var cmd_args = CommandLine.arguments
var program = cmd_args.removeFirst()
var program_paths = program.split(separator: "/")
var program_name = program_paths.last

func cmd_arg_parse() {
    while cmd_args.count > 0 {
        var arg = cmd_args[0]
        if arg.removeFirst() != "-" {
            break
        }
        cmd_args.removeFirst()
        while arg.count > 0 {
            switch (arg.removeFirst())
            {
            case "O":
                flag_overwrite = true
            case "u":
                flag_update_image = false
            case "v":
                flag_verbose = true
            case "f":
                flag_image_filter = false
            case "M":
                ZLibCompress = CompressM
            default:
                usage()
            }
        }
    }
    if cmd_args.count < 2 {
        usage()
    }
}
cmd_arg_parse()

func usage() -> Never {
    print("""
        Usage: \(String(describing: program_name)) [オプション] 入力ファイル 出力ファイル
        オプション:
            -O   既存ファイルへ上書きする
            -u   IDAT を更新しない(複写になる)
            -v   冗長出力モード
        IDAT 更新オプション:
            -f   フィルターなしにする
            -M   Compression モジュールを使う
        """)
    exit(1)
}

func main(_ inpFile: String, _ outFile: String) -> Int32 {
    var path = inpFile
    do {
        var png = try PNGFile(inpFile)
        try UpdateImage(&png)
        path = outFile
        try png.write(outFile, flag_overwrite)
    } catch let error {
        let rError = error as! PNGError
        rError.errorPrint(path)
        return 2
    }
    return 0
}

exit(main(cmd_args[0], cmd_args[1]))
zlibwrapper.m
#import <Foundation/Foundation.h>
#import <zlib.h>

long CompressC(void *buffer, unsigned long length, const void *data, unsigned long size, int level)
{
    unsigned long bufsz = length;
    if (compress2((Bytef*)buffer, &bufsz, (const Bytef*)data, size, level) != 0)
        return -1;
    return bufsz;
}
project-Bridging-Header.h
long CompressC(void *buffer, unsigned long length, const void *data, unsigned long size, int level);

幾つか試した限りでは、データが小さくなった。uncompress → compress (Lv:9) の処理だけでも小さくなるので、書き出したアプリでの圧縮レベルが最大ではないのだろう。更にフィルタ タイプを弄ると僅かに縮むので、悪い選択処理ではなさそうです。

ikiuo
プログラミングを脳トレとする爺。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away