png's are COOL

So made a lot of progress on that libpng wrapper: https://github.com/carlynorama/SwiftLIBPNG.

So far, it compiles fine for MacOS 13+ (Swift 5.7, Xcode 14) using both Intel and M1 hardware with libpng installed via homebrew.

Alternatives of Note

If using a libpng library to avoid Apple-Hardware dependencies, also consider a Package that is all Swift, no C, no Foundation? As of 2023 APR these two had fairly recent activity.

Resources

About libpng

Although some people will tell you that PNG stands for Portable Network Graphic, that is not the original meaning. The recursive pun “PNG Not Gif” is how it started. The official documentation is riddled with the same humor and very deep knowledge about how computers, color and people work. Still worth a read. PNG’s are very cool.

Inspecting Data

To just look at the HEX

Testing and verification

Notes

Managing the pointers

To use libpng to read or write the first step is to allocate memory for structs libpng will use to store information about the png and its settings.

From the documentation (on how to read, but a png_ptr and info_ptr are used for both reading a writing):

The struct at which png_ptr points is used internally by libpng to keep track of the current state of the PNG image at any given moment; info_ptr is used to indicate what its state will be after all of the user-requested transformations are performed. One can also allocate a second information struct, usually referenced via an end_ptr variable; this can be used to hold all of the PNG chunk information that comes after the image data, in case it is important to keep pre- and post-IDAT information separate (as in an image editor, which should preserve as much of the existing PNG structure as possible). For this application, we don’t care where the chunk information comes from, so we will forego the end_ptr information struct and direct everything to info_ptr.

The functions libpng provides for this come in two flavors, ones that return pointers and ones that take them and their handling functions in:

    png_structp png_ptr = png_create_write_struct(
        PNG_LIBPNG_VER_STRING,
        (png_voidp)user_error_ptr, 
        user_error_fn, 
        user_warning_fn
    );
    png_structp png_ptr = png_create_write_struct_2(
        PNG_LIBPNG_VER_STRING, 
            (png_voidp)user_error_ptr,
            user_error_fn, 
            user_warning_fn, 
            (png_voidp)
            user_mem_ptr, 
            user_malloc_fn, 
            user_free_fn
    );

Both styles of struct creation still require a call to png_destroy_write_structs functions on wrap up. Memory management is a big deal in C. Always write the destroy with the create, like closing a parens.

Most of the example code has a set_jmp? Why not yours? (yet)

A lot of example code has a chunk along the lines of: if (setjmp(png_jmpbuf(png_ptr))) { /* DO THIS */ }.

This overrides the default error handling if the file received is not a PNG file, for example. The default behaviors seem to be print statements and PNG_ABORT, from what I can tell, so it would be good to override them. However, using setjmp() and longjmp() to do that is not guaranteed to be thread safe. There is some ambiguity in the documentation (to me), but it seems as if the compiler flag to allow the setting of jumpdef is on by default because the newish “Simplified API” needs it. (It appears it was off by default in 1.4, but now is on again). Some implementations on libpng turn it off.

Why hassle with this very C specific type of error handling? The setjmp saves the calling environment to be used by longjmp. This means inside the setjmp block using png_ptr, info_ptr, or even returning will all work. C callbacks, by contrast, can only use what get offered to them as parameters.

Look in example code for

For more information on libpng and setjmp/longjmp:

For more on setjmp/longjmp in general:

That said, to side step all of this the choices appear to be to using those aforementioned “Simplified libpng API” (No examples of that in this repo, only indexed colors), which return UInt32 error codes.

Why not just use custom error functions?

Well, this code tries but it doesn’t end up with anything that useful.

Setting the error functions in the png_create_*_struct functions, i.e. (png_voidp)user_error_ptr, user_error_fn, user_warning_fn) is not common in example code, but can be done. However, these functions STILL need to have jmpdef’s in them to leave libpng’s handling function so they are not a way to avoid jmpdef.

From the manual, the warning functions passes to png_create_*_struct should be of the formats:

    void user_error_fn(png_structp png_ptr,
        png_const_charp error_msg);

    void user_warning_fn(png_structp png_ptr,
        png_const_charp warning_msg);

//Then, within your user_error_fn or user_warning_fn, you can retrieve
//the error_ptr if you need it, by calling

    png_voidp error_ptr = png_get_error_ptr(png_ptr);

