Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs 4.3 #61

Merged
merged 9 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Notes/improving-deflate-compression-ratio.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Improving *deflate* compression ratios
# Improving DEFLATE compression ratios

Version 4 of Swift *PNG* features a native Swift implementation of the *DEFLATE* and *INFLATE* algorithms, as described in the [rfc-1951](https://tools.ietf.org/html/rfc1951). *DEFLATE* implementations can vary widely in quality, with some implementations producing far better-optimized (higher compression ratio) output streams than others. So, just as it is important to ensure Swift *PNG*’s *INFLATE* procedure [is as fast as that of the *zlib* C library](../low-level-swift-optimization.md), it is also important to ensure Swift *PNG*’s *DEFLATE* output is as optimal as *zlib*’s. This readme documents some comparisons between Swift *PNG* and *libpng*/*zlib*, as well as choices of compression parameters in the framework at the time of writing.
Version 4 of Swift *PNG* features a native Swift implementation of the *DEFLATE* and *INFLATE* algorithms, as described in the [rfc-1951](https://tools.ietf.org/html/rfc1951). *DEFLATE* implementations can vary widely in quality, with some implementations producing far better-optimized (higher compression ratio) output streams than others. So, just as it is important to ensure Swift *PNG*’s *INFLATE* procedure [is as fast as that of the *zlib* C library](https://swiftinit.org/articles/low-level-swift-optimization), it is also important to ensure Swift *PNG*’s *DEFLATE* output is as optimal as *zlib*’s. This readme documents some comparisons between Swift *PNG* and *libpng*/*zlib*, as well as choices of compression parameters in the framework at the time of writing.

## Methodology

Expand Down
71 changes: 14 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,19 @@

*Swift PNG* is a Foundation-less, cross-platform framework for decoding, inspecting, editing, and encoding PNG images. The framework is written in pure Swift, and will compile and provide consistent behavior on all Swift platforms. The library also comes with built-in file system support on linux, macOS, and Windows.

Swift *PNG* is [available](LICENSE) under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/). The [example programs](examples/) are public domain and can be adapted freely.
Swift *PNG* is [available](LICENSE) under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/). The [example programs](Snippets/) are public domain and can be adapted freely.

