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.
-
The main site: http://www.libpng.org
-
How PNGs work. Code is out of date but still very much a good read: http://www.libpng.org/pub/png/book/
-
More up to date than /book/ but still seems to lag :http://www.libpng.org/pub/png/libpng-manual.txt
-
Actual most recent: https://github.com/glennrp/libpng/
-
‘just the spec ma’am’ - https://www.w3.org/TR/2003/REC-PNG-20031110/, https://w3c.github.io/PNG-spec/
-
zlib spec for analyzing IDAT https://www.zlib.net/manual.html
Inspecting Data
To just look at the HEX
- https://hexed.it
- VSCode plugin Microsoft Hex Editor
- Shell option 1
od -t x1 png-transparent.png
- Shell option 2 for bigger files
tail -f png-transparent.png | hexdump -C
Testing and verification
- Very handy PNG verifier: https://www.nayuki.io/page/png-file-chunk-inspector
- “The “official” test-suite for PNG” http://www.schaik.com/pngsuite/
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
PNG_SETJMP_NOT_SUPPORTED
in older code#define PNG_NO_SETJMP
turns the feature off#define PNG_SETJMP_SUPPORTED
turns the feature on)- Also might be
#undef PNG_SETJMP_SUPPORTED
#include <setjmp.h>
is an indicator the code will be usingpng_jmpbuf
style error setting
For more information on libpng and setjmp/longjmp:
- search for “Setjmp/longjmp issues” INSTALL guide
- Also:example.c
- search for “Error handling in libpng is done through” in the manual
For more on setjmp/longjmp in general:
- https://en.wikipedia.org/wiki/Setjmp.h
- https://web.eecs.utk.edu/~huangj/cs360/360/notes/Setjmp/lecture.html
- https://stackoverflow.com/questions/1692814/exception-handling-in-c-what-is-the-use-of-setjmp-returning-0
- https://stackoverflow.com/questions/14685406/practical-usage-of-setjmp-and-longjmp-in-c
- https://stackoverflow.com/questions/7334595/longjmp-out-of-signal-handler
- https://tratt.net/laurie/blog/2005/timing_setjmp_and_the_joy_of_standards.html
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?
- TODO: write about filter types http://www.libpng.org/pub/png/book/chapter09.html None Each byte is unchanged. Sub Each byte is replaced with the difference between it and the ``corresponding byte’’ to its left. Up Each byte is replaced with the difference between it and the byte above it (in the previous row, as it was before filtering). Average Each byte is replaced with the difference between it and the average of the corresponding bytes to its left and above it, truncating any fractional part. Paeth Each byte is replaced with the difference between it and the Paeth predictor of the corresponding bytes to its left, above it, and to its upper left.
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:
- https://developer.apple.com/forums/thread/657913
- https://stackoverflow.com/questions/63607158/xcode-building-for-ios-simulator-but-linking-in-an-object-file-built-for-ios-f
- https://www.dynamsoft.com/barcode-reader/docs/mobile//programming/objectivec-swift/faq/arm64-simulator-error.html
- https://rlaguilar.com/posts/integrate-c-library-ios-project-swift/
- https://thisisyuu.github.io/2020/MISC-Building-universal-binaries-from-C-library-for-an-iOS-project/
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
- TODO: figure out how to make one that works. (may also have to install pkg-config)
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)
}