After initialization png_set_error_fn can be used after struct init to update what error function should be used.

For an idea of the type of strings the error_fn and warning_fn try browsing the results of a search for png_error or png_warning in the libpng github repo.

Examples: - https://github.com/glennrp/libpng/blob/a37d4836519517bdce6cb9d956092321eca3e73b/contrib/visupng/PngFile.c - https://github.com/glennrp/libpng/blob/5f5f98a1a919e89f0bcea26d73d96dec760f222f/contrib/libtests/timepng.c - https://github.com/glennrp/libpng/blob/61bfdb0cb02a6f3a62c929dbc9e832894c0a8df2/contrib/libtests/pngvalid.c - https://github.com/glennrp/libpng/blob/a37d4836519517bdce6cb9d956092321eca3e73b/contrib/gregbook/writepng.c

The addition of an ErrorPointer struct that could hold needed information to pass the the error function can help with clean up. The error_ptr is a void*, as long as YOU know what it is, it can be whatever you need.

If the user will just leave the program at this point missing a dealloc may not the biggest deal, but file IO and network might not be closed, so be sure to check.

If your callback nopes out with an exit or abort, this “Quitting behavior” will prevent being accepted into the app store. That’s also no better than libpng default, so almost might as well have left it nil.

the buildSimpleDataExample calls a working, but not super useful, callback example.

Why doesn’t the IDAT look like the pixels that were passed in/out of libpng?

Why does the writing example use Data instead of [UInt8]?

When trying to write cross-platform code, I tend to try to use the lowest level type as much as possible to move information around. In this case using a [UInt8] over a Data would cost so much additional overhead, it’s not currently worth the hassle.

The UnsafeMutableRawPointer pointer returned from png_get_io_ptr always points to the data structure’s head, whether a file or Data, or something else, not to a cursor location where new Data should be written to. If the buildSimpleDataExample function and it’s writeDataCallback callback were embedded in a class, then one could potentially make a class variable that holds “whats my IO curser offset?” information for the callback function to refer to.

But buildSimpleDataExample is not in a class or even a struct instance! So what is a lone little static function to do?

The good news is that Data is magic. Data (unlike say [UInt8]) has private size variables allocated with it. This means it can implement func append(_ bytes: UnsafeRawPointer, length: Int)

    @inlinable // This is @inlinable as it does not escape the _DataStorage boundary layer.
    func append(_ bytes: UnsafeRawPointer, length: Int) {
        precondition(length >= 0, "Length of appending bytes must not be negative")
        let origLength = _length
        let newLength = origLength + length
        if _capacity < newLength || _bytes == nil {
            ensureUniqueBufferReference(growingTo: newLength, clear: false)
        }
        _length = newLength
        __DataStorage.move(_bytes!.advanced(by: origLength), bytes, length)
    }

The existence of this function means we can append when all we have is a pointer. It’s amazing.

The example C code floating around the internet for writing to a buffer is:

//Make a new compound type that stores the pointer and the cursor location/size
struct mem_encode
{
  char *buffer;
  size_t size;
}
void my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
  /* with libpng15 next line causes pointer deference error; use libpng12 */
  struct mem_encode* p=(struct mem_encode*)png_get_io_ptr(png_ptr); /* was png_ptr->io_ptr */
  size_t nsize = p->size + length;

  /* allocate or grow buffer */
  if(p->buffer)
    p->buffer = realloc(p->buffer, nsize);
  else
    p->buffer = malloc(nsize);

  if(!p->buffer)
    png_error(png_ptr, "Write Error");

  /* copy new bytes to end of buffer */
  memcpy(p->buffer + p->size, data, length);
  p->size += length;
}

Where you can see that it is implementing by hand that buffer tracking (mem_encode) and the buffer expansion by hand.

Thankfully using Data appears to make all of that unnecessary.

Simplified API

There is apparently a simpler API. However, they target “in-memory bitmap formats” not RGBA, which this project requires.

A “simplified API” has been added (see documentation in png.h and a simple example in contrib/examples/pngtopng.c). The new publicly visible API includes the following:

   macros:
     PNG_FORMAT_*
     PNG_IMAGE_*
   structures:
     png_control
     png_image
   read functions
     png_image_begin_read_from_file()
     png_image_begin_read_from_stdio()
     png_image_begin_read_from_memory()
     png_image_finish_read()
     png_image_free()
   write functions
     png_image_write_to_file()
     png_image_write_to_memory()
     png_image_write_to_stdio()