## [tutorials and example programs](examples/)
Swift *PNG*’s [documentation](https://swiftinit.org/docs/swift-png/png) is available on Swiftinit!

1. [basic decoding](Snippets/BasicDecoding.swift) ([sources](Snippets/BasicDecoding.swift))
2. [basic encoding](Snippets/BasicEncoding.swift) ([sources](Snippets/BasicEncoding.swift))
3. [using indexed images](Snippets/Indexing.swift) ([sources](Snippets/Indexing.swift))
4. [using iphone-optimized images](Snippets/iPhoneOptimized.swift) ([sources](Snippets/iPhoneOptimized.swift))
5. [working with metadata](Snippets/ImageMetadata.swift) ([sources](Snippets/ImageMetadata.swift))
6. [using in-memory images](Snippets/ImagesInMemory.swift) ([sources](Snippets/ImagesInMemory.swift))
7. [online decoding](Snippets/OnlineDecoding.swift) ([sources](Snippets/OnlineDecoding.swift))
8. [custom color targets](Snippets/CustomColor.swift) ([sources](Snippets/CustomColor.swift))

## [api reference](https://tayloraswift.github.io/swift-png)

* [**`PNG.PNG`**](https://tayloraswift.github.io/swift-png/PNG)
* [**`PNG.LZ77`**](https://tayloraswift.github.io/swift-png/LZ77)
* [**`PNG.System`**](https://tayloraswift.github.io/swift-png/System)

## getting started
## Getting started

To use *Swift PNG* in a project, add this descriptor to the `dependencies` list in your `Package.swift` file:

```swift
.package(url: "https://github.com/tayloraswift/swift-png", .exact("4.0.3"))
```

## basic usage
## Basic usage

Decode an image:

Expand Down Expand Up @@ -72,52 +57,24 @@ func encode(png path:String, size:(x:Int, y:Int), pixels:[PNG.RGBA<UInt8>]) thro
}
```

## features

- ***Powerful interfaces.*** *Swift PNG*’s expressive, strongly-typed APIs make working with PNG images easy for beginners and advanced users alike. If your code compiles, you’re already most of the way there. Power users can take advantage of [custom indexing](examples/#using-indexed-images), [manual decoding workflows](examples/#online-decoding), and [user-defined color targets](examples/#custom-color-targets).

- ***Superior compression***. *Swift PNG* supports minimum cost path-based [*DEFLATE*](https://tools.ietf.org/html/rfc1951) optimization, which is why it offers four additional compression levels beyond what [*libpng*](http://www.libpng.org/pub/png/libpng.html) supports. *Swift PNG* outperforms *libpng* at its highest compression setting by [significant margins](benchmarks#compression-level-13) for almost all types of input images.

- ***Competitive performance.*** *Swift PNG* offers [competitive performance](benchmarks/) compared to *libpng*. On appropriate CPU architectures, the *Swift PNG* encoder makes use of [hardware-accelerated hash tables](https://engineering.fb.com/2019/04/25/developer-tools/f14/) for even greater performance.

- ***Pure Swift, all the way down.*** *Swift PNG* is powered by its own, native Swift *DEFLATE* implementation. It depends only on other Foundation-less, pure-Swift libraries, and therefore does not need to link Foundation. This also means the core components of *Swift PNG* work on any platform that Swift itself works on, and that *Swift PNG*’s performance [improves as the Swift compiler matures](benchmarks#performance-by-toolchain).

- ***Batteries included.*** *Swift PNG* comes with [built-in color targets](https://tayloraswift.github.io/swift-png/PNG/Color/) with support for [premultiplied alpha](https://tayloraswift.github.io/swift-png/PNG/RGBA/premultiplied/). [Convolution](https://tayloraswift.github.io/swift-png/PNG/convolve(_:dereference:kernel:)/) and [deconvolution](https://tayloraswift.github.io/swift-png/PNG/deconvolve(_:reference:kernel:)/) helper functions make [implementing custom color targets](examples/#custom-color-targets) a breeze.
## Features

On MacOS and Linux, *Swift PNG* has built-in file system support, allowing you to [compress](https://tayloraswift.github.io/swift-png/PNG/Image/compress(path:level:hint:)/) or [decompress](https://tayloraswift.github.io/swift-png/PNG/Image/decompress(path:)/) an image, given a filepath, in a single function call. Other platforms can take advantage of *Swift PNG*’s [protocol-oriented IO](https://tayloraswift.github.io/swift-png/PNG/Bytestream/) to implement their own data loading.
- ***Powerful interfaces.*** *Swift PNG*’s expressive, strongly-typed APIs make working with PNG images easy for beginners and advanced users alike. If your code compiles, you’re already most of the way there. Power users can take advantage of [custom indexing](https://swiftinit.org/docs/swift-png/png/indexing), [manual decoding workflows](https://swiftinit.org/docs/swift-png/png/onlinedecoding), and [user-defined color targets](https://swiftinit.org/docs/swift-png/png/customcolor).

- ***First-class iPhone optimization support.*** *Swift PNG* requires no custom setup or third-party plugins to handle [iPhone-optimized](examples/#using-iphone-optimized-images) PNG images. iPhone-optimized images just work, on all platforms. Reproduce [`pngcrush`](https://developer.apple.com/library/archive/qa/qa1681/_index.html)’s output with [bit width-aware alpha premultiplication](https://tayloraswift.github.io/swift-png/PNG/RGBA/premultiplied(as:)/), for seamless integration anywhere in your application stack.
- ***Superior compression***. *Swift PNG* supports minimum cost path-based [*DEFLATE*](https://tools.ietf.org/html/rfc1951) optimization, which is why it offers four additional compression levels beyond what [*libpng*](http://www.libpng.org/pub/png/libpng.html) supports.

- ***Comprehensive metadata support.*** *Swift PNG* can parse and validate all public PNG chunks, which are accessible as [strongly-typed metadata records](https://tayloraswift.github.io/swift-png/PNG/Metadata/).
- ***Competitive performance.*** *Swift PNG* offers competitive performance compared to *libpng*. On appropriate CPU architectures, the *Swift PNG* encoder makes use of [hardware-accelerated hash tables](https://engineering.fb.com/2019/04/25/developer-tools/f14/) for even greater performance.

- ***Modern error handling.*** *Swift PNG* has a fully stateless and Swift-native [error-handling system](https://tayloraswift.github.io/swift-png/PNG/Error/).
- ***Pure Swift, all the way down.*** *Swift PNG* is powered by its own, native Swift *DEFLATE* implementation. It depends only on other Foundation-less, pure-Swift libraries, and therefore does not need to link Foundation. This also means the core components of *Swift PNG* work on any platform that Swift itself works on, and that *Swift PNG*’s performance improves as the Swift compiler matures.

## infrastructure
- ***Batteries included.*** *Swift PNG* comes with [built-in color targets](https://swiftinit.org/ptcl/swift-png/png/_pngcolor) with support for [premultiplied alpha](https://swiftinit.org/docs/swift-png/png/png/rgba.premultiplied). [Convolution](https://swiftinit.org/docs/swift-png/png/png.convolve(_:dereference:kernel:)?hash=O92V) and [deconvolution](https://swiftinit.org/docs/swift-png/png/png.deconvolve(_:as:depth:kernel:)?hash=2SQA0) helper functions make [implementing custom color targets](https://swiftinit.org/docs/swift-png/png/customcolor) a breeze.

A list of build flags can be found [here](build.md). Project automation scripts live in the [`utils/`](utils/) directory, and are invoked as follows:
- ***First-class iPhone optimization support.*** *Swift PNG* requires no custom setup or third-party plugins to handle [iPhone-optimized](https://swiftinit.org/docs/swift-png/png/iphoneoptimized) PNG images. iPhone-optimized images just work, on all platforms. Reproduce [`pngcrush`](https://developer.apple.com/library/archive/qa/qa1681/_index.html)’s output with [bit width-aware alpha premultiplication](https://swiftinit.org/docs/swift-png/png/png/rgba.premultiplied(as:)), for seamless integration anywhere in your application stack.

- [**`utils/examples`**](utils/examples) `[-c/--configuration debug | release]`
- ***Comprehensive metadata support.*** *Swift PNG* can parse and validate all public PNG chunks, which are accessible as [strongly-typed metadata records](https://swiftinit.org/docs/swift-png/png/png/metadata).

Builds and runs the example programs in the [`examples/`](examples/) directory. The `--configuration` argument specifies the Swift build mode to use.

- [**`utils/benchmark`**](utils/benchmark) `[-t/--trials n0 n1 n2]` `[-s/--save]` `[-l/--load]`

Runs library performance and compression benchmarks, and generates a [performance report](benchmarks/).

The `--trials` argument specifies the number of trials to run, for decompression, compression, and historical toolchain benchmarks, respectively. The `--save` argument makes this script store benchmark measurements in cache files; the `--load` argument makes this script use cached measurements instead of re-running the benchmarks.

- [**`utils/generate-documentation`**](utils/generate-documentation) `[-l/--local]`

Generates documentation pages for the library using [*Entrapta*](https://github.com/tayloraswift/entrapta). A [Github action](.github/workflows/docs.yml) invokes this script and deploys the output to the [*Swift PNG* API reference website](https://tayloraswift.github.io/swift-png) on every commit.

The CI [runs](.github/workflows/build.yml) *Swift PNG*’s test suites with the following invocations:

```bash
swift run PNGTests
swift run -c release PNGIntegrationTests
swift run -c release PNGCompressionTests
```
- ***Modern error handling.*** *Swift PNG* has a fully stateless and Swift-native [error-handling system](https://swiftinit.org/docs/swift-png/png/png/error).

## see also
## See also

* [Swift *JPEG*](https://github.com/tayloraswift/jpeg)
22 changes: 20 additions & 2 deletions Snippets/PNG/BasicEncoding.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// In this example code, we have loaded pixel data from a raw `.rgba` data file using the
// library’s [file system APIs](/PNG/System/File). As previously mentioned, these APIs are only
// available on MacOS and Linux.

// snippet.LOAD_RGBA
import PNG

/// https://commons.wikimedia.org/wiki/File:Photo_of_a_venetian_mask_in_a_studio_photo_session.jpg
let path:String = "Sources/PNG/docs.docc/BasicEncoding/BasicEncoding"
let size:(x:Int, y:Int) = (638, 425)

Expand All @@ -25,28 +29,38 @@ else
fatalError("failed to open file '\(path).rgba'")
}

// snippet.LAYOUT

let layout:(rgb:PNG.Layout, v:PNG.Layout) =
(
rgb: .init(format: .rgb8(palette: [], fill: nil, key: nil)),
v: .init(format: .v8( fill: nil, key: nil))
)

// snippet.end

do
{
// snippet.PACK_RGB
let image:PNG.Image = .init(packing: rgba, size: size, layout: layout.rgb)
// snippet.COMPRESS_RGB
try image.compress(path: "\(path)-color-rgb.png", level: 9)

// snippet.COMPRESS_RGB_LEVELS
for level:Int in [0, 4, 8, 13]
{
try image.compress(path: "\(path)-color-rgb@\(level).png", level: level)
}
// snippet.end
}
do
{
// snippet.SAVE_V
let image:PNG.Image = .init(packing: rgba, size: size, layout: layout.v)
try image.compress(path: "\(path)-color-v.png", level: 9)
// snippet.end
}

// snippet.COMPUTE_LUMINANCE
let luminance:[UInt8] = rgba.map
{
let r:Double = .init($0.r),
Expand All @@ -57,11 +71,15 @@ let luminance:[UInt8] = rgba.map
}
do
{
// snippet.SAVE_V_LUMINANCE
let image:PNG.Image = .init(packing: luminance, size: size, layout: layout.v)
try image.compress(path: "\(path)-luminance-v.png", level: 9)
// snippet.end
}
do
{
// snippet.SAVE_RGB_LUMINANCE
let image:PNG.Image = .init(packing: luminance, size: size, layout: layout.rgb)
try image.compress(path: "\(path)-luminance-rgb.png", level: 9)
// snippet.end
}
31 changes: 20 additions & 11 deletions Snippets/PNG/CustomColor.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import PNG

/// https://commons.wikimedia.org/wiki/File:Alice-in-Wonderland_by-David-Revoy_2010-07-21.jpg
let path:String = "Sources/PNG/docs.docc/CustomColor/CustomColor"

// snippet.HSVA_TYPE
struct HSVA
{
var h:UInt32
var s:UInt16
var v:UInt8
var a:UInt8

// snippet.end
init(h:UInt32, s:UInt16, v:UInt8, a:UInt8)
{
self.h = h
self.s = s
self.v = v
self.a = a
}

// snippet.HSVA_CONVERT_FROM_RGBA
init(r:UInt8, g:UInt8, b:UInt8, a:UInt8)
{
let sorted:(min:UInt8, mid:UInt8, max:UInt8)
Expand Down Expand Up @@ -49,7 +47,7 @@ struct HSVA
self.v = sorted.max
self.a = a
}

// snippet.HSVA_CONVERT_TO_RGBA
var rgba:PNG.RGBA<UInt8>
{
guard self.s > 0, self.v > 0
Expand Down Expand Up @@ -78,8 +76,9 @@ struct HSVA
default: fatalError("unreachable")
}
}
// snippet.end
}

// snippet.HSVA_CONFORMANCE_SIGNATURES
extension HSVA:PNG.Color
{
typealias Aggregate = (UInt8, UInt8, UInt8, UInt8)
Expand All @@ -89,6 +88,7 @@ extension HSVA:PNG.Color
of format:PNG.Format,
deindexer:([(r:UInt8, g:UInt8, b:UInt8, a:UInt8)]) -> (Int) -> Aggregate) -> [Self]
{
// snippet.HSVA_CONFORMANCE_INDEXED
let depth:Int = format.pixel.depth
switch format
{
Expand All @@ -101,7 +101,7 @@ extension HSVA:PNG.Color
(c:(UInt8, UInt8, UInt8, UInt8)) in
.init(r: c.0, g: c.1, b: c.2, a: c.3)
}

// snippet.HSVA_CONFORMANCE_V
case .v1(fill: _, key: nil),
.v2(fill: _, key: nil),
.v4(fill: _, key: nil),
Expand All @@ -117,6 +117,7 @@ extension HSVA:PNG.Color
(v:UInt8, _) in
.init(h: 0, s: 0, v: v, a: .max)
}
// snippet.HSVA_CONFORMANCE_V_KEYED
case .v1(fill: _, key: let key?),
.v2(fill: _, key: let key?),
.v4(fill: _, key: let key?),
Expand All @@ -133,6 +134,7 @@ extension HSVA:PNG.Color
.init(h: 0, s: 0, v: v, a: k == key ? .min : .max)
}

// snippet.HSVA_CONFORMANCE_REST
case .va8(fill: _):
return PNG.convolve(interleaved, of: UInt8.self, depth: depth)
{
Expand Down Expand Up @@ -204,8 +206,10 @@ extension HSVA:PNG.Color
.init(r: c.0, g: c.1, b: c.2, a: c.3)
}
}
// snippet.end
}

// snippet.HSVA_CONFORMANCE_PACK
static
func pack(_ pixels:[Self],
as format:PNG.Format,
Expand Down Expand Up @@ -293,8 +297,12 @@ extension HSVA:PNG.Color
}
}
}
// snippet.end
}

// snippet.LOAD_EXAMPLE
let path:String = "Sources/PNG/docs.docc/CustomColor/CustomColor"

guard
let image:PNG.Image = try .decompress(path: "\(path).png")
else
Expand All @@ -304,27 +312,28 @@ else

let hsva:[HSVA] = image.unpack(as: HSVA.self)

// snippet.SAVE_HUE
let hue:PNG.Image = .init(
packing: hsva.map{ HSVA.init(h: $0.h, s: .max / 2, v: .max, a: $0.a) },
size: image.size,
layout: image.layout,
metadata: image.metadata)
try hue.compress(path: "\(path)-hue.png")

// snippet.SAVE_SATURATION
let saturation:PNG.Image = .init(
packing: hsva.map{ HSVA.init(h: 370000, s: $0.s, v: .max, a: $0.a) },
size: image.size,
layout: image.layout,
metadata: image.metadata)
try saturation.compress(path: "\(path)-saturation.png")

// snippet.SAVE_VALUE
let value:PNG.Image = .init(
packing: hsva.map{ HSVA.init(h: 0, s: 0, v: $0.v, a: $0.a) },
size: image.size,
layout: image.layout,
metadata: image.metadata)
try value.compress(path: "\(path)-value.png")

// snippet.SAVE_EXAMPLE
let new:PNG.Image = .init(packing: hsva,
size: image.size,
layout: image.layout,
Expand Down
11 changes: 8 additions & 3 deletions Snippets/PNG/ImageMetadata.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//

// snippet.LOAD_EXAMPLE
import PNG

/// https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Siam.svg
let path:String = "Sources/PNG/docs.docc/ImageMetadata/ImageMetadata"

guard
Expand All @@ -10,6 +12,7 @@ else
fatalError("failed to open file '\(path).png'")
}

// snippet.INSPECT_CHUNKS
if let time:PNG.TimeModified = image.metadata.time
{
print(time)
Expand All @@ -23,13 +26,15 @@ if let physicalDimensions:PNG.PhysicalDimensions = image.metadata.physicalDimen
print(physicalDimensions)
}

//print(image.metadata)
// snippet.PRINT_CHUNKS
print(image.metadata)

// snippet.MODIFY_CHUNKS
image.metadata.time = .init(year: 1992, month: 8, day: 3, hour: 0, minute: 0, second: 0)

// snippet.SAVE_EXAMPLE
try image.compress(path: "\(path)-newtime.png")


if let image:PNG.Image = try .decompress(path: "\(path)-newtime.png"),
let time:PNG.TimeModified = image.metadata.time ?? nil
{
Expand Down
Loading
Loading