If XCode is having trouble finding libpng

What does compiling in the command line say?

cd $PROJECT_DIR
swift package clean
swift build --verbose

Is package-config installed?

brew install pkg-config

Homebrew path correct?

When installing homebrew did you update the path? Especially important on M1 macs where the install path has changed to /opt/homebrew/ from /usr/local/include/.

Check cat ~/.zprofile it should exist and contain eval "$(/opt/homebrew/bin/brew shellenv)"

If ~/.zprofile does not exist and contain that line run: echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile eval $(/opt/homebrew/bin/brew shellenv)

Commands run: export HOMEBREW_PREFIX="/opt/homebrew"; export HOMEBREW_CELLAR="/opt/homebrew/Cellar"; export HOMEBREW_REPOSITORY="/opt/homebrew"; export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}"; export MANPATH="/opt/homebrew/share/man${MANPATH+:$MANPATH}:"; export INFOPATH="/opt/homebrew/share/info:${INFOPATH:-}";

If this command provides no output if it has already been run.

To check the path: echo $PATH it should now contain /opt/homebrew/bin:/opt/homebrew/sbin:

Build Issues by Platform

Have I gotten this library to compile for…

Intel Mac

Yes, totally fine with pkg-config installed.

Apple Silicon Mac

Yes, totally fine with pkg-config installed.

iOS Simulator: No

Not a high priority to fix.

Building for iOS Simulator, but linking in dylib built for macOS, file '/usr/local/opt/libpng/lib/libpng16.dylib' for architecture x86_64

Options:

iOS Hardware: No

Not a high priority to fix.

Linker command failed with exit code 1 (use -v to see invocation)

Related to above.

Fake Linux (Github Action) - Not yet

Higer priority to fix.

Use action like one checking APITizer

name: Build Linux

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - main
      
jobs:
  build:
    name: Build Linux
    runs-on: ubuntu-latest
    container:
      image: swift:latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Build Linux framework
        run: |
           swift build
           swift test           
      - name: Build Linux Demo
        run: |
            cd Demo/Demo\ Ubuntu
            swift build            

This one fails with warning: you may be able to install libpng using your system-packager:apt-get install libpng-dev

The Swift Code

So far the library isn’t that big, so here’s a copy of the code

Struct declaration

#if os(Linux)
import Glibc
#else
import Darwin
#endif

import Foundation
import png

public struct SwiftLIBPNG {
    //http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.tbl.1
    public static let pngFileSignature:[UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
    
    
    public init() {}
    
    public static func version() {
        let version = png_access_version_number()
        print(version)
    }
    
    public static func versionInfo()
    {
        let test2 = String(format:"   Compiled with libpng %@; using libpng %lu.\n", PNG_LIBPNG_VER_STRING, png_access_version_number())
        print(test2)
        
    }
    
    public static func checkSignature(data:Data, offset:Int = 0) -> Bool {
        withUnsafeBytes(of:data) { bytesPtr in
            if let base = bytesPtr.assumingMemoryBound(to: UInt8.self).baseAddress {
                return isValidSignature(bytePointer: base + offset, start: 0, count: 8)
            } else {
                return false
            }
        }
    }
    
    static func isValidSignature(bytePointer:UnsafePointer<UInt8>, start:Int = 0, count:CInt) -> Bool {
        //png_sig_cmp(T##sig: png_const_bytep!##png_const_bytep!, T##start: Int##Int, T##num_to_check: Int##Int)
        return png_sig_cmp(bytePointer, start, 8) == 0
        
    }
    
    static func printIOState(pngPointer:OpaquePointer) {
//        /* The flags returned by png_get_io_state() are the following: */
//        #  define PNG_IO_NONE        0x0000   /* no I/O at this moment */
//        #  define PNG_IO_READING     0x0001   /* currently reading */
//        #  define PNG_IO_WRITING     0x0002   /* currently writing */
//        #  define PNG_IO_SIGNATURE   0x0010   /* currently at the file signature */
//        #  define PNG_IO_CHUNK_HDR   0x0020   /* currently at the chunk header */
//        #  define PNG_IO_CHUNK_DATA  0x0040   /* currently at the chunk data */
//        #  define PNG_IO_CHUNK_CRC   0x0080   /* currently at the chunk crc */
//        #  define PNG_IO_MASK_OP     0x000f   /* current operation: reading/writing */
//        #  define PNG_IO_MASK_LOC    0x00f0   /* current location: sig/hdr/data/crc */
//        #endif /* IO_STATE */
        let codeString:String = String(String(String(png_get_io_state(pngPointer), radix:2).reversed()).padding(toLength: 8, withPad: "0", startingAt: 0).reversed())
        //1000010 -> currently writing at the chunk header
        //1000010 -> currently writing at the chunk crc
        print(codeString)
    }
    
    //MARK: Global Error Callbacks
    
    struct PNGErrorInfo {
        var png_ptr:OpaquePointer?
        var info_ptr:OpaquePointer?
        var fileHandle:UnsafeMutablePointer<FILE>?
        var testExtraData:UInt32
        
        func print_info() {
            print("\(String(describing: png_ptr)), \(String(describing: info_ptr)), \(String(describing: fileHandle)), \(testExtraData)")
        }
    }

    static let writeErrorCallback:@convention(c) (Optional<OpaquePointer>, Optional<UnsafePointer<CChar>>) -> () = { png_ptr, message in
        if let error_ptr = png_get_error_ptr(png_ptr) {
            print("There was a non nil error pointer set a \(error_ptr)")
            var typed_error_ptr = error_ptr.load(as: PNGErrorInfo.self)//error_ptr.assumingMemoryBound(to: PNGErrorInfo.self)
            typed_error_ptr.print_info()
            //If aborting whole program everything should be freed automatically, but in case not...
            precondition(png_ptr == typed_error_ptr.png_ptr)
            png_destroy_write_struct(&typed_error_ptr.png_ptr, &typed_error_ptr.info_ptr)
            if typed_error_ptr.fileHandle != nil {
                fclose(typed_error_ptr.fileHandle)
            }
        }
    
        if let message {
            print("libpng crashed with warning: \(String(cString: message))")
        } else {
            print("libpng crashed without providing a message.")
        }
        
        //Some way to kill png?
        //How to leave PNG write...
        exit(99)  //see also https://en.cppreference.com/w/c/program/atexit
        //abort() //terminates the process by raising a SIGABRT signal, possible handler?
        
    }
    
    static let writeWarningCallback:@convention(c) (Optional<OpaquePointer>, Optional<UnsafePointer<CChar>>) -> () = { png_ptr, message in
        if let error_ptr = png_get_error_ptr(png_ptr) {
            print("There was a non nil error pointer set a \(error_ptr)")
            
        }
        if let message {
            print("libpng sends warning: \(String(cString: message))")
        } else {
            print("libpng sends unspecified warning")
        }
        
        //Use the error pointer to set flags, etc.
    }
    
    
}

Errors

enum PNGError: Error, CustomStringConvertible {
    case message(String)
    case outOfMemory
    
    public var description: String {
        switch self {
        case let .message(message): return message
        case .outOfMemory:
            return "Out of Memory"
        }
    }
    init(_ message: String) {
        self = .message(message)
    }
}

Reading

#if os(Linux)
import Glibc
#else
import Darwin
#endif

import png


//http://www.libpng.org/pub/png/libpng-manual.txt
// Text with //DOC: prefix is from the documentation above.

extension SwiftLIBPNG {
    
    //TODO: This code (is expected to) hard crash if the file is not a valid PNG. Default error handling is stderr and ABORT.
    
    //NOT using "libpng simplified API"
    public static func simpleFileRead(from path:String) throws -> [UInt8] {
        
        let file_ptr = fopen(path, "r")
        if file_ptr == nil {
            throw PNGError("File pointer not available")
        }
        
        //Make the pointer for storing the png's current state struct.
        //Using this function tells libpng to expect to handle memory management, but `png_destroy_read_struct` will still need to be called.
        //takes the version string define, and some pointers that will override rides to default error handling that are not used in this simple case.
        /*C:-- png_create_read_struct(
         user_png_ver: png_const_charp!,
         error_ptr: png_voidp!,
         error_fn: png_error_ptr! (png_structp?, png_const_charp?) -> Void,
         warn_fn: png_error_ptr!  (png_structp?, png_const_charp?) -> Void)
         */
        var png_ptr:OpaquePointer? = png_create_read_struct(PNG_LIBPNG_VER_STRING, nil, nil,
                                                            nil);
        if (png_ptr == nil) {
            fclose(file_ptr);
            throw PNGError.outOfMemory
        }
        
        //Makes the pointer to handle information about how the underlying PNG data needs to be manipulated.
        //C:-- png_create_info_struct(png_const_structrp!)
        var info_ptr:OpaquePointer? = png_create_info_struct(png_ptr);
        if (info_ptr == nil) {
            png_destroy_read_struct(&png_ptr, nil, nil); //TODO: not all examples do this
            throw PNGError.outOfMemory;
        }
        
        //## Why no setjmp?? - see README
     
        //set destination pointer and file source pointer
        //C:-- png_init_io(png_ptr: png_structrp!, fp: png_FILE_p!)
        png_init_io(png_ptr, file_ptr);
        
        //If you don't want to use fileio , can instead
        /*C:-- png_set_read_fn(
         png_ptr: png_structrp!,
         io_ptr: png_voidp!,
         read_data_fn: png_rw_ptr! (png_structp?, png_bytep?, Int) -> Void)
         */
        //TODO:(see write for example)
        
        
        //Optional: If you used the file stream already and pulled off the file signature for verification purposes, tell the reader it's been done already.
        //png_set_sig_bytes(png_ptr, number);
        
        //Optional: One can also decide how to handle CRC errors (possible bad chunks. )
        //png_set_crc_action(png_ptr, crit_action, ancil_action);
        
        //Other Options: Setting size limits, how to handle chunks that are not defined by the spec
        
        
        //Optional: Set a row-completion handler
        //`pointer` is the png_ptr
        //`row` is the NEXT row number
        //`pass` is always 0 in non interlaced pngs, but also refers to the NEXT row's pass count.
        let rowCompleteCallback:@convention(c) (OpaquePointer?, UInt32, Int32) -> () = { png_ptr, row, pass in
            print(png_ptr ?? "nil", row, pass)
        }
        png_set_read_status_fn(png_ptr, rowCompleteCallback);
        
        //Optional: Do a lot of setting of gamma and alpha handling for your system.
        //Possibly overridden by PNG settings, in some situations.
        //No example code provided.
        
        
        //This is a SIMPLE read. Assumes
        //- the entire file can be _all_ loaded into memory, all at once
        //- that this can be done before any header info needed
        //- don't want to malloc your own row storage
        //- don't need to do successive transforms on the data at read in
        //- any transform you want to do has PNG_TRANSFORM_ define
        
        //Set what transforms desired. If any
        //TODO: Bit print out of masks.
        let png_transforms = PNG_TRANSFORM_IDENTITY //No transforms
        
        //DOC: This call is equivalent to png_read_info(), followed the set of transformations indicated by the transform mask, then png_read_image(), and finally png_read_end().
        png_read_png(png_ptr, info_ptr, png_transforms, nil)
        
        //DOC: If you don't allocate row_pointers ahead of time, png_read_png() will do it, and it'll be free'ed by libpng when you call png_destroy_*().
        let row_pointers = png_get_rows(png_ptr, info_ptr)
        
        //TODO: What happens to end info if no end_info struct? Double make sure is in info_ptr. (doc says it will be.)
        
        let width = png_get_image_width(png_ptr,info_ptr)
        let height = png_get_image_height(png_ptr,info_ptr)
        let color_type_code = png_get_color_type(png_ptr, info_ptr)
        let bit_depth = png_get_bit_depth(png_ptr, info_ptr)
        let channel_count = png_get_channels(png_ptr, info_ptr)
        let row_byte_width = png_get_rowbytes(png_ptr, info_ptr)
        
        print("""
                width: \(width),
                height: \(height),
                colorType: \(color_type_code),
                bitDepth: \(bit_depth),
                channelCount: \(channel_count),
                rowByteWidth: \(row_byte_width)
        """)
        //let pixel_width = channelCountFor(colorTypeCode: color_type_code) * UInt32(bit_depth)
        
        var imagePixels:[UInt8] = []
        let imageRows = UnsafeBufferPointer(start: row_pointers, count: Int(height))
        
        for rowPointer in imageRows {
            
            let rowBufferPtr = UnsafeBufferPointer(start: rowPointer, count: row_byte_width)
            imagePixels.append(contentsOf: rowBufferPtr)
        }
        
        //-------------------------------------------------------   PNG CLEANUP
        
        //if set end_info nuke that too. DO NOT free row_pointers since used `png_get_rows`
        png_destroy_read_struct(&png_ptr, &info_ptr, nil);
        fclose(file_ptr)
        
        //---------------------------------------------------------------------
        
        return imagePixels
        
    }
}

Writing

#if os(Linux)
import Glibc
#else
import Darwin
#endif

import Foundation

import png


extension SwiftLIBPNG {
    // EXAMPLE USAGE
    //    func writeImage() {
    //        let width = 5
    //        let height = 3
    //        var pixelData:[UInt8] = []
    //
    //        for _ in 0..<height {
    //            for _ in 0..<width {
    //                pixelData.append(0x77)
    //                pixelData.append(0x00)
    //                pixelData.append(UInt8.random(in: 0...UInt8.max))
    //                pixelData.append(0xFF)
    //            }
    //        }
    //
    //        let data = try? SwiftLIBPNG.buildSimpleDataExample(width: 5, height: 3, pixelData: pixelData)
    //        if let data {
    //            for item in data {
    //                print(String(format: "0x%02x", item), terminator: "\t")
    //            }
    //            print()
    //
    //            let locationToWrite = URL.documentsDirectory.appendingPathComponent("testImage", conformingTo: .png)
    //            do {
    //                try data.write(to: locationToWrite)
    //            } catch {
    //                print(error.self)
    //            }
    //        }
    //    }
    
    //NOT using "libpng simplified API"
    //takes a width, height and pixel data in RR GG BB AA byte order
    public static func buildSimpleDataExample(width:UInt32, height:UInt32, pixelData:[UInt8]) throws -> Data {
        var pixelsCopy = pixelData //TODO: This or inout? OR... is there away around need for MutableCopy?
        
        //-----------------------------  INTENTIONAL ERROR
        let bitDepth:UInt8 = 1 //should be 8 (1 byte, values 1, 2, 4, 8, or 16) (has to be 8 or 16 for RGBA)
        //-----------------------------  END INTENTIONAL ERROR
        let colorType = PNG_COLOR_TYPE_RGBA //UInt8(6), (1 byte, values 0, 2, 3, 4, or 6) (6 == red, green, blue and alpha)
        
        
        var pngIOBuffer = Data() //:[UInt8] = [] // //
        withUnsafePointer(to: pngIOBuffer) { print("io buffer declared: \($0)") }
        
        var pngWriteErrorInfo = PNGErrorInfo(testExtraData: 42)
        
        //Make the pointer for storing the png's current state struct.
        //Using this function tells libpng to expect to handle memory management, but `png_destroy_write_struct` will still need to be called.
        //takes the version string define, and some pointers that will override rides to default error handling that are not used in this simple case.
        /*C:-- png_create_write_struct(
         user_png_ver: png_const_charp!,
         error_ptr: png_voidp!,  //safe to leave as nil if you don't need to pass a message to your error funcs.
         error_fn: png_error_ptr! (png_structp?, png_const_charp?) -> Void,
         warn_fn: png_error_ptr!  (png_structp?, png_const_charp?) -> Void)
         */
        var png_ptr:OpaquePointer? = png_create_write_struct(PNG_LIBPNG_VER_STRING, &pngWriteErrorInfo, writeErrorCallback, writeWarningCallback)
        if (png_ptr == nil) { throw PNGError.outOfMemory }
        
        //Makes the pointer to handle information about how the underlying PNG data needs to be manipulated.
        //C:-- png_create_info_struct(png_const_structrp!)
        var info_ptr:OpaquePointer? = png_create_info_struct(png_ptr);
        if (info_ptr == nil) {
            png_destroy_write_struct(&png_ptr, nil);
            throw PNGError.outOfMemory;
        }
        
        pngWriteErrorInfo.fileHandle = nil
        pngWriteErrorInfo.png_ptr = png_ptr
        pngWriteErrorInfo.info_ptr = info_ptr
        
        //see README for notes on jmpdef
        
        //If writing palletized PNG, see documentation if want to change
        //default palette checking behavior.
        
        //Set the output destination. If writing to file can use png_init_io(png_ptr, file_ptr); as in simpleFileRead or, one can construct a data receiving function and set it with:
        /*C:--png_set_write_fn(
         png_ptr: png_structrp!,
         io_ptr: png_voidp!,
         write_data_fn: png_rw_ptr! (png_structp?, png_bytep?, Int) -> Void,
         output_flush_fn: png_flush_ptr! (png_structp?) -> Void
         )
         */
        
        let writeDataCallback: @convention(c) (Optional<OpaquePointer>, Optional<UnsafeMutablePointer<UInt8>>, Int) -> Void = { png_ptr, data_io_ptr, length in
            guard let output_ptr:UnsafeMutableRawPointer = png_get_io_ptr(png_ptr) else { return }
            guard let data_ptr:UnsafeMutablePointer<UInt8> = data_io_ptr else { return }
            //print("callback io output buffer: \(output_ptr)")
            //print("callback io data buffer: \(data_ptr)")
            
            let typed_output_ptr = output_ptr.assumingMemoryBound(to: Data.self)
            typed_output_ptr.pointee.append(data_ptr, count: length)
        }
        
        png_set_write_fn(png_ptr, &pngIOBuffer, writeDataCallback, nil)
        
        //if have already handled appending header or otherwise don't need to.
        //png_set_sig_bytes(png_ptr, 8);
        
        //Optional: Set a row-completion handler
        //`pointer` is the png_ptr
        //`row` is the NEXT row number
        //`pass` is always 0 in non interlaced pngs, but also refers to the NEXT row's pass count.
        //READ example uses callback, but also takes closure.
        png_set_write_status_fn(png_ptr) { png_ptr, row, pass in
            print(png_ptr ?? "nil", row, pass)
        }
        
        //Set compression schemes. Don't call to leave as the default. libpng optimizes for a good balance between writing speed and resulting file compression.
        //NOTE: If you need the data "as is", that is not the default scheme.
        //http://www.libpng.org/pub/png/book/chapter09.html
        //https://www.ietf.org/rfc/rfc1951.txt
        //C:--png_set_filter(png_ptr:png_structrp!, method: Int32, filters: Int32)
        
        //-------------------- WHERE INTENTIONAL ERROR HAPPENS
        //---------------------------------------------------------------- IHDR
        png_set_IHDR(png_ptr, info_ptr, width, height,
                     Int32(bitDepth), colorType,
                     PNG_INTERLACE_NONE,
                     PNG_COMPRESSION_TYPE_DEFAULT,
                     PNG_FILTER_TYPE_DEFAULT
        )
        
        //------------------------------------------------------------------
        //Anything it that isn't text or time has to come before IDAT, but it doesn't matter the order.
        //libpng will order them correctly on output.
        
        //---------------------------------------------------------------  IDAT
        
        
        //Note, if instead we were doing the "long form" save we could change the compression
        //schemes mid image. (And this is why you use ImageMagick...)
        pixelsCopy.withUnsafeMutableBufferPointer{ pd_pointer in
            var row_pointers:[Optional<UnsafeMutablePointer<UInt8>>] = []
            for rowIndex in 0..<height {
                let rowStart = rowIndex * width * 4
                row_pointers.append(pd_pointer.baseAddress! + Int(rowStart))
            }
            
            //png_set_rows(png_ptr: png_const_structrp!, info_ptr: png_inforp!, row_pointers: png_bytepp!)
            png_set_rows(png_ptr, info_ptr, &row_pointers)
            
            //high level write.
            //TODO: Confirm theory has to be inside so row pointers still valid.
            png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nil)
        }
        
        //--------------------------------------------------------   PNG CLEANUP
        png_destroy_write_struct(&png_ptr, &info_ptr);
        //---------------------------------------------------------------------
        
        return pngIOBuffer
    }
    
}

Defined in SwiftLIBPNG.swift

    static let writeDataCallback: @convention(c) (Optional<OpaquePointer>, Optional<UnsafeMutablePointer<UInt8>>, Int) -> Void = { png_ptr, data_io_ptr, length in
        guard let output_ptr:UnsafeMutableRawPointer = png_get_io_ptr(png_ptr) else { return }
        guard let data_ptr:UnsafeMutablePointer<UInt8> = data_io_ptr else { return }
        //print("callback io output buffer: \(output_ptr)")
        //print("callback io data buffer: \(data_ptr)")
        
        let typed_output_ptr = output_ptr.assumingMemoryBound(to: Data.self)
        typed_output_ptr.pointee.append(data_ptr, count: length)
    }