diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml new file mode 100644 index 00000000..0d873e26 --- /dev/null +++ b/.github/workflows/Tests.yml @@ -0,0 +1,40 @@ +name: tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + linux: + runs-on: ubuntu-24.04 + name: Ubuntu 24.04 + steps: + - name: Install Swift + uses: tayloraswift/swift-install-action@master + with: + swift-prefix: "swift-6.0.2-release/ubuntu2404/swift-6.0.2-RELEASE" + swift-id: "swift-6.0.2-RELEASE-ubuntu24.04" + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install imagemagick + run: sudo apt install -y imagemagick + + - name: Run tests + run: Scripts/TestAll + + macos: + runs-on: macos-15 + name: macOS + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install imagemagick + run: brew install imagemagick + + - name: Run tests + run: Scripts/TestAll diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 30564c38..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build-macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - # reads .swift-version - - uses: YOCKOW/Action-setup-swift@v1.1.14 - - run: | - brew install imagemagick - utils/tests - utils/examples -c release - - build-linux: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: YOCKOW/Action-setup-swift@v1.1.14 - - run: | - utils/tests - utils/examples -c release diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index cc74f539..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: documentation - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - # uses .swift-version - - uses: YOCKOW/Action-setup-swift@v1.1.14 - - - run: utils/generate-documentation - - - uses: JamesIves/github-pages-deploy-action@releases/v3 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: documentation/ diff --git a/.github/workflows/iOS.yml b/.github/workflows/iOS.yml new file mode 100644 index 00000000..25d87eee --- /dev/null +++ b/.github/workflows/iOS.yml @@ -0,0 +1,14 @@ +name: iOS + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master + with: + xcode-scheme: 'JPEG' + destination: ${{ github.workflow }} diff --git a/.github/workflows/tvOS.yml b/.github/workflows/tvOS.yml new file mode 100644 index 00000000..a8b7e778 --- /dev/null +++ b/.github/workflows/tvOS.yml @@ -0,0 +1,14 @@ +name: tvOS + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master + with: + xcode-scheme: 'JPEG' + destination: ${{ github.workflow }} diff --git a/.github/workflows/visionOS.yml b/.github/workflows/visionOS.yml new file mode 100644 index 00000000..afd4f0ff --- /dev/null +++ b/.github/workflows/visionOS.yml @@ -0,0 +1,14 @@ +name: visionOS + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master + with: + xcode-scheme: 'JPEG' + destination: ${{ github.workflow }} diff --git a/.github/workflows/watchOS.yml b/.github/workflows/watchOS.yml new file mode 100644 index 00000000..bbec38fb --- /dev/null +++ b/.github/workflows/watchOS.yml @@ -0,0 +1,14 @@ +name: watchOS + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master + with: + xcode-scheme: 'JPEG' + destination: ${{ github.workflow }} diff --git a/.gitignore b/.gitignore index 1ece558d..83eab893 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ .DS_Store -/.build +.vscode +.build/ +.build.ssgc/ +.ssgc/ + /Packages /*.xcodeproj /tests/fuzz/data /tests/integration/decode/*.png /tests/integration/decode/*.rgb /tests/integration/encode/*.jpg -/documentation -/.entrapta diff --git a/.swift-version b/.swift-version deleted file mode 100644 index b64ab704..00000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.7.2 diff --git a/Package.swift b/Package.swift index 4e47425a..cad176c0 100644 --- a/Package.swift +++ b/Package.swift @@ -2,62 +2,75 @@ import PackageDescription let package = Package( - name: "jpeg", - products: + name: "swift-jpeg", + products: [ - .library( name: "JPEG", targets: ["JPEG"]), - .executable(name: "fuzzer", targets: ["JPEGFuzzer"]), - .executable(name: "comparator", targets: ["JPEGComparator"]), - .executable(name: "unit-test", targets: ["JPEGUnitTests"]), - .executable(name: "regression-test", targets: ["JPEGRegressionTests"]), - .executable(name: "integration-test", targets: ["JPEGIntegrationTests"]), - - .executable(name: "decode-basic", targets: ["JPEGDecodeBasic"]), - .executable(name: "encode-basic", targets: ["JPEGEncodeBasic"]), - .executable(name: "decode-advanced", targets: ["JPEGDecodeAdvanced"]), - .executable(name: "encode-advanced", targets: ["JPEGEncodeAdvanced"]), - .executable(name: "in-memory", targets: ["JPEGInMemory"]), - .executable(name: "decode-online", targets: ["JPEGDecodeOnline"]), - .executable(name: "recompress", targets: ["JPEGRecompress"]), - .executable(name: "rotate", targets: ["JPEGRotate"]), - .executable(name: "custom-color", targets: ["JPEGCustomColor"]), + .library(name: "JPEG", targets: ["JPEG"]), + .executable(name: "fuzzer", targets: ["JPEGFuzzer"]), + .executable(name: "comparator", targets: ["JPEGComparator"]), + .executable(name: "unit-test", targets: ["JPEGUnitTests"]), + .executable(name: "regression-test", targets: ["JPEGRegressionTests"]), + .executable(name: "integration-test", targets: ["JPEGIntegrationTests"]), + + .executable(name: "decode-basic", targets: ["JPEGDecodeBasic"]), + .executable(name: "encode-basic", targets: ["JPEGEncodeBasic"]), + .executable(name: "decode-advanced", targets: ["JPEGDecodeAdvanced"]), + .executable(name: "encode-advanced", targets: ["JPEGEncodeAdvanced"]), + .executable(name: "in-memory", targets: ["JPEGInMemory"]), + .executable(name: "decode-online", targets: ["JPEGDecodeOnline"]), + .executable(name: "recompress", targets: ["JPEGRecompress"]), + .executable(name: "rotate", targets: ["JPEGRotate"]), + .executable(name: "custom-color", targets: ["JPEGCustomColor"]), ], - targets: + targets: [ - .target( name: "JPEG", path: "sources/jpeg"), - .executableTarget(name: "JPEGFuzzer", dependencies: ["JPEG"], path: "tests/fuzz", - exclude: - [ + .target(name: "JPEG"), + .target(name: "JPEGInspection"), + + .executableTarget(name: "JPEGFuzzer", + dependencies: ["JPEG", "JPEGInspection"], + path: "tests/fuzz", + exclude: [ "data/", ] ), - .executableTarget(name: "JPEGComparator", dependencies: ["JPEG"], path: "tests/compare"), - .executableTarget(name: "JPEGUnitTests", dependencies: ["JPEG"], path: "tests/unit"), - .executableTarget(name: "JPEGRegressionTests", dependencies: ["JPEG"], path: "tests/regression", - exclude: - [ + + .executableTarget(name: "JPEGComparator", + dependencies: ["JPEG", "JPEGInspection"], + path: "tests/compare"), + + .executableTarget(name: "JPEGUnitTests", + dependencies: ["JPEG", "JPEGInspection"], + path: "tests/unit"), + + .executableTarget(name: "JPEGRegressionTests", + dependencies: ["JPEG", "JPEGInspection"], + path: "tests/regression", + exclude: [ "gold/", - ] - ), - .executableTarget(name: "JPEGIntegrationTests", dependencies: ["JPEG"], path: "tests/integration", - exclude: - [ + ]), + + .executableTarget(name: "JPEGIntegrationTests", + dependencies: ["JPEG", "JPEGInspection"], + path: "tests/integration", + exclude: [ "decode/", "encode/", - ] - ), - - .executableTarget(name: "JPEGDecodeBasic", dependencies: ["JPEG"], path: "examples/decode-basic", - exclude: - [ + ]), + + .executableTarget(name: "JPEGDecodeBasic", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/decode-basic", + exclude: [ "karlie-kwk-2019.jpg.rgb", "karlie-kwk-2019.jpg", "karlie-kwk-2019.jpg.rgb.png", - ] - ), - .executableTarget(name: "JPEGEncodeBasic", dependencies: ["JPEG"], path: "examples/encode-basic", - exclude: - [ + ]), + + .executableTarget(name: "JPEGEncodeBasic", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/encode-basic", + exclude: [ "karlie-milan-sp12-2011-4-4-0-4.0.jpg", "karlie-milan-sp12-2011-4-2-2-1.0.jpg", "karlie-milan-sp12-2011-4-4-0-2.0.jpg", @@ -92,9 +105,11 @@ let package = Package( "karlie-milan-sp12-2011-4-4-0-0.25.jpg", "karlie-milan-sp12-2011-4-2-0-0.0.jpg", "karlie-milan-sp12-2011-4-4-4-0.125.jpg", - ] - ), - .executableTarget(name: "JPEGDecodeAdvanced", dependencies: ["JPEG"], path: "examples/decode-advanced", + ]), + + .executableTarget(name: "JPEGDecodeAdvanced", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/decode-advanced", exclude: [ "karlie-2019.jpg-0.640x432.gray", @@ -106,26 +121,29 @@ let package = Package( "karlie-2019.jpg-2.320x216.gray", "karlie-2019.jpg.rgb", "karlie-2019.jpg-0.640x432.gray.png", - ] - ), - .executableTarget(name: "JPEGEncodeAdvanced", dependencies: ["JPEG"], path: "examples/encode-advanced", + ]), + .executableTarget(name: "JPEGEncodeAdvanced", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/encode-advanced", exclude: [ "karlie-cfdas-2011.png.rgb", "karlie-cfdas-2011.png", "karlie-cfdas-2011.png.rgb.jpg", - ] - ), - .executableTarget(name: "JPEGInMemory", dependencies: ["JPEG"], path: "examples/in-memory", + ]), + .executableTarget(name: "JPEGInMemory", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/in-memory", exclude: [ "karlie-2011.jpg.rgb.png", "karlie-2011.jpg", "karlie-2011.jpg.rgb", "karlie-2011.jpg.jpg", - ] - ), - .executableTarget(name: "JPEGDecodeOnline", dependencies: ["JPEG"], path: "examples/decode-online", + ]), + .executableTarget(name: "JPEGDecodeOnline", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/decode-online", exclude: [ "karlie-oscars-2017.jpg-9.rgb.png", @@ -169,26 +187,29 @@ let package = Package( "karlie-oscars-2017.jpg-difference-9.rgb", "karlie-oscars-2017.jpg-difference-3.rgb", "karlie-oscars-2017.jpg-difference-1.rgb", - ] - ), - .executableTarget(name: "JPEGRecompress", dependencies: ["JPEG"], path: "examples/recompress", + ]), + .executableTarget(name: "JPEGRecompress", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/recompress", exclude: [ "recompressed-requantized.jpg", "original.jpg", "recompressed-full-cycle.jpg", - ] - ), - .executableTarget(name: "JPEGRotate", dependencies: ["JPEG"], path: "examples/rotate", + ]), + .executableTarget(name: "JPEGRotate", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/rotate", exclude: [ "karlie-kwk-wwdc-2017.jpg", "karlie-kwk-wwdc-2017-iii.jpg", "karlie-kwk-wwdc-2017-ii.jpg", "karlie-kwk-wwdc-2017-iv.jpg", - ] - ), - .executableTarget(name: "JPEGCustomColor", dependencies: ["JPEG"], path: "examples/custom-color", + ]), + .executableTarget(name: "JPEGCustomColor", + dependencies: ["JPEG", "JPEGInspection"], + path: "examples/custom-color", exclude: [ "output.jpg", @@ -196,8 +217,7 @@ let package = Package( "output.jpg.rgb-difference.png", "output.jpg.rgb-8.png", "output.jpg.rgb", - ] - ), - ], + ]), + ], swiftLanguageVersions: [.v4_2, .v5] ) diff --git a/README.md b/README.md index 223241e3..15c9cae4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![language](https://img.shields.io/badge/version-swift_5.5-ffa020.svg)](https://swift.org) [![license](https://img.shields.io/badge/license-MPL2-ff3079.svg)](https://github.com/kelvin13/jpeg/blob/master/LICENSE) -Swift *JPEG* is a cross-platform pure Swift framework for decoding, inspecting, editing, and encoding JPEG images. The core framework has no external dependencies, including *Foundation*, and should compile and provide consistent behavior on *all* Swift platforms. The framework supports additional features, such as file system support, on Linux and MacOS. +Swift *JPEG* is a cross-platform pure Swift framework for decoding, inspecting, editing, and encoding JPEG images. The core framework has no external dependencies, including *Foundation*, and should compile and provide consistent behavior on *all* Swift platforms. The framework supports additional features, such as file system support, on Linux and MacOS. Swift *JPEG* is available 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. @@ -30,60 +30,60 @@ Swift *JPEG* is available under the [Mozilla Public License 2.0](https://www.moz * [`JPEG.General`](https://kelvin13.github.io/jpeg/General/) * [`JPEG.System`](https://kelvin13.github.io/jpeg/System/) -## getting started +## getting started To Swift *JPEG* in a project, add this descriptor to the `dependencies` list in your `Package.swift`: -```swift -.package(url: "https://github.com/kelvin13/jpeg", .exact("1.0.0")) +```swift +.package(url: "https://github.com/kelvin13/jpeg", .exact("1.0.0")) ``` ## basic usage Decode an image: -```swift +```swift import JPEG func decode(jpeg path:String) throws { guard let image:JPEG.Data.Rectangular = try .decompress(path: path) - else + else { // failed to access file from file system } - let rgb:[JPEG.RGB] = image.unpack(as: JPEG.RGB.self), + let rgb:[JPEG.RGB] = image.unpack(as: JPEG.RGB.self), size:(x:Int, y:Int) = image.size // ... } ``` -Encode an image: +Encode an image: -```swift +```swift import JPEG -func encode(jpeg path:String, size:(x:Int, y:Int), pixels:[JPEG.RGB], +func encode(jpeg path:String, size:(x:Int, y:Int), pixels:[JPEG.RGB], compression:Double) // 0.0 = highest quality - throws + throws { let layout:JPEG.Layout = .init( format: .ycc8, - process: .baseline, - components: + process: .baseline, + components: [ 1: (factor: (2, 2), qi: 0), // Y 2: (factor: (1, 1), qi: 1), // Cb - 3: (factor: (1, 1), qi: 1), // Cr - ], - scans: + 3: (factor: (1, 1), qi: 1), // Cr + ], + scans: [ .sequential((1, \.0, \.0), (2, \.1, \.1), (3, \.1, \.1)), ]) let jfif:JPEG.JFIF = .init(version: .v1_2, density: (72, 72, .inches)) - let image:JPEG.Data.Rectangular = + let image:JPEG.Data.Rectangular = .pack(size: size, layout: layout, metadata: [.jfif(jfif)], pixels: rgb) - try image.compress(path: path, quanta: + try image.compress(path: path, quanta: [ 0: JPEG.CompressionLevel.luminance( compression).quanta, 1: JPEG.CompressionLevel.chrominance(compression).quanta diff --git a/Scripts/TestAll b/Scripts/TestAll new file mode 100755 index 00000000..93e6063d --- /dev/null +++ b/Scripts/TestAll @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +swift --version + +utils/tests +utils/examples -c release diff --git a/sources/jpeg/common.swift b/Sources/JPEG/common.swift similarity index 78% rename from sources/jpeg/common.swift rename to Sources/JPEG/common.swift index f4e47015..fe2f6bbd 100644 --- a/sources/jpeg/common.swift +++ b/Sources/JPEG/common.swift @@ -2,115 +2,115 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -/// enum General +/// enum General /// A namespace for general functionality. /// # [Range types](general-range-types) /// # [Integer storage](general-storage-types) /// # [See also](top-level-namespaces) /// ## (1:top-level-namespaces) -public -enum General +public +enum General { } -extension General +extension General { - /// struct General.Storage - /// where I:Swift.FixedWidthInteger & Swift.BinaryInteger - /// @propertyWrapper - /// A property wrapper providing an immutable [`Swift.Int`] interface backed + /// struct General.Storage + /// where I:Swift.FixedWidthInteger & Swift.BinaryInteger + /// @propertyWrapper + /// A property wrapper providing an immutable [`Swift.Int`] interface backed /// by a different integer type. /// # [See also](general-storage-types) /// ## (general-storage-types) - @propertyWrapper - public - struct Storage where I:FixedWidthInteger & BinaryInteger + @propertyWrapper + public + struct Storage where I:FixedWidthInteger & BinaryInteger { - private - var storage:I + private + var storage:I /// init General.Storage.init(wrappedValue:) - /// Creates an instance of this property wrapper, with the given value + /// Creates an instance of this property wrapper, with the given value /// truncated to the width of the storage type [`I`]. - /// - wrappedValue : Swift.Int + /// - wrappedValue : Swift.Int /// The value to wrap. - public - init(wrappedValue:Int) + public + init(wrappedValue:Int) { self.storage = .init(truncatingIfNeeded: wrappedValue) } /// var General.Storage.wrappedValue : Swift.Int { get } /// The value wrapped by this property wrapper, expanded to an [`Swift.Int`]. - public - var wrappedValue:Int + public + var wrappedValue:Int { .init(self.storage) } } - /// struct General.Storage2 - /// where I:Swift.FixedWidthInteger & Swift.BinaryInteger - /// @propertyWrapper - /// A property wrapper providing an immutable `(`[`Swift.Int`]`, `[`Swift.Int`]`)` + /// struct General.Storage2 + /// where I:Swift.FixedWidthInteger & Swift.BinaryInteger + /// @propertyWrapper + /// A property wrapper providing an immutable `(`[`Swift.Int`]`, `[`Swift.Int`]`)` /// interface backed by a different integer type. /// # [See also](general-storage-types) /// ## (general-storage-types) - @propertyWrapper - public - struct Storage2 where I:FixedWidthInteger & BinaryInteger + @propertyWrapper + public + struct Storage2 where I:FixedWidthInteger & BinaryInteger { - private - var storage:(x:I, y:I) + private + var storage:(x:I, y:I) /// init General.Storage2.init(wrappedValue:) - /// Creates an instance of this property wrapper, with the given values + /// Creates an instance of this property wrapper, with the given values /// truncated to the width of the storage type [`I`]. /// - wrappedValue : (x:Swift.Int, y:Swift.Int) /// The values to wrap. - public - init(wrappedValue:(x:Int, y:Int)) + public + init(wrappedValue:(x:Int, y:Int)) { - self.storage = + self.storage = ( .init(truncatingIfNeeded: wrappedValue.x), .init(truncatingIfNeeded: wrappedValue.y) ) } /// var General.Storage2.wrappedValue : Swift.Int { get } - /// The values wrapped by this property wrapper, expanded to an + /// The values wrapped by this property wrapper, expanded to an /// `(`[`Swift.Int`]`, `[`Swift.Int`]`)` tuple. - public - var wrappedValue:(x:Int, y:Int) + public + var wrappedValue:(x:Int, y:Int) { (.init(self.storage.x), .init(self.storage.y)) } } - /// struct General.MutableStorage - /// where I:Swift.FixedWidthInteger & Swift.BinaryInteger - /// @propertyWrapper - /// A property wrapper providing a mutable [`Swift.Int`] interface backed + /// struct General.MutableStorage + /// where I:Swift.FixedWidthInteger & Swift.BinaryInteger + /// @propertyWrapper + /// A property wrapper providing a mutable [`Swift.Int`] interface backed /// by a different integer type. /// # [See also](general-storage-types) /// ## (general-storage-types) - @propertyWrapper - public - struct MutableStorage where I:FixedWidthInteger & BinaryInteger + @propertyWrapper + public + struct MutableStorage where I:FixedWidthInteger & BinaryInteger { - private - var storage:I + private + var storage:I /// init General.MutableStorage.init(wrappedValue:) - /// Creates an instance of this property wrapper, with the given value + /// Creates an instance of this property wrapper, with the given value /// truncated to the width of the storage type [`I`]. - /// - wrappedValue : Swift.Int + /// - wrappedValue : Swift.Int /// The value to wrap. - public - init(wrappedValue:Int) + public + init(wrappedValue:Int) { self.storage = .init(truncatingIfNeeded: wrappedValue) } /// var General.MutableStorage.wrappedValue : Swift.Int { get set } /// The value wrapped by this property wrapper, expanded to an [`Swift.Int`]. - public - var wrappedValue:Int + public + var wrappedValue:Int { - get + get { .init(self.storage) } @@ -122,13 +122,13 @@ extension General } } -extension General -{ - struct Heap where Key:Comparable +extension General +{ + struct Heap where Key:Comparable { - private + private var storage:[(Key, Value)] - + // support 1-based indexing private subscript(index:Int) -> (key:Key, value:Value) @@ -151,18 +151,18 @@ extension General { self.storage.first } - var isEmpty:Bool + var isEmpty:Bool { - self.storage.isEmpty + self.storage.isEmpty } - - private - var startIndex:Int + + private + var startIndex:Int { 1 } - private - var endIndex:Int + private + var endIndex:Int { 1 + self.count } @@ -171,35 +171,35 @@ extension General extension General.Heap { @inline(__always) - private static + private static func left(index:Int) -> Int { return index << 1 } @inline(__always) - private static + private static func right(index:Int) -> Int { return index << 1 + 1 } @inline(__always) - private static + private static func parent(index:Int) -> Int { return index >> 1 } - + private func highest(above child:Int) -> Int? { let p:Int = Self.parent(index: child) // make sure it’s not the root - guard p >= self.startIndex - else + guard p >= self.startIndex + else { - return nil + return nil } - + // and the element is higher than the parent return self[child].key < self[p].key ? p : nil } @@ -218,13 +218,13 @@ extension General.Heap guard r < self.endIndex else { - return self[l].key < self[parent].key ? l : nil + return self[l].key < self[parent].key ? l : nil } - + let c:Int = self[r].key < self[l].key ? r : l - return self[c].key < self[parent].key ? c : nil + return self[c].key < self[parent].key ? c : nil } - + @inline(__always) private mutating @@ -252,7 +252,7 @@ extension General.Heap { return } - + self.swapAt (index, child) self.siftDown(index: child) } @@ -263,30 +263,30 @@ extension General.Heap self.storage.append((key, value)) self.siftUp(index: self.endIndex - 1) } - + mutating func dequeue() -> (key:Key, value:Value)? { - switch self.count + switch self.count { case 0: - return nil + return nil case 1: return self.storage.removeLast() default: self.swapAt(self.startIndex, self.endIndex - 1) - defer + defer { self.siftDown(index: self.startIndex) } return self.storage.removeLast() } } - - init(_ sequence:S) where S:Sequence, S.Element == (Key, Value) + + init(_ sequence:S) where S:Sequence, S.Element == (Key, Value) { self.storage = .init(sequence) - // heapify + // heapify let halfway:Int = Self.parent(index: self.endIndex - 1) + 1 for i:Int in (self.startIndex ..< halfway).reversed() { @@ -294,40 +294,40 @@ extension General.Heap } } } -extension General.Heap:ExpressibleByArrayLiteral +extension General.Heap:ExpressibleByArrayLiteral { - init(arrayLiteral:(key:Key, value:Value)...) + init(arrayLiteral:(key:Key, value:Value)...) { self.init(arrayLiteral) } -} +} -// 2d iterators -extension General +// 2d iterators +extension General { - /// struct General.Range2 - /// where Bound:Swift.Comparable + /// struct General.Range2 + /// where Bound:Swift.Comparable /// : Swift.Sequence where Bound:Swift.Strideable, Bound.Stride:Swift.SignedInteger /// A two-dimensional open range. /// ## (general-range-types) - public - struct Range2 where Bound:Comparable + public + struct Range2 where Bound:Comparable { let lowerBound:(x:Bound, y:Bound) let upperBound:(x:Bound, y:Bound) - + init(lowerBound:(x:Bound, y:Bound), upperBound:(x:Bound, y:Bound)) { precondition(lowerBound.x <= upperBound.x, "x lower bound cannot be greater than upper bound") precondition(lowerBound.y <= upperBound.y, "y lower bound cannot be greater than upper bound") - + self.lowerBound = lowerBound self.upperBound = upperBound } } } -func ..< (lhs:(x:Bound, y:Bound), rhs:(x:Bound, y:Bound)) -> General.Range2 +func ..< (lhs:(x:Bound, y:Bound), rhs:(x:Bound, y:Bound)) -> General.Range2 where Bound:Comparable { return .init(lowerBound: lhs, upperBound: rhs) @@ -335,81 +335,81 @@ func ..< (lhs:(x:Bound, y:Bound), rhs:(x:Bound, y:Bound)) -> General.Rang extension General.Range2:Sequence where Bound:Strideable, Bound.Stride:SignedInteger { - /// typealias General.Range2.Element = (x:Bound, y:Bound) + /// typealias General.Range2.Element = (x:Bound, y:Bound) /// ?: Swift.Sequence where Bound:Swift.Strideable, Bound.Stride:Swift.SignedInteger - public + public typealias Element = (x:Bound, y:Bound) - - /// struct General.Range2.Iterator + + /// struct General.Range2.Iterator /// ?: Swift.Sequence where Bound:Swift.Strideable, Bound.Stride:Swift.SignedInteger - /// : Swift.IteratorProtocol + /// : Swift.IteratorProtocol /// A two-dimensional range iterator. /// ## (general-range-types) - public + public struct Iterator { - var x:Bound, - y:Bound + var x:Bound, + y:Bound let bound:(x:(Bound, Bound), y:Bound) } - + /// func General.Range2.makeIterator() /// ?: Swift.Sequence where Bound:Swift.Strideable, Bound.Stride:Swift.SignedInteger - /// Creates an iterator for this range instance. - /// - /// This iterator will traverse the range space in row-major order. For - /// example, if the bounds are `(x: 0, y: 0)` and `(x: 2, y: 2)`, the iterator - /// will yield the elements `(x: 0, y: 0)`, `(x: 1, y: 0)`, `(x: 0, y: 1)`, + /// Creates an iterator for this range instance. + /// + /// This iterator will traverse the range space in row-major order. For + /// example, if the bounds are `(x: 0, y: 0)` and `(x: 2, y: 2)`, the iterator + /// will yield the elements `(x: 0, y: 0)`, `(x: 1, y: 0)`, `(x: 0, y: 1)`, /// and `(x: 1, y: 1)`, in that order. - /// - -> : Iterator + /// - -> : Iterator /// An iterator. - public - func makeIterator() -> Iterator + public + func makeIterator() -> Iterator { - .init(x: self.lowerBound.x, y: self.lowerBound.y, + .init(x: self.lowerBound.x, y: self.lowerBound.y, bound: ((self.lowerBound.x, self.upperBound.x), self.upperBound.y)) } } extension General.Range2.Iterator:IteratorProtocol { /// mutating func General.Range2.Iterator.next() - /// ?: Swift.IteratorProtocol + /// ?: Swift.IteratorProtocol /// Advances to the next element and returns it, or `nil` if no next element exists. - /// - -> : (x:Bound, y:Bound)? - /// The next element in the two-dimensional range sequence, if it exists, - /// otherwise `nil`. If advancing the `x` index would cause it to reach its - /// upper bound, this iterator will advance to the next `y` index and reset + /// - -> : (x:Bound, y:Bound)? + /// The next element in the two-dimensional range sequence, if it exists, + /// otherwise `nil`. If advancing the `x` index would cause it to reach its + /// upper bound, this iterator will advance to the next `y` index and reset /// the `x` index to its lower bound. - public mutating - func next() -> (x:Bound, y:Bound)? + public mutating + func next() -> (x:Bound, y:Bound)? { - if self.x < self.bound.x.1 + if self.x < self.bound.x.1 { - defer + defer { self.x = self.x.advanced(by: 1) } - + return (self.x, self.y) } - else + else { self.y = self.y.advanced(by: 1) - - if self.y < self.bound.y + + if self.y < self.bound.y { - self.x = self.bound.x.0 + self.x = self.bound.x.0 return self.next() } - else + else { - return nil + return nil } } } } -// raw buffer utilities +// raw buffer utilities extension ArraySlice where Element == UInt8 { // Loads this array slice as a misaligned big-endian integer value, diff --git a/sources/jpeg/debug.swift b/Sources/JPEG/debug.swift similarity index 71% rename from sources/jpeg/debug.swift rename to Sources/JPEG/debug.swift index b9c975f0..fbec7dbd 100644 --- a/sources/jpeg/debug.swift +++ b/Sources/JPEG/debug.swift @@ -2,35 +2,35 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// literal forms -extension JPEG.Component.Key:ExpressibleByIntegerLiteral +// literal forms +extension JPEG.Component.Key:ExpressibleByIntegerLiteral { /// init JPEG.Component.Key.init(integerLiteral:) - /// ?: Swift.ExpressibleByIntegerLiteral + /// ?: Swift.ExpressibleByIntegerLiteral /// - integerLiteral : Swift.UInt8 - public - init(integerLiteral:UInt8) + public + init(integerLiteral:UInt8) { self.init(integerLiteral) } } -extension JPEG.Table.Quantization.Key:ExpressibleByIntegerLiteral +extension JPEG.Table.Quantization.Key:ExpressibleByIntegerLiteral { /// init JPEG.Table.Quantization.Key.init(integerLiteral:) - /// ?: Swift.ExpressibleByIntegerLiteral + /// ?: Swift.ExpressibleByIntegerLiteral /// - integerLiteral : Swift.Int - public - init(integerLiteral:Int) + public + init(integerLiteral:Int) { self.init(integerLiteral) } } // print descriptions -extension String +extension String { - public - init(selector:WritableKeyPath<(Delegate?, Delegate?, Delegate?, Delegate?), Delegate?>) + public + init(selector:WritableKeyPath<(Delegate?, Delegate?, Delegate?, Delegate?), Delegate?>) { switch selector { @@ -42,7 +42,7 @@ extension String self = "2" case \.3: self = "3" - + default: self = "" } @@ -51,10 +51,10 @@ extension String extension JPEG.Process:CustomStringConvertible { - public - var description:String + public + var description:String { - switch self + switch self { case .baseline: return "baseline sequential DCT" @@ -68,51 +68,51 @@ extension JPEG.Process:CustomStringConvertible } } -extension JPEG.Component:CustomStringConvertible +extension JPEG.Component:CustomStringConvertible { - public - var description:String + public + var description:String { return "{quantization table: \(String.init(selector: self.selector)), sample factors: (\(self.factor.x), \(self.factor.y))}" } } -extension JPEG.Scan.Component:CustomStringConvertible +extension JPEG.Scan.Component:CustomStringConvertible { - public - var description:String + public + var description:String { "{dc huffman table: \(String.init(selector: self.selector.dc)), ac huffman table: \(String.init(selector: self.selector.ac))}" } } -extension JPEG.Component.Key:CustomStringConvertible +extension JPEG.Component.Key:CustomStringConvertible { - public - var description:String + public + var description:String { "[\(self.value)]" } } -extension JPEG.Table.Quantization.Key:CustomStringConvertible +extension JPEG.Table.Quantization.Key:CustomStringConvertible { - public - var description:String + public + var description:String { "[\(self.value)]" } } -extension JPEG.Header.Frame:CustomStringConvertible +extension JPEG.Header.Frame:CustomStringConvertible { - public - var description:String + public + var description:String { """ - frame header: + frame header: { - mode : \(self.process), - precision : \(self.precision), - initial size : (\(self.size.x), \(self.size.y)), - components : + mode : \(self.process), + precision : \(self.precision), + initial size : (\(self.size.x), \(self.size.y)), + components : [ \(self.components.sorted(by: { $0.key < $1.key }).map { @@ -126,19 +126,19 @@ extension JPEG.Header.Frame:CustomStringConvertible extension JPEG.Header.Scan:CustomStringConvertible { - public - var description:String + public + var description:String { """ scan header (\(Self.self)): { - band : \(self.band.lowerBound) ..< \(self.band.upperBound), - bits : \(self.bits.lowerBound) ..< \(self.bits.upperBound), - components : + band : \(self.band.lowerBound) ..< \(self.band.upperBound), + bits : \(self.bits.lowerBound) ..< \(self.bits.upperBound), + components : [ \(self.components.map - { - "[\($0.ci)]: \($0)" + { + "[\($0.ci)]: \($0)" }.joined(separator: ", \n ")) ] } @@ -148,8 +148,8 @@ extension JPEG.Header.Scan:CustomStringConvertible extension JPEG.Table.Huffman:CustomStringConvertible { - public - var description:String + public + var description:String { """ huffman table (\(Self.self)) @@ -162,8 +162,8 @@ extension JPEG.Table.Huffman:CustomStringConvertible extension JPEG.Table.Quantization:CustomStringConvertible { - public - var description:String + public + var description:String { """ quantization table (\(Self.self)) @@ -176,8 +176,8 @@ extension JPEG.Table.Quantization:CustomStringConvertible extension JPEG.JFIF:CustomStringConvertible { - public - var description:String + public + var description:String { """ metadata (\(Self.self)) @@ -191,14 +191,14 @@ extension JPEG.JFIF:CustomStringConvertible } extension JPEG.EXIF:CustomStringConvertible { - public - var description:String + public + var description:String { """ metadata (\(Self.self)) { endianness : \(self.endianness) - storage : \(self.storage.count) bytes + storage : \(self.storage.count) bytes } """ } diff --git a/sources/jpeg/decode.swift b/Sources/JPEG/decode.swift similarity index 75% rename from sources/jpeg/decode.swift rename to Sources/JPEG/decode.swift index 9d1f657c..6c4441d4 100644 --- a/sources/jpeg/decode.swift +++ b/Sources/JPEG/decode.swift @@ -2,229 +2,229 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// binary utilities +// binary utilities -/// protocol JPEG.Bytestream.Source +/// protocol JPEG.Bytestream.Source /// A source bytestream. -/// -/// To implement a custom data source type, conform it to this protocol by -/// implementing [`(Source).read(count:)`]. It can +/// +/// To implement a custom data source type, conform it to this protocol by +/// implementing [`(Source).read(count:)`]. It can /// then be used with the library’s core decompression interfaces. /// # [See also](file-io-protocols) /// ## (1:file-io-protocols) /// ## (1:lexing-and-formatting) -public -protocol _JPEGBytestreamSource +public +protocol _JPEGBytestreamSource { /// mutating func JPEG.Bytestream.Source.read(count:) - /// required + /// required /// Attempts to read and return the given number of bytes from this stream. - /// - /// A successful call to this function should affect the bytestream state + /// + /// A successful call to this function should affect the bytestream state /// such that subsequent calls should pick up where the last call left off. - /// - /// The rest of the library interprets a `nil` return value from this function + /// + /// The rest of the library interprets a `nil` return value from this function /// as indicating end-of-stream. - /// - count : Swift.Int - /// The number of bytes to read. + /// - count : Swift.Int + /// The number of bytes to read. /// - -> : [Swift.UInt8]? - /// The `count` bytes read, or `nil` if the read attempt failed. This + /// The `count` bytes read, or `nil` if the read attempt failed. This /// method should return `nil` even if any number of bytes less than `count` /// were successfully read. - mutating + mutating func read(count:Int) -> [UInt8]? } -extension JPEG +extension JPEG { - /// enum JPEG.Bytestream + /// enum JPEG.Bytestream /// A namespace for bytestream utilities. /// # [File IO](file-io-protocols) /// ## (0:file-io-protocols) /// ## (0:lexing-and-formatting) - public - enum Bytestream + public + enum Bytestream { - public + public typealias Source = _JPEGBytestreamSource - } + } } -// lexing -extension JPEG.Bytestream.Source +// lexing +extension JPEG.Bytestream.Source { - private mutating + private mutating func read() -> UInt8? { return self.read(count: 1)?[0] } - - // segment lexing - private mutating + + // segment lexing + private mutating func tail(type:JPEG.Marker) throws -> [UInt8] { - switch type + switch type { case .start, .end, .restart: return [] default: guard let header:[UInt8] = self.read(count: 2) - else + else { throw JPEG.LexingError.truncatedMarkerSegmentHeader } let length:Int = header.load(bigEndian: UInt16.self, as: Int.self, at: 0) - + guard length >= 2 - else + else { throw JPEG.LexingError.invalidMarkerSegmentLength(length) } guard let data:[UInt8] = self.read(count: length - 2) - else + else { throw JPEG.LexingError.truncatedMarkerSegmentBody(expected: length - 2) } - + return data } } - /// mutating func JPEG.Bytestream.Source.segment() + /// mutating func JPEG.Bytestream.Source.segment() /// throws - /// Lexes a single marker segment from this bytestream, assuming there - /// is no entropy-coded data prefixed to it. - /// + /// Lexes a single marker segment from this bytestream, assuming there + /// is no entropy-coded data prefixed to it. + /// /// Calling this function is roughly equivalent to calling [`segment(prefix:)`] - /// with the `prefix` parameter set to `false`, except that the empty + /// with the `prefix` parameter set to `false`, except that the empty /// prefix array is omitted from the return value. - /// - /// This function can throw a [`(JPEG).LexingError`] if it encounters an + /// + /// This function can throw a [`(JPEG).LexingError`] if it encounters an /// unexpected end-of-stream. /// - -> : (JPEG.Marker, [Swift.UInt8]) - /// A tuple containing the marker segment type and the marker segment data. + /// A tuple containing the marker segment type and the marker segment data. /// The data array does *not* include the marker segment length field /// from the segment header. - public mutating + public mutating func segment() throws -> (JPEG.Marker, [UInt8]) { try self.segment(prefix: false).1 } - /// mutating func JPEG.Bytestream.Source.segment(prefix:) + /// mutating func JPEG.Bytestream.Source.segment(prefix:) /// throws - /// Optionally lexes a single entropy-coded segment followed by a single marker + /// Optionally lexes a single entropy-coded segment followed by a single marker /// segment from this bytestream. - /// - /// This function can throw a [`(JPEG).LexingError`] if it encounters an + /// + /// This function can throw a [`(JPEG).LexingError`] if it encounters an /// unexpected end-of-stream. - /// - prefix : Swift.Bool - /// Whether this function should expect an entropy-coded segment prefixed - /// to the marker segment. If this parameter is set to `false`, and this - /// function encounters a prefixed entropy-coded segment, it will throw + /// - prefix : Swift.Bool + /// Whether this function should expect an entropy-coded segment prefixed + /// to the marker segment. If this parameter is set to `false`, and this + /// function encounters a prefixed entropy-coded segment, it will throw /// a [`(JPEG).LexingError`]. /// - -> : ([Swift.UInt8], (JPEG.Marker, [Swift.UInt8])) - /// A tuple containing the entropy-coded segment, marker segment type, - /// and the marker segment data, in that order. If `prefix` was false, - /// the entropy-coded segment data array will be empty. + /// A tuple containing the entropy-coded segment, marker segment type, + /// and the marker segment data, in that order. If `prefix` was false, + /// the entropy-coded segment data array will be empty. /// The data array does *not* include the marker segment length field /// from the segment header. - public mutating + public mutating func segment(prefix:Bool) throws -> ([UInt8], (JPEG.Marker, [UInt8])) { - // buffering would help immensely here + // buffering would help immensely here var ecs:[UInt8] = [] let append:(_ byte:UInt8) throws -> () - - if prefix + + if prefix { - append = + append = { ecs.append($0) } - } - else + } + else { - append = + append = { throw JPEG.LexingError.invalidMarkerSegmentPrefix($0) } } - + outer: - while var byte:UInt8 = self.read() + while var byte:UInt8 = self.read() { - guard byte == 0xff - else + guard byte == 0xff + else { try append(byte) continue outer } - + repeat { - guard let next:UInt8 = self.read() - else + guard let next:UInt8 = self.read() + else { throw JPEG.LexingError.truncatedMarkerSegmentType } - + byte = next - - guard byte != 0x00 - else + + guard byte != 0x00 + else { try append(0xff) - continue outer + continue outer } - } - while byte == 0xff - + } + while byte == 0xff + guard let marker:JPEG.Marker = JPEG.Marker.init(code: byte) - else + else { throw JPEG.LexingError.invalidMarkerSegmentType(byte) } - + return (ecs, (marker, try self.tail(type: marker))) } - + throw JPEG.LexingError.truncatedEntropyCodedSegment } } -// parsing +// parsing -/// protocol JPEG.Bitstream.AnySymbol -/// : Swift.Hashable +/// protocol JPEG.Bitstream.AnySymbol +/// : Swift.Hashable /// Functionality common to all bitstream symbols. /// # [Symbol types](entropy-coding-symbols) /// ## (3:entropy-coding-symbols) /// ## (4:entropy-coding) -public +public protocol _JPEGBitstreamAnySymbol:Hashable { /// init JPEG.Bitstream.AnySymbol.init(_:) - /// required + /// required /// Creates a symbol instance. - /// - _ : Swift.UInt8 + /// - _ : Swift.UInt8 /// The byte value of this symbol. init(_:UInt8) /// var JPEG.Bitstream.AnySymbol.value:Swift.UInt8 { get } /// The byte value of this symbol. - var value:UInt8 + var value:UInt8 { - get + get } } -extension JPEG.Bitstream +extension JPEG.Bitstream { - public + public typealias AnySymbol = _JPEGBitstreamAnySymbol /// enum JPEG.Bitstream.Symbol /// A namespace for bitstream symbol types. /// # [Symbol types](entropy-coding-symbols) /// ## (0:entropy-coding-symbols) /// ## (1:entropy-coding) - public - enum Symbol + public + enum Symbol { /// enum JPEG.Bitstream.Symbol.DC /// : JPEG.Bitstream.AnySymbol @@ -232,23 +232,23 @@ extension JPEG.Bitstream /// # [See also](entropy-coding-symbols) /// ## (1:entropy-coding-symbols) /// ## (2:entropy-coding) - public + public struct DC:AnySymbol { /// let JPEG.Bitstream.Symbol.DC.value:Swift.UInt8 /// ?: JPEG.Bitstream.AnySymbol /// The raw byte value of this symbol. - public - let value:UInt8 + public + let value:UInt8 /// init JPEG.Bitstream.Symbol.DC.init(_:) /// ?: JPEG.Bitstream.AnySymbol /// Creates a DC symbol instance. - /// - value : Swift.UInt8 + /// - value : Swift.UInt8 /// The raw byte value of this symbol. - public - init(_ value:UInt8) + public + init(_ value:UInt8) { - self.value = value + self.value = value } } /// enum JPEG.Bitstream.Symbol.AC @@ -257,180 +257,180 @@ extension JPEG.Bitstream /// # [See also](entropy-coding-symbols) /// ## (2:entropy-coding-symbols) /// ## (3:entropy-coding) - public + public struct AC:AnySymbol { /// let JPEG.Bitstream.Symbol.AC.value:Swift.UInt8 /// ?: JPEG.Bitstream.AnySymbol /// The raw byte value of this symbol. - public + public let value:UInt8 /// init JPEG.Bitstream.Symbol.AC.init(_:) /// ?: JPEG.Bitstream.AnySymbol /// Creates an AC symbol instance. - /// - value : Swift.UInt8 + /// - value : Swift.UInt8 /// The raw byte value of this symbol. - public - init(_ value:UInt8) + public + init(_ value:UInt8) { - self.value = value + self.value = value } } } } -// table parsing -extension JPEG.AnyTable +// table parsing +extension JPEG.AnyTable { - static + static func parse(selector:UInt8) -> Self.Selector? { switch selector & 0x0f { case 0: - return \.0 + return \.0 case 1: - return \.1 + return \.1 case 2: - return \.2 + return \.2 case 3: - return \.3 + return \.3 default: - return nil + return nil } } } -extension JPEG.Table.Huffman +extension JPEG.Table.Huffman { // determine the value of n, explained in `Table.Huffman.decode()`, - // as well as the useful size of the table (often, a large region of the high codeword + // as well as the useful size of the table (often, a large region of the high codeword // space is unused so it can be excluded) // also validates leaf counts to make sure they define a valid 16-bit tree private static func size(_ levels:[Int]) -> (n:Int, z:Int)? { - // count the interior nodes - var interior:Int = 1 // count the root - for leaves:Int in levels[0 ..< 8] + // count the interior nodes + var interior:Int = 1 // count the root + for leaves:Int in levels[0 ..< 8] { - guard interior > 0 - else + guard interior > 0 + else { return nil } - + // every interior node on the level above generates two new nodes. // some of the new nodes are leaf nodes, the rest are interior nodes. interior = 2 * interior - leaves } - - // the number of interior nodes remaining is the number of child trees, with - // the possible exception of a fake all-ones branch - let n:Int = 256 - interior + + // the number of interior nodes remaining is the number of child trees, with + // the possible exception of a fake all-ones branch + let n:Int = 256 - interior var z:Int = n - // finish validating the tree + // finish validating the tree for (i, leaves):(offset:Int, element:Int) in levels[8 ..< 16].enumerated() { - guard interior > 0 - else + guard interior > 0 + else { return nil } - + z += leaves << (7 - i) - interior = 2 * interior - leaves + interior = 2 * interior - leaves } - + guard interior > 0 - else + else { return nil } - + return (n, z) } - - // internal unsafe init + + // internal unsafe init init(validated symbols:[[Symbol]], target:Selector) { precondition(symbols.count == 16) guard let size:(n:Int, z:Int) = Self.size(symbols.map(\.count)) - else + else { fatalError("unreachable") } - + self.symbols = symbols - self.target = target + self.target = target self.size = size } - - init?(counts:[Int], values:RAC, target:Selector) + + init?(counts:[Int], values:RAC, target:Selector) where RAC:RandomAccessCollection, RAC.Element == UInt8, RAC.Index == Int { var symbols:[[Symbol]] = [] - var begin:Int = values.startIndex - for leaves:Int in counts + var begin:Int = values.startIndex + for leaves:Int in counts { - let end:Int = begin + leaves + let end:Int = begin + leaves symbols.append(values[begin ..< end].map(Symbol.init(_:))) - begin = end + begin = end } - + self.init(symbols, target: target) } /// init JPEG.Table.Huffman.init?(_:target:) /// Creates a huffman tree from the given leaf nodes. - /// - /// This initializer determines the shape of the tree from the shape of - /// the leaf array input. It has no knowledge of symbol frequencies or + /// + /// This initializer determines the shape of the tree from the shape of + /// the leaf array input. It has no knowledge of symbol frequencies or /// priority. To build an *optimal* huffman tree, use the [`init(frequencies:target:)`] /// initializer. - /// - /// This initializer will return `nil` if the sizes of the given leaf arrays do not - /// describe a [full binary tree](https://en.wikipedia.org/wiki/Binary_tree#full). + /// + /// This initializer will return `nil` if the sizes of the given leaf arrays do not + /// describe a [full binary tree](https://en.wikipedia.org/wiki/Binary_tree#full). /// (The last level is allowed to be incomplete.) - /// For example, the leaf counts (3,\ 0,\ 0,\ …\ ) are invalid because + /// For example, the leaf counts (3,\ 0,\ 0,\ …\ ) are invalid because /// no binary tree can have three leaf nodes in its first level. /// - symbols : [[Symbol]] - /// The leaf nodes in each level of the tree. The tree root is always - /// assumed to be internal, so the 0th sub-array of this array should - /// contain the leaves in the first level of the tree. This array must - /// contain 16 sub-arrays, even if the deeper levels of the tree are + /// The leaf nodes in each level of the tree. The tree root is always + /// assumed to be internal, so the 0th sub-array of this array should + /// contain the leaves in the first level of the tree. This array must + /// contain 16 sub-arrays, even if the deeper levels of the tree are /// empty, or this initializer will suffer a precondition failure. - /// - target : Selector + /// - target : Selector /// The table selector this huffman table is meant to be stored at. - public + public init?(_ symbols:[[Symbol]], target:Selector) { precondition(symbols.count == 16) - // validate leaf counts + // validate leaf counts guard let size:(n:Int, z:Int) = Self.size(symbols.map(\.count)) - else + else { return nil } - + self.symbols = symbols - self.target = target + self.target = target self.size = size - } + } } -extension JPEG.Table.Quantization +extension JPEG.Table.Quantization { - init(precision:Precision, values:RAC, target:Selector) + init(precision:Precision, values:RAC, target:Selector) where RAC:RandomAccessCollection, RAC.Element == UInt8, RAC.Index == Int { - switch precision + switch precision { case .uint8: - // doing the `UInt16` conversion here potentially saves a copy in - // the public initializer + // doing the `UInt16` conversion here potentially saves a copy in + // the public initializer let uint16:[UInt16] = values.map(UInt16.init(_:)) self.init(precision: .uint8, values: uint16, target: target) case .uint16: - let base:Int = values.startIndex - let uint16:[UInt16] = (0 ..< 64).map + let base:Int = values.startIndex + let uint16:[UInt16] = (0 ..< 64).map { let bytes:[UInt8] = .init(values[base + 2 * $0 ..< base + 2 * $0 + 2]) return bytes.load(bigEndian: UInt16.self, as: UInt16.self, at: 0) @@ -440,42 +440,42 @@ extension JPEG.Table.Quantization } /// init JPEG.Table.Quantization.init(precision:values:target:) /// Creates a quantization table from the given quantum values. - /// - precision : Precision + /// - precision : Precision /// The bit width of the integer type to encode the quanta as. /// - values : [Swift.UInt16] - /// The quantum values, in zigzag order. This array must have exactly 64 - /// elements. If the `precision` is [`(Precision).uint8`], all of the values - /// must be within the range of a [`Swift.UInt8`]. Passing an invalid + /// The quantum values, in zigzag order. This array must have exactly 64 + /// elements. If the `precision` is [`(Precision).uint8`], all of the values + /// must be within the range of a [`Swift.UInt8`]. Passing an invalid /// array will result in a precondition failure. - /// - target : Selector + /// - target : Selector /// The table selector this quantization table is meant to be stored at. - public - init(precision:Precision, values:[UInt16], target:Selector) + public + init(precision:Precision, values:[UInt16], target:Selector) { precondition(values.count == 64, "quantization table must have exactly 64 quanta") precondition(precision == .uint16 || values.allSatisfy{ $0 & 0xff00 == 0 }, "8-bit quantization table values must be representable by `UInt8`") self.precision = precision - self.storage = values - self.target = target + self.storage = values + self.target = target } } -extension JPEG.Table +extension JPEG.Table { /// static func JPEG.Table.parse(huffman:) - /// throws + /// throws /// Parses a [`(Marker).huffman`] segment into huffman tables. - /// - /// If the given data does not parse to valid huffman tables, this function + /// + /// If the given data does not parse to valid huffman tables, this function /// will throw a [`(JPEG).ParsingError`]. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - -> : (dc:[HuffmanDC], ac:[HuffmanAC]) + /// - -> : (dc:[HuffmanDC], ac:[HuffmanAC]) /// The parsed DC and AC huffman tables. - public static - func parse(huffman data:[UInt8]) throws -> (dc:[HuffmanDC], ac:[HuffmanAC]) + public static + func parse(huffman data:[UInt8]) throws -> (dc:[HuffmanDC], ac:[HuffmanAC]) { var tables:(dc:[HuffmanDC], ac:[HuffmanAC]) = ([], []) - + var base:Int = 0 while base < data.count { @@ -483,149 +483,149 @@ extension JPEG.Table else { // data buffer does not contain enough data - throw JPEG.ParsingError.mismatched(marker: .huffman, + throw JPEG.ParsingError.mismatched(marker: .huffman, count: data.count, minimum: base + 17) } - + // huffman tables have variable length that can only be determined // by examining the first 17 bytes of each table which means checks // have to be done midway through the parsing let leaf:(counts:[Int], values:ArraySlice) leaf.counts = data[base + 1 ..< base + 17].map(Int.init(_:)) - - // count the number of expected leaves + + // count the number of expected leaves let count:Int = leaf.counts.reduce(0, +) guard data.count >= base + 17 + count - else + else { - throw JPEG.ParsingError.mismatched(marker: .huffman, + throw JPEG.ParsingError.mismatched(marker: .huffman, count: data.count, minimum: base + 17 + count) } - defer + defer { base += 17 + count } - + leaf.values = data[base + 17 ..< base + 17 + count] - - switch data[base] >> 4 + + switch data[base] >> 4 { case 0: guard let target:HuffmanDC.Selector = HuffmanDC.parse(selector: data[base]) - else + else { - break + break } - - guard let table:HuffmanDC = + + guard let table:HuffmanDC = HuffmanDC.init(counts: leaf.counts, values: leaf.values, target: target) - else + else { - throw JPEG.ParsingError.invalidHuffmanTable + throw JPEG.ParsingError.invalidHuffmanTable } - + tables.dc.append(table) - continue - + continue + case 1: guard let target:HuffmanAC.Selector = HuffmanAC.parse(selector: data[base]) - else + else { - break + break } - - guard let table:HuffmanAC = + + guard let table:HuffmanAC = HuffmanAC.init(counts: leaf.counts, values: leaf.values, target: target) - else + else { - throw JPEG.ParsingError.invalidHuffmanTable + throw JPEG.ParsingError.invalidHuffmanTable } - + tables.ac.append(table) - continue - + continue + default: throw JPEG.ParsingError.invalidHuffmanTypeCode(data[base] >> 4) } - + // huffman table has invalid binding index throw JPEG.ParsingError.invalidHuffmanTargetCode(data[base] & 0x0f) } - + return tables } /// static func JPEG.Table.parse(quantization:) - /// throws + /// throws /// Parses a [`(Marker).quantization`] segment into huffman tables. - /// - /// If the given data does not parse to valid quantization tables, this function + /// + /// If the given data does not parse to valid quantization tables, this function /// will throw a [`(JPEG).ParsingError`]. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - -> : [Quantization] + /// - -> : [Quantization] /// The parsed quantization tables. - public static - func parse(quantization data:[UInt8]) throws -> [Quantization] + public static + func parse(quantization data:[UInt8]) throws -> [Quantization] { var tables:[Quantization] = [] - - var base:Int = 0 - while base < data.count + + var base:Int = 0 + while base < data.count { guard let target:Quantization.Selector = Quantization.parse(selector: data[base]) - else + else { throw JPEG.ParsingError.invalidQuantizationTargetCode(data[base] & 0x0f) } - + let table:Quantization switch data[base] >> 4 { case 0: - guard data.count >= base + 65 - else + guard data.count >= base + 65 + else { - throw JPEG.ParsingError.mismatched(marker: .quantization, + throw JPEG.ParsingError.mismatched(marker: .quantization, count: data.count, minimum: base + 65) } - - table = .init(precision: .uint8, values: data[base + 1 ..< base + 65], + + table = .init(precision: .uint8, values: data[base + 1 ..< base + 65], target: target) - base += 65 + base += 65 case 1: - guard data.count >= base + 129 - else + guard data.count >= base + 129 + else { - throw JPEG.ParsingError.mismatched(marker: .quantization, + throw JPEG.ParsingError.mismatched(marker: .quantization, count: data.count, minimum: base + 129) } - - table = .init(precision: .uint16, values: data[base + 1 ..< base + 129], + + table = .init(precision: .uint16, values: data[base + 1 ..< base + 129], target: target) - base += 129 - + base += 129 + default: throw JPEG.ParsingError.invalidQuantizationPrecisionCode(data[base] >> 4) } - + tables.append(table) } - + return tables } } -// frame/scan header parsing -extension JPEG.Header.HeightRedefinition +// frame/scan header parsing +extension JPEG.Header.HeightRedefinition { /// static func JPEG.Header.HeightRedefinition.parse(_:) - /// throws + /// throws /// Parses a [`(Marker).height`] segment into a height redefinition. - /// - /// If the given data does not parse to a valid height redefinition, + /// + /// If the given data does not parse to a valid height redefinition, /// this function will throw a [`(JPEG).ParsingError`]. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - -> : Self + /// - -> : Self /// The parsed height redefinition. public static func parse(_ data:[UInt8]) throws -> Self @@ -637,19 +637,19 @@ extension JPEG.Header.HeightRedefinition } return .init(height: data.load(bigEndian: UInt16.self, as: Int.self, at: 0)) - } + } } -extension JPEG.Header.RestartInterval +extension JPEG.Header.RestartInterval { /// static func JPEG.Header.RestartInterval.parse(_:) - /// throws + /// throws /// Parses an [`(Marker).interval`] segment into a restart interval definition. - /// - /// If the given data does not parse to a valid restart interval definition, + /// + /// If the given data does not parse to a valid restart interval definition, /// this function will throw a [`(JPEG).ParsingError`]. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - -> : Self + /// - -> : Self /// The parsed restart definition. public static func parse(_ data:[UInt8]) throws -> Self @@ -659,61 +659,61 @@ extension JPEG.Header.RestartInterval { throw JPEG.ParsingError.mismatched(marker: .height, count: data.count, expected: 2) } - + let value:Int = data.load(bigEndian: UInt16.self, as: Int.self, at: 0) return .init(interval: value == 0 ? nil : value) - } + } } -extension JPEG.Header.Frame +extension JPEG.Header.Frame { /// static func JPEG.Header.Frame.validate(process:precision:size:components:) - /// throws + /// throws /// Creates a frame header after validating the given field values. - /// - /// If the given parameters are not consistent with one another, and the - /// [JPEG standard](https://www.w3.org/Graphics/JPEG/itu-t81.pdf), this + /// + /// If the given parameters are not consistent with one another, and the + /// [JPEG standard](https://www.w3.org/Graphics/JPEG/itu-t81.pdf), this /// function will throw a [`(JPEG).ParsingError`], unless otherwise noted. - /// - process : JPEG.Process + /// - process : JPEG.Process /// The coding process used by the image. - /// - precision : Swift.Int - /// The bit depth of the image. If the `process` is [`(JPEG.Process).baseline`], - /// this parameter must be 8. If the `process` is [`(JPEG.Process).extended(coding:differential:)`] - /// or [`(JPEG.Process).progressive(coding:differential:)`], this parameter - /// must be either 8 or 12. If the process is [`(JPEG.Process).lossless(coding:differential:)`], + /// - precision : Swift.Int + /// The bit depth of the image. If the `process` is [`(JPEG.Process).baseline`], + /// this parameter must be 8. If the `process` is [`(JPEG.Process).extended(coding:differential:)`] + /// or [`(JPEG.Process).progressive(coding:differential:)`], this parameter + /// must be either 8 or 12. If the process is [`(JPEG.Process).lossless(coding:differential:)`], /// this parameter must be within the interval `2 ... 16`. /// - size : (x:Swift.Int, y:Swift.Int) - /// The size of the image, in pixels. Passing a negative height will result - /// in a precondition failure. Passing a negative or zero width will result - /// in a [`(JPEG).ParsingError`]. This constructor treats the two failure - /// conditions differently because the latter one is the only one that can + /// The size of the image, in pixels. Passing a negative height will result + /// in a precondition failure. Passing a negative or zero width will result + /// in a [`(JPEG).ParsingError`]. This constructor treats the two failure + /// conditions differently because the latter one is the only one that can /// occur when parsing a frame header from input data. /// - components: [JPEG.Component.Key: JPEG.Component] - /// The components in the image. This dictionary must have at least one - /// element. If the `process` is [`(JPEG.Process).progressive(coding:differential:)`], - /// it can have no more than four elements. The sampling factors of each - /// component must be within the interval `1 ... 4` in both directions. - /// if the `process` is [`(JPEG.Process).baseline`], the components can + /// The components in the image. This dictionary must have at least one + /// element. If the `process` is [`(JPEG.Process).progressive(coding:differential:)`], + /// it can have no more than four elements. The sampling factors of each + /// component must be within the interval `1 ... 4` in both directions. + /// if the `process` is [`(JPEG.Process).baseline`], the components can /// only use the quantization table selectors `\.0` and `\.1`. - /// - -> : Self + /// - -> : Self /// A frame header. - public static - func validate(process:JPEG.Process, precision:Int, size:(x:Int, y:Int), - components:[JPEG.Component.Key: JPEG.Component]) throws -> Self + public static + func validate(process:JPEG.Process, precision:Int, size:(x:Int, y:Int), + components:[JPEG.Component.Key: JPEG.Component]) throws -> Self { - // this is a precondition and not a guard because the height field - // gets parsed from a UInt16, so the only way for this value to be negative + // this is a precondition and not a guard because the height field + // gets parsed from a UInt16, so the only way for this value to be negative // is through direct programmer action precondition(size.y >= 0, "frame header cannot have negative height") - guard size.x > 0 - else + guard size.x > 0 + else { throw JPEG.ParsingError.invalidFrameWidth(size.x) } - - for (ci, component):(JPEG.Component.Key, JPEG.Component) in components + + for (ci, component):(JPEG.Component.Key, JPEG.Component) in components { - // we don’t enforce the scan volume constraint in the parsing stage - // because it only applies to interleaved scans (so a 4x4 sampled + // we don’t enforce the scan volume constraint in the parsing stage + // because it only applies to interleaved scans (so a 4x4 sampled // component is legal as long as count == 1) guard 1 ... 4 ~= component.factor.x, 1 ... 4 ~= component.factor.y @@ -722,26 +722,26 @@ extension JPEG.Header.Frame throw JPEG.ParsingError.invalidFrameComponentSamplingFactor( component.factor, ci) } - - if case .baseline = process + + if case .baseline = process { - // make sure only selectors 0 and 1 are used - switch component.selector + // make sure only selectors 0 and 1 are used + switch component.selector { case \.0, \.1: - break + break default: - throw JPEG.ParsingError.invalidFrameQuantizationSelector(component.selector, + throw JPEG.ParsingError.invalidFrameQuantizationSelector(component.selector, process) } } } - - switch (process, precision) + + switch (process, precision) { - case (.baseline, 8), - (.extended, 8), (.extended, 12), - (.progressive, 8), (.progressive, 12), + case (.baseline, 8), + (.extended, 8), (.extended, 12), + (.progressive, 8), (.progressive, 12), (.lossless, 2 ... 16): break @@ -749,12 +749,12 @@ extension JPEG.Header.Frame // invalid precision throw JPEG.ParsingError.invalidFramePrecision(precision, process) } - - switch (process, components.count) + + switch (process, components.count) { - case (.baseline, 1 ... 255), - (.extended, 1 ... 255), - (.progressive, 1 ... 4), + case (.baseline, 1 ... 255), + (.extended, 1 ... 255), + (.progressive, 1 ... 4), (.lossless, 1 ... 255): break @@ -762,23 +762,23 @@ extension JPEG.Header.Frame // invalid count throw JPEG.ParsingError.invalidFrameComponentCount(components.count, process) } - - return .init(process: process, precision: precision, size: size, + + return .init(process: process, precision: precision, size: size, components: components) } /// static func JPEG.Header.Frame.parse(_:process:) - /// throws + /// throws /// Parses a [`(Marker).frame(_:)`] segment into a frame header. - /// - /// If the given data does not parse to a valid frame header, - /// this function will throw a [`(JPEG).ParsingError`]. This function - /// invokes [`validate(process:precision:size:components:)`], so any errors + /// + /// If the given data does not parse to a valid frame header, + /// this function will throw a [`(JPEG).ParsingError`]. This function + /// invokes [`validate(process:precision:size:components:)`], so any errors /// it can throw can also be thrown by this function. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - process : JPEG.Process + /// - process : JPEG.Process /// The coding process used by the image. - /// - -> : Self + /// - -> : Self /// The parsed frame header. public static func parse(_ data:[UInt8], process:JPEG.Process) throws -> Self @@ -786,23 +786,23 @@ extension JPEG.Header.Frame guard data.count >= 6 else { - throw JPEG.ParsingError.mismatched(marker: .frame(process), + throw JPEG.ParsingError.mismatched(marker: .frame(process), count: data.count, minimum: 6) } let precision:Int = .init(data[0]) - let size:(x:Int, y:Int) = + let size:(x:Int, y:Int) = ( data.load(bigEndian: UInt16.self, as: Int.self, at: 3), data.load(bigEndian: UInt16.self, as: Int.self, at: 1) ) let count:Int = .init(data[5]) - + guard data.count == 3 * count + 6 else { // wrong segment size - throw JPEG.ParsingError.mismatched(marker: .frame(process), + throw JPEG.ParsingError.mismatched(marker: .frame(process), count: data.count, expected: 3 * count + 6) } @@ -811,75 +811,75 @@ extension JPEG.Header.Frame { let base:Int = 3 * i + 6 let byte:(UInt8, UInt8, UInt8) = (data[base], data[base + 1], data[base + 2]) - + let factor:(x:Int, y:Int) = (.init(byte.1 >> 4), .init(byte.1 & 0x0f)) let ci:JPEG.Component.Key = .init(byte.0) - - guard let selector:JPEG.Table.Quantization.Selector = + + guard let selector:JPEG.Table.Quantization.Selector = JPEG.Table.Quantization.parse(selector: byte.2) - else + else { throw JPEG.ParsingError.invalidFrameQuantizationSelectorCode(byte.2) } - + let component:JPEG.Component = .init(factor: factor, selector: selector) - // make sure no duplicate component indices are used - guard components.updateValue(component, forKey: ci) == nil - else + // make sure no duplicate component indices are used + guard components.updateValue(component, forKey: ci) == nil + else { throw JPEG.ParsingError.duplicateFrameComponentIndex(ci) } } - return try .validate(process: process, precision: precision, size: size, + return try .validate(process: process, precision: precision, size: size, components: components) } -} -extension JPEG.Header.Scan +} +extension JPEG.Header.Scan { /// static func JPEG.Header.Scan.validate(process:band:bits:components:) /// throws /// Creates a scan header after validating the given field values. - /// - /// If the given parameters are not consistent with one another, and the - /// [JPEG standard](https://www.w3.org/Graphics/JPEG/itu-t81.pdf), this + /// + /// If the given parameters are not consistent with one another, and the + /// [JPEG standard](https://www.w3.org/Graphics/JPEG/itu-t81.pdf), this /// function will throw a [`(JPEG).ParsingError`]. - /// - process : JPEG.Process + /// - process : JPEG.Process /// The coding process used by the image. /// - band : Swift.Range - /// The frequency band encoded by the scan, in zigzag order. It must be - /// within the interval of 0 to 64. If the `process` is - /// [`(Process).progressive(coding:differential:)`], this parameter must - /// either be `0 ..< 1`, or some range within the interval `1 ..< 64`. + /// The frequency band encoded by the scan, in zigzag order. It must be + /// within the interval of 0 to 64. If the `process` is + /// [`(Process).progressive(coding:differential:)`], this parameter must + /// either be `0 ..< 1`, or some range within the interval `1 ..< 64`. /// Otherwise, this parameter must be set to `0 ..< 64`. /// - bits : Swift.Range - /// The bit range encoded by the scan, where bit zero is the least significant - /// bit. The upper range bound must be either infinity ([`Swift.Int`max`]) + /// The bit range encoded by the scan, where bit zero is the least significant + /// bit. The upper range bound must be either infinity ([`Swift.Int`max`]) /// or one greater than the lower bound. If the `process` is not - /// [`(Process).progressive(coding:differential:)`], this value must + /// [`(Process).progressive(coding:differential:)`], this value must /// be set to `0 ..< .max`. /// - components: [JPEG.Scan.Component] - /// The color components in the scan, in the order in which their - /// data units are interleaved. If the scan is an AC progressive scan, - /// this array must have exactly one element. Otherwise, it must have - /// between one and four elements. If the `process` is [`(Process).baseline`], + /// The color components in the scan, in the order in which their + /// data units are interleaved. If the scan is an AC progressive scan, + /// this array must have exactly one element. Otherwise, it must have + /// between one and four elements. If the `process` is [`(Process).baseline`], /// the components can only use the huffman table selectors `\.0` and `\.1`. - /// - -> : Self + /// - -> : Self /// A scan header. - public static - func validate(process:JPEG.Process, - band:Range, bits:Range, components:[JPEG.Scan.Component]) - throws -> Self + public static + func validate(process:JPEG.Process, + band:Range, bits:Range, components:[JPEG.Scan.Component]) + throws -> Self { - for component:JPEG.Scan.Component in components + for component:JPEG.Scan.Component in components { - if case .baseline = process + if case .baseline = process { - // make sure only selectors 0 and 1 are used + // make sure only selectors 0 and 1 are used switch component.selector.dc { case \.0, \.1: - break + break default: throw JPEG.ParsingError.invalidScanHuffmanDCSelector( component.selector.dc, process) @@ -887,152 +887,152 @@ extension JPEG.Header.Scan switch component.selector.ac { case \.0, \.1: - break + break default: throw JPEG.ParsingError.invalidScanHuffmanACSelector( component.selector.ac, process) } } } - - // validate subsetting + + // validate subsetting let a:Int = bits.lowerBound - switch (process, (band.lowerBound, band.upperBound), (bits.lowerBound, bits.upperBound)) + switch (process, (band.lowerBound, band.upperBound), (bits.lowerBound, bits.upperBound)) { - case (.baseline, (0, 64), (0, .max)), - (.extended, (0, 64), (0, .max)), + case (.baseline, (0, 64), (0, .max)), + (.extended, (0, 64), (0, .max)), (.progressive, (0, 1), (0..., .max)), // unlimited bits per initial scan - (.progressive, (0, 1), (0..., a + 1)): // 1 bit per refining scan + (.progressive, (0, 1), (0..., a + 1)): // 1 bit per refining scan guard 1 ... 4 ~= components.count - else + else { - throw JPEG.ParsingError.invalidScanComponentCount(components.count, + throw JPEG.ParsingError.invalidScanComponentCount(components.count, process) - } - case (.progressive, (1..., 2 ... 64), (0..., .max)), - (.progressive, (1..., 2 ... 64), (0..., a + 1)): + } + case (.progressive, (1..., 2 ... 64), (0..., .max)), + (.progressive, (1..., 2 ... 64), (0..., a + 1)): guard 1 ... 1 ~= components.count - else + else { // progressive scans that code for AC components cannot be interleaved - throw JPEG.ParsingError.invalidScanComponentCount(components.count, + throw JPEG.ParsingError.invalidScanComponentCount(components.count, process) } - + default: throw JPEG.ParsingError.invalidScanProgressiveSubset( - band: (band.lowerBound, band.upperBound), - bits: (bits.lowerBound, bits.upperBound), + band: (band.lowerBound, band.upperBound), + bits: (bits.lowerBound, bits.upperBound), process) } - + return .init(band: band, bits: bits, components: components) } /// static func JPEG.Header.Scan.parse(_:process:) - /// throws + /// throws /// Parses a [`(Marker).scan`] segment into a scan header. - /// - /// If the given data does not parse to a valid scan header, - /// this function will throw a [`(JPEG).ParsingError`]. This function - /// invokes [`validate(process:band:bits:components:)`], so any errors + /// + /// If the given data does not parse to a valid scan header, + /// this function will throw a [`(JPEG).ParsingError`]. This function + /// invokes [`validate(process:band:bits:components:)`], so any errors /// it can throw can also be thrown by this function. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - process : JPEG.Process + /// - process : JPEG.Process /// The coding process used by the image. - /// - -> : Self + /// - -> : Self /// The parsed scan header. - public static + public static func parse(_ data:[UInt8], process:JPEG.Process) throws -> Self { - guard data.count >= 4 - else + guard data.count >= 4 + else { - throw JPEG.ParsingError.mismatched(marker: .scan, + throw JPEG.ParsingError.mismatched(marker: .scan, count: data.count, minimum: 4) } - + let count:Int = .init(data[0]) - + guard data.count == 2 * count + 4 - else + else { // wrong segment size - throw JPEG.ParsingError.mismatched(marker: .scan, + throw JPEG.ParsingError.mismatched(marker: .scan, count: data.count, expected: 2 * count + 4) } - - let components:[JPEG.Scan.Component] = try (0 ..< count).map + + let components:[JPEG.Scan.Component] = try (0 ..< count).map { let base:Int = 2 * $0 + 1 let byte:(UInt8, UInt8) = (data[base], data[base + 1]) - + let ci:JPEG.Component.Key = .init(byte.0) - guard let dc:JPEG.Table.HuffmanDC.Selector = - JPEG.Table.HuffmanDC.parse(selector: byte.1 >> 4), - let ac:JPEG.Table.HuffmanAC.Selector = + guard let dc:JPEG.Table.HuffmanDC.Selector = + JPEG.Table.HuffmanDC.parse(selector: byte.1 >> 4), + let ac:JPEG.Table.HuffmanAC.Selector = JPEG.Table.HuffmanAC.parse(selector: byte.1 & 0xf) - else + else { throw JPEG.ParsingError.invalidScanHuffmanSelectorCode(byte.1) } - + return .init(ci: ci, selector: (dc, ac)) } - - // parse spectral parameters + + // parse spectral parameters let base:Int = 2 * count + 1 let byte:(UInt8, UInt8, UInt8) = (data[base], data[base + 1], data[base + 2]) - + let band:(Int, Int) = (.init(byte.0), .init(byte.1) + 1) - let bits:(Int, Int) = + let bits:(Int, Int) = ( - .init(byte.2 & 0x0f), + .init(byte.2 & 0x0f), byte.2 & 0xf0 == 0 ? .max : .init(byte.2 >> 4) ) - - guard band.0 < band.1, // is valid range + + guard band.0 < band.1, // is valid range bits.0 < bits.1 - else + else { throw JPEG.ParsingError.invalidScanProgressiveSubset( band: band, bits: bits, process) } - - return try .validate(process: process, + + return try .validate(process: process, band: band.0 ..< band.1, bits: bits.0 ..< bits.1, components: components) } } -// huffman decoder -extension JPEG.Table.Huffman +// huffman decoder +extension JPEG.Table.Huffman { - struct Decoder + struct Decoder { - struct Entry + struct Entry { let symbol:Symbol - @General.Storage - var length:Int + @General.Storage + var length:Int } - - private - let storage:[Entry], + + private + let storage:[Entry], n:Int, // number of level 0 entries ζ:Int // logical size of the table (where the n level 0 entries are each 256 units big) - - init(_ storage:[Entry], n:Int, ζ:Int) + + init(_ storage:[Entry], n:Int, ζ:Int) { - self.storage = storage + self.storage = storage self.n = n self.ζ = ζ } } } -extension JPEG.Table.Huffman +extension JPEG.Table.Huffman { - // this is a (relatively) expensive function. however most jpegs define a - // fresh set of huffman tables for each scan, so it is very unlikely that + // this is a (relatively) expensive function. however most jpegs define a + // fresh set of huffman tables for each scan, so it is very unlikely that // this function will get called redundantly. func decoder() -> Decoder { @@ -1042,9 +1042,9 @@ extension JPEG.Table.Huffman number of leaf nodes at each level of the tree. combined with a rule that says that leaf nodes always occur on the “leftmost” side of the tree, this uniquely determines a huffman tree. - + Given: leaves per level = [0, 3, 1, 1, ... ] - + ___0___[root]___1___ / \ __0__[ ]__1__ __0__[ ]__1__ @@ -1054,17 +1054,17 @@ extension JPEG.Table.Huffman [d] _0_[ ]_1_ / \ [e] reserved - + note that in a huffman tree, level 0 always contains 0 leaf nodes (why?) so the huffman table omits level 0 in the leaf counts list. - + we *could* build a tree data structure, and traverse it as we read in the coded bits, but that would be slow and require a shift for every bit. instead we extend the huffman tree into a perfect tree, and assign the new leaf nodes the values of their parents. - + ________[root]________ / \ _____[ ]_____ _____[ ]_____ @@ -1072,7 +1072,7 @@ extension JPEG.Table.Huffman [a] [b] [c] ___[ ]___ / \ / \ / \ / \ (a) (a) (b) (b) (c) (c) [d] ... - + this lets us make a table of huffman codes where all the codes are “padded” to the same length. note that codewords that occur higher up the tree occur multiple times because @@ -1081,7 +1081,7 @@ extension JPEG.Table.Huffman the length of the original code so we know how many bits we should advance the current bit position by once we match a code. - + code value length ————————— ————————— ———————— 000 'a' 2 @@ -1092,29 +1092,29 @@ extension JPEG.Table.Huffman 101 'c' 2 110 'd' 3 111 ... >3 - + decoding coded data then becomes a matter of matching a fixed length bitstream against the table (the code works as an integer index!) since all possible combinations of trailing “padding” bits are represented in the table. - + in jpeg, codewords can be a maximum of 16 bits long. this means in theory we need a table with 2^16 entries. that’s a huge table considering there are only 256 actual encoded values, and since this is the kind of thing that really needs to be optimized for speed, this needs to be as cache friendly as possible. - + we can reduce the table size by splitting the 16-bit table into two 8-bit levels. this means we have one 8-bit “root” tree, and k 8-bit child trees rooted on the internal nodes at level 8 of the original tree. - - so far, we’ve looked at the huffman tree as a tree. however - it actually makes more sense to look at it as a table, just - like its implementation. remember that the tree is right-heavy, - so the first 8 levels will look something like - + + so far, we’ve looked at the huffman tree as a tree. however + it actually makes more sense to look at it as a table, just + like its implementation. remember that the tree is right-heavy, + so the first 8 levels will look something like + +———————————————————+ 0 | | | | @@ -1160,22 +1160,22 @@ extension JPEG.Table.Huffman +———————————————————+ | | / ///////////////////// - - this is awesome because we don’t need to store anything in - the table entries themselves to know if they are direct entries - or indirect entries. if the index of the entry is greater than - or equal to `n` (the number of direct entries), it is an - indirect entry, and its indirect index is given by the first - byte of the codeword with `n` subtracted from it. - level-1 subtables are always 256 entries long since they are - leaf tables. this means their positions can be computed in - constant time, given `n`, which is also the position of the + + this is awesome because we don’t need to store anything in + the table entries themselves to know if they are direct entries + or indirect entries. if the index of the entry is greater than + or equal to `n` (the number of direct entries), it is an + indirect entry, and its indirect index is given by the first + byte of the codeword with `n` subtracted from it. + level-1 subtables are always 256 entries long since they are + leaf tables. this means their positions can be computed in + constant time, given `n`, which is also the position of the first level-1 table. - - (for computational ease, we store `s = 256 - n` instead. - `s` can be interpreted as the number of level-1 subtables + + (for computational ease, we store `s = 256 - n` instead. + `s` can be interpreted as the number of level-1 subtables trail the level-0 table in the storage buffer) - + how big can `s` be? well, remember that there are only 256 different encoded values which means the original tree can only have 256 leaves. any full binary tree with height at @@ -1189,119 +1189,119 @@ extension JPEG.Table.Huffman this to show that we can only have up to k ≤ 129 child trees. in fact, we can reduce this even further to k ≤ 128 because if the rightmost tree only contains 1 leaf, there has to be at - least one other tree with an odd number of leaves to make the - total add up to 256, and that number has to be at least 3. - in reality, k is rarely bigger than 7 or 8 yielding a significant + least one other tree with an odd number of leaves to make the + total add up to 256, and that number has to be at least 3. + in reality, k is rarely bigger than 7 or 8 yielding a significant size savings. - - because we don’t need to store pointers, each table entry can - be just 2 bytes long — 1 byte for the encoded value, and 1 byte + + because we don’t need to store pointers, each table entry can + be just 2 bytes long — 1 byte for the encoded value, and 1 byte to store the length of the codeword. - + a buffer like this will never have size greater than 2 * 256 × (128 + 1) = 65_792 bytes, compared with 2 × (1 << 16) = 131_072 bytes for the 16-bit table. in reality the 2 layer table is usually on the order of 2–4 kB. - + why not compact the child trees further, since not all of them actually have height 8? we could do that, and get some serious worst-case memory savings, but then we couldn’t access the child tables at constant offsets from the buffer base. we’d - need to store whole ≥16-bit pointers to the specific byte offset - where the variable-length child table lives, and perform a - conditional bit shift to transform the input bits into an + need to store whole ≥16-bit pointers to the specific byte offset + where the variable-length child table lives, and perform a + conditional bit shift to transform the input bits into an appropriate index into the table. not a good look. */ - + // z is the physical size of the table in memory - let (n, z):(Int, Int) = self.size - + let (n, z):(Int, Int) = self.size + var storage:[Decoder.Entry] = [] storage.reserveCapacity(z) - + for (l, symbols):(Int, [Symbol]) in self.symbols.enumerated() { - guard storage.count < z - else + guard storage.count < z + else { break - } - + } + let clones:Int = 0x8080 >> l & 0xff - for symbol:Symbol in symbols + for symbol:Symbol in symbols { let entry:Decoder.Entry = .init(symbol: symbol, length: l + 1) storage.append(contentsOf: repeatElement(entry, count: clones)) } } - + assert(storage.count == z) return .init(storage, n: n, ζ: z + n * 255) } } -// table accessors -extension JPEG.Table.Huffman.Decoder +// table accessors +extension JPEG.Table.Huffman.Decoder { // codeword is big-endian - subscript(codeword:UInt16) -> Entry + subscript(codeword:UInt16) -> Entry { // [ level 0 index | offset ] let i:Int = .init(codeword >> 8) - if i < self.n + if i < self.n { return self.storage[i] } - else + else { let j:Int = .init(codeword) - guard j < self.ζ - else + guard j < self.ζ + else { return .init(symbol: .init(0), length: 16) } - + return self.storage[j - self.n * 255] } } } -extension JPEG.Table.Quantization +extension JPEG.Table.Quantization { /// static func JPEG.Table.Quantization.z(k:h:) - /// @inlinable + /// @inlinable /// Converts a coefficient grid index to a zigzag index. /// /// It is easier to convert grid indices (*k*,\ *h*) to zigzag indices (*z*) - /// than the other way around, so most library APIs store coefficient-related + /// than the other way around, so most library APIs store coefficient-related /// information natively in zigzag order. - /// - /// The JPEG format only uses the grid domain `0 ..< 8`\ ×\ `0 ..< 8`, which - /// maps to the zigzag range `0 ..< 64`. However, this function works for + /// + /// The JPEG format only uses the grid domain `0 ..< 8`\ ×\ `0 ..< 8`, which + /// maps to the zigzag range `0 ..< 64`. However, this function works for /// any non-negative input coordinate. - /// - x : Swift.Int + /// - x : Swift.Int /// The horizontal frequency index. - /// - y : Swift.Int + /// - y : Swift.Int /// The vertical frequency index. - /// - ->: Swift.Int + /// - ->: Swift.Int /// The corresponding zigzag index. /// # [See also](quantization-table-subscripts) @inlinable - public static - func z(k x:Int, h y:Int) -> Int + public static + func z(k x:Int, h y:Int) -> Int { - let p:Int = x + y < 8 ? 1 : 0, + let p:Int = x + y < 8 ? 1 : 0, q:Int = (x + y) & 1 - let a:Int = 72 * (p ^ 1), + let a:Int = 72 * (p ^ 1), b:Int = 2 * p - 1 let n:Int = b * (x + y) - 14 * p + 15 let t:Int = (n * (n + 1)) >> 1 return a + b * t - q * x - (q ^ 1) * y - 1 } - + /// subscript JPEG.Table.Quantization[k:h:] { get set } /// @inlinable /// Accesses the quantum value at the given grid index. - /// - /// Using this subscript is equivalent to using [`[z:]`] with the output + /// + /// Using this subscript is equivalent to using [`[z:]`] with the output /// of [`z(k:h:)`]. /// - k : Swift.Int /// The horizontal frequency index. This value must be in the range `0 ..< 8`. @@ -1312,16 +1312,16 @@ extension JPEG.Table.Quantization /// # [See also](quantization-table-subscripts) /// ## (quantization-table-subscripts) @inlinable - public - subscript(k k:Int, h h:Int) -> UInt16 + public + subscript(k k:Int, h h:Int) -> UInt16 { - get + get { self[z: Self.z(k: k, h: h)] } set(value) { - self[z: Self.z(k: k, h: h)] = value + self[z: Self.z(k: k, h: h)] = value } } /// subscript JPEG.Table.Quantization[z:] { get set } @@ -1332,10 +1332,10 @@ extension JPEG.Table.Quantization /// The quantum value. /// # [See also](quantization-table-subscripts) /// ## (quantization-table-subscripts) - public - subscript(z z:Int) -> UInt16 + public + subscript(z z:Int) -> UInt16 { - get + get { self.storage[z] } @@ -1347,42 +1347,42 @@ extension JPEG.Table.Quantization } // intermediate forms -extension JPEG +extension JPEG { - /// enum JPEG.Data + /// enum JPEG.Data /// A namespace for image representation types. /// # [Image representations](image-data-types) /// ## (image-data-types-and-namespace) - public - enum Data + public + enum Data { } } -extension JPEG.Data +extension JPEG.Data { - private static - func units(_ size:Int, stride:Int) -> Int + private static + func units(_ size:Int, stride:Int) -> Int { - let complete:Int = size / stride, - partial:Int = size % stride != 0 ? 1 : 0 - return complete + partial + let complete:Int = size / stride, + partial:Int = size % stride != 0 ? 1 : 0 + return complete + partial } - /// struct JPEG.Data.Spectral + /// struct JPEG.Data.Spectral /// where Format:JPEG.Format /// : Swift.RandomAccessCollection /// A planar image represented in the frequency domain. - /// - /// A spectral image stores its data in blocks called *data units*. Each - /// block is a square 8×8 matrix of frequency coefficients. The data units + /// + /// A spectral image stores its data in blocks called *data units*. Each + /// block is a square 8×8 matrix of frequency coefficients. The data units /// themselves have the same spatial arrangement they do in the spatial domain. - /// - /// A spectral image always stores a whole number of data units in both + /// + /// A spectral image always stores a whole number of data units in both /// dimensions, even if the image dimensions in pixels are not multiples of 8. - /// Because each component in an image has its own sampling factors, the + /// Because each component in an image has its own sampling factors, the /// image planes may not have the same size. - /// - /// The spectral representation is a lossless representation. JPEG - /// images that have been decoded to this representation can be re-encoded + /// + /// The spectral representation is a lossless representation. JPEG + /// images that have been decoded to this representation can be re-encoded /// without loss of information or compression. /// # [Creating an image](spectral-create-image) /// # [Saving an image](spectral-save-image) @@ -1393,105 +1393,105 @@ extension JPEG.Data /// # [See also](image-data-types) /// ## (image-data-types) /// ## (image-data-types-and-namespace) - public - struct Spectral where Format:JPEG.Format + public + struct Spectral where Format:JPEG.Format { - /// struct JPEG.Data.Spectral.Quanta + /// struct JPEG.Data.Spectral.Quanta /// : Swift.RandomAccessCollection /// A container for the quantization tables used by a spectral image. - public - struct Quanta + public + struct Quanta { - private - var quanta:[JPEG.Table.Quantization], + private + var quanta:[JPEG.Table.Quantization], q:[JPEG.Table.Quantization.Key: Int] } - /// struct JPEG.Data.Spectral.Plane + /// struct JPEG.Data.Spectral.Plane /// A plane of an image in the frequency domain, containing one color channel. - public - struct Plane + public + struct Plane { /// var JPEG.Data.Spectral.Plane.units : (x:Swift.Int, y:Swift.Int) { get } - /// The number of data units in this plane in the horizontal and + /// The number of data units in this plane in the horizontal and /// vertical directions. public internal(set) var units:(x:Int, y:Int) - + /// var JPEG.Data.Spectral.Plane.factor : (x:Swift.Int, y:Swift.Int) { get } /// @ : General.Storage2 /// The sampling factors of the color component this plane stores. - /// - /// This property is backed by two [`Swift.Int16`]s to circumvent compiler - /// size limits for the `read` and `modify` accessors that the image + /// + /// This property is backed by two [`Swift.Int16`]s to circumvent compiler + /// size limits for the `read` and `modify` accessors that the image /// planes are subscriptable through. @General.Storage2 - public - var factor:(x:Int, y:Int) + public + var factor:(x:Int, y:Int) @General.MutableStorage var q:Int - - private + + private var buffer:[Int16] - + /// subscript JPEG.Data.Spectral.Plane[x:y:z:] { get set } - /// Accesses the frequency coefficient at the specified zigzag index + /// Accesses the frequency coefficient at the specified zigzag index /// in the specified data unit. - /// - /// The `x` and `y` indices of this subscript have no index bounds. - /// Out-of-bounds reads will return 0; out-of-bounds writes will - /// have no effect. The `z` index still has to be within the + /// + /// The `x` and `y` indices of this subscript have no index bounds. + /// Out-of-bounds reads will return 0; out-of-bounds writes will + /// have no effect. The `z` index still has to be within the /// correct range. - /// - x : Swift.Int + /// - x : Swift.Int /// The horizontal index of the data unit to access. - /// - y : Swift.Int - /// The vertical index of the data unit to access. Index 0 + /// - y : Swift.Int + /// The vertical index of the data unit to access. Index 0 /// corresponds to the visual top of the image. - /// - z : Swift.Int - /// The zigzag index of the coefficient to access. This index must - /// be in the range `0 ..< 64`. - /// - ->: Swift.Int16 + /// - z : Swift.Int + /// The zigzag index of the coefficient to access. This index must + /// be in the range `0 ..< 64`. + /// - ->: Swift.Int16 /// The frequency coefficient. - public - subscript(x x:Int, y y:Int, z z:Int) -> Int16 + public + subscript(x x:Int, y y:Int, z z:Int) -> Int16 { - get + get { - guard 0 ..< self.units.x ~= x, - 0 ..< self.units.y ~= y - else + guard 0 ..< self.units.x ~= x, + 0 ..< self.units.y ~= y + else { - return 0 + return 0 } - + return self.buffer[64 * (self.units.x * y + x) + z] } - set(value) + set(value) { - guard 0 ..< self.units.x ~= x, - 0 ..< self.units.y ~= y - else + guard 0 ..< self.units.x ~= x, + 0 ..< self.units.y ~= y + else { - return + return } - - self.buffer[64 * (self.units.x * y + x) + z] = value + + self.buffer[64 * (self.units.x * y + x) + z] = value } } } /// var JPEG.Data.Spectral.size : (x:Swift.Int, y:Swift.Int) { get } - /// The size of this image, in pixels. - /// + /// The size of this image, in pixels. + /// /// In general, this size is not the same as the size of the image planes. /// # [See also](spectral-query-image) /// ## (0:spectral-query-image) public private(set) - var size:(x:Int, y:Int), + var size:(x:Int, y:Int), /// var JPEG.Data.Spectral.blocks : (x:Swift.Int, y:Swift.Int) { get } - /// The number of minimum-coded units in this image, in the horizontal + /// The number of minimum-coded units in this image, in the horizontal /// and vertical directions. - /// - /// The size of the minimum-coded unit, in 8×8 blocks of pixels, - /// is given by [`layout``(Layout).scale`]. + /// + /// The size of the minimum-coded unit, in 8×8 blocks of pixels, + /// is given by [`layout``(Layout).scale`]. /// # [See also](spectral-query-image) /// ## (1:spectral-query-image) blocks:(x:Int, y:Int) @@ -1505,31 +1505,31 @@ extension JPEG.Data /// The metadata records in this image. /// # [See also](spectral-query-image) /// ## (4:spectral-query-image) - public + public var metadata:[JPEG.Metadata] - + /// var JPEG.Data.Spectral.quanta : Quanta { get } /// The quantization tables used by this image. /// # [See also](spectral-query-image) /// ## (3:spectral-query-image) - public private(set) + public private(set) var quanta:Quanta - private + private var planes:[Plane] } - /// struct JPEG.Data.Planar + /// struct JPEG.Data.Planar /// where Format:JPEG.Format /// A planar image represented in the spatial domain. - /// - /// A planar image stores its data in blocks called *data units*. Each - /// block is an 8×8-pixel square. A planar image always stores a whole - /// number of data units in both dimensions, even if the image dimensions - /// in pixels are not multiples of 8. Because each component in an image + /// + /// A planar image stores its data in blocks called *data units*. Each + /// block is an 8×8-pixel square. A planar image always stores a whole + /// number of data units in both dimensions, even if the image dimensions + /// in pixels are not multiples of 8. Because each component in an image /// has its own sampling factors, the image planes may not have the same size. - /// - /// A planar image is the result of applying an *inverse discrete cosine - /// transformation* to a spectral image. It can be converted back into a spectral - /// image (with some floating point error) with a *forward discrete cosine + /// + /// A planar image is the result of applying an *inverse discrete cosine + /// transformation* to a spectral image. It can be converted back into a spectral + /// image (with some floating point error) with a *forward discrete cosine /// transformation*. /// # [Creating an image](planar-create-image) /// # [Saving an image](planar-save-image) @@ -1539,104 +1539,104 @@ extension JPEG.Data /// # [See also](image-data-types) /// ## (image-data-types) /// ## (image-data-types-and-namespace) - public + public struct Planar where Format:JPEG.Format { - /// struct JPEG.Data.Planar.Plane + /// struct JPEG.Data.Planar.Plane /// A plane of an image in the spatial domain, containing one color channel. - public - struct Plane + public + struct Plane { /// let JPEG.Data.Planar.Plane.units : (x:Swift.Int, y:Swift.Int) - /// The number of data units in this plane in the horizontal and + /// The number of data units in this plane in the horizontal and /// vertical directions. - public + public let units:(x:Int, y:Int) /// var JPEG.Data.Planar.Plane.size : (x:Swift.Int, y:Swift.Int) { get } - /// The size of this plane, in pixels. It is equivalent to multiplying + /// The size of this plane, in pixels. It is equivalent to multiplying /// [`units`] by 8. - public - var size:(x:Int, y:Int) + public + var size:(x:Int, y:Int) { (8 * self.units.x, 8 * self.units.y) } - + /// var JPEG.Data.Planar.Plane.factor : (x:Swift.Int, y:Swift.Int) { get } /// @ : General.Storage2 /// The sampling factors of the color component this plane stores. - /// - /// This property is backed by two [`Swift.Int32`]s to circumvent compiler - /// size limits for the `read` and `modify` accessors that the image + /// + /// This property is backed by two [`Swift.Int32`]s to circumvent compiler + /// size limits for the `read` and `modify` accessors that the image /// planes are subscriptable through. @General.Storage2 - public - var factor:(x:Int, y:Int) - - private + public + var factor:(x:Int, y:Int) + + private var buffer:[UInt16] /// subscript JPEG.Data.Planar.Plane[x:y:] { get set } /// Accesses the sample at the specified pixel location. - /// - x : Swift.Int + /// - x : Swift.Int /// The horizontal pixel index of the sample to access. - /// - y : Swift.Int - /// The vertical pixel index of the sample to access. Index 0 + /// - y : Swift.Int + /// The vertical pixel index of the sample to access. Index 0 /// corresponds to the visual top of the image. - /// - ->: Swift.UInt16 + /// - ->: Swift.UInt16 /// The sample. - public + public subscript(x x:Int, y y:Int) -> UInt16 { - get + get { self.buffer[x + self.size.x * y] } - set(value) + set(value) { - self.buffer[x + self.size.x * y] = value + self.buffer[x + self.size.x * y] = value } } } /// let JPEG.Data.Planar.size : (x:Swift.Int, y:Swift.Int) - /// The size of this image, in pixels. - /// + /// The size of this image, in pixels. + /// /// In general, this size is not the same as the size of the image planes. /// # [See also](planar-query-image) /// ## (planar-query-image) - public + public let size:(x:Int, y:Int) /// let JPEG.Data.Planar.layout : JPEG.Layout /// The layout of this image. /// # [See also](planar-query-image) /// ## (planar-query-image) - public - let layout:JPEG.Layout, + public + let layout:JPEG.Layout, /// let JPEG.Data.Planar.metadata : [JPEG.Metadata] /// The metadata records in this image. /// # [See also](planar-query-image) /// ## (planar-query-image) metadata:[JPEG.Metadata] - - private - var planes:[Plane] - - init(size:(x:Int, y:Int), - layout:JPEG.Layout, + + private + var planes:[Plane] + + init(size:(x:Int, y:Int), + layout:JPEG.Layout, metadata:[JPEG.Metadata], planes:[JPEG.Data.Planar.Plane]) { self.size = size self.layout = layout self.metadata = metadata - self.planes = planes - } + self.planes = planes + } } - /// struct JPEG.Data.Rectangular + /// struct JPEG.Data.Rectangular /// where Format:JPEG.Format /// A rectangular image. - /// - /// A rectangular image resamples all planes at the same sampling level, + /// + /// A rectangular image resamples all planes at the same sampling level, /// giving a rectangular array of interleaved samples. - /// + /// /// It can be unpacked to various color targets to get a pixel color array. /// # [Creating an image](rectangular-create-image) /// # [Saving an image](rectangular-save-image) @@ -1646,68 +1646,68 @@ extension JPEG.Data /// # [See also](image-data-types) /// ## (image-data-types) /// ## (image-data-types-and-namespace) - public - struct Rectangular where Format:JPEG.Format + public + struct Rectangular where Format:JPEG.Format { /// let JPEG.Data.Rectangular.size : (x:Swift.Int, y:Swift.Int) - /// The size of this image, in pixels. + /// The size of this image, in pixels. /// # [See also](rectangular-query-image) /// ## (rectangular-query-image) - public - let size:(x:Int, y:Int), + public + let size:(x:Int, y:Int), /// let JPEG.Data.Rectangular.layout : JPEG.Layout /// The layout of this image. /// # [See also](rectangular-query-image) /// ## (rectangular-query-image) - layout:JPEG.Layout, + layout:JPEG.Layout, /// let JPEG.Data.Rectangular.metadata : [JPEG.Metadata] /// The metadata records in this image. /// # [See also](rectangular-query-image) /// ## (rectangular-query-image) metadata:[JPEG.Metadata] - + var values:[UInt16] /// let JPEG.Data.Rectangular.stride : JPEG.Layout - /// The stride of the interleaved samples in this image. - /// + /// The stride of the interleaved samples in this image. + /// /// This value is analogous to the plane `count` of a planar or spectral image. /// For example, the rectangular representation of a planar YCbCr image /// with 3 planes would have a stride of 3. /// # [See also](rectangular-accessing-samples) /// ## (1:rectangular-accessing-samples) - public - var stride:Int + public + var stride:Int { - self.layout.recognized.count + self.layout.recognized.count } - /// init JPEG.Data.Rectangular.init(size:layout:metadata:values:) - /// Creates a rectangular image with the given image parameters and + /// init JPEG.Data.Rectangular.init(size:layout:metadata:values:) + /// Creates a rectangular image with the given image parameters and /// interleaved samples. - /// - /// Passing an invalid `size`, or an array of the wrong `count` will + /// + /// Passing an invalid `size`, or an array of the wrong `count` will /// result in a precondition failure. /// - size : (x:Swift.Int, y:Swift.Int) /// The size of the image, in pixels. Both dimensions must be positive. - /// - layout : JPEG.Layout + /// - layout : JPEG.Layout /// The layout of the image. /// - metadata : [JPEG.Metadata] /// The metadata records in the image. /// - values : [Swift.UInt16] - /// An array of interleaved samples, in row major order, and without - /// padding. The array must have exactly + /// An array of interleaved samples, in row major order, and without + /// padding. The array must have exactly /// [`layout``(Layout).recognized`count`]\ ×\ [`size`x`]\ ×\ [`size`y`] samples. - /// Each [`Swift.UInt16`] is one sample. The samples should not be - /// normalized, so an image with a [`layout``(Layout).format``(Format).precision`] of - /// 8 should only have samples in the range `0 ... 255`. + /// Each [`Swift.UInt16`] is one sample. The samples should not be + /// normalized, so an image with a [`layout``(Layout).format``(Format).precision`] of + /// 8 should only have samples in the range `0 ... 255`. /// # [See also](rectangular-create-image) /// ## (0:rectangular-create-image) - public - init(size:(x:Int, y:Int), - layout:JPEG.Layout, - metadata:[JPEG.Metadata], + public + init(size:(x:Int, y:Int), + layout:JPEG.Layout, + metadata:[JPEG.Metadata], values:[UInt16]) { - precondition(values.count == layout.recognized.count * size.x * size.y, + precondition(values.count == layout.recognized.count * size.x * size.y, "array count does not match size and layout") precondition(size.x > 0 && size.y > 0, "size must be positive") self.size = size @@ -1725,36 +1725,36 @@ extension JPEG.Data.Spectral.Quanta // generate the ‘default’ quantization table at `qi = -1`, `q = 0` self.quanta = [`default`] self.q = [-1: 0] - } - - mutating - func push(qi:JPEG.Table.Quantization.Key, quanta table:JPEG.Table.Quantization) - -> Int + } + + mutating + func push(qi:JPEG.Table.Quantization.Key, quanta table:JPEG.Table.Quantization) + -> Int { self.q.updateValue(self.quanta.endIndex, forKey: qi) self.quanta.append(table) return self.quanta.endIndex - 1 } - - mutating + + mutating func removeAll() { self.quanta.removeAll() self.q.removeAll() } /// func JPEG.Data.Spectral.Quanta.mapValues(_:) - /// rethrows - /// Returns a dictionary of the quantization tables in this container with + /// rethrows + /// Returns a dictionary of the quantization tables in this container with /// the quantum values of each table transformed by the given closure. /// - transform : ([Swift.UInt16]) throws -> [Swift.UInt16] - /// A closure that transforms a value. This closure accepts a 64-element - /// zigzag-indexed array of the quantum values in each table as its parameter, + /// A closure that transforms a value. This closure accepts a 64-element + /// zigzag-indexed array of the quantum values in each table as its parameter, /// and returns a transformed value of the same or of a different type. /// - -> : [JPEG.Table.Quantization.Key: R] - /// A dictionary containing the keys and transformed quanta of the + /// A dictionary containing the keys and transformed quanta of the /// quantization tables in this container. - public - func mapValues(_ transform:([UInt16]) throws -> R) + public + func mapValues(_ transform:([UInt16]) throws -> R) rethrows -> [JPEG.Table.Quantization.Key: R] { try self.q.mapValues @@ -1763,124 +1763,124 @@ extension JPEG.Data.Spectral.Quanta } } } -// RAC conformance for planar types -extension JPEG.Data.Spectral.Quanta:RandomAccessCollection +// RAC conformance for planar types +extension JPEG.Data.Spectral.Quanta:RandomAccessCollection { /// var JPEG.Data.Spectral.Quanta.startIndex:Swift.Int { get } /// ?: Swift.RandomAccessCollection - /// The index of the first quantization table in this container. - /// - /// The default (all-zeroes) quantization table is not part of the - /// [`Swift.RandomAccessCollection`]. This index is 1 greater than the + /// The index of the first quantization table in this container. + /// + /// The default (all-zeroes) quantization table is not part of the + /// [`Swift.RandomAccessCollection`]. This index is 1 greater than the /// index of the default quanta. - public - var startIndex:Int + public + var startIndex:Int { // don’t include the default quanta self.quanta.startIndex + 1 } /// var JPEG.Data.Spectral.Quanta.endIndex:Swift.Int { get } /// ?: Swift.RandomAccessCollection - /// The index one greater than the index of the last quantization table - /// in this container. - public - var endIndex:Int + /// The index one greater than the index of the last quantization table + /// in this container. + public + var endIndex:Int { self.quanta.endIndex } /// subscript JPEG.Data.Spectral.Quanta[_:] { get set } /// ?: Swift.RandomAccessCollection /// Accesses the quantization table at the given index. - /// - /// The getter and setter of this subscript yield the quantization table + /// + /// The getter and setter of this subscript yield the quantization table /// using `read` and `modify`. - /// - q : Swift.Int + /// - q : Swift.Int /// The index of the quantization table to access. /// - -> : JPEG.Table.Quantization /// The quantization table. - public + public subscript(q:Int) -> JPEG.Table.Quantization { - _read + _read { yield self.quanta[q] } - _modify + _modify { yield &self.quanta[q] } } /// func JPEG.Data.Spectral.Quanta.index(forKey:) /// Returns the index of the table with the given key. - /// + /// /// An instance of this type which is part of a [`Spectral`] - /// instance will always contain all quanta keys used by its [`(Spectral).layout`], + /// instance will always contain all quanta keys used by its [`(Spectral).layout`], /// including keys used only by non-recognized components. - /// - qi : JPEG.Table.Quantization.Key + /// - qi : JPEG.Table.Quantization.Key /// The quanta key. Passing a key that does not exist in this container /// will result in a precondition failure. - /// - -> : Swift.Int + /// - -> : Swift.Int /// The integer index. This index can be used with the [`[_:]`] subscript. - public + public func index(forKey qi:JPEG.Table.Quantization.Key) -> Int { - guard let q:Int = self.q[qi] - else + guard let q:Int = self.q[qi] + else { preconditionFailure("key error: attempt to lookup index for invalid quanta key") } return q } - // impossible for lookup to fail if only public apis are used + // impossible for lookup to fail if only public apis are used func contains(key qi:JPEG.Table.Quantization.Key) -> Int? { self.q[qi] } } -extension JPEG.Data.Spectral:RandomAccessCollection +extension JPEG.Data.Spectral:RandomAccessCollection { /// var JPEG.Data.Spectral.startIndex:Swift.Int { get } /// ?: Swift.RandomAccessCollection - /// The index of the first plane in this image. - /// + /// The index of the first plane in this image. + /// /// This index is always 0. /// # [See also](spectral-accessing-planes) /// ## (1:spectral-accessing-planes) - public - var startIndex:Int + public + var startIndex:Int { self.planes.startIndex } /// var JPEG.Data.Spectral.endIndex:Swift.Int { get } /// ?: Swift.RandomAccessCollection - /// The index one greater than the index of the last plane in this image. - /// - /// This index is always the number of recognized components in the image’s + /// The index one greater than the index of the last plane in this image. + /// + /// This index is always the number of recognized components in the image’s /// [`layout``(JPEG.Layout).format`]. /// # [See also](spectral-accessing-planes) /// ## (2:spectral-accessing-planes) - public - var endIndex:Int + public + var endIndex:Int { self.planes.endIndex } /// subscript JPEG.Data.Spectral[_:] { get set } /// ?: Swift.RandomAccessCollection /// Accesses the plane at the given index. - /// - /// The getter and setter of this subscript yield the plane + /// + /// The getter and setter of this subscript yield the plane /// using `read` and `modify`. - /// - p : Swift.Int - /// The index of the plane to access. This index must be within the index + /// - p : Swift.Int + /// The index of the plane to access. This index must be within the index /// bounds of this [`Swift.RandomAccessCollection`]. /// - -> : Plane /// The plane. /// # [See also](spectral-accessing-planes) /// ## (0:spectral-accessing-planes) - public - subscript(p:Int) -> Plane + public + subscript(p:Int) -> Plane { - _read + _read { yield self.planes[p] } @@ -1890,66 +1890,66 @@ extension JPEG.Data.Spectral:RandomAccessCollection } } /// func JPEG.Data.Spectral.index(forKey:) - /// Returns the index of the plane storing the color channel represented - /// by the given component key, or `nil` if the component key is a + /// Returns the index of the plane storing the color channel represented + /// by the given component key, or `nil` if the component key is a /// non-recognized component. - /// - ci : JPEG.Component.Key - /// The component key. - /// - -> : Swift.Int? - /// The integer index of the plane, or `nil`. If not `nil`, this index + /// - ci : JPEG.Component.Key + /// The component key. + /// - -> : Swift.Int? + /// The integer index of the plane, or `nil`. If not `nil`, this index /// can be used with the [`[_:]`] subscript. /// # [See also](spectral-accessing-planes) /// ## (3:spectral-accessing-planes) - public - func index(forKey ci:JPEG.Component.Key) -> Int? + public + func index(forKey ci:JPEG.Component.Key) -> Int? { self.layout.index(ci: ci) } } -extension JPEG.Data.Planar:RandomAccessCollection +extension JPEG.Data.Planar:RandomAccessCollection { /// var JPEG.Data.Planar.startIndex:Swift.Int { get } /// ?: Swift.RandomAccessCollection - /// The index of the first plane in this image. - /// + /// The index of the first plane in this image. + /// /// This index is always 0. /// # [See also](planar-accessing-planes) /// ## (1:planar-accessing-planes) - public - var startIndex:Int + public + var startIndex:Int { self.planes.startIndex } /// var JPEG.Data.Planar.endIndex:Swift.Int { get } /// ?: Swift.RandomAccessCollection - /// The index one greater than the index of the last plane in this image. - /// - /// This index is always the number of recognized components in the image’s + /// The index one greater than the index of the last plane in this image. + /// + /// This index is always the number of recognized components in the image’s /// [`layout``(JPEG.Layout).format`]. /// # [See also](planar-accessing-planes) /// ## (2:planar-accessing-planes) - public - var endIndex:Int + public + var endIndex:Int { self.planes.endIndex } /// subscript JPEG.Data.Planar[_:] { get set } /// ?: Swift.RandomAccessCollection /// Accesses the plane at the given index. - /// - /// The getter and setter of this subscript yield the plane + /// + /// The getter and setter of this subscript yield the plane /// using `read` and `modify`. - /// - p : Swift.Int - /// The index of the plane to access. This index must be within the index + /// - p : Swift.Int + /// The index of the plane to access. This index must be within the index /// bounds of this [`Swift.RandomAccessCollection`]. /// - -> : Plane /// The plane. /// # [See also](planar-accessing-planes) /// ## (0:planar-accessing-planes) - public - subscript(p:Int) -> Plane + public + subscript(p:Int) -> Plane { - _read + _read { yield self.planes[p] } @@ -1959,209 +1959,209 @@ extension JPEG.Data.Planar:RandomAccessCollection } } /// func JPEG.Data.Planar.index(forKey:) - /// Returns the index of the plane storing the color channel represented - /// by the given component key, or `nil` if the component key is a + /// Returns the index of the plane storing the color channel represented + /// by the given component key, or `nil` if the component key is a /// non-recognized component. - /// - ci : JPEG.Component.Key - /// The component key. - /// - -> : Swift.Int? - /// The integer index of the plane, or `nil`. If not `nil`, this index + /// - ci : JPEG.Component.Key + /// The component key. + /// - -> : Swift.Int? + /// The integer index of the plane, or `nil`. If not `nil`, this index /// can be used with the [`[_:]`] subscript. /// # [See also](planar-accessing-planes) /// ## (3:planar-accessing-planes) - public - func index(forKey ci:JPEG.Component.Key) -> Int? + public + func index(forKey ci:JPEG.Component.Key) -> Int? { self.layout.index(ci: ci) } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { /// subscript JPEG.Data.Rectangular[x:y:p:] { get set } /// Accesses the sample at the specified pixel location and offset. - /// - x : Swift.Int + /// - x : Swift.Int /// The horizontal pixel index of the sample to access. - /// - y : Swift.Int - /// The vertical pixel index of the sample to access. Index 0 + /// - y : Swift.Int + /// The vertical pixel index of the sample to access. Index 0 /// corresponds to the visual top of the image. - /// - p : Swift.Int - /// The interleaved offset of the sample. This offset is analogous to the + /// - p : Swift.Int + /// The interleaved offset of the sample. This offset is analogous to the /// plane index in the planar image representations. - /// - ->: Swift.UInt16 + /// - ->: Swift.UInt16 /// The sample. /// # [See also](rectangular-accessing-samples) /// ## (0:rectangular-accessing-samples) - public - subscript(x x:Int, y y:Int, p:Int) -> UInt16 + public + subscript(x x:Int, y y:Int, p:Int) -> UInt16 { - get + get { self.values[p + self.stride * (x + self.size.x * y)] } - set(value) + set(value) { self.values[p + self.stride * (x + self.size.x * y)] = value } } /// func JPEG.Data.Rectangular.offset(forKey:) - /// Returns the interleaved offset of the color channel represented - /// by the given component key, or `nil` if the component key is a + /// Returns the interleaved offset of the color channel represented + /// by the given component key, or `nil` if the component key is a /// non-recognized component. - /// - ci : JPEG.Component.Key - /// The component key. - /// - -> : Swift.Int? - /// The interleaved offset of the channel, or `nil`. If not `nil`, this offset + /// - ci : JPEG.Component.Key + /// The component key. + /// - -> : Swift.Int? + /// The interleaved offset of the channel, or `nil`. If not `nil`, this offset /// can be used as the `p` parameter to the [`[x:y:p:]`] subscript. /// # [See also](rectangular-accessing-samples) /// ## (2:rectangular-accessing-samples) - public - func offset(forKey ci:JPEG.Component.Key) -> Int? + public + func offset(forKey ci:JPEG.Component.Key) -> Int? { self.layout.index(ci: ci) } } -// `indices` property for plane types -extension JPEG.Data.Spectral.Plane +// `indices` property for plane types +extension JPEG.Data.Spectral.Plane { /// var JPEG.Data.Spectral.Plane.indices : General.Range2 { get } /// A two-dimensional index range encompassing the data units in this plane. - /// - /// This index range is a [`Swift.Sequence`] which can be used to iterate + /// + /// This index range is a [`Swift.Sequence`] which can be used to iterate /// through its index space in row-major order. - public - var indices:General.Range2 + public + var indices:General.Range2 { - (0, 0) ..< self.units + (0, 0) ..< self.units } } -extension JPEG.Data.Planar.Plane +extension JPEG.Data.Planar.Plane { /// var JPEG.Data.Planar.Plane.indices : General.Range2 { get } /// A two-dimensional index range encompassing the data units in this plane. - /// - /// This index range is a [`Swift.Sequence`] which can be used to iterate + /// + /// This index range is a [`Swift.Sequence`] which can be used to iterate /// through its index space in row-major order. - public - var indices:General.Range2 + public + var indices:General.Range2 { - (0, 0) ..< self.size + (0, 0) ..< self.size } } -// “with” regulated accessors for plane mutation by component index -extension JPEG.Data.Spectral +// “with” regulated accessors for plane mutation by component index +extension JPEG.Data.Spectral { - // cannot have both of them named `with(ci:_)` since this leads to ambiguity + // cannot have both of them named `with(ci:_)` since this leads to ambiguity // at the call site - - /// func JPEG.Data.Spectral.read(ci:_:) - /// rethrows - /// Calls the given closure on the plane and associated quantization table - /// for the given component key. - /// - ci : JPEG.Component.Key - /// The component key of the plane to access. This component must be a + + /// func JPEG.Data.Spectral.read(ci:_:) + /// rethrows + /// Calls the given closure on the plane and associated quantization table + /// for the given component key. + /// - ci : JPEG.Component.Key + /// The component key of the plane to access. This component must be a /// recognized component, or this function will suffer a precondition failure. - /// - body : (Plane, JPEG.Table.Quantization) throws -> R - /// The closure to apply to the plane and associated quantization table. + /// - body : (Plane, JPEG.Table.Quantization) throws -> R + /// The closure to apply to the plane and associated quantization table. /// Its return value is the return value of the surrounding function. - /// - -> : R + /// - -> : R /// The return value of the given closure. /// # [See also](spectral-accessing-planes) /// ## (4:spectral-accessing-planes) - public - func read(ci:JPEG.Component.Key, - _ body:(Plane, JPEG.Table.Quantization) throws -> R) + public + func read(ci:JPEG.Component.Key, + _ body:(Plane, JPEG.Table.Quantization) throws -> R) rethrows -> R { guard let p:Int = self.index(forKey: ci) - else + else { preconditionFailure("component key out of range") } return try body(self[p], self.quanta[self[p].q]) } - /// mutating func JPEG.Data.Spectral.with(ci:_:) - /// rethrows - /// Calls the given closure on the plane and associated quantization table - /// for the given component key. - /// - /// The closure passed to this method can mutate the plane in this image - /// specified by the component key. The associated quantization table is still - /// immutable, because editing it would also affect all other planes referencing + /// mutating func JPEG.Data.Spectral.with(ci:_:) + /// rethrows + /// Calls the given closure on the plane and associated quantization table + /// for the given component key. + /// + /// The closure passed to this method can mutate the plane in this image + /// specified by the component key. The associated quantization table is still + /// immutable, because editing it would also affect all other planes referencing /// that table. - /// - ci : JPEG.Component.Key - /// The component key of the plane to access. This component must be a + /// - ci : JPEG.Component.Key + /// The component key of the plane to access. This component must be a /// recognized component, or this function will suffer a precondition failure. - /// - body : (inout Plane, JPEG.Table.Quantization) throws -> R - /// The closure to apply to the plane and associated quantization table. + /// - body : (inout Plane, JPEG.Table.Quantization) throws -> R + /// The closure to apply to the plane and associated quantization table. /// Its return value is the return value of the surrounding function. - /// - -> : R + /// - -> : R /// The return value of the given closure. /// # [See also](spectral-accessing-planes) /// ## (5:spectral-accessing-planes) - public mutating - func with(ci:JPEG.Component.Key, - _ body:(inout Plane, JPEG.Table.Quantization) throws -> R) + public mutating + func with(ci:JPEG.Component.Key, + _ body:(inout Plane, JPEG.Table.Quantization) throws -> R) rethrows -> R { guard let p:Int = self.index(forKey: ci) - else + else { preconditionFailure("component key out of range") } return try body(&self[p], self.quanta[self[p].q]) } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { - /// func JPEG.Data.Planar.read(ci:_:) - /// rethrows - /// Calls the given closure on the plane for the given component key. - /// - ci : JPEG.Component.Key - /// The component key of the plane to access. This component must be a + /// func JPEG.Data.Planar.read(ci:_:) + /// rethrows + /// Calls the given closure on the plane for the given component key. + /// - ci : JPEG.Component.Key + /// The component key of the plane to access. This component must be a /// recognized component, or this function will suffer a precondition failure. - /// - body : (Plane) throws -> R - /// The closure to apply to the plane. + /// - body : (Plane) throws -> R + /// The closure to apply to the plane. /// Its return value is the return value of the surrounding function. - /// - -> : R + /// - -> : R /// The return value of the given closure. /// # [See also](planar-accessing-planes) /// ## (4:planar-accessing-planes) - public - func read(ci:JPEG.Component.Key, - body:(Plane) throws -> R) + public + func read(ci:JPEG.Component.Key, + body:(Plane) throws -> R) rethrows -> R { guard let p:Int = self.index(forKey: ci) - else + else { preconditionFailure("component key out of range") } return try body(self[p]) } - /// mutating func JPEG.Data.Planar.with(ci:_:) - /// rethrows - /// Calls the given closure on the plane for the given component key. - /// - /// The closure passed to this method can mutate the plane in this image - /// specified by the component key. - /// - ci : JPEG.Component.Key - /// The component key of the plane to access. This component must be a + /// mutating func JPEG.Data.Planar.with(ci:_:) + /// rethrows + /// Calls the given closure on the plane for the given component key. + /// + /// The closure passed to this method can mutate the plane in this image + /// specified by the component key. + /// - ci : JPEG.Component.Key + /// The component key of the plane to access. This component must be a /// recognized component, or this function will suffer a precondition failure. - /// - body : (inout Plane) throws -> R - /// The closure to apply to the plane. + /// - body : (inout Plane) throws -> R + /// The closure to apply to the plane. /// Its return value is the return value of the surrounding function. - /// - -> : R + /// - -> : R /// The return value of the given closure. /// # [See also](planar-accessing-planes) /// ## (5:planar-accessing-planes) - public mutating - func with(ci:JPEG.Component.Key, - body:(inout Plane) throws -> R) + public mutating + func with(ci:JPEG.Component.Key, + body:(inout Plane) throws -> R) rethrows -> R { guard let p:Int = self.index(forKey: ci) - else + else { preconditionFailure("component key out of range") } @@ -2169,48 +2169,48 @@ extension JPEG.Data.Planar } } -// shared properties needed for initializing planar, spectral, and other layout types -extension JPEG.Layout +// shared properties needed for initializing planar, spectral, and other layout types +extension JPEG.Layout { /// var JPEG.Layout.scale : (x:Swift.Int, y:Swift.Int) { get } - /// The size of the minimum-coded unit of the image, in data units. - /// - /// This value is the maximum of all the sampling [`(JPEG.Component).factor`]s + /// The size of the minimum-coded unit of the image, in data units. + /// + /// This value is the maximum of all the sampling [`(JPEG.Component).factor`]s /// of the components in the image, including the non-recognized components. - public - var scale:(x:Int, y:Int) + public + var scale:(x:Int, y:Int) { self.planes.reduce((0, 0)) { ( - Swift.max($0.x, $1.component.factor.x), + Swift.max($0.x, $1.component.factor.x), Swift.max($0.y, $1.component.factor.y) ) } } /// var JPEG.Layout.components : [JPEG.Component.Key: (factor:(x:Swift.Int, y:Swift.Int), qi:JPEG.Table.Quantization.Key)] { get } - /// A dictionary mapping all of the resident components in the image to - /// their sampling factors and quanta keys. - /// - /// This property should be equivalent to the `components` dictionary - /// that would be used to construct this instance using - /// [`init(format:process:components:scans:)`]. As long as the [`Format`] - /// type is properly implemented, this dictionary will always have at + /// A dictionary mapping all of the resident components in the image to + /// their sampling factors and quanta keys. + /// + /// This property should be equivalent to the `components` dictionary + /// that would be used to construct this instance using + /// [`init(format:process:components:scans:)`]. As long as the [`Format`] + /// type is properly implemented, this dictionary will always have at /// least one element. - public + public var components: [ JPEG.Component.Key: (factor:(x:Int, y:Int), qi:JPEG.Table.Quantization.Key) - ] + ] { - self.residents.mapValues + self.residents.mapValues { (self.planes[$0].component.factor, self.planes[$0].qi) } } } // spectral type APIs -extension JPEG.Data.Spectral.Plane +extension JPEG.Data.Spectral.Plane { init(factor:(x:Int, y:Int)) { @@ -2219,50 +2219,50 @@ extension JPEG.Data.Spectral.Plane self._q = .init(wrappedValue: 0) // the default quanta (qi = -1) self._factor = .init(wrappedValue: factor) } - + // used by the `fdct(_:quanta:precision:)` function defined in `encode.swift` - mutating + mutating func set(values:[Int16], units:(x:Int, y:Int)) { precondition(values.count == 64 * units.x * units.y) - self.buffer = values + self.buffer = values self.units = units } - // width is in units, not pixels - mutating - func set(width x:Int) + // width is in units, not pixels + mutating + func set(width x:Int) { - guard x != self.units.x - else + guard x != self.units.x + else { - return + return } - + let count:Int = 64 * x * self.units.y - let new:[Int16] = .init(unsafeUninitializedCapacity: count) + let new:[Int16] = .init(unsafeUninitializedCapacity: count) { - guard let base:UnsafeMutablePointer = $0.baseAddress - else + guard let base:UnsafeMutablePointer = $0.baseAddress + else { - $1 = 0 - return + $1 = 0 + return } - - self.buffer.withUnsafeBufferPointer + + self.buffer.withUnsafeBufferPointer { - guard let source:UnsafePointer = $0.baseAddress - else + guard let source:UnsafePointer = $0.baseAddress + else { base.initialize(repeating: 0, count: count) - return + return } - - let stride:(old:Int, new:Int) = + + let stride:(old:Int, new:Int) = ( - 64 * self.units.x, + 64 * self.units.x, 64 * x ) - if stride.old < stride.new + if stride.old < stride.new { for y:Int in 0 ..< self.units.y { @@ -2272,7 +2272,7 @@ extension JPEG.Data.Spectral.Plane repeating: 0, count: stride.new - stride.old) } } - else + else { for y:Int in 0 ..< self.units.y { @@ -2281,179 +2281,179 @@ extension JPEG.Data.Spectral.Plane } } } - - $1 = count + + $1 = count } - - self.buffer = new + + self.buffer = new self.units.x = x } - mutating - func set(height y:Int) + mutating + func set(height y:Int) { - guard y != self.units.y - else + guard y != self.units.y + else { - return + return } - - let count:Int = 64 * self.units.x * y, + + let count:Int = 64 * self.units.x * y, change:Int = count - self.buffer.count if change < 0 { self.buffer.removeLast(-change) } - else + else { self.buffer.append(contentsOf: repeatElement(0, count: change)) } - + self.units.y = y } /// subscript JPEG.Data.Spectral.Plane[x:y:k:h:] { get set } /// @inlinable /// Accesses the frequency coefficient at the specified grid index /// in the specified data unit. - /// - /// The `x` and `y` indices of this subscript have no index bounds. - /// Out-of-bounds reads will return 0; out-of-bounds writes will - /// have no effect. The `k` and `h` indices still have to be within the + /// + /// The `x` and `y` indices of this subscript have no index bounds. + /// Out-of-bounds reads will return 0; out-of-bounds writes will + /// have no effect. The `k` and `h` indices still have to be within the /// correct range. /// - /// Using this subscript is equivalent to using [`[x:y:z:]`] with the `z` + /// Using this subscript is equivalent to using [`[x:y:z:]`] with the `z` /// index set to the output of [`Table.Quantization.z(k:h:)`]. - /// - x : Swift.Int + /// - x : Swift.Int /// The horizontal index of the data unit to access. - /// - y : Swift.Int - /// The vertical index of the data unit to access. Index 0 + /// - y : Swift.Int + /// The vertical index of the data unit to access. Index 0 /// corresponds to the visual top of the image. /// - k : Swift.Int - /// The horizontal frequency index of the coefficient to access. + /// The horizontal frequency index of the coefficient to access. /// This value must be in the range `0 ..< 8`. /// - h : Swift.Int - /// The vertical frequency index of the coefficient to access. + /// The vertical frequency index of the coefficient to access. /// This value must be in the range `0 ..< 8`. - /// - ->: Swift.Int16 + /// - ->: Swift.Int16 /// The frequency coefficient. @inlinable - public - subscript(x x:Int, y y:Int, k k:Int, h h:Int) -> Int16 + public + subscript(x x:Int, y y:Int, k k:Int, h h:Int) -> Int16 { - get + get { self[x: x, y: y, z: JPEG.Table.Quantization.z(k: k, h: h)] } set(value) { - self[x: x, y: y, z: JPEG.Table.Quantization.z(k: k, h: h)] = value + self[x: x, y: y, z: JPEG.Table.Quantization.z(k: k, h: h)] = value } } } -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { - // this function is supposed to match the public `encode()` function, - // but since it returns an incomplete Spectral struct, we don’t make it public - // (even if that makes the name of the `encode()` function not make any sense + // this function is supposed to match the public `encode()` function, + // but since it returns an incomplete Spectral struct, we don’t make it public + // (even if that makes the name of the `encode()` function not make any sense // anymore) - static - func decode(frame:JPEG.Header.Frame) throws -> Self + static + func decode(frame:JPEG.Header.Frame) throws -> Self { // catch unsupported processes - switch frame.process + switch frame.process { - case .baseline, + case .baseline, .extended (coding: .huffman, differential: false), .progressive(coding: .huffman, differential: false): - break + break default: throw JPEG.DecodingError.unsupportedFrameCodingProcess(frame.process) } - - // recognize format - guard let format:Format = + + // recognize format + guard let format:Format = .recognize(.init(frame.components.keys), precision: frame.precision) - else + else { throw JPEG.DecodingError.unrecognizedColorFormat( .init(frame.components.keys), frame.precision, Format.self) } - - let layout:JPEG.Layout = + + let layout:JPEG.Layout = .init(format: format, process: frame.process, components: frame.components) - + var spectral:Self = .init(layout: layout) spectral.set(width: frame.size.x) spectral.set(height: frame.size.y) - return spectral + return spectral } /// init JPEG.Data.Spectral.init(size:layout:metadata:quanta:) /// Creates a blank spectral image with the given image parameters and quanta. - /// + /// /// This initializer will initialize all frequency coefficients in the image to zero. - /// - size : (x:Swift.Int, y:Swift.Int) - /// The size of the image, in pixels. Passing a negative or zero width, or + /// - size : (x:Swift.Int, y:Swift.Int) + /// The size of the image, in pixels. Passing a negative or zero width, or /// a negative height, will result in a precondition failure. /// - layout : JPEG.Layout /// The layout of the image. /// - metadata : [JPEG.Metadata] /// The metadata records in the image. /// - quanta : [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by the given `layout`, - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by the given `layout`, + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width - /// determined by the [`(JPEG.Format).precision`] of the color [`(Layout).format`] - /// of the given layout, and all the values must be in the correct range + /// determined by the [`(JPEG.Format).precision`] of the color [`(Layout).format`] + /// of the given layout, and all the values must be in the correct range /// for that bit width. - /// + /// /// Passing an invalid quanta dictionary will result in a precondition failure. /// # [See also](spectral-create-image) /// ## (0:spectral-create-image) - public - init(size:(x:Int, y:Int), layout:JPEG.Layout, - metadata:[JPEG.Metadata], + public + init(size:(x:Int, y:Int), layout:JPEG.Layout, + metadata:[JPEG.Metadata], quanta:[JPEG.Table.Quantization.Key: [UInt16]]) { self.init(layout: layout) self.set(width: size.x) self.set(height: size.y) self.set(quanta: quanta) - + self.metadata.append(contentsOf: metadata) } - - init(layout:JPEG.Layout) + + init(layout:JPEG.Layout) { self.layout = layout - - self.metadata = [] - self.planes = layout.recognized.indices.map + + self.metadata = [] + self.planes = layout.recognized.indices.map { .init(factor: layout.planes[$0].component.factor) } self.quanta = .init(default: .init( - precision: layout.format.precision > 8 ? .uint16 : .uint8, - values: .init(repeating: 0, count: 64), + precision: layout.format.precision > 8 ? .uint16 : .uint8, + values: .init(repeating: 0, count: 64), target: \.0)) - + self.size = (0, 0) self.blocks = (0, 0) } - + /// mutating func JPEG.Data.Spectral.set(width:) /// Sets the width of this image, in pixels. - /// - /// Existing data in this image will either be preserved, or cropped. - /// Any additional data units created by this function will have all coefficients + /// + /// Existing data in this image will either be preserved, or cropped. + /// Any additional data units created by this function will have all coefficients /// initialized to zero. - /// - x : Swift.Int - /// The new width of this image, in pixels. This width is measured from the - /// left side of the image. Passing a negative or zero value will result + /// - x : Swift.Int + /// The new width of this image, in pixels. This width is measured from the + /// left side of the image. Passing a negative or zero value will result /// in a precondition failure. /// # [See also](spectral-edit-image) /// ## (spectral-edit-image) - public mutating - func set(width x:Int) + public mutating + func set(width x:Int) { precondition(x > 0, "width must be set to a positive value.") let scale:Int = self.layout.scale.x @@ -2461,27 +2461,27 @@ extension JPEG.Data.Spectral self.size.x = x for p:Int in self.indices { - // x * factor + // x * factor // ceil( ------------ ) - // 8 * scale + // 8 * scale let u:Int = JPEG.Data.units(x * self[p].factor.x, stride: 8 * scale) self[p].set(width: u) } } /// mutating func JPEG.Data.Spectral.set(height:) /// Sets the height of this image, in pixels. - /// - /// Existing data in this image will either be preserved, or cropped. - /// Any additional data units created by this function will have all coefficients + /// + /// Existing data in this image will either be preserved, or cropped. + /// Any additional data units created by this function will have all coefficients /// initialized to zero. - /// - y : Swift.Int - /// The new height of this image, in pixels. This height is measured from the - /// top of the image. Passing a negative value will result + /// - y : Swift.Int + /// The new height of this image, in pixels. This height is measured from the + /// top of the image. Passing a negative value will result /// in a precondition failure. /// # [See also](spectral-edit-image) /// ## (spectral-edit-image) - public mutating - func set(height y:Int) + public mutating + func set(height y:Int) { precondition(y >= 0, "height must be set to zero or a positive value.") let scale:Int = self.layout.scale.y @@ -2495,144 +2495,144 @@ extension JPEG.Data.Spectral } /// mutating func JPEG.Data.Spectral.set(quanta:) /// Replaces the quantization tables in this image. - /// + /// /// This function will invalidate all existing quantization table indices. /// - quanta : [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by this image’s [`layout`], - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by this image’s [`layout`], + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width /// determined by this image’s [`layout``(Layout).format``(JPEG.Format).precision`], /// and all the values must be in the correct range for that bit width. /// # [See also](spectral-edit-image) /// ## (spectral-edit-image) - public mutating + public mutating func set(quanta:[JPEG.Table.Quantization.Key: [UInt16]]) { self.quanta.removeAll() for (ci, c):(JPEG.Component.Key, Int) in self.layout.residents { let qi:JPEG.Table.Quantization.Key = self.layout.planes[c].qi - let q:Int + let q:Int if let index:Int = self.quanta.contains(key: qi) { - q = index + q = index } - else + else { guard let values:[UInt16] = quanta[qi] - else + else { preconditionFailure("missing quantization table for component \(ci)") } - + let table:JPEG.Table.Quantization = .init( - precision: self.layout.format.precision > 8 ? .uint16 : .uint8, - values: values, + precision: self.layout.format.precision > 8 ? .uint16 : .uint8, + values: values, target: self.layout.planes[c].component.selector) - + q = self.quanta.push(qi: qi, quanta: table) } - + if let p:Int = self.index(forKey: ci) { - self[p].q = q + self[p].q = q } } } - - mutating - func push(qi:JPEG.Table.Quantization.Key, quanta:JPEG.Table.Quantization) - throws -> Int + + mutating + func push(qi:JPEG.Table.Quantization.Key, quanta:JPEG.Table.Quantization) + throws -> Int { - switch (self.layout.format.precision, quanta.precision) + switch (self.layout.format.precision, quanta.precision) { - // the only thing the jpeg standard says about this is “an 8-bit dct-based + // the only thing the jpeg standard says about this is “an 8-bit dct-based // process shall not use a 16-bit quantization table” - case (1 ... 16, .uint8), + case (1 ... 16, .uint8), (9 ... 16, .uint16): - break + break default: throw JPEG.DecodingError.invalidScanQuantizationPrecision(quanta.precision) - } - + } + self.layout.push(qi: qi) return self.quanta.push(qi: qi, quanta: quanta) } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { /// init JPEG.Data.Planar.init(size:layout:metadata:initializingWith:) - /// rethrows + /// rethrows /// Creates a planar image with the given image parameters and generator. - /// - size : (x:Swift.Int, y:Swift.Int) - /// The size of the image, in pixels. Both dimensions must be positive, + /// - size : (x:Swift.Int, y:Swift.Int) + /// The size of the image, in pixels. Both dimensions must be positive, /// or this initializer will suffer a precondition failure. /// - layout : JPEG.Layout /// The layout of the image. /// - metadata : [JPEG.Metadata] /// The metadata records in the image. /// - initializer : (Swift.Int, (x:Swift.Int, y:Swift.Int), (x:Swift.Int, y:Swift.Int), Swift.UnsafeMutableBufferPointer) throws -> () - /// A closure called by this function to initialize the contents of each + /// A closure called by this function to initialize the contents of each /// plane in this image. - /// + /// /// The first closure argument is the index of the plane being initialized. - /// - /// The second closure argument is a tuple containing the size of the + /// + /// The second closure argument is a tuple containing the size of the /// plane being initialized, in data units. - /// - /// The third closure argument is a tuple containing the sampling factors - /// of the plane being initialized. - /// - /// The last closure argument is an uninitialized buffer containing the - /// samples in the plane, in row-major order. This buffer contains - /// 64\ *x*\ *y* samples, where (*x*,\ *y*) is the size of the plane, + /// + /// The third closure argument is a tuple containing the sampling factors + /// of the plane being initialized. + /// + /// The last closure argument is an uninitialized buffer containing the + /// samples in the plane, in row-major order. This buffer contains + /// 64\ *x*\ *y* samples, where (*x*,\ *y*) is the size of the plane, /// in data units. /// # [See also](planar-create-image) /// ## (0:planar-create-image) - public - init(size:(x:Int, y:Int), layout:JPEG.Layout, metadata:[JPEG.Metadata], + public + init(size:(x:Int, y:Int), layout:JPEG.Layout, metadata:[JPEG.Metadata], initializingWith initializer: (Int, (x:Int, y:Int), (x:Int, y:Int), UnsafeMutableBufferPointer) throws -> ()) - rethrows + rethrows { precondition(size.x > 0 && size.y > 0, "size must be positive") - + self.layout = layout self.size = size self.metadata = metadata - + let scale:(x:Int, y:Int) = layout.scale - self.planes = try layout.recognized.indices.map + self.planes = try layout.recognized.indices.map { (p:Int) -> Plane in - + let factor:(x:Int, y:Int) = layout.planes[p].component.factor - let units:(x:Int, y:Int) = + let units:(x:Int, y:Int) = ( JPEG.Data.units(size.x * factor.x, stride: 8 * scale.x), JPEG.Data.units(size.y * factor.y, stride: 8 * scale.y) ) - + let count:Int = 64 * units.x * units.y let plane:[UInt16] = try .init(unsafeUninitializedCapacity: count) { try initializer(p, units, factor, $0) - $1 = count + $1 = count } return .init(plane, units: units, factor: factor) } } /// init JPEG.Data.Planar.init(size:layout:metadata:) - /// Creates a planar image with the given image parameters, initializing + /// Creates a planar image with the given image parameters, initializing /// all image samples to a neutral color. - /// - /// This initializer will initialize all samples in all planes to the - /// midpoint of this image’s sample range. The midpoint is equal to + /// + /// This initializer will initialize all samples in all planes to the + /// midpoint of this image’s sample range. The midpoint is equal to /// 2^*w*\ –\ 1^, where *w*\ =\ [`layout``(Layout).format``(JPEG.Format).precision`]. - /// - size : (x:Swift.Int, y:Swift.Int) - /// The size of the image, in pixels. Both dimensions must be positive, + /// - size : (x:Swift.Int, y:Swift.Int) + /// The size of the image, in pixels. Both dimensions must be positive, /// or this initializer will suffer a precondition failure. /// - layout : JPEG.Layout /// The layout of the image. @@ -2640,27 +2640,27 @@ extension JPEG.Data.Planar /// The metadata records in the image. /// # [See also](planar-create-image) /// ## (1:planar-create-image) - public + public init(size:(x:Int, y:Int), layout:JPEG.Layout, metadata:[JPEG.Metadata]) { let midpoint:UInt16 = 1 << (layout.format.precision - 1 as Int) - self.init(size: size, layout: layout, metadata: metadata) + self.init(size: size, layout: layout, metadata: metadata) { $3.initialize(repeating: midpoint) } } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { /// init JPEG.Data.Rectangular.init(size:layout:metadata:) - /// Creates a rectangular image with the given image parameters, initializing + /// Creates a rectangular image with the given image parameters, initializing /// all image samples to a neutral color. - /// - /// This initializer will initialize all samples to the - /// midpoint of this image’s sample range. The midpoint is equal to + /// + /// This initializer will initialize all samples to the + /// midpoint of this image’s sample range. The midpoint is equal to /// 2^*w*\ –\ 1^, where *w*\ =\ [`layout``(Layout).format``(JPEG.Format).precision`]. - /// - size : (x:Swift.Int, y:Swift.Int) - /// The size of the image, in pixels. Both dimensions must be positive, + /// - size : (x:Swift.Int, y:Swift.Int) + /// The size of the image, in pixels. Both dimensions must be positive, /// or this initializer will suffer a precondition failure. /// - layout : JPEG.Layout /// The layout of the image. @@ -2668,18 +2668,18 @@ extension JPEG.Data.Rectangular /// The metadata records in the image. /// # [See also](rectangular-create-image) /// ## (1:rectangular-create-image) - public + public init(size:(x:Int, y:Int), layout:JPEG.Layout, metadata:[JPEG.Metadata]) { precondition(size.x > 0 && size.y > 0, "size must be positive") - + let midpoint:UInt16 = 1 << (layout.format.precision - 1 as Int) - self.init(size: size, layout: layout, metadata: metadata, + self.init(size: size, layout: layout, metadata: metadata, values: .init(repeating: midpoint, count: layout.recognized.count * size.x * size.y)) } } -// huffman symbol and composite value semantics +// huffman symbol and composite value semantics // entropy-coded bitstreams look like this (not all intervals may be present) // @@ -2687,25 +2687,25 @@ extension JPEG.Data.Rectangular // |<------- symbol -------->| // ... [ zeroes:binade or binade ][ tail ][ refining bits ] ... // -// the refining bits are *not* part of the composite values (even though -// the composite values themselves have trailing “extra bits”). the difference -// between the “extra bits” (tail) and the refining bits is that the length -// of the tail is completely determined by the value of the preceeding symbol, +// the refining bits are *not* part of the composite values (even though +// the composite values themselves have trailing “extra bits”). the difference +// between the “extra bits” (tail) and the refining bits is that the length +// of the tail is completely determined by the value of the preceeding symbol, // whereas the number of refining bits can depend on previously decoded information -// -// note: in a DC refining scan, the composite values do not exist, and each -// coefficient gets exactly one refining bit. in an AC refining scan, the +// +// note: in a DC refining scan, the composite values do not exist, and each +// coefficient gets exactly one refining bit. in an AC refining scan, the // number of refining bits is the same as the number of non-zero previously- // decoded coefficients within the run described by the symbol `zeroes` field extension JPEG.Bitstream.Symbol.DC { // SSSS - var binade:Int + var binade:Int { .init(self.value) } } -extension JPEG.Bitstream.Symbol.AC +extension JPEG.Bitstream.Symbol.AC { // RRRR var zeroes:Int @@ -2713,159 +2713,159 @@ extension JPEG.Bitstream.Symbol.AC .init(self.value >> 4) } // SSSS - var binade:Int + var binade:Int { .init(self.value & 0x0f) } } extension JPEG.Bitstream { - enum Composite + enum Composite { - struct DC + struct DC { - let difference:Int16 - - init(difference:Int16) + let difference:Int16 + + init(difference:Int16) { self.difference = difference } } - enum AC + enum AC { case run(Int, value:Int16) case eob(Int) } } - - static + + static func extend(binade:Int, _ tail:UInt16, as _:I.Type) -> I where I:FixedWidthInteger & SignedInteger { assert(binade > 0) - // 0 for lower half of range, 1 for upper half + // 0 for lower half of range, 1 for upper half let sign:UInt16 = tail &>> (binade &- 1) // [0000 0000 0000 0000] // [1111 1111 1100 0000] - let high:UInt16 = (0xffff &+ sign) &<< binade + let high:UInt16 = (0xffff &+ sign) &<< binade let low:UInt16 = tail &+ (sign ^ 1) let combined:Int16 = .init(bitPattern: high | low) return .init(combined) } - - static + + static func compact(_ x:I) -> (binade:Int, tail:UInt16) where I:FixedWidthInteger & SignedInteger { let x:Int16 = .init(x) - // one of the advantages of swift is that we can query this through a CPU - // intrinsic as opposed to loop-based queries found in much example c code + // one of the advantages of swift is that we can query this through a CPU + // intrinsic as opposed to loop-based queries found in much example c code let position:Int = abs(x).leadingZeroBitCount let binade:Int = Int16.bitWidth &- position - + let sign:UInt16 = .init(bitPattern: x) &>> (Int16.bitWidth - 1) - // can use &<< because binade is always less than 16 (because of abs(_:) when + // can use &<< because binade is always less than 16 (because of abs(_:) when // computing `position`) let tail:UInt16 = (.init(bitPattern: x) &- sign) & ~(.max &<< binade) return (binade: binade, tail: tail) } - + func refinement(_ i:inout Int) throws -> Int16 { - guard i < self.count - else + guard i < self.count + else { throw JPEG.DecodingError.truncatedEntropyCodedSegment } - - defer + + defer { i += 1 } return self[i, as: Int16.self] } - - func composite(_ i:inout Int, table:JPEG.Table.HuffmanDC.Decoder) throws -> Composite.DC + + func composite(_ i:inout Int, table:JPEG.Table.HuffmanDC.Decoder) throws -> Composite.DC { // read SSSS:[extra] (huffman coded) - guard i < self.count - else + guard i < self.count + else { throw JPEG.DecodingError.truncatedEntropyCodedSegment } - + let entry:JPEG.Table.HuffmanDC.Decoder.Entry = table[self[i, count: 16]] - let binade:Int = entry.symbol.binade + let binade:Int = entry.symbol.binade i += entry.length - + guard binade > 0 - else + else { - return .init(difference: 0) + return .init(difference: 0) } - + // read `binade` additional bits (raw) - guard i + binade <= self.count - else + guard i + binade <= self.count + else { throw JPEG.DecodingError.truncatedEntropyCodedSegment } - defer + defer { i += binade } - + let value:Int16 = Self.extend(binade: binade, self[i, count: binade], as: Int16.self) return .init(difference: value) } - - func composite(_ i:inout Int, table:JPEG.Table.HuffmanAC.Decoder) throws -> Composite.AC + + func composite(_ i:inout Int, table:JPEG.Table.HuffmanAC.Decoder) throws -> Composite.AC { // read RRRR:SSSS:[extra] (huffman coded) - guard i < self.count - else + guard i < self.count + else { throw JPEG.DecodingError.truncatedEntropyCodedSegment } - + let entry:JPEG.Table.HuffmanAC.Decoder.Entry = table[self[i, count: 16]] - let zeroes:Int = entry.symbol.zeroes, - binade:Int = entry.symbol.binade + let zeroes:Int = entry.symbol.zeroes, + binade:Int = entry.symbol.binade i += entry.length - - switch (zeroes, binade) + + switch (zeroes, binade) { case (0, 0): return .eob(1) case (1 ... 14, 0): // read `zeroes` additional bits (raw) - guard i + zeroes <= self.count - else + guard i + zeroes <= self.count + else { throw JPEG.DecodingError.truncatedEntropyCodedSegment - } - defer + } + defer { i += zeroes } - + let run:Int = 1 &<< zeroes | .init(self[i, count: zeroes]) return .eob(run) - + case (_, 0): return .run(15, value: 0) // `zeroes` is always in the range `0 ... 15` - + default: - guard i + binade <= self.count - else + guard i + binade <= self.count + else { throw JPEG.DecodingError.truncatedEntropyCodedSegment } - defer + defer { i += binade } - + let value:Int16 = Self.extend(binade: binade, self[i, count: binade], as: Int16.self) return .run(zeroes, value: value) } @@ -2873,140 +2873,140 @@ extension JPEG.Bitstream } // decoding processes -extension JPEG.Data.Spectral.Plane +extension JPEG.Data.Spectral.Plane { - // sequential mode - mutating - func decode(_ data:[UInt8], blocks:Range, component:JPEG.Scan.Component, - tables slots:(dc:JPEG.Table.HuffmanDC.Slots, ac:JPEG.Table.HuffmanAC.Slots), - extend:Bool) throws + // sequential mode + mutating + func decode(_ data:[UInt8], blocks:Range, component:JPEG.Scan.Component, + tables slots:(dc:JPEG.Table.HuffmanDC.Slots, ac:JPEG.Table.HuffmanAC.Slots), + extend:Bool) throws { - guard let dc:JPEG.Table.HuffmanDC.Decoder = + guard let dc:JPEG.Table.HuffmanDC.Decoder = slots.dc[keyPath: component.selector.dc]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanDCReference(component.selector.dc) } - guard let ac:JPEG.Table.HuffmanAC.Decoder = + guard let ac:JPEG.Table.HuffmanAC.Decoder = slots.ac[keyPath: component.selector.ac]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanACReference(component.selector.ac) } - - let rows:Range = + + let rows:Range = (blocks.lowerBound / self.units.x ..< blocks.upperBound / self.units.x) .clamped(to: 0 ..< (extend ? .max : self.units.y)) let bits:JPEG.Bitstream = .init(data) var b:Int = 0 var predecessor:Int16 = 0 - row: + row: for y:Int in rows { - if extend + if extend { - guard b < bits.count, bits[b, count: 16] != 0xffff - else + guard b < bits.count, bits[b, count: 16] != 0xffff + else { - break row + break row } - + if y >= self.units.y { self.set(height: y + 1) } } - + column: - for x:Int in 0 ..< self.units.x + for x:Int in 0 ..< self.units.x { // dc let composite:JPEG.Bitstream.Composite.DC = try bits.composite(&b, table: dc) predecessor &+= composite.difference - self[x: x, y: y, z: 0] = predecessor - + self[x: x, y: y, z: 0] = predecessor + // ac var z:Int = 1 - frequency: + frequency: while z < 64 { switch try bits.composite(&b, table: ac) { case .run(let run, value: let v): - z += run - + z += run + guard z < 64 - else + else { break frequency } - - self[x: x, y: y, z: z] = v + + self[x: x, y: y, z: z] = v z += 1 - + case .eob(1): break frequency - + case .eob(let v): throw JPEG.DecodingError.invalidCompositeBlockRun(v, expected: 1 ... 1) } - } + } } } } - - // progressive mode - mutating - func decode(_ data:[UInt8], blocks:Range, bits a:PartialRangeFrom, - component:JPEG.Scan.Component, tables slots:JPEG.Table.HuffmanDC.Slots, - extend:Bool) throws + + // progressive mode + mutating + func decode(_ data:[UInt8], blocks:Range, bits a:PartialRangeFrom, + component:JPEG.Scan.Component, tables slots:JPEG.Table.HuffmanDC.Slots, + extend:Bool) throws { - guard let table:JPEG.Table.HuffmanDC.Decoder = + guard let table:JPEG.Table.HuffmanDC.Decoder = slots[keyPath: component.selector.dc]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanDCReference(component.selector.dc) } - - let rows:Range = + + let rows:Range = (blocks.lowerBound / self.units.x ..< blocks.upperBound / self.units.x) .clamped(to: 0 ..< (extend ? .max : self.units.y)) let bits:JPEG.Bitstream = .init(data) var b:Int = 0 var predecessor:Int16 = 0 - row: + row: for y:Int in rows { - if extend + if extend { - guard b < bits.count, bits[b, count: 16] != 0xffff - else + guard b < bits.count, bits[b, count: 16] != 0xffff + else { - break row + break row } - + if y >= self.units.y { self.set(height: y + 1) } } - + column: - for x:Int in 0 ..< self.units.x + for x:Int in 0 ..< self.units.x { let composite:JPEG.Bitstream.Composite.DC = try bits.composite(&b, table: table) - // it’s not well-defined what should happen if the dc coefficients - // overflow, so we just use Int16 wraparound to avoid crashing + // it’s not well-defined what should happen if the dc coefficients + // overflow, so we just use Int16 wraparound to avoid crashing predecessor &+= composite.difference self[x: x, y: y, z: 0] = predecessor << a.lowerBound } } - } - - mutating - func decode(_ data:[UInt8], blocks:Range, bit a:Int) throws + } + + mutating + func decode(_ data:[UInt8], blocks:Range, bit a:Int) throws { - let rows:Range = blocks.lowerBound / self.units.x ..< + let rows:Range = blocks.lowerBound / self.units.x ..< Swift.min(blocks.upperBound / self.units.x, self.units.y) let bits:JPEG.Bitstream = .init(data) var b:Int = 0 @@ -3016,193 +3016,193 @@ extension JPEG.Data.Spectral.Plane self[x: x, y: y, z: 0] |= refinement << a } } - - mutating - func decode(_ data:[UInt8], blocks:Range, band:Range, bits a:PartialRangeFrom, + + mutating + func decode(_ data:[UInt8], blocks:Range, band:Range, bits a:PartialRangeFrom, component:JPEG.Scan.Component, tables slots:JPEG.Table.HuffmanAC.Slots) throws { - guard let table:JPEG.Table.HuffmanAC.Decoder = + guard let table:JPEG.Table.HuffmanAC.Decoder = slots[keyPath: component.selector.ac]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanACReference(component.selector.ac) } - - let rows:Range = blocks.lowerBound / self.units.x ..< + + let rows:Range = blocks.lowerBound / self.units.x ..< Swift.min(blocks.upperBound / self.units.x, self.units.y) let bits:JPEG.Bitstream = .init(data) - var b:Int = 0, + var b:Int = 0, skip:Int = 0 for (x, y):(x:Int, y:Int) in (0, rows.lowerBound) ..< (self.units.x, rows.upperBound) { var z:Int = band.lowerBound - frequency: - while z < band.upperBound + frequency: + while z < band.upperBound { guard skip == 0 - else + else { skip -= 1 - break frequency - } - + break frequency + } + switch try bits.composite(&b, table: table) { case .run(let run, value: let v): - z += run - - guard z < band.upperBound - else + z += run + + guard z < band.upperBound + else { break frequency } - + self[x: x, y: y, z: z] = v << a.lowerBound z += 1 - + case .eob(let blocks): skip = blocks - 1 - break frequency + break frequency } - } + } } } - - mutating - func decode(_ data:[UInt8], blocks:Range, band:Range, bit a:Int, + + mutating + func decode(_ data:[UInt8], blocks:Range, band:Range, bit a:Int, component:JPEG.Scan.Component, tables slots:JPEG.Table.HuffmanAC.Slots) throws { - guard let table:JPEG.Table.HuffmanAC.Decoder = + guard let table:JPEG.Table.HuffmanAC.Decoder = slots[keyPath: component.selector.ac]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanACReference(component.selector.ac) } - - let rows:Range = blocks.lowerBound / self.units.x ..< + + let rows:Range = blocks.lowerBound / self.units.x ..< Swift.min(blocks.upperBound / self.units.x, self.units.y) let bits:JPEG.Bitstream = .init(data) - var b:Int = 0, + var b:Int = 0, skip:Int = 0 for (x, y):(x:Int, y:Int) in (0, rows.lowerBound) ..< (self.units.x, rows.upperBound) { var z:Int = band.lowerBound frequency: - while z < band.upperBound + while z < band.upperBound { - let zeroes:Int, + let zeroes:Int, delta:Int16 - if skip > 0 + if skip > 0 { - zeroes = 64 + zeroes = 64 delta = 0 skip -= 1 - } - else + } + else { switch try bits.composite(&b, table: table) { case .run(let run, value: let v): - guard -1 ... 1 ~= v - else + guard -1 ... 1 ~= v + else { throw JPEG.DecodingError.invalidCompositeValue(v, expected: -1 ... 1) } - - zeroes = run - delta = v - + + zeroes = run + delta = v + case .eob(let blocks): - zeroes = 64 - delta = 0 + zeroes = 64 + delta = 0 skip = blocks - 1 } } - + var skipped:Int = 0 - repeat + repeat { - defer + defer { z += 1 } - + let unrefined:Int16 = self[x: x, y: y, z: z] - if unrefined == 0 + if unrefined == 0 { - guard skipped < zeroes - else + guard skipped < zeroes + else { self[x: x, y: y, z: z] = delta << a - continue frequency + continue frequency } - + skipped += 1 } - else + else { let delta:Int16 = (unrefined < 0 ? -1 : 1) * (try bits.refinement(&b)) self[x: x, y: y, z: z] &+= delta << a } } while z < band.upperBound - + break frequency } - } + } } } -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { - // sequential mode - private mutating - func decode(_ data:[UInt8], blocks:Range, - components:[(c:Int, component:JPEG.Scan.Component)], - tables slots:(dc:JPEG.Table.HuffmanDC.Slots, ac:JPEG.Table.HuffmanAC.Slots), - extend:Bool) throws + // sequential mode + private mutating + func decode(_ data:[UInt8], blocks:Range, + components:[(c:Int, component:JPEG.Scan.Component)], + tables slots:(dc:JPEG.Table.HuffmanDC.Slots, ac:JPEG.Table.HuffmanAC.Slots), + extend:Bool) throws { - guard components.count > 1 - else + guard components.count > 1 + else { // noninterleaved precondition(components.count == 1, "components array cannot be empty") let (p, component):(Int, JPEG.Scan.Component) = components[0] guard self.indices ~= p - else + else { - return + return } - - try self[p].decode(data, blocks: blocks, component: component, + + try self[p].decode(data, blocks: blocks, component: component, tables: slots, extend: extend) - return + return } - - typealias Descriptor = + + typealias Descriptor = ( - p:Int?, - factor:(x:Int, y:Int), + p:Int?, + factor:(x:Int, y:Int), table:(dc:JPEG.Table.HuffmanDC.Decoder, ac:JPEG.Table.HuffmanAC.Decoder) ) - let descriptors:[Descriptor] = try components.map + let descriptors:[Descriptor] = try components.map { - guard let dc:JPEG.Table.HuffmanDC.Decoder = + guard let dc:JPEG.Table.HuffmanDC.Decoder = slots.dc[keyPath: $0.component.selector.dc]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanDCReference($0.component.selector.dc) } - guard let ac:JPEG.Table.HuffmanAC.Decoder = + guard let ac:JPEG.Table.HuffmanAC.Decoder = slots.ac[keyPath: $0.component.selector.ac]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanACReference($0.component.selector.ac) } - + let factor:(x:Int, y:Int) = self.layout.planes[$0.c].component.factor return (self.indices ~= $0.c ? $0.c : nil, factor, (dc, ac)) } - - let rows:Range = + + let rows:Range = (blocks.lowerBound / self.blocks.x ..< blocks.upperBound / self.blocks.x) .clamped(to: 0 ..< (extend ? .max : self.blocks.y)) let bits:JPEG.Bitstream = .init(data) @@ -3211,22 +3211,22 @@ extension JPEG.Data.Spectral row: for my:Int in rows { - if extend + if extend { - guard b < bits.count, bits[b, count: 16] != 0xffff - else + guard b < bits.count, bits[b, count: 16] != 0xffff + else { - break row + break row } - - for (p, factor, _):Descriptor in descriptors + + for (p, factor, _):Descriptor in descriptors { - guard let p:Int = p - else + guard let p:Int = p + else { - continue + continue } - + let height:Int = (my + 1) * factor.y if height > self[p].units.y { @@ -3234,105 +3234,105 @@ extension JPEG.Data.Spectral } } } - + column: - for mx:Int in 0 ..< self.blocks.x + for mx:Int in 0 ..< self.blocks.x { for (c, (p, factor, table)):(Int, Descriptor) in zip(predecessor.indices, descriptors) { - let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), - end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) - for (x, y):(x:Int, y:Int) in start ..< end + let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), + end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) + for (x, y):(x:Int, y:Int) in start ..< end { - // dc - let composite:JPEG.Bitstream.Composite.DC = + // dc + let composite:JPEG.Bitstream.Composite.DC = try bits.composite(&b, table: table.dc) - - if let p:Int = p + + if let p:Int = p { - predecessor[c] &+= composite.difference - self[p][x: x, y: y, z: 0] = predecessor[c] + predecessor[c] &+= composite.difference + self[p][x: x, y: y, z: 0] = predecessor[c] } - + // ac var z:Int = 1 - frequency: + frequency: while z < 64 { switch try bits.composite(&b, table: table.ac) { case .run(let run, value: let v): - z += run - + z += run + guard z < 64 - else + else { - break frequency - } - - if let p:Int = p + break frequency + } + + if let p:Int = p { - self[p][x: x, y: y, z: z] = v + self[p][x: x, y: y, z: z] = v } - + z += 1 - + case .eob(1): break frequency - + case .eob(let v): throw JPEG.DecodingError.invalidCompositeBlockRun(v, expected: 1 ... 1) } - } + } } } } } } - - // progressive mode - private mutating - func decode(_ data:[UInt8], blocks:Range, bits a:PartialRangeFrom, - components:[(c:Int, component:JPEG.Scan.Component)], - tables slots:JPEG.Table.HuffmanDC.Slots, + + // progressive mode + private mutating + func decode(_ data:[UInt8], blocks:Range, bits a:PartialRangeFrom, + components:[(c:Int, component:JPEG.Scan.Component)], + tables slots:JPEG.Table.HuffmanDC.Slots, extend:Bool) throws { - // it is allowed (in the case of a custom implementation of `Format`) for - // jpegs to encode components that aren’t represented by a plane in this - // data structure. hence, the `p:Int?` being an optional. - // the scan header parser should enforce the membership of the `ci` index - // in the frame header, we don’t care about that here - guard components.count > 1 - else + // it is allowed (in the case of a custom implementation of `Format`) for + // jpegs to encode components that aren’t represented by a plane in this + // data structure. hence, the `p:Int?` being an optional. + // the scan header parser should enforce the membership of the `ci` index + // in the frame header, we don’t care about that here + guard components.count > 1 + else { // noninterleaved precondition(components.count == 1, "components array cannot be empty") let (p, component):(Int, JPEG.Scan.Component) = components[0] guard self.indices ~= p - else + else { - return + return } - - try self[p].decode(data, blocks: blocks, bits: a, component: component, + + try self[p].decode(data, blocks: blocks, bits: a, component: component, tables: slots, extend: extend) - return + return } - + typealias Descriptor = (p:Int?, factor:(x:Int, y:Int), table:JPEG.Table.HuffmanDC.Decoder) - let descriptors:[Descriptor] = try components.map + let descriptors:[Descriptor] = try components.map { - guard let huffman:JPEG.Table.HuffmanDC.Decoder = + guard let huffman:JPEG.Table.HuffmanDC.Decoder = slots[keyPath: $0.component.selector.dc]?.decoder() - else + else { throw JPEG.DecodingError.undefinedScanHuffmanDCReference($0.component.selector.dc) } let factor:(x:Int, y:Int) = self.layout.planes[$0.c].component.factor return (self.indices ~= $0.c ? $0.c : nil, factor, huffman) } - - let rows:Range = + + let rows:Range = (blocks.lowerBound / self.blocks.x ..< blocks.upperBound / self.blocks.x) .clamped(to: 0 ..< (extend ? .max : self.blocks.y)) let bits:JPEG.Bitstream = .init(data) @@ -3341,22 +3341,22 @@ extension JPEG.Data.Spectral row: for my:Int in rows { - if extend + if extend { - guard b < bits.count, bits[b, count: 16] != 0xffff - else + guard b < bits.count, bits[b, count: 16] != 0xffff + else { - break row + break row } - - for (p, factor, _):Descriptor in descriptors + + for (p, factor, _):Descriptor in descriptors { - guard let p:Int = p - else + guard let p:Int = p + else { - continue + continue } - + let height:Int = (my + 1) * factor.y if height > self[p].units.y { @@ -3364,61 +3364,61 @@ extension JPEG.Data.Spectral } } } - + column: - for mx:Int in 0 ..< self.blocks.x + for mx:Int in 0 ..< self.blocks.x { for (c, (p, factor, table)):(Int, Descriptor) in zip(predecessor.indices, descriptors) { - let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), - end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) - for (x, y):(x:Int, y:Int) in start ..< end + let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), + end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) + for (x, y):(x:Int, y:Int) in start ..< end { - let composite:JPEG.Bitstream.Composite.DC = + let composite:JPEG.Bitstream.Composite.DC = try bits.composite(&b, table: table) - - guard let p:Int = p - else + + guard let p:Int = p + else { - continue + continue } - - predecessor[c] &+= composite.difference + + predecessor[c] &+= composite.difference self[p][x: x, y: y, z: 0] = predecessor[c] << a.lowerBound } } } } } - - private mutating - func decode(_ data:[UInt8], blocks:Range, bit a:Int, + + private mutating + func decode(_ data:[UInt8], blocks:Range, bit a:Int, components:[(c:Int, component:JPEG.Scan.Component)]) throws { - guard components.count > 1 - else + guard components.count > 1 + else { // noninterleaved precondition(components.count == 1, "components array cannot be empty") let p:Int = components[0].c guard self.indices ~= p - else + else { - return + return } - + try self[p].decode(data, blocks: blocks, bit: a) - return + return } - + typealias Descriptor = (p:Int?, factor:(x:Int, y:Int)) - let descriptors:[Descriptor] = components.map + let descriptors:[Descriptor] = components.map { let factor:(x:Int, y:Int) = self.layout.planes[$0.c].component.factor return (self.indices ~= $0.c ? $0.c : nil, factor) } - - let rows:Range = blocks.lowerBound / self.blocks.x ..< + + let rows:Range = blocks.lowerBound / self.blocks.x ..< Swift.min(blocks.upperBound / self.blocks.x, self.blocks.y) let bits:JPEG.Bitstream = .init(data) var b:Int = 0 @@ -3426,181 +3426,181 @@ extension JPEG.Data.Spectral { for (p, factor):Descriptor in descriptors { - let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), - end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) - for (x, y):(x:Int, y:Int) in start ..< end + let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), + end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) + for (x, y):(x:Int, y:Int) in start ..< end { - let refinement:Int16 = try bits.refinement(&b) - - guard let p:Int = p - else + let refinement:Int16 = try bits.refinement(&b) + + guard let p:Int = p + else { - continue + continue } - + self[p][x: x, y: y, z: 0] |= refinement << a } } } - } - - // this function doesn’t actually dequantize anything, it just sets the quantization - // table pointer in the relevant plane structs to the corresponding quantization + } + + // this function doesn’t actually dequantize anything, it just sets the quantization + // table pointer in the relevant plane structs to the corresponding quantization // table already in `self` - private mutating - func dequantize(components:[(c:Int, component:JPEG.Scan.Component)], + private mutating + func dequantize(components:[(c:Int, component:JPEG.Scan.Component)], tables slots:JPEG.Table.Quantization.Slots) throws { - for (p, _):(Int, JPEG.Scan.Component) in components + for (p, _):(Int, JPEG.Scan.Component) in components { guard self.indices ~= p - else + else { - continue + continue } - - let selector:JPEG.Table.Quantization.Selector = + + let selector:JPEG.Table.Quantization.Selector = self.layout.planes[p].component.selector guard let (q, qi):(Int, JPEG.Table.Quantization.Key) = slots[keyPath: selector] - else + else { throw JPEG.DecodingError.undefinedScanQuantizationReference(selector) } - + self[p].q = q self.layout.planes[p].qi = qi } } - - mutating + + mutating func decode(ecss:[[UInt8]], interval:Int, scan:JPEG.Header.Scan, tables slots: ( - dc:JPEG.Table.HuffmanDC.Slots, - ac:JPEG.Table.HuffmanAC.Slots, + dc:JPEG.Table.HuffmanDC.Slots, + ac:JPEG.Table.HuffmanAC.Slots, quanta:JPEG.Table.Quantization.Slots - ), - extend:Bool) throws + ), + extend:Bool) throws { let scan:JPEG.Scan = try self.layout.push(scan: scan) switch (initial: scan.bits.upperBound == .max, band: scan.band) { case (initial: true, band: 0 ..< 64): try self.dequantize(components: scan.components, tables: slots.quanta) - + case (initial: true, band: 0 ..< 1): - // in a progressive image, the dc scan must be the first scan for a - // particular component, so this is when we select and push the + // in a progressive image, the dc scan must be the first scan for a + // particular component, so this is when we select and push the // quantization tables try self.dequantize(components: scan.components, tables: slots.quanta) - + default: break } - - for (start, data):(Int, [UInt8]) in zip(stride(from: 0, to: .max, by: interval), ecss) + + for (start, data):(Int, [UInt8]) in zip(stride(from: 0, to: .max, by: interval), ecss) { let blocks:Range = start ..< start + interval switch (initial: scan.bits.upperBound == .max, band: scan.band) { case (initial: true, band: 0 ..< 64): - try self.decode(data, blocks: blocks, components: scan.components, + try self.decode(data, blocks: blocks, components: scan.components, tables: (slots.dc, slots.ac), extend: extend) - + case (initial: false, band: 0 ..< 64): - // successive approximation cannot happen without spectral selection. - // the scan header parser should enforce this + // successive approximation cannot happen without spectral selection. + // the scan header parser should enforce this fatalError("unreachable") - + case (initial: true, band: 0 ..< 1): - try self.decode(data, blocks: blocks, bits: scan.bits.lowerBound..., - components: scan.components, tables: slots.dc, extend: extend) - + try self.decode(data, blocks: blocks, bits: scan.bits.lowerBound..., + components: scan.components, tables: slots.dc, extend: extend) + case (initial: false, band: 0 ..< 1): - try self.decode(data, blocks: blocks, bit: scan.bits.lowerBound, + try self.decode(data, blocks: blocks, bit: scan.bits.lowerBound, components: scan.components) - + case (initial: true, band: let band): // scan initializer should have validated this assert(scan.components.count == 1) - + let (p, component):(Int, JPEG.Scan.Component) = scan.components[0] guard self.indices ~= p - else + else { - return + return } - - try self[p].decode(data, blocks: blocks, band: band, bits: scan.bits.lowerBound..., + + try self[p].decode(data, blocks: blocks, band: band, bits: scan.bits.lowerBound..., component: component, tables: slots.ac) - + case (initial: false, band: let band): // scan initializer should have validated this assert(scan.components.count == 1) - + let (p, component):(Int, JPEG.Scan.Component) = scan.components[0] guard self.indices ~= p - else + else { - return + return } - - try self[p].decode(data, blocks: blocks, band: band, bit: scan.bits.lowerBound, + + try self[p].decode(data, blocks: blocks, band: band, bit: scan.bits.lowerBound, component: component, tables: slots.ac) } } } } -extension JPEG +extension JPEG { - /// struct JPEG.Context - /// where Format:JPEG.Format - /// A contextual state manager used for manual decoding. - /// - /// The main use case for this type is to observe the visual state of a - /// partially-decoded image, for example, when performing + /// struct JPEG.Context + /// where Format:JPEG.Format + /// A contextual state manager used for manual decoding. + /// + /// The main use case for this type is to observe the visual state of a + /// partially-decoded image, for example, when performing /// [online decoding](https://github.com/kelvin13/jpeg/tree/master/examples#online-decoding). /// ## (manual-decoding) - public + public struct Context where Format:JPEG.Format { private var tables: ( - dc:Table.HuffmanDC.Slots, - ac:Table.HuffmanAC.Slots, + dc:Table.HuffmanDC.Slots, + ac:Table.HuffmanAC.Slots, quanta:Table.Quantization.Slots - ) - - private + ) + + private var interval:Int? - + /// var JPEG.Context.spectral : JPEG.Data.Spectral { get } /// The spectral image, as currently decoded. public private(set) var spectral:Data.Spectral - private - var progression:Layout.Progression - - private - var counter:Int + private + var progression:Layout.Progression + + private + var counter:Int } } -extension JPEG.Context +extension JPEG.Context { /// init JPEG.Context.init(frame:) - /// throws + /// throws /// Initializes the decoder context from the given frame header. - /// - frame : JPEG.Header.Frame - /// The frame header of the image. This frame header is used to allocate + /// - frame : JPEG.Header.Frame + /// The frame header of the image. This frame header is used to allocate /// a [`(Data).Spectral`] image. - public - init(frame:JPEG.Header.Frame) throws + public + init(frame:JPEG.Header.Frame) throws { self.counter = 0 self.spectral = try .decode(frame: frame) self.progression = .init(self.spectral.layout.recognized) - self.tables = + self.tables = ( (nil, nil, nil, nil), (nil, nil, nil, nil), @@ -3610,60 +3610,60 @@ extension JPEG.Context } /// mutating func JPEG.Context.push(height:) /// Updates the decoder state with the given height redefinition. - /// + /// /// This method calls [`(Data.Spectral).set(height:)`] on the stored image. - /// - height : JPEG.Header.HeightRedefinition + /// - height : JPEG.Header.HeightRedefinition /// The height redefinition. - public mutating - func push(height:JPEG.Header.HeightRedefinition) + public mutating + func push(height:JPEG.Header.HeightRedefinition) { self.spectral.set(height: height.height) } /// mutating func JPEG.Context.push(interval:) /// Updates the decoder state with the given restart interval definition. - /// - interval : JPEG.Header.RestartInterval + /// - interval : JPEG.Header.RestartInterval /// The restart interval definition. - public mutating - func push(interval:JPEG.Header.RestartInterval) + public mutating + func push(interval:JPEG.Header.RestartInterval) { - self.interval = interval.interval + self.interval = interval.interval } /// mutating func JPEG.Context.push(dc:) /// Updates the decoder state with the given DC huffman table. - /// - /// This method binds the table to its target [`(Table.HuffmanDC).Selector`] + /// + /// This method binds the table to its target [`(Table.HuffmanDC).Selector`] /// within this instance. /// - table : JPEG.Table.HuffmanDC /// The DC huffman table. - public mutating - func push(dc table:JPEG.Table.HuffmanDC) + public mutating + func push(dc table:JPEG.Table.HuffmanDC) { self.tables.dc[keyPath: table.target] = table } /// mutating func JPEG.Context.push(ac:) /// Updates the decoder state with the given AC huffman table. - /// - /// This method binds the table to its target [`(Table.HuffmanAC).Selector`] + /// + /// This method binds the table to its target [`(Table.HuffmanAC).Selector`] /// within this instance. /// - table : JPEG.Table.HuffmanAC /// The AC huffman table. - public mutating - func push(ac table:JPEG.Table.HuffmanAC) + public mutating + func push(ac table:JPEG.Table.HuffmanAC) { self.tables.ac[keyPath: table.target] = table } /// mutating func JPEG.Context.push(quanta:) - /// throws + /// throws /// Updates the decoder state with the given quantization table. - /// - /// This method binds the table to its target [`(Table.Quantization).Selector`] + /// + /// This method binds the table to its target [`(Table.Quantization).Selector`] /// within this instance. /// - table : JPEG.Table.Quantization /// The quantization table. - public mutating - func push(quanta table:JPEG.Table.Quantization) throws + public mutating + func push(quanta table:JPEG.Table.Quantization) throws { - // generate a new `qi`, and get the corresponding `q` from the + // generate a new `qi`, and get the corresponding `q` from the // `spectral.push` function let qi:JPEG.Table.Quantization.Key = .init(self.counter) let q:Int = try self.spectral.push(qi: qi, quanta: table) @@ -3672,225 +3672,225 @@ extension JPEG.Context } /// mutating func JPEG.Context.push(metadata:) /// Updates the decoder state with the given metadata record. - /// - /// This method adds the metadata record to the [`(Data.Spectral).metadata`] + /// + /// This method adds the metadata record to the [`(Data.Spectral).metadata`] /// array in the stored image. /// - metadata : JPEG.Metadata /// The metadata record. - public mutating - func push(metadata:JPEG.Metadata) + public mutating + func push(metadata:JPEG.Metadata) { self.spectral.metadata.append(metadata) } /// mutating func JPEG.Context.push(scan:ecss:extend:) - /// throws - /// Updates the decoder state with the given scan header, and decodes the + /// throws + /// Updates the decoder state with the given scan header, and decodes the /// given entropy-coded segment. - /// - /// This type tracks the scan progression of the stored image, and will + /// + /// This type tracks the scan progression of the stored image, and will /// validate the newly pushed `scan` header against the stored progressive state. - /// - scan : JPEG.Header.Scan + /// - scan : JPEG.Header.Scan /// The scan header associated with the given entropy-coded segment. /// - ecss : [[Swift.UInt8]] - /// The entropy-coded segment. Each sub-array is one restart interval of - /// segment. - /// - extend: Swift.Bool - /// Specifies whether or not the decoder is allowed to dynamically extend - /// the height of the image if the entropy-coded segment contains more + /// The entropy-coded segment. Each sub-array is one restart interval of + /// segment. + /// - extend: Swift.Bool + /// Specifies whether or not the decoder is allowed to dynamically extend + /// the height of the image if the entropy-coded segment contains more /// rows of image data than implied by the frame header [`(Header.Frame).size`]. - /// - /// This argument should be set to `true` for the first scan in the file, - /// to accommodate a possible [`(Header).HeightRedefinition`], and `false` + /// + /// This argument should be set to `true` for the first scan in the file, + /// to accommodate a possible [`(Header).HeightRedefinition`], and `false` /// for all other scans. - public mutating - func push(scan:JPEG.Header.Scan, ecss:[[UInt8]], extend:Bool) throws + public mutating + func push(scan:JPEG.Header.Scan, ecss:[[UInt8]], extend:Bool) throws { let interval:Int - if let stride:Int = self.interval + if let stride:Int = self.interval { - interval = stride + interval = stride } else if ecss.count == 1 { - interval = .max + interval = .max } - else + else { throw JPEG.DecodingError.missingRestartIntervalSegment } - + try self.progression.update(scan) - try self.spectral.decode(ecss: ecss, interval: interval, scan: scan, + try self.spectral.decode(ecss: ecss, interval: interval, scan: scan, tables: self.tables, extend: extend) } - - static - func decompress(stream:inout Source) throws -> JPEG.Data.Spectral + + static + func decompress(stream:inout Source) throws -> JPEG.Data.Spectral where Source:JPEG.Bytestream.Source { - var marker:(type:JPEG.Marker, data:[UInt8]) - - // start of image + var marker:(type:JPEG.Marker, data:[UInt8]) + + // start of image marker = try stream.segment() - guard case .start = marker.type - else + guard case .start = marker.type + else { throw JPEG.DecodingError.missingStartOfImage(marker.type) } - - // read metadata headers. jfif and exif standard are incompatible (both - // segments are supposed to be the second segment in the file), but since + + // read metadata headers. jfif and exif standard are incompatible (both + // segments are supposed to be the second segment in the file), but since // many applications include both of them, we look for all “application” - // segments immediately following the start-of-image + // segments immediately following the start-of-image var metadata:[JPEG.Metadata] = [] var seen:(jfif:Bool, exif:Bool) = (false, false) marker = try stream.segment() - preamble: - while true + preamble: + while true { switch (seen, marker.type) { - case ((jfif: false, exif: _), .application(0)): // JFIF + case ((jfif: false, exif: _), .application(0)): // JFIF let jfif:JPEG.JFIF = try .parse(marker.data) metadata.append(.jfif(jfif)) seen.jfif = true - - case ((jfif: _, exif: false), .application(1)): // EXIF + + case ((jfif: _, exif: false), .application(1)): // EXIF let exif:JPEG.EXIF = try .parse(marker.data) metadata.append(.exif(exif)) - seen.exif = true - + seen.exif = true + case ((jfif: _, exif: _), .application(let application)): metadata.append(.application(application, data: marker.data)) - + case ((jfif: _, exif: _), .comment): metadata.append(.comment(data: marker.data)) - + default: - break preamble + break preamble } - - marker = try stream.segment() + + marker = try stream.segment() } - - var dc:[JPEG.Table.HuffmanDC] = [], - ac:[JPEG.Table.HuffmanAC] = [], + + var dc:[JPEG.Table.HuffmanDC] = [], + ac:[JPEG.Table.HuffmanAC] = [], quanta:[JPEG.Table.Quantization] = [] - var interval:JPEG.Header.RestartInterval?, + var interval:JPEG.Header.RestartInterval?, frame:JPEG.Header.Frame? definitions: - while true + while true { - switch marker.type + switch marker.type { case .frame(let process): frame = try .parse(marker.data, process: process) - marker = try stream.segment() + marker = try stream.segment() break definitions - + case .quantization: - let parsed:[JPEG.Table.Quantization] = + let parsed:[JPEG.Table.Quantization] = try JPEG.Table.parse(quantization: marker.data) quanta.append(contentsOf: parsed) - + case .huffman: - let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = + let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = try JPEG.Table.parse(huffman: marker.data) dc.append(contentsOf: parsed.dc) ac.append(contentsOf: parsed.ac) - + case .interval: interval = try .parse(marker.data) - - // an APP0 segment after the preamble just gets recorded as an APP0 segment, + + // an APP0 segment after the preamble just gets recorded as an APP0 segment, // not a JFIF record case .application(let application): metadata.append(.application(application, data: marker.data)) case .comment: metadata.append(.comment(data: marker.data)) - + case .scan: throw JPEG.DecodingError.prematureScanHeaderSegment case .height: throw JPEG.DecodingError.prematureHeightRedefinitionSegment - + case .end: throw JPEG.DecodingError.prematureEndOfImage case .start: throw JPEG.DecodingError.duplicateStartOfImage case .restart(_): throw JPEG.DecodingError.unexpectedRestart - - // unimplemented + + // unimplemented case .arithmeticCodingCondition: - break + break case .hierarchical: - break + break case .expandReferenceComponents: - break + break } - - marker = try stream.segment() + + marker = try stream.segment() } - + // can use `!` here, previous loop cannot exit without initializing `frame` var context:Self = try .init(frame: frame!) - for metadata:JPEG.Metadata in metadata + for metadata:JPEG.Metadata in metadata { context.push(metadata: metadata) } - for table:JPEG.Table.HuffmanDC in dc + for table:JPEG.Table.HuffmanDC in dc { context.push(dc: table) } - for table:JPEG.Table.HuffmanAC in ac + for table:JPEG.Table.HuffmanAC in ac { context.push(ac: table) } - for table:JPEG.Table.Quantization in quanta + for table:JPEG.Table.Quantization in quanta { try context.push(quanta: table) } - if let interval:JPEG.Header.RestartInterval = interval + if let interval:JPEG.Header.RestartInterval = interval { context.push(interval: interval) } - + var first:Bool = true scans: - while true + while true { - switch marker.type + switch marker.type { case .frame: throw JPEG.DecodingError.duplicateFrameHeaderSegment - + case .quantization: - for table:JPEG.Table.Quantization in + for table:JPEG.Table.Quantization in try JPEG.Table.parse(quantization: marker.data) { try context.push(quanta: table) } - + case .huffman: - let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = + let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = try JPEG.Table.parse(huffman: marker.data) - for table:JPEG.Table.HuffmanDC in parsed.dc + for table:JPEG.Table.HuffmanDC in parsed.dc { context.push(dc: table) } - for table:JPEG.Table.HuffmanAC in parsed.ac + for table:JPEG.Table.HuffmanAC in parsed.ac { context.push(ac: table) } - + case .application(let application): context.push(metadata: .application(application, data: marker.data)) case .comment: - context.push(metadata: .comment(data: marker.data)) + context.push(metadata: .comment(data: marker.data)) case .scan: - let scan:JPEG.Header.Scan = try .parse(marker.data, + let scan:JPEG.Header.Scan = try .parse(marker.data, process: context.spectral.layout.process) var ecss:[[UInt8]] = [] for index:Int in 0... @@ -3899,175 +3899,175 @@ extension JPEG.Context (ecs, marker) = try stream.segment(prefix: true) ecss.append(ecs) guard case .restart(let phase) = marker.type - else + else { try context.push(scan: scan, ecss: ecss, extend: first) - if first + if first { let height:JPEG.Header.HeightRedefinition - if case .height = marker.type + if case .height = marker.type { height = try .parse(marker.data) - marker = try stream.segment() + marker = try stream.segment() } // same guarantees for `!` as before else if frame!.size.y > 0 { height = .init(height: frame!.size.y) } - else + else { throw JPEG.DecodingError.missingHeightRedefinitionSegment } context.push(height: height) - first = false + first = false } - continue scans + continue scans } - - guard phase == index % 8 - else + + guard phase == index % 8 + else { throw JPEG.DecodingError.invalidRestartPhase(phase, expected: index % 8) } } - + case .interval: context.push(interval: try .parse(marker.data)) - + case .end: - return context.spectral - + return context.spectral + case .start: throw JPEG.DecodingError.duplicateStartOfImage - + case .restart(_): throw JPEG.DecodingError.unexpectedRestart case .height: throw JPEG.DecodingError.unexpectedHeightRedefinitionSegment - - // unimplemented + + // unimplemented case .arithmeticCodingCondition: - break + break case .hierarchical: - break + break case .expandReferenceComponents: - break + break } - - marker = try stream.segment() + + marker = try stream.segment() } } } -// signal processing and upscaling -extension JPEG.Data.Spectral.Plane +// signal processing and upscaling +extension JPEG.Data.Spectral.Plane { - typealias Block8x8 = - (SIMD8, SIMD8, SIMD8, SIMD8, SIMD8, SIMD8, SIMD8, SIMD8) + typealias Block8x8 = + (SIMD8, SIMD8, SIMD8, SIMD8, SIMD8, SIMD8, SIMD8, SIMD8) where T:SIMDScalar - - static + + static func transpose(_ h:Block8x8) -> Block8x8 where T:SIMDScalar { @inline(__always) - func column(_ k:Int) -> SIMD8 + func column(_ k:Int) -> SIMD8 { .init(h.0[k], h.1[k], h.2[k], h.3[k], h.4[k], h.5[k], h.6[k], h.7[k]) } - - return (column(0), column(1), column(2), column(3), + + return (column(0), column(1), column(2), column(3), column(4), column(5), column(6), column(7)) } - - static - func modulate(quanta table:JPEG.Table.Quantization, scale:Float) -> Block8x8 + + static + func modulate(quanta table:JPEG.Table.Quantization, scale:Float) -> Block8x8 { @inline(__always) - func row(_ h:Int) -> SIMD8 + func row(_ h:Int) -> SIMD8 { scale * .init(.init( table[k: 0, h: h], table[k: 1, h: h], table[k: 2, h: h], table[k: 3, h: h], - + table[k: 4, h: h], table[k: 5, h: h], table[k: 6, h: h], table[k: 7, h: h])) } - + let r:SIMD8 = .init( - 1, 1.387039845, 1.306562965, 1.175875602, + 1, 1.387039845, 1.306562965, 1.175875602, 1, 0.785694958, 0.541196100, 0.275899379) - let h:Block8x8 = (r, r, r, r, r, r, r, r), + let h:Block8x8 = (r, r, r, r, r, r, r, r), v:Block8x8 = Self.transpose(h) - return + return ( - h.0 * v.0 * row(0), - h.1 * v.1 * row(1), - h.2 * v.2 * row(2), - h.3 * v.3 * row(3), - h.4 * v.4 * row(4), - h.5 * v.5 * row(5), - h.6 * v.6 * row(6), + h.0 * v.0 * row(0), + h.1 * v.1 * row(1), + h.2 * v.2 * row(2), + h.3 * v.3 * row(3), + h.4 * v.4 * row(4), + h.5 * v.5 * row(5), + h.6 * v.6 * row(6), h.7 * v.7 * row(7) ) } - - fileprivate + + fileprivate func load(x:Int, y:Int, quanta:Block8x8) -> Block8x8 { @inline(__always) - func row(_ h:Int) -> SIMD8 + func row(_ h:Int) -> SIMD8 { .init(.init( self[x: x, y: y, k: 0, h: h], self[x: x, y: y, k: 1, h: h], self[x: x, y: y, k: 2, h: h], self[x: x, y: y, k: 3, h: h], - + self[x: x, y: y, k: 4, h: h], self[x: x, y: y, k: 5, h: h], self[x: x, y: y, k: 6, h: h], self[x: x, y: y, k: 7, h: h])) } - - return (quanta.0 * row(0), quanta.1 * row(1), quanta.2 * row(2), quanta.3 * row(3), + + return (quanta.0 * row(0), quanta.1 * row(1), quanta.2 * row(2), quanta.3 * row(3), quanta.4 * row(4), quanta.5 * row(5), quanta.6 * row(6), quanta.7 * row(7)) } - - private static + + private static func idct8(_ h:Block8x8, shift:Float) -> Block8x8 { - // even rows - let a:(SIMD8, SIMD8) = + // even rows + let a:(SIMD8, SIMD8) = ( shift + h.0 + h.4, shift + h.0 - h.4 ) - let b:SIMD8 = h.2 + h.6, + let b:SIMD8 = h.2 + h.6, c:SIMD8 = 1.414213562 * (h.2 - h.6) - b - - let r:(SIMD8, SIMD8, SIMD8, SIMD8) = + + let r:(SIMD8, SIMD8, SIMD8, SIMD8) = ( - a.0 + b, - a.1 + c, + a.0 + b, + a.1 + c, a.1 - c, a.0 - b ) - // odd rows - let d:(SIMD8, SIMD8, SIMD8, SIMD8) = + // odd rows + let d:(SIMD8, SIMD8, SIMD8, SIMD8) = ( h.5 - h.3, h.1 + h.7 , h.1 - h.7 , h.5 + h.3 ) - let f:SIMD8 = 1.414213562 * (d.1 - d.3), + let f:SIMD8 = 1.414213562 * (d.1 - d.3), l:SIMD8 = 1.847759065 * (d.0 + d.2) - let m:(SIMD8, SIMD8) = + let m:(SIMD8, SIMD8) = ( l - d.2 * 1.082392200, l - d.0 * 2.613125930 @@ -4077,212 +4077,212 @@ extension JPEG.Data.Spectral.Plane s.1 = m.1 - s.0 s.2 = f - s.1 s.3 = m.0 - s.2 - - return + + return ( r.0 + s.0, r.1 + s.1, r.2 + s.2, - r.3 + s.3, - + r.3 + s.3, + r.3 - s.3, r.2 - s.2, r.1 - s.1, r.0 - s.0 ) } - private static + private static func idct8x8(_ h:Block8x8, shift:Float) -> Block8x8 { - let f:Block8x8 = Self.transpose(Self.idct8(h, shift: 0)), + let f:Block8x8 = Self.transpose(Self.idct8(h, shift: 0)), g:Block8x8 = Self.transpose(Self.idct8(f, shift: shift)) return g } - func idct(quanta table:JPEG.Table.Quantization, precision:Int) + func idct(quanta table:JPEG.Table.Quantization, precision:Int) -> JPEG.Data.Planar.Plane { - let count:Int = 64 * self.units.x * self.units.y - let values:[UInt16] = .init(unsafeUninitializedCapacity: count) + let count:Int = 64 * self.units.x * self.units.y + let values:[UInt16] = .init(unsafeUninitializedCapacity: count) { let q:Block8x8 = Self.modulate(quanta: table, scale: 0x1p-3) - + let stride:Int = 8 * self.units.x - let level:Float = + let level:Float = .init(sign: .plus, exponent: precision - 1, significand: 1) + 0.5 - let limit:SIMD8 = .init(repeating: + let limit:SIMD8 = .init(repeating: .init(sign: .plus, exponent: precision , significand: 1)) - 1 - for (x, y):(x:Int, y:Int) in (0, 0) ..< self.units + for (x, y):(x:Int, y:Int) in (0, 0) ..< self.units { let h:Block8x8 = self.load(x: x, y: y, quanta: q), g:Block8x8 = Self.idct8x8(h, shift: level) for (i, t):(offset:Int, element:SIMD8) in [g.0, g.1, g.2, g.3, g.4, g.5, g.6, g.7].enumerated() { - let u:SIMD8 = + let u:SIMD8 = .init(t.clamped(lowerBound: .zero, upperBound: limit)) - for j:Int in 0 ..< 8 + for j:Int in 0 ..< 8 { $0[(8 * y + i) * stride + 8 * x + j] = u[j] } } } - - $1 = count + + $1 = count } - return .init(values, units: self.units, factor: self.factor) + return .init(values, units: self.units, factor: self.factor) } } -extension JPEG.Data.Planar.Plane +extension JPEG.Data.Planar.Plane { init(_ values:[UInt16], units:(x:Int, y:Int), factor:(x:Int, y:Int)) { - self.buffer = values - self.units = units + self.buffer = values + self.units = units self._factor = .init(wrappedValue: factor) } } -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { /// func JPEG.Data.Spectral.idct() - /// Converts this spectral image into its planar, spatial representation. - /// - -> : JPEG.Data.Planar + /// Converts this spectral image into its planar, spatial representation. + /// - -> : JPEG.Data.Planar /// The output of an inverse discrete cosine transform performed on this image. /// # [See also](spectral-change-representation) /// ## (0:spectral-change-representation) - public - func idct() -> JPEG.Data.Planar + public + func idct() -> JPEG.Data.Planar { let precision:Int = self.layout.format.precision - let planes:[JPEG.Data.Planar.Plane] = self.indices.map + let planes:[JPEG.Data.Planar.Plane] = self.indices.map { self[$0].idct(quanta: self.quanta[self[$0].q], precision: precision) } - return .init(size: self.size, - layout: self.layout, + return .init(size: self.size, + layout: self.layout, metadata: self.metadata, planes: planes) } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { /// func JPEG.Data.Planar.interleaved(cosite:) - /// Converts this planar image into its rectangular representation. - /// - cosited : Swift.Bool - /// The upsampling method to use. Setting this parameter to `true` co-sites + /// Converts this planar image into its rectangular representation. + /// - cosited : Swift.Bool + /// The upsampling method to use. Setting this parameter to `true` co-sites /// the samples; setting it to `false` centers them instead. - /// + /// /// The default value is `false`. - /// - -> : JPEG.Data.Rectangular - /// A rectangular image created by upsampling all planes in the input to + /// - -> : JPEG.Data.Rectangular + /// A rectangular image created by upsampling all planes in the input to /// the same sampling factor. /// # [See also](planar-change-representation) /// ## (0:planar-change-representation) - public - func interleaved(cosite cosited:Bool = false) -> JPEG.Data.Rectangular + public + func interleaved(cosite cosited:Bool = false) -> JPEG.Data.Rectangular { - var interleaved:[UInt16] - if self.count == 1 + var interleaved:[UInt16] + if self.count == 1 { let count:Int = self.size.x * self.size.y interleaved = .init(unsafeUninitializedCapacity: count) { for (x, y):(x:Int, y:Int) in (0, 0) ..< self.size { - $0[y * self.size.x + x] = self[0][x: x, y: y] + $0[y * self.size.x + x] = self[0][x: x, y: y] } - + $1 = count } } - else + else { let scale:(x:Int, y:Int) = self.layout.scale let count:Int = self.size.x * self.size.y * self.count interleaved = .init(unsafeUninitializedCapacity: count) { - for (p, plane):(Int, Plane) in self.enumerated() + for (p, plane):(Int, Plane) in self.enumerated() { guard plane.factor != scale - else + else { - // fast path - for (x, y):(x:Int, y:Int) in (0, 0) ..< self.size + // fast path + for (x, y):(x:Int, y:Int) in (0, 0) ..< self.size { $0[(y * self.size.x + x) * self.count + p] = plane[x: x, y: y] } - continue + continue } - + // a + b * x // ----------- // c let a:(x:Int, y:Int), - b:(x:Int, y:Int), + b:(x:Int, y:Int), c:(x:Int, y:Int) - if cosited + if cosited { a = (0, 0) - b = plane.factor - c = scale + b = plane.factor + c = scale } - else + else { a = (plane.factor.x - scale.x, plane.factor.y - scale.y) b = (2 * plane.factor.x, 2 * plane.factor.y) c = (2 * scale.x, 2 * scale.y) } let d:(x:Int, y:Int) = (plane.size.x - 1, plane.size.y - 1) - for (x, y):(x:Int, y:Int) in (0, 0) ..< self.size + for (x, y):(x:Int, y:Int) in (0, 0) ..< self.size { - let i:(x:Int, y:Int), + let i:(x:Int, y:Int), f:(x:Int, y:Int) (i.x, f.x) = (a.x + b.x * x).quotientAndRemainder(dividingBy: c.x) (i.y, f.y) = (a.y + b.y * y).quotientAndRemainder(dividingBy: c.y) - - let j:(x:Int, y:Int) = + + let j:(x:Int, y:Int) = ( - Swift.min(i.x + 1, d.x), + Swift.min(i.x + 1, d.x), Swift.min(i.y + 1, d.y) ) - let t:(x:Float, y:Float) = + let t:(x:Float, y:Float) = ( Swift.max(0, Swift.min(.init(f.x) / .init(c.x), 1)), Swift.max(0, Swift.min(.init(f.y) / .init(c.y), 1)) ) - let u:((Float, Float), (Float, Float)) = + let u:((Float, Float), (Float, Float)) = ( (.init(plane[x: i.x, y: i.y]), .init(plane[x: j.x, y: i.y])), (.init(plane[x: i.x, y: j.y]), .init(plane[x: j.x, y: j.y])) ) - let v:(Float, Float) = + let v:(Float, Float) = ( u.0.0 * (1 - t.x) + u.0.1 * t.x, u.1.0 * (1 - t.x) + u.1.1 * t.x ) - $0[(y * self.size.x + x) * self.count + p] = + $0[(y * self.size.x + x) * self.count + p] = .init((v.0 * (1 - t.y) + v.1 * t.y).rounded()) } } - + $1 = count } } - - return .init(size: self.size, - layout: self.layout, - metadata: self.metadata, + + return .init(size: self.size, + layout: self.layout, + metadata: self.metadata, values: interleaved) } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { /// func JPEG.Data.Rectangular.unpack(as:) - /// where Color:JPEG.Color, Color.Format == Format + /// where Color:JPEG.Color, Color.Format == Format /// @ specialized where Color == JPEG.YCbCr /// @ specialized where Color == JPEG.RGB /// Unpacks the data in this image into pixels of the given color target. - /// - _ : Color.Type + /// - _ : Color.Type /// The color target. /// - ->: [Color] /// A row-major array containing pixels of the image in the specified color space. @@ -4290,84 +4290,84 @@ extension JPEG.Data.Rectangular /// ## (0:rectangular-change-representation) @_specialize(where Color == JPEG.YCbCr, Format == JPEG.Common) @_specialize(where Color == JPEG.RGB, Format == JPEG.Common) - public - func unpack(as _:Color.Type) -> [Color] - where Color:JPEG.Color, Color.Format == Format + public + func unpack(as _:Color.Type) -> [Color] + where Color:JPEG.Color, Color.Format == Format { Color.unpack(self.values, of: self.layout.format) } } -// staged APIs -extension JPEG.Data.Spectral +// staged APIs +extension JPEG.Data.Spectral { - /// static func JPEG.Data.Spectral.decompress(stream:) - /// throws - /// where Source:JPEG.Bytestream.Source + /// static func JPEG.Data.Spectral.decompress(stream:) + /// throws + /// where Source:JPEG.Bytestream.Source /// Decompresses a spectral image from the given data source. - /// - stream : inout Source + /// - stream : inout Source /// A source bytestream. /// - -> : Self /// The decompressed image. /// # [See also](spectral-create-image) /// ## (1:spectral-create-image) - public static + public static func decompress(stream:inout Source) throws -> Self - where Source:JPEG.Bytestream.Source + where Source:JPEG.Bytestream.Source { return try JPEG.Context.decompress(stream: &stream) } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { - /// static func JPEG.Data.Planar.decompress(stream:) - /// throws - /// where Source:JPEG.Bytestream.Source + /// static func JPEG.Data.Planar.decompress(stream:) + /// throws + /// where Source:JPEG.Bytestream.Source /// Decompresses a planar image from the given data source. - /// - /// This function is a convenience function which calls [`Spectral.decompress(stream:)`] - /// to obtain a spectral image, and then calls [`(Spectral).idct()`] on the + /// + /// This function is a convenience function which calls [`Spectral.decompress(stream:)`] + /// to obtain a spectral image, and then calls [`(Spectral).idct()`] on the /// output to return a planar image. - /// - stream : inout Source + /// - stream : inout Source /// A source bytestream. /// - -> : Self /// The decompressed image. /// # [See also](planar-create-image) /// ## (2:planar-create-image) - public static + public static func decompress(stream:inout Source) throws -> Self - where Source:JPEG.Bytestream.Source + where Source:JPEG.Bytestream.Source { let spectral:JPEG.Data.Spectral = try .decompress(stream: &stream) return spectral.idct() } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { - /// static func JPEG.Data.Rectangular.decompress(stream:cosite:) - /// throws - /// where Source:JPEG.Bytestream.Source + /// static func JPEG.Data.Rectangular.decompress(stream:cosite:) + /// throws + /// where Source:JPEG.Bytestream.Source /// Decompresses a rectangular image from the given data source. - /// - /// This function is a convenience function which calls [`Planar.decompress(stream:)`] - /// to obtain a planar image, and then calls [`(Planar).interleaved(cosite:)`] + /// + /// This function is a convenience function which calls [`Planar.decompress(stream:)`] + /// to obtain a planar image, and then calls [`(Planar).interleaved(cosite:)`] /// on the output to return a rectangular image. - /// - stream : inout Source + /// - stream : inout Source /// A source bytestream. - /// - cosited : Swift.Bool - /// The upsampling method to use. Setting this parameter to `true` co-sites + /// - cosited : Swift.Bool + /// The upsampling method to use. Setting this parameter to `true` co-sites /// the samples; setting it to `false` centers them instead. - /// + /// /// The default value is `false`. /// - -> : Self /// The decompressed image. /// # [See also](rectangular-create-image) /// ## (3:rectangular-create-image) - public static + public static func decompress(stream:inout Source, cosite cosited:Bool = false) throws -> Self - where Source:JPEG.Bytestream.Source + where Source:JPEG.Bytestream.Source { - let planar:JPEG.Data.Planar = try .decompress(stream: &stream) + let planar:JPEG.Data.Planar = try .decompress(stream: &stream) return planar.interleaved(cosite: cosited) } } diff --git a/sources/jpeg/encode.swift b/Sources/JPEG/encode.swift similarity index 74% rename from sources/jpeg/encode.swift rename to Sources/JPEG/encode.swift index aac291c1..7a8d2cb0 100644 --- a/sources/jpeg/encode.swift +++ b/Sources/JPEG/encode.swift @@ -2,11 +2,11 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -extension JPEG.Marker +extension JPEG.Marker { - var code:UInt8 + var code:UInt8 { - switch self + switch self { case .frame(.baseline): return 0xc0 @@ -14,44 +14,44 @@ extension JPEG.Marker return 0xc1 case .frame(.progressive(coding: .huffman, differential: false)): return 0xc2 - + case .frame(.lossless (coding: .huffman, differential: false)): return 0xc3 - + case .huffman: return 0xc4 - + case .frame(.extended (coding: .huffman, differential: true)): return 0xc5 case .frame(.progressive(coding: .huffman, differential: true)): return 0xc6 case .frame(.lossless (coding: .huffman, differential: true)): return 0xc7 - + case .frame(.extended (coding: .arithmetic, differential: false)): return 0xc9 case .frame(.progressive(coding: .arithmetic, differential: false)): return 0xca case .frame(.lossless (coding: .arithmetic, differential: false)): return 0xcb - + case .arithmeticCodingCondition: return 0xcc - + case .frame(.extended (coding: .arithmetic, differential: true)): return 0xcd case .frame(.progressive(coding: .arithmetic, differential: true)): return 0xce case .frame(.lossless (coding: .arithmetic, differential: true)): return 0xcf - + case .restart(let n): return 0xd0 + .init(n & 0x07) - + case .start: return 0xd8 case .end: - return 0xd9 + return 0xd9 case .scan: return 0xda case .quantization: @@ -64,7 +64,7 @@ extension JPEG.Marker return 0xde case .expandReferenceComponents: return 0xdf - + case .application(let n): return 0xe0 + .init(n & 0x0f) case .comment: @@ -73,38 +73,38 @@ extension JPEG.Marker } } -// forward dct -extension JPEG.Data.Planar.Plane +// forward dct +extension JPEG.Data.Planar.Plane { - fileprivate - func load(x:Int, y:Int, limit:SIMD8) + fileprivate + func load(x:Int, y:Int, limit:SIMD8) -> JPEG.Data.Spectral.Plane.Block8x8 { @inline(__always) - func row(_ h:Int) -> SIMD8 + func row(_ h:Int) -> SIMD8 { pointwiseMin(limit, .init(.init( self[x: 8 * x + 0, y: 8 * y + h], self[x: 8 * x + 1, y: 8 * y + h], self[x: 8 * x + 2, y: 8 * y + h], self[x: 8 * x + 3, y: 8 * y + h], - + self[x: 8 * x + 4, y: 8 * y + h], self[x: 8 * x + 5, y: 8 * y + h], self[x: 8 * x + 6, y: 8 * y + h], self[x: 8 * x + 7, y: 8 * y + h]))) } - + return (row(0), row(1), row(2), row(3), row(4), row(5), row(6), row(7)) } } -extension JPEG.Data.Spectral.Plane +extension JPEG.Data.Spectral.Plane { - private static - var zigzag:Block8x8 + private static + var zigzag:Block8x8 { @inline(__always) - func row(_ h:Int) -> SIMD8 + func row(_ h:Int) -> SIMD8 { .init( JPEG.Table.Quantization.z(k: 0, h: h), @@ -119,106 +119,106 @@ extension JPEG.Data.Spectral.Plane } return (row(0), row(1), row(2), row(3), row(4), row(5), row(6), row(7)) } - private static + private static func fdct8(_ g:Block8x8, shift:Float) -> Block8x8 { - // even rows - let a:(SIMD8, SIMD8, SIMD8, SIMD8) = + // even rows + let a:(SIMD8, SIMD8, SIMD8, SIMD8) = ( g.0 + g.7, - g.1 + g.6, - g.2 + g.5, + g.1 + g.6, + g.2 + g.5, g.3 + g.4 ) - let b:(SIMD8, SIMD8, SIMD8, SIMD8) = + let b:(SIMD8, SIMD8, SIMD8, SIMD8) = ( - a.0 + a.3, - a.1 + a.2 , - a.1 - a.2 , + a.0 + a.3, + a.1 + a.2 , + a.1 - a.2 , a.0 - a.3 ) let c:SIMD8 = 0.707106781 * (b.2 + b.3) - let r:(SIMD8, SIMD8, SIMD8, SIMD8) = + let r:(SIMD8, SIMD8, SIMD8, SIMD8) = ( b.0 + b.1 - shift, - b.3 + c , - b.0 - b.1 , + b.3 + c , + b.0 - b.1 , b.3 - c ) - // odd rows - let d:(SIMD8, SIMD8, SIMD8, SIMD8) = + // odd rows + let d:(SIMD8, SIMD8, SIMD8, SIMD8) = ( g.3 - g.4, g.2 - g.5, g.1 - g.6, g.0 - g.7 ) - let f:(SIMD8, SIMD8, SIMD8) = + let f:(SIMD8, SIMD8, SIMD8) = ( - d.0 + d.1, - d.1 + d.2, + d.0 + d.1, + d.1 + d.2, d.2 + d.3 ) - let k:SIMD8 = 0.707106781 * f.1, + let k:SIMD8 = 0.707106781 * f.1, l:SIMD8 = 0.382683433 * (f.0 - f.2) - let m:(SIMD8, SIMD8) = + let m:(SIMD8, SIMD8) = ( - l + f.0 * 0.541196100, + l + f.0 * 0.541196100, l + f.2 * 1.306562965 ) - let n:(SIMD8, SIMD8) = + let n:(SIMD8, SIMD8) = ( - d.3 + k, + d.3 + k, d.3 - k ) - let s:(SIMD8, SIMD8, SIMD8, SIMD8) = + let s:(SIMD8, SIMD8, SIMD8, SIMD8) = ( - n.0 + m.1, - n.1 - m.0 , - n.1 + m.0 , + n.0 + m.1, + n.1 - m.0 , + n.1 + m.0 , n.0 - m.1 ) - return + return ( - r.0, s.0, - r.1, s.1, - r.2, s.2, + r.0, s.0, + r.1, s.1, + r.2, s.2, r.3, s.3 ) } - - private static + + private static func fdct8x8(_ g:Block8x8, shift:Float) -> Block8x8 { - let f:Block8x8 = Self.fdct8(Self.transpose(g), shift: shift), + let f:Block8x8 = Self.fdct8(Self.transpose(g), shift: shift), h:Block8x8 = Self.fdct8(Self.transpose(f), shift: 0) return h } - - mutating - func fdct(_ plane:JPEG.Data.Planar.Plane, - quanta table:JPEG.Table.Quantization, precision:Int) + + mutating + func fdct(_ plane:JPEG.Data.Planar.Plane, + quanta table:JPEG.Table.Quantization, precision:Int) { let count:Int = 64 * plane.units.x * plane.units.y let values:[Int16] = .init(unsafeUninitializedCapacity: count) { - var scale:Float + var scale:Float { 0x1p3 } let q:Block8x8 = Self.modulate(quanta: table, scale: scale) let z:Block8x8 = Self.zigzag - + let stride:Int = 64 * plane.units.x - // dont’s add 0.5 to the level shift, since the 0.5 is simply to - // emulate nearest-integer rounding - let level:Float = + // dont’s add 0.5 to the level shift, since the 0.5 is simply to + // emulate nearest-integer rounding + let level:Float = .init(sign: .plus, exponent: precision - 1, significand: 1) * scale - let limit:SIMD8 = .init(repeating: + let limit:SIMD8 = .init(repeating: .init(sign: .plus, exponent: precision , significand: 1)) - 1 for (x, y):(x:Int, y:Int) in (0, 0) ..< plane.units { - let g:Block8x8 = plane.load(x: x, y: y, limit: limit), + let g:Block8x8 = plane.load(x: x, y: y, limit: limit), h:Block8x8 = Self.fdct8x8(g, shift: level) let t: [(SIMD8, SIMD8)] = [ @@ -234,64 +234,64 @@ extension JPEG.Data.Spectral.Plane for (z, v):(SIMD8, SIMD8) in t { let w:SIMD8 = .init(v, rounding: .toNearestOrAwayFromZero) - for j:Int in 0 ..< 8 + for j:Int in 0 ..< 8 { $0[y * stride + 64 * x + z[j]] = w[j] } } } - + $1 = count } - + self.set(values: values, units: plane.units) } } extension JPEG { - /// enum JPEG.CompressionLevel + /// enum JPEG.CompressionLevel /// A basic image quality parameter. - /// - /// This is a toy API which generates acceptable defaults for a range of - /// quality settings. For finer-grained control, specify coefficient-wise + /// + /// This is a toy API which generates acceptable defaults for a range of + /// quality settings. For finer-grained control, specify coefficient-wise /// quantum values manually. /// ## (1:image-quality) - public - enum CompressionLevel + public + enum CompressionLevel { /// case JPEG.CompressionLevel.luminance(_:) /// A quality level for a luminance component. - /// - _ : Swift.Double - /// The quality parameter. A value of `0.0` represents the highest + /// - _ : Swift.Double + /// The quality parameter. A value of `0.0` represents the highest /// possible image quality. A value of `1.0` represents a “medium” /// compression level. This value can be greater than `1.0`. case luminance(Double) /// case JPEG.CompressionLevel.chrominance(_:) /// A quality level for a chrominance component. - /// - _ : Swift.Double - /// The quality parameter. A value of `0.0` represents the highest + /// - _ : Swift.Double + /// The quality parameter. A value of `0.0` represents the highest /// possible image quality. A value of `1.0` represents a “medium” /// compression level. This value can be greater than `1.0`. case chrominance(Double) } } -extension JPEG.CompressionLevel +extension JPEG.CompressionLevel { - // taken from the T-81 recommendations - + // taken from the T-81 recommendations + /// var JPEG.CompressionLevel.quanta : [Swift.UInt16] { get } - /// A 64-component array containing quantum values determined by this + /// A 64-component array containing quantum values determined by this /// quality parameter, in zigzag order. - public + public var quanta:[UInt16] { - let t:Double + let t:Double let keyframe:[UInt16] - switch self + switch self { case .luminance(let level): - t = level - keyframe = + t = level + keyframe = [ 16, 11, 10, 16, 124, 140, 151, 161, 12, 12, 14, 19, 126, 158, 160, 155, @@ -303,8 +303,8 @@ extension JPEG.CompressionLevel 72, 92, 95, 98, 112, 100, 103, 199, ] case .chrominance(let level): - t = level - keyframe = + t = level + keyframe = [ 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, @@ -316,223 +316,223 @@ extension JPEG.CompressionLevel 99, 99, 99, 99, 99, 99, 99, 99, ] } - - let interpolated:[UInt16] = keyframe.map + + let interpolated:[UInt16] = keyframe.map { .init(max(1.0, min((1.0 * (1 - t) + .init($0) * t).rounded(), 255.0))) } return .init(unsafeUninitializedCapacity: 64) { - for (k, h):(x:Int, y:Int) in (0, 0) ..< (8, 8) + for (k, h):(x:Int, y:Int) in (0, 0) ..< (8, 8) { $0[JPEG.Table.Quantization.z(k: k, h: h)] = interpolated[8 * h + k] } - + $1 = 64 } } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { /// func JPEG.Data.Planar.fdct(quanta:) - /// Converts this planar image into its spectral representation. - /// + /// Converts this planar image into its spectral representation. + /// /// This method is the inverse of [`Spectral.idct()`] /// - quanta: [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by this image’s [`layout`], - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by this image’s [`layout`], + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width /// determined by this image’s [`layout``(Layout).format``(JPEG.Format).precision`], /// and all the values must be in the correct range for that bit width. - /// - -> : JPEG.Data.Spectral + /// - -> : JPEG.Data.Spectral /// The output of a forward discrete cosine transform performed on this image. /// # [See also](planar-change-representation) /// ## (1:planar-change-representation) - public - func fdct(quanta:[JPEG.Table.Quantization.Key: [UInt16]]) + public + func fdct(quanta:[JPEG.Table.Quantization.Key: [UInt16]]) -> JPEG.Data.Spectral { let precision:Int = self.layout.format.precision var spectral:JPEG.Data.Spectral = .init(layout: self.layout) spectral.set(quanta: quanta) - for (p, plane):(Int, JPEG.Data.Planar.Plane) in + for (p, plane):(Int, JPEG.Data.Planar.Plane) in zip(spectral.indices, self) { - spectral[p].fdct(plane, quanta: spectral.quanta[spectral[p].q], + spectral[p].fdct(plane, quanta: spectral.quanta[spectral[p].q], precision: precision) } spectral.set(width: self.size.x) spectral.set(height: self.size.y) spectral.metadata.append(contentsOf: self.metadata) - + return spectral } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { /// func JPEG.Data.Rectangular.decomposed() - /// Converts this rectangular image into its planar representation. - /// - /// This method uses a basic box-filter to perform downsampling. A box-filter - /// is a relatively poor low-pass filter, so it may be worthwhile to - /// perform component resampling manually and construct a planar image + /// Converts this rectangular image into its planar representation. + /// + /// This method uses a basic box-filter to perform downsampling. A box-filter + /// is a relatively poor low-pass filter, so it may be worthwhile to + /// perform component resampling manually and construct a planar image /// directly using [`(Planar).init(size:layout:metadata:initializingWith:)`]. - /// + /// /// This method is the inverse of [`Planar.interleaved(cosite:)`]. - /// - -> : JPEG.Data.Planar - /// A planar image created by resampling all components in the input + /// - -> : JPEG.Data.Planar + /// A planar image created by resampling all components in the input /// according to their sampling factors in the image [`layout`]. /// # [See also](rectangular-change-representation) /// ## (1:rectangular-change-representation) - public + public func decomposed() -> JPEG.Data.Planar { .init(size: self.size, layout: self.layout, metadata: self.metadata) { ( - p:Int, - units:(x:Int, y:Int), - factor:(x:Int, y:Int), + p:Int, + units:(x:Int, y:Int), + factor:(x:Int, y:Int), buffer:UnsafeMutableBufferPointer - ) in - - // this is a terrible low-pass filter, but it’s the best we can come + ) in + + // this is a terrible low-pass filter, but it’s the best we can come // up with without making this v complicated let scale:(x:Int, y:Int) = self.layout.scale let response:(x:Int, y:Int) = (scale.x / factor.x, scale.y / factor.y) let magnitude:Float = .init(response.x * response.y) for (x, y):(x:Int, y:Int) in (0, 0) ..< (8 * units.x, 8 * units.y) { - let base:(x:Int, y:Int) = + let base:(x:Int, y:Int) = ( x * scale.x / factor.x, y * scale.y / factor.y ) let sum:Int = (base ..< (base.x + response.x, base.y + response.y)).reduce(0) { - let i:(x:Int, y:Int) = + let i:(x:Int, y:Int) = ( - Swift.min($1.x, self.size.x - 1), + Swift.min($1.x, self.size.x - 1), Swift.min($1.y, self.size.y - 1) ) return $0 + .init(self.values[(self.size.x * i.y + i.x) * self.stride + p]) } - + buffer[8 * units.x * y + x] = .init(.init(sum) / magnitude) } } } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { /// static func JPEG.Data.Rectangular.pack(size:layout:metadata:pixels:) - /// where Color:JPEG.Color, Color.Format == Format + /// where Color:JPEG.Color, Color.Format == Format /// @ specialized where Color == JPEG.YCbCr /// @ specialized where Color == JPEG.RGB - /// Packs the given row-major pixels into rectangular image data and creates + /// Packs the given row-major pixels into rectangular image data and creates /// a rectangular image with the given image parameters and layout. - /// - /// Passing an invalid `size`, or a pixel array of the wrong `count` will + /// + /// Passing an invalid `size`, or a pixel array of the wrong `count` will /// result in a precondition failure. - /// + /// /// This function is the inverse of [`unpack(as:)`]. /// - size : (x:Swift.Int, y:Swift.Int) /// The size of the image, in pixels. Both dimensions must be positive. - /// - layout : JPEG.Layout + /// - layout : JPEG.Layout /// The layout of the image. /// - metadata : [JPEG.Metadata] /// The metadata records in the image. /// - pixels : [Swift.UInt16] - /// An array of pixels, in row major order, and without + /// An array of pixels, in row major order, and without /// padding. The array must have exactly [`size`x`]\ ×\ [`size`y`] pixels. - /// - -> : Self + /// - -> : Self /// A rectangular image. /// # [See also](rectangular-create-image) /// ## (2:rectangular-create-image) @_specialize(where Color == JPEG.YCbCr, Format == JPEG.Common) @_specialize(where Color == JPEG.RGB, Format == JPEG.Common) - public static - func pack(size:(x:Int, y:Int), - layout:JPEG.Layout, - metadata:[JPEG.Metadata], - pixels:[Color]) -> Self - where Color:JPEG.Color, Color.Format == Format + public static + func pack(size:(x:Int, y:Int), + layout:JPEG.Layout, + metadata:[JPEG.Metadata], + pixels:[Color]) -> Self + where Color:JPEG.Color, Color.Format == Format { - .init(size: size, layout: layout, metadata: metadata, + .init(size: size, layout: layout, metadata: metadata, values: Color.pack(pixels, as: layout.format)) } } -// strict constructors -extension JPEG.JFIF +// strict constructors +extension JPEG.JFIF { // due to a compiler issue, this initializer has to live in `decode.swift` } -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { - // this property is not to be used within the library, it is used for encoding + // this property is not to be used within the library, it is used for encoding // to obtain a valid frame header from a `Spectral` struct - + /// func JPEG.Data.Spectral.encode() /// Creates a frame header for this image. - /// - /// The encoded frame header contains only the recognized components in - /// this image. It encodes the image height eagerly (as opposed to lazily, + /// + /// The encoded frame header contains only the recognized components in + /// this image. It encodes the image height eagerly (as opposed to lazily, /// with a [`(JPEG.Header).HeightRedefinition`] header). - /// - -> : JPEG.Header.Frame + /// - -> : JPEG.Header.Frame /// The encoded frame header. /// # [See also](spectral-change-representation) /// ## (1:spectral-change-representation) - public - func encode() -> JPEG.Header.Frame + public + func encode() -> JPEG.Header.Frame { - do + do { // strip the resident components return try .validate( - process: self.layout.process, - precision: self.layout.format.precision, - size: self.size, - components: .init(uniqueKeysWithValues: zip(self.layout.recognized, + process: self.layout.process, + precision: self.layout.format.precision, + size: self.size, + components: .init(uniqueKeysWithValues: zip(self.layout.recognized, self.layout.planes[self.layout.recognized.indices].map(\.component)))) } - catch + catch { - // there are only a few ways validation can fail, and all of them + // there are only a few ways validation can fail, and all of them // are programmer errors (zero width, broken `Format` implementation) preconditionFailure((error as? JPEG.Error)?.message ?? "\(error)") } } } -extension JPEG.Layout +extension JPEG.Layout { // note: all components referenced by the scan headers in `self.scans` // must be recognized components. - + /// var JPEG.Layout.scans : [JPEG.Header.Scan] { get } /// The scan decomposition of this image layout, filtered to include only - /// recognized components. - /// - /// This property is derived from the this layout’s [`definitions`]. - /// Scans containing only non-recognized components are omitted from this - /// array. - public - var scans:[JPEG.Header.Scan] + /// recognized components. + /// + /// This property is derived from the this layout’s [`definitions`]. + /// Scans containing only non-recognized components are omitted from this + /// array. + public + var scans:[JPEG.Header.Scan] { let recognized:Set = .init(self.recognized) return self.definitions.flatMap { - $0.scans.compactMap + $0.scans.compactMap { - let components:[JPEG.Scan.Component] = $0.components.compactMap - { - recognized.contains($0.component.ci) ? $0.component : nil + let components:[JPEG.Scan.Component] = $0.components.compactMap + { + recognized.contains($0.component.ci) ? $0.component : nil } - guard !components.isEmpty - else + guard !components.isEmpty + else { - return nil + return nil } return .init(band: $0.band, bits: $0.bits, components: components) } @@ -540,49 +540,49 @@ extension JPEG.Layout } } -extension JPEG.Table.Huffman +extension JPEG.Table.Huffman { - // indirect enum would entail too much copying - final + // indirect enum would entail too much copying + final class Subtree { - enum Node + enum Node { case leaf(Element) case interior(left:Subtree, right:Subtree) } - + let node:Node - - init(_ node:Node) + + init(_ node:Node) { - self.node = node + self.node = node } } } -extension JPEG.Table.Huffman.Subtree +extension JPEG.Table.Huffman.Subtree { - private - var children:[JPEG.Table.Huffman.Subtree] + private + var children:[JPEG.Table.Huffman.Subtree] { - switch self.node + switch self.node { case .leaf: - return [] + return [] case .interior(left: let left, right: let right): return [left, right] } } - func levels() -> [Int] + func levels() -> [Int] { var levels:[Int] = [] var queue:[JPEG.Table.Huffman.Subtree] = [self] - while !queue.isEmpty + while !queue.isEmpty { - var leaves:Int = 0 - for subtree:JPEG.Table.Huffman.Subtree in queue + var leaves:Int = 0 + for subtree:JPEG.Table.Huffman.Subtree in queue { - if case .leaf = subtree.node + if case .leaf = subtree.node { leaves += 1 } @@ -590,239 +590,239 @@ extension JPEG.Table.Huffman.Subtree levels.append(leaves) queue = queue.flatMap(\.children) } - - return levels + + return levels } } -extension JPEG.Table.Huffman +extension JPEG.Table.Huffman { - // limit the height of the generated tree to the given height, and also - // removes the slot corresponding to the all-ones code at the end - private static + // limit the height of the generated tree to the given height, and also + // removes the slot corresponding to the all-ones code at the end + private static func limit(height:Int, of uncompacted:ArraySlice) -> [Int] { var levels:[Int] = .init(uncompacted) guard levels.count > height - else + else { - // remove the all-ones code + // remove the all-ones code levels[levels.endIndex - 1] -= 1 - return levels + return levels } - - // collect unhoused nodes: from the bottom to level 17, we gather up - // node pairs (since huffman trees are always full trees). one of the - // child nodes gets promoted to the level above, the other node goes - // into a pool of unhoused nodes - var unhoused:Int = 0 - for l:Int in (height ..< levels.endIndex).reversed() + + // collect unhoused nodes: from the bottom to level 17, we gather up + // node pairs (since huffman trees are always full trees). one of the + // child nodes gets promoted to the level above, the other node goes + // into a pool of unhoused nodes + var unhoused:Int = 0 + for l:Int in (height ..< levels.endIndex).reversed() { assert(levels[l] & 1 == 0) - + let pairs:Int = levels[l] >> 1 - unhoused += pairs - levels[l - 1] += pairs + unhoused += pairs + levels[l - 1] += pairs } levels.removeLast(levels.count - height) - - // for the remaining unhoused nodes, our strategy is to look for a level - // at least 1 step above the bottom (meaning, indices 0 ..< 15) and split - // one of its leaves, reducing the leaf count of that level by 1, and + + // for the remaining unhoused nodes, our strategy is to look for a level + // at least 1 step above the bottom (meaning, indices 0 ..< 15) and split + // one of its leaves, reducing the leaf count of that level by 1, and // increasing the leaf count of the level below it by 2 var split:Int = height - 2 - while unhoused > 0 + while unhoused > 0 { - guard levels[split] > 0 - else + guard levels[split] > 0 + else { split -= 1 - // traversal pattern should make it impossible to go below 0 so - // long as total leaf population is less than 2^16 (it can never + // traversal pattern should make it impossible to go below 0 so + // long as total leaf population is less than 2^16 (it can never // be greater than 257 anyway) assert(split > 0) - continue + continue } - + let resettled:Int = min(levels[split], unhoused) - unhoused -= resettled - levels[split] -= resettled - levels[split + 1] += 2 * resettled - - if split < height - 2 + unhoused -= resettled + levels[split] -= resettled + levels[split + 1] += 2 * resettled + + if split < height - 2 { // since we have added new leaves to this level split += 1 - } + } } - - // remove the all-ones code + + // remove the all-ones code levels[height - 1] -= 1 return levels } - - private static + + private static func assign(_ symbols:Int, levels:[Int]) -> [Encoder.Codeword] { var codewords:[Encoder.Codeword] = [] var counter:UInt16 = 0 - for (length, leaves):(Int, Int) in zip(1 ... 16, levels) + for (length, leaves):(Int, Int) in zip(1 ... 16, levels) { - for _ in 0 ..< leaves + for _ in 0 ..< leaves { codewords.append(.init(bits: counter, length: length)) counter += 1 } - + counter <<= 1 } - + return codewords } - - // `frequencies` must always contain 256 entries + + // `frequencies` must always contain 256 entries /// init JPEG.Table.Huffman.init(frequencies:target:) - /// Creates a huffman table containing a near-optimal huffman tree from + /// Creates a huffman table containing a near-optimal huffman tree from /// the given symbol frequencies and table selector. - /// + /// /// This initializer uses the standard huffman tree construction algorithm - /// to determine optimal codeword assignments. These assignments are modified + /// to determine optimal codeword assignments. These assignments are modified /// slightly to fit codeword length constraints imposed by the JPEG specification. /// - frequencies : [Swift.Int] - /// An array of symbol frequencies. This array must contain exactly 256 - /// elements, corresponding to the 256 possible 8-bit symbols. The *i*th - /// array element specifies the frequency of the symbol with the - /// [`(Bitstream.AnySymbol).value`] *i*. - /// - /// At least one symbol must have a non-zero frequency. Passing an invalid + /// An array of symbol frequencies. This array must contain exactly 256 + /// elements, corresponding to the 256 possible 8-bit symbols. The *i*th + /// array element specifies the frequency of the symbol with the + /// [`(Bitstream.AnySymbol).value`] *i*. + /// + /// At least one symbol must have a non-zero frequency. Passing an invalid /// frequency array will result in a precondition failure. - /// - target : Selector - /// The selector target for the created huffman table. - public - init(frequencies:[Int], target:Selector) + /// - target : Selector + /// The selector target for the created huffman table. + public + init(frequencies:[Int], target:Selector) { - precondition(frequencies.count == 256, + precondition(frequencies.count == 256, "frequency array must have exactly 256 elements") - precondition(!frequencies.allSatisfy{ $0 <= 0 }, + precondition(!frequencies.allSatisfy{ $0 <= 0 }, "at least one symbol must have non-zero frequency") - + // sort non-zero symbols by (decreasing) frequency // this is nlog(n), but so is the heap stuff later on - let sorted:[(frequency:Int, symbol:Symbol)] = (UInt8.min ... UInt8.max).compactMap + let sorted:[(frequency:Int, symbol:Symbol)] = (UInt8.min ... UInt8.max).compactMap { - (value:UInt8) -> (Int, Symbol)? in - + (value:UInt8) -> (Int, Symbol)? in + let frequency:Int = frequencies[.init(value)] - guard frequency > 0 - else + guard frequency > 0 + else { - return nil + return nil } - + return (frequency, .init(value)) }.sorted { $0.frequency > $1.frequency } - - // reversing (to get canonically sorted array) gets the heapify below - // to its best-case O(n) time, not that O matters for n = 256 - var heap:General.Heap> = .init(sorted.reversed().map + + // reversing (to get canonically sorted array) gets the heapify below + // to its best-case O(n) time, not that O matters for n = 256 + var heap:General.Heap> = .init(sorted.reversed().map { ($0.frequency, .init(.leaf(()))) }) - // insert dummy value with frequency 0 to occupy the all-ones codeword + // insert dummy value with frequency 0 to occupy the all-ones codeword heap.enqueue(key: 0, value: .init(.leaf(()))) - + // standard huffman tree construction algorithm - while let first:(key:Int, value:Subtree) = heap.dequeue() + while let first:(key:Int, value:Subtree) = heap.dequeue() { - guard let second:(key:Int, value:Subtree) = heap.dequeue() - else + guard let second:(key:Int, value:Subtree) = heap.dequeue() + else { - // drop the first level, since it corresponds to the tree root + // drop the first level, since it corresponds to the tree root let levels:ArraySlice = first.value.levels().dropFirst() assert(!levels.isEmpty) - - // convert level counts to codeword assignments + + // convert level counts to codeword assignments let limited:[Int] = Self.limit(height: 16, of: levels) - - // split symbols list into levels - var base:Int = 0, + + // split symbols list into levels + var base:Int = 0, symbols:[[Symbol]] = [] symbols.reserveCapacity(limited.count) - for leaves:Int in limited + for leaves:Int in limited { symbols.append(sorted[base ..< base + leaves].map(\.symbol)) - base += leaves + base += leaves } // symbols array must have length exactly equal to 16 symbols.append(contentsOf: repeatElement([], count: 16 - symbols.count)) - + self.init(validated: symbols, target: target) - return + return } - + let merged:Subtree = .init(.interior(left: first.value, right: second.value)) - let weight:Int = first.key + second.key - + let weight:Int = first.key + second.key + heap.enqueue(key: weight, value: merged) } - + fatalError("unreachable") } } -// inverse huffman tables -extension JPEG.Table.Huffman +// inverse huffman tables +extension JPEG.Table.Huffman { struct Encoder { - struct Codeword + struct Codeword { // the inhabited bits are in the most significant end of the `UInt16` let bits:UInt16 - @General.Storage - var length:Int + @General.Storage + var length:Int } - - private + + private let storage:[Codeword] - - init(_ storage:[Codeword]) + + init(_ storage:[Codeword]) { - self.storage = storage + self.storage = storage } } } -extension JPEG.Table.Huffman +extension JPEG.Table.Huffman { - func encoder() -> Encoder + func encoder() -> Encoder { - var storage:[Encoder.Codeword] = + var storage:[Encoder.Codeword] = .init(repeating: .init(bits: 0, length: 0), count: 256) - - let levels:[Int] = self.symbols.map(\.count), + + let levels:[Int] = self.symbols.map(\.count), count:Int = levels.reduce(0, +) let codewords:[Encoder.Codeword] = Self.assign(count, levels: levels) - + var base:Int = 0 - for symbols:[Symbol] in self.symbols + for symbols:[Symbol] in self.symbols { for (i, symbol):(Int, Symbol) in zip(base ..< base + symbols.count, symbols) { storage[.init(symbol.value)] = codewords[i] } - - base += symbols.count + + base += symbols.count } - + return .init(storage) } } -// table accessors -extension JPEG.Table.Huffman.Encoder +// table accessors +extension JPEG.Table.Huffman.Encoder { - subscript(symbol:Symbol) -> Codeword + subscript(symbol:Symbol) -> Codeword { self.storage[.init(symbol.value)] } @@ -832,15 +832,15 @@ extension JPEG.Table.Huffman.Encoder // encoders (opposite of decoders) extension JPEG.Bitstream.Symbol.DC { - init(binade:Int) + init(binade:Int) { assert(0 ..< 16 ~= binade) self.value = .init(binade) } } -extension JPEG.Bitstream.Symbol.AC +extension JPEG.Bitstream.Symbol.AC { - init(zeroes:Int, binade:Int) + init(zeroes:Int, binade:Int) { assert(0 ..< 16 ~= zeroes) assert(0 ..< 16 ~= binade) @@ -860,111 +860,111 @@ extension JPEG.Bitstream.Composite.AC { var decomposed:(symbol:JPEG.Bitstream.Symbol.AC, tail:UInt16, length:Int) { - switch self + switch self { case .run(let zeroes, value: let value): let (binade, tail):(Int, UInt16) = JPEG.Bitstream.compact(value) let symbol:JPEG.Bitstream.Symbol.AC = .init(zeroes: zeroes, binade: binade) return (symbol, tail, binade) - + case .eob(let run): assert(run > 0) let binade:Int = Int.bitWidth - run.leadingZeroBitCount - 1 let tail:UInt16 = .init(~(1 &<< binade) & run) - + let symbol:JPEG.Bitstream.Symbol.AC = .init(zeroes: binade, binade: 0) return (symbol, tail, binade) } } } -extension JPEG.Bitstream -{ - mutating - func append(composite:Composite.DC, table:JPEG.Table.HuffmanDC.Encoder) +extension JPEG.Bitstream +{ + mutating + func append(composite:Composite.DC, table:JPEG.Table.HuffmanDC.Encoder) { - let (symbol, tail, length):(JPEG.Bitstream.Symbol.DC, UInt16, Int) = - composite.decomposed - + let (symbol, tail, length):(JPEG.Bitstream.Symbol.DC, UInt16, Int) = + composite.decomposed + let codeword:JPEG.Table.HuffmanDC.Encoder.Codeword = table[symbol] self.append(codeword.bits, count: codeword.length) self.append(tail, count: length) - } - mutating - func append(composite:Composite.AC, table:JPEG.Table.HuffmanAC.Encoder) + } + mutating + func append(composite:Composite.AC, table:JPEG.Table.HuffmanAC.Encoder) { - let (symbol, tail, length):(JPEG.Bitstream.Symbol.AC, UInt16, Int) = - composite.decomposed - + let (symbol, tail, length):(JPEG.Bitstream.Symbol.AC, UInt16, Int) = + composite.decomposed + let codeword:JPEG.Table.HuffmanAC.Encoder.Codeword = table[symbol] self.append(codeword.bits, count: codeword.length) self.append(tail, count: length) - } + } } extension JPEG.Bitstream.AnySymbol { - static + static func frequencies(of path:KeyPath, in sequence:S) -> [Int] where S:Sequence { var frequencies:[Int] = .init(repeating: 0, count: 256) - for element:S.Element in sequence + for element:S.Element in sequence { frequencies[.init(element[keyPath: path].value)] += 1 } return frequencies } } -extension JPEG.Data.Spectral.Plane +extension JPEG.Data.Spectral.Plane { func encode(x:Int, y:Int, predecessor:inout Int16) -> (JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC]) { // dc coefficient - let coefficient:Int16 = self[x: x, y: y, z: 0] - let composite:JPEG.Bitstream.Composite.DC = + let coefficient:Int16 = self[x: x, y: y, z: 0] + let composite:JPEG.Bitstream.Composite.DC = .init(difference: coefficient &- predecessor) - - predecessor = coefficient + + predecessor = coefficient // ac coefficients var composites:[JPEG.Bitstream.Composite.AC] = [] var zeroes = 0 for z:Int in 1 ..< 64 { let coefficient:Int16 = self[x: x, y: y, z: z] - if coefficient == 0 + if coefficient == 0 { - if zeroes == 15 + if zeroes == 15 { composites.append(.run(zeroes, value: 0)) - zeroes = 0 + zeroes = 0 } - else + else { - zeroes += 1 + zeroes += 1 } } - else + else { composites.append(.run(zeroes, value: coefficient)) zeroes = 0 } } - - if zeroes > 0 + + if zeroes > 0 { composites.append(.eob(1)) } - + return (composite, composites) } - - // sequential mode - func encode(component:JPEG.Scan.Component) + + // sequential mode + func encode(component:JPEG.Scan.Component) -> ([UInt8], JPEG.Table.HuffmanDC, JPEG.Table.HuffmanAC) { let count:Int = self.units.x * self.units.y - let composites:[(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC])] = - .init(unsafeUninitializedCapacity: count) + let composites:[(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC])] = + .init(unsafeUninitializedCapacity: count) { var predecessor:Int16 = 0 for (x, y):(Int, Int) in self.indices @@ -973,96 +973,96 @@ extension JPEG.Data.Spectral.Plane ($0.baseAddress! + y * self.units.x + x).initialize(to: self.encode(x: x, y: y, predecessor: &predecessor)) } - - $1 = count + + $1 = count } - + let frequencies:(dc:[Int], ac:[Int]) = ( - JPEG.Bitstream.Symbol.DC.frequencies(of: \.0.decomposed.symbol, + JPEG.Bitstream.Symbol.DC.frequencies(of: \.0.decomposed.symbol, in: composites), - JPEG.Bitstream.Symbol.AC.frequencies(of: \.decomposed.symbol, + JPEG.Bitstream.Symbol.AC.frequencies(of: \.decomposed.symbol, in: composites.flatMap(\.1)) ) - + let table:(dc:JPEG.Table.HuffmanDC, ac:JPEG.Table.HuffmanAC) = ( .init(frequencies: frequencies.dc, target: component.selector.dc), - .init(frequencies: frequencies.ac, target: component.selector.ac) + .init(frequencies: frequencies.ac, target: component.selector.ac) ) - let encoder:(dc:JPEG.Table.HuffmanDC.Encoder, ac:JPEG.Table.HuffmanAC.Encoder) = + let encoder:(dc:JPEG.Table.HuffmanDC.Encoder, ac:JPEG.Table.HuffmanAC.Encoder) = ( table.dc.encoder(), table.ac.encoder() ) - + var bits:JPEG.Bitstream = [] - for (composite, ac):(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC]) in - composites + for (composite, ac):(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC]) in + composites { bits.append(composite: composite, table: encoder.dc) - for composite:JPEG.Bitstream.Composite.AC in ac + for composite:JPEG.Bitstream.Composite.AC in ac { bits.append(composite: composite, table: encoder.ac) } } return (bits.bytes(escaping: 0xff, with: (0xff, 0x00)), table.dc, table.ac) } - + // progressive - func encode(bits a:PartialRangeFrom, component:JPEG.Scan.Component) + func encode(bits a:PartialRangeFrom, component:JPEG.Scan.Component) -> ([UInt8], JPEG.Table.HuffmanDC) { let count:Int = self.units.x * self.units.y - let composites:[JPEG.Bitstream.Composite.DC] = - .init(unsafeUninitializedCapacity: count) + let composites:[JPEG.Bitstream.Composite.DC] = + .init(unsafeUninitializedCapacity: count) { var predecessor:Int16 = 0 for (x, y):(Int, Int) in self.indices { let high:Int16 = self[x: x, y: y, z: 0] >> a.lowerBound $0[y * self.units.x + x] = .init(difference: high &- predecessor) - predecessor = high + predecessor = high } - - $1 = count + + $1 = count } - - let frequencies:[Int] = + + let frequencies:[Int] = JPEG.Bitstream.Symbol.DC.frequencies(of: \.decomposed.symbol, in: composites) - - let table:JPEG.Table.HuffmanDC = - .init(frequencies: frequencies, target: component.selector.dc) + + let table:JPEG.Table.HuffmanDC = + .init(frequencies: frequencies, target: component.selector.dc) let encoder:JPEG.Table.HuffmanDC.Encoder = table.encoder() - + var bits:JPEG.Bitstream = [] - for composite:JPEG.Bitstream.Composite.DC in composites + for composite:JPEG.Bitstream.Composite.DC in composites { bits.append(composite: composite, table: encoder) } return (bits.bytes(escaping: 0xff, with: (0xff, 0x00)), table) } - - func encode(bit a:Int) + + func encode(bit a:Int) -> [UInt8] { var bits:JPEG.Bitstream = [] for y:Int in 0 ..< self.units.y { - for x:Int in 0 ..< self.units.x + for x:Int in 0 ..< self.units.x { bits.append(bit: self[x: x, y: y, z: 0] >> a & 1) } } return bits.bytes(escaping: 0xff, with: (0xff, 0x00)) - } - - func encode(band:Range, bits a:PartialRangeFrom, component:JPEG.Scan.Component) + } + + func encode(band:Range, bits a:PartialRangeFrom, component:JPEG.Scan.Component) -> ([UInt8], JPEG.Table.HuffmanAC) { assert(band.lowerBound > 0) assert(band.upperBound <= 64) - + var composites:[JPEG.Bitstream.Composite.AC] = [] for (x, y):(Int, Int) in self.indices { @@ -1071,89 +1071,89 @@ extension JPEG.Data.Spectral.Plane { let coefficient:Int16 = self[x: x, y: y, z: z] // TODO: overflow probably possible here - let sign:Int16 = coefficient < 0 ? -1 : 1, + let sign:Int16 = coefficient < 0 ? -1 : 1, magnitude:Int16 = abs(coefficient) - let high:Int16 = sign * magnitude >> a.lowerBound - if high == 0 + let high:Int16 = sign * magnitude >> a.lowerBound + if high == 0 { - zeroes += 1 + zeroes += 1 } - else + else { - composites.append(contentsOf: + composites.append(contentsOf: repeatElement(.run( 15, value: 0), count: zeroes / 16)) composites.append(.run(zeroes % 16, value: high)) zeroes = 0 } } - - if zeroes > 0 + + if zeroes > 0 { if case .eob(let count)? = composites.last, count < 4096 { composites[composites.endIndex - 1] = .eob(count + 1) } - else + else { composites.append(.eob(1)) } } } - - let frequencies:[Int] = + + let frequencies:[Int] = JPEG.Bitstream.Symbol.AC.frequencies(of: \.decomposed.symbol, in: composites) - - let table:JPEG.Table.HuffmanAC = + + let table:JPEG.Table.HuffmanAC = .init(frequencies: frequencies, target: component.selector.ac) let encoder:JPEG.Table.HuffmanAC.Encoder = table.encoder() - + var bits:JPEG.Bitstream = [] - for composite:JPEG.Bitstream.Composite.AC in composites + for composite:JPEG.Bitstream.Composite.AC in composites { bits.append(composite: composite, table: encoder) } - + return (bits.bytes(escaping: 0xff, with: (0xff, 0x00)), table) } - - func encode(band:Range, bit a:Int, component:JPEG.Scan.Component) + + func encode(band:Range, bit a:Int, component:JPEG.Scan.Component) -> ([UInt8], JPEG.Table.HuffmanAC) { assert(band.lowerBound > 0) assert(band.upperBound <= 64) - + let mask:Int16 = .init(bitPattern: UInt16.max << (a + 1)) var pairs:[(JPEG.Bitstream.Composite.AC, [Bool])] = [] for (x, y):(Int, Int) in self.indices { var zeroes = 0 - var refinements:[[Bool]] = [], + var refinements:[[Bool]] = [], staged:[Bool] = [] for z:Int in band { let coefficient:Int16 = self[x: x, y: y, z: z] - + // TODO: overflow probably possible here - let sign:Int16 = coefficient < 0 ? -1 : 1, + let sign:Int16 = coefficient < 0 ? -1 : 1, magnitude:Int16 = abs(coefficient) - let product:Int16 = magnitude & mask, + let product:Int16 = magnitude & mask, remainder:Int16 = magnitude & ~mask let low:Int16 = sign * remainder >> a - - if product == 0 + + if product == 0 { - if low == 0 + if low == 0 { zeroes += 1 - if zeroes % 16 == 0 + if zeroes % 16 == 0 { refinements.append(staged) staged = [] } } - else + else { - pairs.append(contentsOf: refinements.map + pairs.append(contentsOf: refinements.map { ( .run( 15, value: 0), $0) }) @@ -1161,42 +1161,42 @@ extension JPEG.Data.Spectral.Plane refinements = [] staged = [] zeroes = 0 - } + } } - else + else { staged.append(low != 0) } } - + refinements.append(staged) let aggregated:[Bool] = refinements.flatMap{ $0 } - if zeroes > 0 || !aggregated.isEmpty + if zeroes > 0 || !aggregated.isEmpty { if case .eob(let count)? = pairs.last?.0, count < 4096 { pairs[pairs.endIndex - 1].0 = .eob(count + 1) pairs[pairs.endIndex - 1].1.append(contentsOf: aggregated) } - else + else { pairs.append((.eob(1), aggregated)) } } } - - let frequencies:[Int] = + + let frequencies:[Int] = JPEG.Bitstream.Symbol.AC.frequencies(of: \.0.decomposed.symbol, in: pairs) - - let table:JPEG.Table.HuffmanAC = + + let table:JPEG.Table.HuffmanAC = .init(frequencies: frequencies, target: component.selector.ac) let encoder:JPEG.Table.HuffmanAC.Encoder = table.encoder() - + var bits:JPEG.Bitstream = [] - for (composite, refinements):(JPEG.Bitstream.Composite.AC, [Bool]) in pairs + for (composite, refinements):(JPEG.Bitstream.Composite.AC, [Bool]) in pairs { bits.append(composite: composite, table: encoder) - for refinement:Bool in refinements + for refinement:Bool in refinements { bits.append(bit: refinement ? 1 : 0) } @@ -1204,300 +1204,300 @@ extension JPEG.Data.Spectral.Plane return (bits.bytes(escaping: 0xff, with: (0xff, 0x00)), table) } } -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { - // sequential mode - private - func encode(components:[(c:Int, component:JPEG.Scan.Component)]) + // sequential mode + private + func encode(components:[(c:Int, component:JPEG.Scan.Component)]) -> ([UInt8], [JPEG.Table.HuffmanDC], [JPEG.Table.HuffmanAC]) { - guard components.count > 1 - else + guard components.count > 1 + else { // noninterleaved precondition(components.count == 1, "components array cannot be empty") let (p, component):(Int, JPEG.Scan.Component) = components[0] guard self.indices ~= p - else + else { preconditionFailure("scan component not a member of this spectral image") } - - let (bytes, dc, ac):([UInt8], JPEG.Table.HuffmanDC, JPEG.Table.HuffmanAC) = + + let (bytes, dc, ac):([UInt8], JPEG.Table.HuffmanDC, JPEG.Table.HuffmanAC) = self[p].encode(component: component) return (bytes, [dc], [ac]) } - - let factors:[(x:Int, y:Int)] = components.map + + let factors:[(x:Int, y:Int)] = components.map { guard self.indices ~= $0.c - else + else { - // unlike in the decoder, we don’t have a good reason to allow scans to - // reference components which have not been included in the spectral image, + // unlike in the decoder, we don’t have a good reason to allow scans to + // reference components which have not been included in the spectral image, // so every component must be linked to an existing plane index (non-optional `p`) preconditionFailure("scan component not a member of this spectral image") } return self.layout.planes[$0.c].component.factor } - - let stride:Int = factors.reduce(0){ $0 + $1.x * $1.y } - // some components may specify the same table selectors, which means + + let stride:Int = factors.reduce(0){ $0 + $1.x * $1.y } + // some components may specify the same table selectors, which means // those components are sharing the same huffman table. var globals: ( - dc:[JPEG.Table.HuffmanDC.Selector: [Int]], + dc:[JPEG.Table.HuffmanDC.Selector: [Int]], ac:[JPEG.Table.HuffmanAC.Selector: [Int]] - ) = + ) = ([:], [:]) - + let count:Int = self.blocks.x * self.blocks.y * stride - let composites:[(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC])] = + let composites:[(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC])] = .init(unsafeUninitializedCapacity: count) { var offset:Int = 0 - for ((p, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in - zip(components, factors) + for ((p, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in + zip(components, factors) { - // to avoid doing tons of dictionary lookups, maintain a local - // frequency count, and then merge it into the dictionary one - var frequencies:(dc:[Int], ac:[Int]) = + // to avoid doing tons of dictionary lookups, maintain a local + // frequency count, and then merge it into the dictionary one + var frequencies:(dc:[Int], ac:[Int]) = ( .init(repeating: 0, count: 256), .init(repeating: 0, count: 256) ) var predecessor:Int16 = 0 - for (mx, my):(x:Int, y:Int) in (0, 0) ..< self.blocks + for (mx, my):(x:Int, y:Int) in (0, 0) ..< self.blocks { - let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), - end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) + let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), + end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) for (i, (x, y)):(offset:Int, element:(x:Int, y:Int)) in (start ..< end).enumerated() { - let composite:JPEG.Bitstream.Composite.DC, + let composite:JPEG.Bitstream.Composite.DC, ac:[JPEG.Bitstream.Composite.AC] - + (composite, ac) = self[p].encode(x: x, y: y, predecessor: &predecessor) - + frequencies.dc[.init(composite.decomposed.symbol.value)] += 1 - for composite:JPEG.Bitstream.Composite.AC in ac + for composite:JPEG.Bitstream.Composite.AC in ac { frequencies.ac[.init(composite.decomposed.symbol.value)] += 1 } - + let index:Int = (my * self.blocks.x + mx) * stride + offset + i - // can use `!` here because this loop never runs unless + // can use `!` here because this loop never runs unless // `self.blocks` and at least one of the `factor`s is nonzero ($0.baseAddress! + index).initialize(to: (composite, ac)) } } - - // merge frequency counts - if let global:[Int] = globals.dc[component.selector.dc] + + // merge frequency counts + if let global:[Int] = globals.dc[component.selector.dc] { globals.dc[component.selector.dc] = zip(global, frequencies.dc).map - { - $0.0 + $0.1 + { + $0.0 + $0.1 } } - else + else { globals.dc[component.selector.dc] = frequencies.dc } - if let global:[Int] = globals.ac[component.selector.ac] + if let global:[Int] = globals.ac[component.selector.ac] { globals.ac[component.selector.ac] = zip(global, frequencies.ac).map - { - $0.0 + $0.1 + { + $0.0 + $0.1 } } - else + else { globals.ac[component.selector.ac] = frequencies.ac } - + offset += factor.x * factor.y - } - - $1 = count + } + + $1 = count } - - // construct tables - let tables:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = + + // construct tables + let tables:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = ( globals.dc.map{ .init(frequencies: $0.value, target: $0.key) }, globals.ac.map{ .init(frequencies: $0.value, target: $0.key) } ) - - typealias Descriptor = + + typealias Descriptor = (offset:Int, volume:Int, table: ( dc:JPEG.Table.HuffmanDC.Encoder, ac:JPEG.Table.HuffmanAC.Encoder )) - let dc:[JPEG.Table.HuffmanDC.Selector: JPEG.Table.HuffmanDC.Encoder] = - .init(uniqueKeysWithValues: tables.dc.map + let dc:[JPEG.Table.HuffmanDC.Selector: JPEG.Table.HuffmanDC.Encoder] = + .init(uniqueKeysWithValues: tables.dc.map { ($0.target, $0.encoder()) }) - let ac:[JPEG.Table.HuffmanAC.Selector: JPEG.Table.HuffmanAC.Encoder] = - .init(uniqueKeysWithValues: tables.ac.map + let ac:[JPEG.Table.HuffmanAC.Selector: JPEG.Table.HuffmanAC.Encoder] = + .init(uniqueKeysWithValues: tables.ac.map { ($0.target, $0.encoder()) }) var offset:Int = 0 - var descriptors:[Descriptor] = [] + var descriptors:[Descriptor] = [] descriptors.reserveCapacity(components.count) - for ((_, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in - zip(components, factors) + for ((_, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in + zip(components, factors) { let volume:Int = factor.x * factor.y descriptors.append(( - offset: offset, + offset: offset, volume: volume, // `!` is unreachable table: (dc[component.selector.dc]!, ac[component.selector.ac]!) )) offset += volume } - + var bits:JPEG.Bitstream = [] - for base:Int in + for base:Int in Swift.stride(from: composites.startIndex, to: composites.endIndex, by: stride) { - for descriptor:Descriptor in descriptors + for descriptor:Descriptor in descriptors { - let start:Int = base + descriptor.offset, + let start:Int = base + descriptor.offset, end:Int = start + descriptor.volume - for (composite, ac):(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC]) in + for (composite, ac):(JPEG.Bitstream.Composite.DC, [JPEG.Bitstream.Composite.AC]) in composites[start ..< end] { bits.append(composite: composite, table: descriptor.table.dc) - for composite:JPEG.Bitstream.Composite.AC in ac + for composite:JPEG.Bitstream.Composite.AC in ac { bits.append(composite: composite, table: descriptor.table.ac) } } } } - + return (bits.bytes(escaping: 0xff, with: (0xff, 0x00)), tables.dc, tables.ac) } - // progressive mode - private - func encode(bits a:PartialRangeFrom, - components:[(c:Int, component:JPEG.Scan.Component)]) + // progressive mode + private + func encode(bits a:PartialRangeFrom, + components:[(c:Int, component:JPEG.Scan.Component)]) -> ([UInt8], [JPEG.Table.HuffmanDC]) { - guard components.count > 1 - else + guard components.count > 1 + else { // noninterleaved precondition(components.count == 1, "components array cannot be empty") let (p, component):(Int, JPEG.Scan.Component) = components[0] guard self.indices ~= p - else + else { preconditionFailure("scan component not a member of this spectral image") } - let (bytes, table):([UInt8], JPEG.Table.HuffmanDC) = + let (bytes, table):([UInt8], JPEG.Table.HuffmanDC) = self[p].encode(bits: a, component: component) return (bytes, [table]) } - - let factors:[(x:Int, y:Int)] = components.map + + let factors:[(x:Int, y:Int)] = components.map { guard self.indices ~= $0.c - else + else { preconditionFailure("scan component not a member of this spectral image") } return self.layout.planes[$0.c].component.factor } - let stride:Int = factors.reduce(0){ $0 + $1.x * $1.y } - // some components may specify the same table selectors, which means + let stride:Int = factors.reduce(0){ $0 + $1.x * $1.y } + // some components may specify the same table selectors, which means // those components are sharing the same huffman table. var globals:[JPEG.Table.HuffmanDC.Selector: [Int]] = [:] - + let count:Int = self.blocks.x * self.blocks.y * stride - let composites:[JPEG.Bitstream.Composite.DC] = + let composites:[JPEG.Bitstream.Composite.DC] = .init(unsafeUninitializedCapacity: count) { var offset:Int = 0 - for ((p, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in - zip(components, factors) + for ((p, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in + zip(components, factors) { var frequencies:[Int] = .init(repeating: 0, count: 256) var predecessor:Int16 = 0 - for (mx, my):(x:Int, y:Int) in (0, 0) ..< self.blocks + for (mx, my):(x:Int, y:Int) in (0, 0) ..< self.blocks { - let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), - end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) - for (i, (x, y)):(offset:Int, element:(x:Int, y:Int)) in (start ..< end).enumerated() + let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), + end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) + for (i, (x, y)):(offset:Int, element:(x:Int, y:Int)) in (start ..< end).enumerated() { let high:Int16 = self[p][x: x, y: y, z: 0] >> a.lowerBound - let composite:JPEG.Bitstream.Composite.DC = + let composite:JPEG.Bitstream.Composite.DC = .init(difference: high &- predecessor) - predecessor = high - + predecessor = high + frequencies[.init(composite.decomposed.symbol.value)] += 1 let index:Int = (my * self.blocks.x + mx) * stride + offset + i - $0[index] = composite + $0[index] = composite } } - - // merge frequency counts - if let global:[Int] = globals[component.selector.dc] + + // merge frequency counts + if let global:[Int] = globals[component.selector.dc] { globals[component.selector.dc] = zip(global, frequencies).map - { - $0.0 + $0.1 + { + $0.0 + $0.1 } } - else + else { globals[component.selector.dc] = frequencies } - + offset += factor.x * factor.y - } - - $1 = count + } + + $1 = count } - - // construct tables - let tables:[JPEG.Table.HuffmanDC] = globals.map + + // construct tables + let tables:[JPEG.Table.HuffmanDC] = globals.map { .init(frequencies: $0.value, target: $0.key) } - + typealias Descriptor = (offset:Int, volume:Int, table:JPEG.Table.HuffmanDC.Encoder) - let encoders:[JPEG.Table.HuffmanDC.Selector: JPEG.Table.HuffmanDC.Encoder] = - .init(uniqueKeysWithValues: tables.map + let encoders:[JPEG.Table.HuffmanDC.Selector: JPEG.Table.HuffmanDC.Encoder] = + .init(uniqueKeysWithValues: tables.map { ($0.target, $0.encoder()) }) var offset:Int = 0 var descriptors:[Descriptor] = [] descriptors.reserveCapacity(components.count) - for ((_, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in - zip(components, factors) + for ((_, component), factor):((Int, JPEG.Scan.Component), (x:Int, y:Int)) in + zip(components, factors) { let volume:Int = factor.x * factor.y descriptors.append(( - offset: offset, + offset: offset, volume: volume, // `!` is unreachable table: encoders[component.selector.dc]! )) offset += volume } - + var bits:JPEG.Bitstream = [] - for base:Int in + for base:Int in Swift.stride(from: composites.startIndex, to: composites.endIndex, by: stride) { - for descriptor:Descriptor in descriptors + for descriptor:Descriptor in descriptors { - let start:Int = base + descriptor.offset, + let start:Int = base + descriptor.offset, end:Int = start + descriptor.volume for composite:JPEG.Bitstream.Composite.DC in composites[start ..< end] { @@ -1505,48 +1505,48 @@ extension JPEG.Data.Spectral } } } - + return (bits.bytes(escaping: 0xff, with: (0xff, 0x00)), tables) - } - - private - func encode(bit a:Int, components:[(c:Int, component:JPEG.Scan.Component)]) + } + + private + func encode(bit a:Int, components:[(c:Int, component:JPEG.Scan.Component)]) -> [UInt8] { - guard components.count > 1 - else + guard components.count > 1 + else { // noninterleaved precondition(components.count == 1, "components array cannot be empty") let p:Int = components[0].c guard self.indices ~= p - else + else { preconditionFailure("scan component not a member of this spectral image") } - + return self[p].encode(bit: a) } - - typealias Descriptor = (p:Int, factor:(x:Int, y:Int)) - let descriptors:[Descriptor] = components.map + + typealias Descriptor = (p:Int, factor:(x:Int, y:Int)) + let descriptors:[Descriptor] = components.map { guard self.indices ~= $0.c - else + else { preconditionFailure("scan component not a member of this spectral image") } - + return ($0.c, self.layout.planes[$0.c].component.factor) } - + var bits:JPEG.Bitstream = [] - for (mx, my):(x:Int, y:Int) in (0, 0) ..< self.blocks + for (mx, my):(x:Int, y:Int) in (0, 0) ..< self.blocks { - for (p, factor):Descriptor in descriptors + for (p, factor):Descriptor in descriptors { - let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), - end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) + let start:(x:Int, y:Int) = ( mx * factor.x, my * factor.y), + end:(x:Int, y:Int) = (start.x + factor.x, start.y + factor.y) for (x, y):(x:Int, y:Int) in start ..< end { bits.append(bit: self[p][x: x, y: y, z: 0] >> a & 1) @@ -1554,65 +1554,65 @@ extension JPEG.Data.Spectral } } return bits.bytes(escaping: 0xff, with: (0xff, 0x00)) - } - - func encode(scan:JPEG.Scan) - -> + } + + func encode(scan:JPEG.Scan) + -> ( - dc:[JPEG.Table.HuffmanDC], + dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC], - header:JPEG.Header.Scan, + header:JPEG.Header.Scan, ecs:[UInt8] ) { - // “unresolve” the component keys - let header:JPEG.Header.Scan = - .init(band: scan.band, bits: scan.bits, components: + // “unresolve” the component keys + let header:JPEG.Header.Scan = + .init(band: scan.band, bits: scan.bits, components: scan.components.map(\.component)) - + switch (initial: scan.bits.upperBound == .max, band: scan.band) { case (initial: true, band: 0 ..< 64): // sequential mode jpeg - let (data, dc, ac):([UInt8], [JPEG.Table.HuffmanDC], [JPEG.Table.HuffmanAC]) = - self.encode(components: scan.components) + let (data, dc, ac):([UInt8], [JPEG.Table.HuffmanDC], [JPEG.Table.HuffmanAC]) = + self.encode(components: scan.components) return (dc, ac, header, data) - + case (initial: false, band: 0 ..< 64): fatalError("unreachable") - + case (initial: true, band: 0 ..< 1): - let (data, dc):([UInt8], [JPEG.Table.HuffmanDC]) = - self.encode(bits: scan.bits.lowerBound..., components: scan.components) + let (data, dc):([UInt8], [JPEG.Table.HuffmanDC]) = + self.encode(bits: scan.bits.lowerBound..., components: scan.components) return (dc, [], header, data) - + case (initial: false, band: 0 ..< 1): - let data:[UInt8] = + let data:[UInt8] = self.encode(bit: scan.bits.lowerBound, components: scan.components) return ([], [], header, data) - + case (initial: true, band: let band): precondition(scan.components.count == 1, "progressive ac scan cannot be interleaved") let (p, component):(Int, JPEG.Scan.Component) = scan.components[0] guard self.indices ~= p - else + else { - preconditionFailure("scan component not a member of this spectral image") + preconditionFailure("scan component not a member of this spectral image") } - + let (data, ac):([UInt8], JPEG.Table.HuffmanAC) = self[p].encode( band: band, bits: scan.bits.lowerBound..., component: component) return ([], [ac], header, data) - + case (initial: false, band: let band): precondition(scan.components.count == 1, "progressive ac scan cannot be interleaved") let (p, component):(Int, JPEG.Scan.Component) = scan.components[0] guard self.indices ~= p - else + else { - preconditionFailure("scan component not a member of this spectral image") + preconditionFailure("scan component not a member of this spectral image") } - + let (data, ac):([UInt8], JPEG.Table.HuffmanAC) = self[p].encode( band: band, bit: scan.bits.lowerBound, component: component) return ([], [ac], header, data) @@ -1621,12 +1621,12 @@ extension JPEG.Data.Spectral } // serializers (opposite of parsers) -extension JPEG.AnyTable +extension JPEG.AnyTable { - static - func serialize(selector:Self.Selector) -> UInt8 + static + func serialize(selector:Self.Selector) -> UInt8 { - switch selector + switch selector { case \.0: return 0 @@ -1641,7 +1641,7 @@ extension JPEG.AnyTable } } } -extension JPEG.Table.Huffman +extension JPEG.Table.Huffman { // bytes 1 ..< 17 + count (does not include selector byte) func serialized() -> [UInt8] @@ -1649,75 +1649,75 @@ extension JPEG.Table.Huffman return self.symbols.map{ .init($0.count) } + self.symbols.flatMap{ $0.map(\.value) } } } -extension JPEG.Table.Quantization +extension JPEG.Table.Quantization { // bytes 1 ..< 1 + 64 * stride (does not include selector byte) func serialized() -> [UInt8] { - switch self.precision + switch self.precision { case .uint8: return self.storage.map(UInt8.init(_:)) case .uint16: return self.storage.flatMap{ [UInt8].store($0, asBigEndian: UInt16.self) } } - } + } } -extension JPEG.Table +extension JPEG.Table { - /// func JPEG.Table.serialize(_:_:) + /// func JPEG.Table.serialize(_:_:) /// Serializes the given huffman tables as segment data. - /// - /// The DC tables appear before the AC tables in the serialized + /// + /// The DC tables appear before the AC tables in the serialized /// segment. - /// + /// /// This method is the inverse of [`parse(huffman:)`]. /// - _ : [HuffmanDC] - /// The DC huffman tables to serialize. The tables will appear in the + /// The DC huffman tables to serialize. The tables will appear in the /// serialized segment in the same order they appear in this array. /// - _ : [HuffmanAC] - /// The AC huffman tables to serialize. The tables will appear in the + /// The AC huffman tables to serialize. The tables will appear in the /// serialized segment in the same order they appear in this array. /// - -> : [Swift.UInt8] - /// A marker segment body. This array does not include the marker type + /// A marker segment body. This array does not include the marker type /// indicator, or the marker segment length field. - public static + public static func serialize(_ dc:[HuffmanDC], _ ac:[HuffmanAC]) -> [UInt8] { var bytes:[UInt8] = [] - for table:HuffmanDC in dc + for table:HuffmanDC in dc { bytes.append(0x00 | HuffmanDC.serialize(selector: table.target)) bytes.append(contentsOf: table.serialized()) } - for table:HuffmanAC in ac + for table:HuffmanAC in ac { bytes.append(0x10 | HuffmanAC.serialize(selector: table.target)) bytes.append(contentsOf: table.serialized()) } - - return bytes + + return bytes } - /// func JPEG.Table.serialize(_:) + /// func JPEG.Table.serialize(_:) /// Serializes the given quantization tables as segment data. - /// + /// /// This method is the inverse of [`parse(quantization:)`]. /// - _ : [Quantization] - /// The quantization tables to serialize. The tables will appear in the + /// The quantization tables to serialize. The tables will appear in the /// serialized segment in the same order they appear in this array. /// - -> : [Swift.UInt8] - /// A marker segment body. This array does not include the marker type + /// A marker segment body. This array does not include the marker type /// indicator, or the marker segment length field. - public static - func serialize(_ tables:[Quantization]) -> [UInt8] + public static + func serialize(_ tables:[Quantization]) -> [UInt8] { var bytes:[UInt8] = [] - for table:Quantization in tables + for table:Quantization in tables { - // yes all the information needed to encode the sigil byte is in the - // table data structure itself, but for consistency with the huffman + // yes all the information needed to encode the sigil byte is in the + // table data structure itself, but for consistency with the huffman // table serializers, we encode it in the caller body - switch table.precision + switch table.precision { case .uint8: bytes.append(0x00 | Quantization.serialize(selector: table.target)) @@ -1727,201 +1727,201 @@ extension JPEG.Table bytes.append(contentsOf: table.serialized()) } } - - return bytes + + return bytes } } -extension JPEG.Header.Frame +extension JPEG.Header.Frame { - /// func JPEG.Header.Frame.serialized() + /// func JPEG.Header.Frame.serialized() /// Serializes this frame header as segment data. - /// + /// /// This method is the inverse of [`parse(_:process:)`]. /// - -> : [Swift.UInt8] - /// A marker segment body. This array does not include the marker type + /// A marker segment body. This array does not include the marker type /// indicator, or the marker segment length field. - public + public func serialized() -> [UInt8] { var bytes:[UInt8] = [.init(self.precision)] bytes.append(contentsOf: [UInt8].store(self.size.y, asBigEndian: UInt16.self)) bytes.append(contentsOf: [UInt8].store(self.size.x, asBigEndian: UInt16.self)) bytes.append(.init(self.components.count)) - + // must be sorted, as ordering in scan header must match ordering in frame header - for (ci, component):(JPEG.Component.Key, JPEG.Component) in + for (ci, component):(JPEG.Component.Key, JPEG.Component) in self.components.sorted(by: { $0.key < $1.key }) { bytes.append(.init(ci.value)) bytes.append(.init(component.factor.x) << 4 | .init(component.factor.y)) bytes.append(JPEG.Table.Quantization.serialize(selector: component.selector)) } - + return bytes } } -extension JPEG.Header.Scan +extension JPEG.Header.Scan { - /// func JPEG.Header.Scan.serialized() + /// func JPEG.Header.Scan.serialized() /// Serializes this scan header as segment data. - /// + /// /// This method is the inverse of [`parse(_:process:)`]. /// - -> : [Swift.UInt8] - /// A marker segment body. This array does not include the marker type + /// A marker segment body. This array does not include the marker type /// indicator, or the marker segment length field. - public - func serialized() -> [UInt8] + public + func serialized() -> [UInt8] { var bytes:[UInt8] = [.init(self.components.count)] - for component:JPEG.Scan.Component in self.components + for component:JPEG.Scan.Component in self.components { let dc:UInt8 = JPEG.Table.HuffmanDC.serialize(selector: component.selector.dc), ac:UInt8 = JPEG.Table.HuffmanAC.serialize(selector: component.selector.ac) bytes.append(.init(component.ci.value)) bytes.append(dc << 4 | ac) } - + bytes.append(.init(self.band.lowerBound)) bytes.append(.init(self.band.upperBound - 1)) - - let pt:(UInt8, UInt8) = + + let pt:(UInt8, UInt8) = ( - .init(self.bits.lowerBound), + .init(self.bits.lowerBound), self.bits.upperBound == .max ? 0 : .init(self.bits.upperBound) ) bytes.append(pt.1 << 4 | pt.0) - return bytes + return bytes } } -// formatters (opposite of lexers) +// formatters (opposite of lexers) -/// protocol JPEG.Bytestream.Destination +/// protocol JPEG.Bytestream.Destination /// A destination bytestream. -/// -/// To implement a custom data destination type, conform it to this protocol by -/// implementing [`(Destination).write(_:)`]. It can +/// +/// To implement a custom data destination type, conform it to this protocol by +/// implementing [`(Destination).write(_:)`]. It can /// then be used with the library’s core compression interfaces. /// # [See also](file-io-protocols) /// ## (2:file-io-protocols) /// ## (2:lexing-and-formatting) -public -protocol _JPEGBytestreamDestination +public +protocol _JPEGBytestreamDestination { /// mutating func JPEG.Bytestream.Destination.write(_:) - /// required + /// required /// Attempts to write the given bytes to this stream. - /// - /// A successful call to this function should affect the bytestream state + /// + /// A successful call to this function should affect the bytestream state /// such that subsequent calls should pick up where the last call left off. - /// - /// The rest of the library interprets a `nil` return value from this function + /// + /// The rest of the library interprets a `nil` return value from this function /// as indicating a write failure. /// - bytes : [Swift.UInt8] - /// The bytes to write. + /// The bytes to write. /// - -> : Swift.Void? - /// A [`Swift.Void`] tuple, or `nil` if the write attempt failed. This - /// method should return `nil` even if any number of bytes less than + /// A [`Swift.Void`] tuple, or `nil` if the write attempt failed. This + /// method should return `nil` even if any number of bytes less than /// `bytes.count` were successfully written. - mutating + mutating func write(_ bytes:[UInt8]) -> Void? } -extension JPEG.Bytestream +extension JPEG.Bytestream { - public + public typealias Destination = _JPEGBytestreamDestination } -extension JPEG.Bytestream.Destination +extension JPEG.Bytestream.Destination { /// mutating func JPEG.Bytestream.Destination.format(marker:) /// throws /// Formats a single marker into this bytestream. - /// - /// This function is meant to be used with markers without segment bodies, + /// + /// This function is meant to be used with markers without segment bodies, /// such as [`(Marker).start`], [`(Marker).end`], and [`(Marker).restart(_:)`]. - /// - /// This function can throw a [`FormattingError`] if it fails to write + /// + /// This function can throw a [`FormattingError`] if it fails to write /// to the bytestream. - /// - marker : JPEG.Marker + /// - marker : JPEG.Marker /// The type indicator of the marker to format. - public mutating - func format(marker:JPEG.Marker) throws + public mutating + func format(marker:JPEG.Marker) throws { guard let _:Void = self.write([0xff, marker.code]) - else + else { - throw JPEG.FormattingError.invalidDestination + throw JPEG.FormattingError.invalidDestination } } /// mutating func JPEG.Bytestream.Destination.format(marker:tail:) /// throws /// Formats a single marker segment into this bytestream. - /// - /// This function will output a segment length field, even if no marker data - /// is provided, and so should *not* be used with markers without segment + /// + /// This function will output a segment length field, even if no marker data + /// is provided, and so should *not* be used with markers without segment /// bodies, such as [`(Marker).start`], [`(Marker).end`], and [`(Marker).restart(_:)`]. - /// - /// This function can throw a [`FormattingError`] if it fails to write + /// + /// This function can throw a [`FormattingError`] if it fails to write /// to the bytestream. - /// - marker : JPEG.Marker + /// - marker : JPEG.Marker /// The type indicator of the marker to format. - /// - tail : [Swift.UInt8] - /// The marker segment body. This array should not include the marker type + /// - tail : [Swift.UInt8] + /// The marker segment body. This array should not include the marker type /// indicator, or the marker segment length field. - public mutating - func format(marker:JPEG.Marker, tail:[UInt8]) throws + public mutating + func format(marker:JPEG.Marker, tail:[UInt8]) throws { let length:Int = tail.count + 2 - let bytes:[UInt8] = + let bytes:[UInt8] = [0xff, marker.code] + [UInt8].store(length, asBigEndian: UInt16.self) + tail guard let _:Void = self.write(bytes) - else + else { - throw JPEG.FormattingError.invalidDestination + throw JPEG.FormattingError.invalidDestination } } /// mutating func JPEG.Bytestream.Destination.format(prefix:) /// throws /// Formats the given entropy-coded data into this bytestream. - /// - /// This function is essentially a wrapper around [`write(_:)`] which converts + /// + /// This function is essentially a wrapper around [`write(_:)`] which converts /// `nil` return values to a thrown [`FormattingError`]. - /// - prefix : JPEG.Marker + /// - prefix : JPEG.Marker /// The data to write to the bytestream. - public mutating - func format(prefix:[UInt8]) throws + public mutating + func format(prefix:[UInt8]) throws { - guard let _:Void = self.write(prefix) - else + guard let _:Void = self.write(prefix) + else { - throw JPEG.FormattingError.invalidDestination + throw JPEG.FormattingError.invalidDestination } } } // staged APIs -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { - /// func JPEG.Data.Spectral.compress(stream:) - /// throws - /// where Destination:JPEG.Bytestream.Destination - /// Compresses a spectral image to the given data destination. - /// - /// All metadata records in this image will be emitted at the beginning of + /// func JPEG.Data.Spectral.compress(stream:) + /// throws + /// where Destination:JPEG.Bytestream.Destination + /// Compresses a spectral image to the given data destination. + /// + /// All metadata records in this image will be emitted at the beginning of /// the outputted file, in the order they appear in the [`metadata`] array. - /// - stream : inout Destination + /// - stream : inout Destination /// A destination bytestream. /// # [See also](spectral-save-image) /// ## (1:spectral-save-image) - public - func compress(stream:inout Destination) throws + public + func compress(stream:inout Destination) throws where Destination:JPEG.Bytestream.Destination { try stream.format(marker: .start) - for metadata:JPEG.Metadata in self.metadata + for metadata:JPEG.Metadata in self.metadata { - switch metadata + switch metadata { case .jfif(let jfif): try stream.format(marker: .application(0), tail: jfif.serialized()) @@ -1933,103 +1933,103 @@ extension JPEG.Data.Spectral try stream.format(marker: .comment, tail: data) } } - - let frame:JPEG.Header.Frame = self.encode() + + let frame:JPEG.Header.Frame = self.encode() try stream.format(marker: .frame(frame.process), tail: frame.serialized()) - for (qi, scans):([JPEG.Table.Quantization.Key], [JPEG.Scan]) in - self.layout.definitions + for (qi, scans):([JPEG.Table.Quantization.Key], [JPEG.Scan]) in + self.layout.definitions { let quanta:[JPEG.Table.Quantization] = qi.map - { + { self.quanta[self.quanta.index(forKey: $0)] } - + if !quanta.isEmpty { try stream.format(marker: .quantization, tail: JPEG.Table.serialize(quanta)) } - - for scan:JPEG.Scan in scans + + for scan:JPEG.Scan in scans { let dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC], - header:JPEG.Header.Scan, + header:JPEG.Header.Scan, ecs:[UInt8] - + (dc, ac, header, ecs) = self.encode(scan: scan) - - if !dc.isEmpty || !ac.isEmpty + + if !dc.isEmpty || !ac.isEmpty { try stream.format(marker: .huffman, tail: JPEG.Table.serialize(dc, ac)) } - + try stream.format(marker: .scan, tail: header.serialized()) try stream.format(prefix: ecs) } } - + try stream.format(marker: .end) } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { - /// func JPEG.Data.Planar.compress(stream:quanta:) - /// throws - /// where Destination:JPEG.Bytestream.Destination - /// Compresses a planar image to the given data destination. - /// - /// All metadata records in this image will be emitted at the beginning of + /// func JPEG.Data.Planar.compress(stream:quanta:) + /// throws + /// where Destination:JPEG.Bytestream.Destination + /// Compresses a planar image to the given data destination. + /// + /// All metadata records in this image will be emitted at the beginning of /// the outputted file, in the order they appear in the [`metadata`] array. - /// + /// /// This function is a convenience function which calls [`fdct(quanta:)`] - /// to obtain a spectral image, and then calls [`(Spectral).compress(stream:)`] + /// to obtain a spectral image, and then calls [`(Spectral).compress(stream:)`] /// on the output. - /// - stream : inout Destination + /// - stream : inout Destination /// A destination bytestream. /// - quanta: [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by this image’s [`layout`], - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by this image’s [`layout`], + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width /// determined by this image’s [`layout``(Layout).format``(JPEG.Format).precision`], /// and all the values must be in the correct range for that bit width. /// # [See also](planar-save-image) /// ## (0:planar-save-image) - public - func compress(stream:inout Destination, - quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws + public + func compress(stream:inout Destination, + quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws where Destination:JPEG.Bytestream.Destination { try self.fdct(quanta: quanta).compress(stream: &stream) } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { - /// func JPEG.Data.Rectangular.compress(stream:quanta:) - /// throws - /// where Destination:JPEG.Bytestream.Destination - /// Compresses a rectangular image to the given data destination. - /// - /// All metadata records in this image will be emitted at the beginning of + /// func JPEG.Data.Rectangular.compress(stream:quanta:) + /// throws + /// where Destination:JPEG.Bytestream.Destination + /// Compresses a rectangular image to the given data destination. + /// + /// All metadata records in this image will be emitted at the beginning of /// the outputted file, in the order they appear in the [`metadata`] array. - /// + /// /// This function is a convenience function which calls [`decomposed()`] - /// to obtain a planar image, and then calls [`(Planar).compress(stream:quanta:)`] + /// to obtain a planar image, and then calls [`(Planar).compress(stream:quanta:)`] /// on the output. - /// - stream : inout Destination + /// - stream : inout Destination /// A destination bytestream. /// - quanta: [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by this image’s [`layout`], - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by this image’s [`layout`], + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width /// determined by this image’s [`layout``(Layout).format``(JPEG.Format).precision`], /// and all the values must be in the correct range for that bit width. /// # [See also](rectangular-save-image) /// ## (0:rectangular-save-image) - public - func compress(stream:inout Destination, - quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws + public + func compress(stream:inout Destination, + quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws where Destination:JPEG.Bytestream.Destination { try self.decomposed().compress(stream: &stream, quanta: quanta) diff --git a/sources/jpeg/error.swift b/Sources/JPEG/error.swift similarity index 82% rename from sources/jpeg/error.swift rename to Sources/JPEG/error.swift index 4807577e..d140e49a 100644 --- a/sources/jpeg/error.swift +++ b/Sources/JPEG/error.swift @@ -3,100 +3,100 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /// protocol JPEG.Error -/// : Swift.Error +/// : Swift.Error /// Functionality common to all library error types. /// # [See also](error-types) /// ## (error-handling) -public -protocol _JPEGError:Swift.Error +public +protocol _JPEGError:Swift.Error { /// static var JPEG.Error.namespace : Swift.String { get } - /// required + /// required /// The human-readable namespace for errors of this type. - static - var namespace:String + static + var namespace:String { - get + get } /// var JPEG.Error.message : Swift.String { get } - /// required + /// required /// A basic description of this error instance. - var message:String + var message:String { - get + get } /// var JPEG.Error.details : Swift.String? { get } - /// required + /// required /// A detailed description of this error instance, if available. - var details:String? + var details:String? { - get + get } } -extension JPEG +extension JPEG { - public + public typealias Error = _JPEGError /// enum JPEG.LexingError - /// : JPEG.Error + /// : JPEG.Error /// A lexing error. /// # [See also](error-types) /// ## (error-types) /// ## (error-handling) - public + public enum LexingError:JPEG.Error { - /// case JPEG.LexingError.truncatedMarkerSegmentType - /// The lexer encountered end-of-stream while lexing a marker + /// case JPEG.LexingError.truncatedMarkerSegmentType + /// The lexer encountered end-of-stream while lexing a marker /// segment type indicator. case truncatedMarkerSegmentType /// case JPEG.LexingError.truncatedMarkerSegmentHeader - /// The lexer encountered end-of-stream while lexing a marker + /// The lexer encountered end-of-stream while lexing a marker /// segment length field. case truncatedMarkerSegmentHeader /// case JPEG.LexingError.truncatedMarkerSegmentBody(expected:) - /// The lexer encountered end-of-stream while lexing a marker + /// The lexer encountered end-of-stream while lexing a marker /// segment body. - /// - expected:Swift.Int + /// - expected:Swift.Int /// The number of bytes the lexer was expecting to read. case truncatedMarkerSegmentBody(expected:Int) /// case JPEG.LexingError.truncatedEntropyCodedSegment - /// The lexer encountered end-of-stream while lexing an entropy-coded + /// The lexer encountered end-of-stream while lexing an entropy-coded /// segment, usually because it was expecting a subsequent marker segment. case truncatedEntropyCodedSegment /// case JPEG.LexingError.invalidMarkerSegmentLength(_:) - /// The lexer read a marker segment length field, but the value did + /// The lexer read a marker segment length field, but the value did /// not make sense. - /// - _ :Swift.Int + /// - _ :Swift.Int /// The value that the lexer read from the marker segment length field. case invalidMarkerSegmentLength(Int) /// case JPEG.LexingError.invalidMarkerSegmentPrefix(_:) - /// The lexer encountered a prefixed entropy-coded segment where it + /// The lexer encountered a prefixed entropy-coded segment where it /// was expecting none. - /// - _ :Swift.UInt8 + /// - _ :Swift.UInt8 /// The first invalid byte encountered by the lexer. case invalidMarkerSegmentPrefix(UInt8) /// case JPEG.LexingError.invalidMarkerSegmentType(_:) - /// The lexer encountered a marker segment with a reserved type indicator + /// The lexer encountered a marker segment with a reserved type indicator /// code. - /// - _ :Swift.UInt8 + /// - _ :Swift.UInt8 /// The invalid type indicator code encountered by the lexer. case invalidMarkerSegmentType(UInt8) /// static var JPEG.LexingError.namespace : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns the string `"lexing error"`. - public static - var namespace:String + public static + var namespace:String { - "lexing error" + "lexing error" } /// var JPEG.LexingError.message : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a basic description of this lexing error. - public - var message:String + public + var message:String { - switch self + switch self { case .truncatedMarkerSegmentType: return "truncated marker segment type" @@ -106,22 +106,22 @@ extension JPEG return "truncated marker segment body" case .truncatedEntropyCodedSegment: return "truncated entropy coded segment" - + case .invalidMarkerSegmentLength: return "invalid value in marker segment length field" case .invalidMarkerSegmentPrefix: return "invalid marker segment prefix" case .invalidMarkerSegmentType: return "invalid marker segment type code" - } + } } /// var JPEG.LexingError.details : Swift.String? { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a detailed description of this lexing error, if available. - public - var details:String? + public + var details:String? { - switch self + switch self { case .truncatedMarkerSegmentType: return "unexpected end-of-stream while lexing marker segment type field" @@ -131,61 +131,61 @@ extension JPEG return "unexpected end-of-stream while lexing marker segment body (expected \(expected) bytes)" case .truncatedEntropyCodedSegment: return "unexpected end-of-stream while lexing entropy coded segment" - + case .invalidMarkerSegmentLength(let length): return "value of marker segment length field (\(length)) cannot be less than 2" case .invalidMarkerSegmentPrefix(let byte): return "padding byte (0x\(String.init(byte, radix: 16))) preceeding marker segment must be 0xff" case .invalidMarkerSegmentType(let code): return "marker segment type code (0x\(String.init(code, radix: 16))) is a reserved marker code" - } + } } } /// enum JPEG.ParsingError - /// : JPEG.Error + /// : JPEG.Error /// A parsing error. /// # [See also](error-types) /// ## (error-types) /// ## (error-handling) - public - enum ParsingError:JPEG.Error + public + enum ParsingError:JPEG.Error { - /// case JPEG.ParsingError.truncatedMarkerSegmentBody(_:_:expected:) + /// case JPEG.ParsingError.truncatedMarkerSegmentBody(_:_:expected:) /// A marker segment contained less than the expected amount of data. - /// - _ : JPEG.Marker + /// - _ : JPEG.Marker /// The marker segment type. - /// - _ : Swift.Int + /// - _ : Swift.Int /// The size of the marker segment, in bytes. - /// - expected : Swift.ClosedRange + /// - expected : Swift.ClosedRange /// The range of marker segment sizes that was expected, in bytes. case truncatedMarkerSegmentBody(Marker, Int, expected:ClosedRange) - /// case JPEG.ParsingError.extraneousMarkerSegmentData(_:_:expected:) + /// case JPEG.ParsingError.extraneousMarkerSegmentData(_:_:expected:) /// A marker segment contained more than the expected amount of data. - /// - _ : JPEG.Marker + /// - _ : JPEG.Marker /// The marker segment type. - /// - _ : Swift.Int + /// - _ : Swift.Int /// The size of the marker segment, in bytes. /// - expected : Swift.Int /// The amount of data that was expected, in bytes. case extraneousMarkerSegmentData(Marker, Int, expected:Int) /// case JPEG.ParsingError.invalidJFIFSignature(_:) /// A JFIF segment had an invalid signature. - /// - _ : [Swift.UInt8] + /// - _ : [Swift.UInt8] /// The signature read from the segment. case invalidJFIFSignature([UInt8]) /// case JPEG.ParsingError.invalidJFIFVersionCode(_:) - /// A JFIF segment had an invalid version code. - /// - _ : (major:Swift.UInt8, minor:Swift.UInt8) + /// A JFIF segment had an invalid version code. + /// - _ : (major:Swift.UInt8, minor:Swift.UInt8) /// The version code read from the segment. case invalidJFIFVersionCode((major:UInt8, minor:UInt8)) /// case JPEG.ParsingError.invalidJFIFDensityUnitCode(_:) - /// A JFIF segment had an invalid density unit code. - /// - _ : Swift.UInt8 + /// A JFIF segment had an invalid density unit code. + /// - _ : Swift.UInt8 /// The density unit code read from the segment. case invalidJFIFDensityUnitCode(UInt8) /// case JPEG.ParsingError.invalidEXIFSignature(_:) /// An EXIF segment had an invalid signature. - /// - _ : [Swift.UInt8] + /// - _ : [Swift.UInt8] /// The signature read from the segment. case invalidEXIFSignature([UInt8]) /// case JPEG.ParsingError.invalidEXIFEndiannessCode(_:) @@ -193,173 +193,173 @@ extension JPEG /// - _ : (Swift.UInt8, Swift.UInt8, Swift.UInt8, Swift.UInt8) /// The endianness specifier read from the segment. case invalidEXIFEndiannessCode((UInt8, UInt8, UInt8, UInt8)) - + /// case JPEG.ParsingError.invalidFrameWidth(_:) - /// A frame header segment had a negative or zero width field. - /// - _ : Swift.Int + /// A frame header segment had a negative or zero width field. + /// - _ : Swift.Int /// The value of the width field read from the segment. case invalidFrameWidth(Int) /// case JPEG.ParsingError.invalidFramePrecision(_:_:) - /// A frame header segment had an invalid precision field. - /// - _ : Swift.Int + /// A frame header segment had an invalid precision field. + /// - _ : Swift.Int /// The value of the precision field read from the segment. - /// - _ : JPEG.Process + /// - _ : JPEG.Process /// The coding process specified by the frame header. case invalidFramePrecision(Int, Process) /// case JPEG.ParsingError.invalidFrameComponentCount(_:_:) - /// A frame header segment had an invalid number of components. - /// - _ : Swift.Int + /// A frame header segment had an invalid number of components. + /// - _ : Swift.Int /// The number of components in the segment. - /// - _ : JPEG.Process + /// - _ : JPEG.Process /// The coding process specified by the frame header. case invalidFrameComponentCount(Int, Process) /// case JPEG.ParsingError.invalidFrameQuantizationSelectorCode(_:) - /// A component in a frame header segment had an invalid quantization - /// table selector code. - /// - _ : Swift.UInt8 + /// A component in a frame header segment had an invalid quantization + /// table selector code. + /// - _ : Swift.UInt8 /// The selector code read from the segment. case invalidFrameQuantizationSelectorCode(UInt8) /// case JPEG.ParsingError.invalidFrameQuantizationSelector(_:_:) - /// A component in a frame header segment used a quantization table + /// A component in a frame header segment used a quantization table /// selector which is well-formed but unavailable given the frame header coding process. - /// - _ : JPEG.Table.Quantization.Selector - /// The quantization table selector. - /// - _ : JPEG.Process + /// - _ : JPEG.Table.Quantization.Selector + /// The quantization table selector. + /// - _ : JPEG.Process /// The coding process specified by the frame header. case invalidFrameQuantizationSelector(JPEG.Table.Quantization.Selector, Process) /// case JPEG.ParsingError.invalidFrameComponentSamplingFactor(_:_:) - /// A component in a frame header had an invalid sampling factor. - /// + /// A component in a frame header had an invalid sampling factor. + /// /// Sampling factors must be within the range `1 ... 4`. /// - _ : (x:Swift.Int, y:Swift.Int) /// The sampling factor of the component. - /// - _ : JPEG.Component.Key + /// - _ : JPEG.Component.Key /// The component key. case invalidFrameComponentSamplingFactor((x:Int, y:Int), Component.Key) /// case JPEG.ParsingError.duplicateFrameComponentIndex(_:) - /// The same component key occurred more than once in the same frame header. - /// - _ : JPEG.Component.Key + /// The same component key occurred more than once in the same frame header. + /// - _ : JPEG.Component.Key /// The duplicated component key. case duplicateFrameComponentIndex(Component.Key) - + /// case JPEG.ParsingError.invalidScanHuffmanSelectorCode(_:) - /// A component in a frame header segment had an invalid quantization - /// table selector code. - /// - _ : Swift.UInt8 + /// A component in a frame header segment had an invalid quantization + /// table selector code. + /// - _ : Swift.UInt8 /// The selector code read from the segment. case invalidScanHuffmanSelectorCode(UInt8) /// case JPEG.ParsingError.invalidScanHuffmanDCSelector(_:_:) - /// A component in a frame header segment used a DC huffman table + /// A component in a frame header segment used a DC huffman table /// selector which is well-formed but unavailable given the frame header coding process. - /// - _ : JPEG.Table.HuffmanDC.Selector - /// The huffman table selector. - /// - _ : JPEG.Process + /// - _ : JPEG.Table.HuffmanDC.Selector + /// The huffman table selector. + /// - _ : JPEG.Process /// The coding process specified by the frame header. case invalidScanHuffmanDCSelector(JPEG.Table.HuffmanDC.Selector, Process) /// case JPEG.ParsingError.invalidScanHuffmanACSelector(_:_:) - /// A component in a frame header segment used an AC huffman table + /// A component in a frame header segment used an AC huffman table /// selector which is well-formed but unavailable given the frame header coding process. - /// - _ : JPEG.Table.HuffmanAC.Selector - /// The huffman table selector. - /// - _ : JPEG.Process + /// - _ : JPEG.Table.HuffmanAC.Selector + /// The huffman table selector. + /// - _ : JPEG.Process /// The coding process specified by the frame header. case invalidScanHuffmanACSelector(JPEG.Table.HuffmanAC.Selector, Process) /// case JPEG.ParsingError.invalidScanComponentCount(_:_:) - /// A scan header had more that the maximum allowed number of components - /// given the image coding process. - /// - _ : Swift.Int - /// The number of components in the scan header. - /// - _ : JPEG.Process + /// A scan header had more that the maximum allowed number of components + /// given the image coding process. + /// - _ : Swift.Int + /// The number of components in the scan header. + /// - _ : JPEG.Process /// The coding process used by the image. case invalidScanComponentCount(Int, Process) /// case JPEG.ParsingError.invalidScanProgressiveSubset(band:bits:_:) - /// A scan header specified an invalid progressive frequency band - /// or bit range given the image coding process. + /// A scan header specified an invalid progressive frequency band + /// or bit range given the image coding process. /// - band : (Swift.Int, Swift.Int) /// The lower and upper bounds of the frequency band read from the scan header. /// - bits : (Swift.Int, Swift.Int) /// The lower and upper bounds of the bit range read from the scan header. - /// - _ : JPEG.Process + /// - _ : JPEG.Process /// The coding process used by the image. case invalidScanProgressiveSubset(band:(Int, Int), bits:(Int, Int), Process) - + /// case JPEG.ParsingError.invalidHuffmanTargetCode(_:) - /// A huffman table definition had an invalid huffman table - /// selector code. - /// - _ : Swift.UInt8 + /// A huffman table definition had an invalid huffman table + /// selector code. + /// - _ : Swift.UInt8 /// The selector code read from the segment. case invalidHuffmanTargetCode(UInt8) /// case JPEG.ParsingError.invalidHuffmanTypeCode(_:) - /// A huffman table definition had an invalid type indicator code. - /// - _ : Swift.UInt8 + /// A huffman table definition had an invalid type indicator code. + /// - _ : Swift.UInt8 /// The type indicator code read from the segment. case invalidHuffmanTypeCode(UInt8) /// case JPEG.ParsingError.invalidHuffmanTable /// A huffman table definition did not define a valid binary tree. case invalidHuffmanTable - + /// case JPEG.ParsingError.invalidQuantizationTargetCode(_:) - /// A quantization table definition had an invalid quantization table - /// selector code. - /// - _ : Swift.UInt8 + /// A quantization table definition had an invalid quantization table + /// selector code. + /// - _ : Swift.UInt8 /// The selector code read from the segment. case invalidQuantizationTargetCode(UInt8) /// case JPEG.ParsingError.invalidQuantizationPrecisionCode(_:) - /// A quantization table definition had an invalid precision indicator code. - /// - _ : Swift.UInt8 + /// A quantization table definition had an invalid precision indicator code. + /// - _ : Swift.UInt8 /// The precision indicator code read from the segment. case invalidQuantizationPrecisionCode(UInt8) - - static - func mismatched(marker:Marker, count:Int, minimum:Int) -> Self + + static + func mismatched(marker:Marker, count:Int, minimum:Int) -> Self { .truncatedMarkerSegmentBody(marker, count, expected: minimum ... .max) } - static - func mismatched(marker:Marker, count:Int, expected:Int) -> Self + static + func mismatched(marker:Marker, count:Int, expected:Int) -> Self { - if count < expected + if count < expected { return .truncatedMarkerSegmentBody(marker, count, expected: expected ... expected) } - else + else { return .extraneousMarkerSegmentData(marker, count, expected: expected) } } /// static var JPEG.ParsingError.namespace: Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns the string `"parsing error"`. - public static - var namespace:String + public static + var namespace:String { - "parsing error" + "parsing error" } /// var JPEG.ParsingError.message : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a basic description of this parsing error. - public - var message:String + public + var message:String { - switch self + switch self { case .truncatedMarkerSegmentBody: return "truncated marker segment body" case .extraneousMarkerSegmentData: return "extraneous data in marker segment body" - + case .invalidJFIFSignature: return "invalid JFIF signature" case .invalidJFIFVersionCode: return "invalid JFIF version" case .invalidJFIFDensityUnitCode: return "invalid JFIF density unit" - + case .invalidEXIFSignature: return "invalid EXIF signature" case .invalidEXIFEndiannessCode: return "invalid EXIF endianness code" - + case .invalidFrameWidth: return "invalid frame width" case .invalidFramePrecision: @@ -374,7 +374,7 @@ extension JPEG return "invalid component sampling factors" case .duplicateFrameComponentIndex: return "duplicate component indices" - + case .invalidScanHuffmanSelectorCode: return "invalid huffman table selector pair code" case .invalidScanHuffmanDCSelector: @@ -385,14 +385,14 @@ extension JPEG return "invalid scan component count" case .invalidScanProgressiveSubset: return "invalid spectral selection or successive approximation" - + case .invalidHuffmanTargetCode: return "invalid huffman table destination" case .invalidHuffmanTypeCode: return "invalid huffman table type specifier" case .invalidHuffmanTable: return "malformed huffman table" - + case .invalidQuantizationTargetCode: return "invalid quantization table destination" case .invalidQuantizationPrecisionCode: @@ -400,50 +400,50 @@ extension JPEG } } /// var JPEG.ParsingError.details : Swift.String? { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a detailed description of this parsing error, if available. - public - var details:String? + public + var details:String? { - switch self + switch self { case .truncatedMarkerSegmentBody(let marker, let count, expected: let expected): if expected.count == 1 { return "\(marker) segment (\(count) bytes) must be exactly \(expected.lowerBound) bytes long" } - else + else { return "\(marker) segment (\(count) bytes) must be at least \(expected.lowerBound) bytes long" } case .extraneousMarkerSegmentData(let marker, let count, expected: let expected): return "\(marker) segment (\(count) bytes) must be exactly \(expected) bytes long" - + case .invalidJFIFSignature(let string): return "string (\(string.map{ "0x\(String.init($0, radix: 16))" }.joined(separator: ", "))) is not a valid JFIF signature" case .invalidJFIFVersionCode(let (major, minor)): return "version (\(major).\(minor)) must be within 1.0 ... 1.2" case .invalidJFIFDensityUnitCode(let code): return "density code (\(code)) does not correspond to a valid density unit" - + case .invalidEXIFSignature(let string): return "string (\(string.map{ "0x\(String.init($0, radix: 16))" }.joined(separator: ", "))) is not a valid EXIF signature" case .invalidEXIFEndiannessCode(let code): return "endianness code (\(code.0), \(code.1), \(code.2), \(code.3)) does not correspond to a valid EXIF endianness" - + case .invalidFrameWidth(let width): return "frame cannot have width \(width)" case .invalidFramePrecision(let precision, let process): return "precision (\(precision)) is not allowed for frame coding process '\(process)'" case .invalidFrameComponentCount(let count, let process): - if count == 0 + if count == 0 { return "frame must have at least one component" } - else + else { return "frame (\(count) components) with coding process '\(process)' has disallowed component count" - } + } case .invalidFrameQuantizationSelectorCode(let code): return "quantization table selector code (\(code)) must be within 0 ... 3" case .invalidFrameQuantizationSelector(let selector, let process): @@ -452,7 +452,7 @@ extension JPEG return "both sampling factors (\(factor.x), \(factor.y)) for component index \(ci) must be within 1 ... 4" case .duplicateFrameComponentIndex(let ci): return "component index (\(ci)) conflicts with previously defined component" - + case .invalidScanHuffmanSelectorCode(let code): return "huffman table selector pair code (\(code)) must be within 0 ... 3 or 16 ... 19" case .invalidScanHuffmanDCSelector(let selector, let process): @@ -460,24 +460,24 @@ extension JPEG case .invalidScanHuffmanACSelector(let selector, let process): return "ac huffman table selector (\(String.init(selector: selector))) is not allowed for coding process '\(process)'" case .invalidScanComponentCount(let count, let process): - if count == 0 + if count == 0 { return "scan must contain at least one component" } - else + else { return "scan component count (\(count)) is not allowed for coding process '\(process)'" - } + } case .invalidScanProgressiveSubset(band: let band, bits: let bits, let process): return "scan cannot define spectral selection (\(band.0) ..< \(band.1)) with successive approximation (\(bits.0) ..< \(bits.1)) for coding process '\(process)'" - + case .invalidHuffmanTargetCode(let code): return "selector code (0x\(String.init(code, radix: 16))) does not correspond to a valid huffman table destination" case .invalidHuffmanTypeCode(let code): return "code (\(code)) does not correspond to a valid huffman table type" case .invalidHuffmanTable: return nil - + case .invalidQuantizationTargetCode(let code): return "selector code (0x\(String.init(code, radix: 16))) does not correspond to a valid quantization table destination" case .invalidQuantizationPrecisionCode(let code): @@ -486,220 +486,220 @@ extension JPEG } } /// enum JPEG.DecodingError - /// : JPEG.Error + /// : JPEG.Error /// A decoding error. /// # [See also](error-types) /// ## (error-types) /// ## (error-handling) - public - enum DecodingError:JPEG.Error + public + enum DecodingError:JPEG.Error { /// case JPEG.DecodingError.truncatedEntropyCodedSegment /// An entropy-coded segment contained less than the expected amount of data. case truncatedEntropyCodedSegment - + /// case JPEG.DecodingError.invalidRestartPhase(_:expected:) - /// A restart marker appeared out-of-phase. - /// + /// A restart marker appeared out-of-phase. + /// /// Restart markers should cycle from 0 to 7, in that order. - /// - _ : Swift.Int - /// The phase read from the restart marker. - /// - expected : Swift.Int - /// The expected phase, which is one greater than the phase of the - /// last-encountered restart marker (modulo 8), or 0 if this is the + /// - _ : Swift.Int + /// The phase read from the restart marker. + /// - expected : Swift.Int + /// The expected phase, which is one greater than the phase of the + /// last-encountered restart marker (modulo 8), or 0 if this is the /// first restart marker in the entropy-coded segment. case invalidRestartPhase(Int, expected:Int) /// case JPEG.DecodingError.missingRestartIntervalSegment - /// A restart marker appeared, but no restart interval was ever defined, + /// A restart marker appeared, but no restart interval was ever defined, /// or restart markers were disabled. case missingRestartIntervalSegment - + /// case JPEG.DecodingError.invalidSpectralSelectionProgression(_:_:) - /// The first scan for a component encoded a frequency band that + /// The first scan for a component encoded a frequency band that /// did not include the DC coefficient. - /// - _ : Swift.Range + /// - _ : Swift.Range /// The frequency band encoded by the scan. - /// - _ : JPEG.Component.Key + /// - _ : JPEG.Component.Key /// The component key of the invalidated color channel. case invalidSpectralSelectionProgression(Range, Component.Key) /// case JPEG.DecodingError.invalidSuccessiveApproximationProgression(_:_:z:_:) - /// A scan did not follow the correct successive approximation sequence + /// A scan did not follow the correct successive approximation sequence /// for at least one frequency coefficient. - /// - /// Successive approximation must refine bits starting from the most-significant - /// and going towards the least-significant, only the initial scan - /// for each coefficient can encode more than one bit at a time. + /// + /// Successive approximation must refine bits starting from the most-significant + /// and going towards the least-significant, only the initial scan + /// for each coefficient can encode more than one bit at a time. /// - _ : Swift.Range - /// The bit range encoded by the scan. - /// - _ : Swift.Int - /// The index of the least-significant bit encoded so far for the coefficient `z`. - /// - z : Swift.Int - /// The zigzag index of the coefficient. - /// - _ : JPEG.Component.Key + /// The bit range encoded by the scan. + /// - _ : Swift.Int + /// The index of the least-significant bit encoded so far for the coefficient `z`. + /// - z : Swift.Int + /// The zigzag index of the coefficient. + /// - _ : JPEG.Component.Key /// The component key of the invalidated color channel. case invalidSuccessiveApproximationProgression(Range, Int, z:Int, Component.Key) - + /// case JPEG.DecodingError.invalidCompositeValue(_:expected:) - /// The decoder decoded an out-of-range composite value. - /// - /// This error occurs when a refining AC scan encodes any composite - /// value that is not –1, 0, or +1, because refining scans can only + /// The decoder decoded an out-of-range composite value. + /// + /// This error occurs when a refining AC scan encodes any composite + /// value that is not –1, 0, or +1, because refining scans can only /// refine one bit at a time. - /// - _ : Swift.Int16 + /// - _ : Swift.Int16 /// The decoded composite value. - /// - expected : Swift.ClosedRange + /// - expected : Swift.ClosedRange /// The expected range for the composite value. case invalidCompositeValue(Int16, expected:ClosedRange) /// case JPEG.DecodingError.invalidCompositeBlockRun(_:expected:) - /// The decoder decoded an out-of-range end-of-band/end-of-block run count. - /// + /// The decoder decoded an out-of-range end-of-band/end-of-block run count. + /// /// This error occurs when a sequential scan tries to encode an end-of-band - /// run, which is a progressive coding process concept only. Sequential + /// run, which is a progressive coding process concept only. Sequential /// scans can only end-of-block runs of length 1. - /// - _ : Swift.Int16 + /// - _ : Swift.Int16 /// The decoded end-of-band/end-of-block run count. - /// - expected : Swift.ClosedRange + /// - expected : Swift.ClosedRange /// The expected range for the end-of-band/end-of-block run count. case invalidCompositeBlockRun(Int, expected:ClosedRange) - + /// case JPEG.DecodingError.undefinedScanComponentReference(_:_:) - /// A scan encoded a component with a key that was not one of the + /// A scan encoded a component with a key that was not one of the /// resident components declared in the frame header. - /// - _ : JPEG.Component.Key - /// The undefined component key. + /// - _ : JPEG.Component.Key + /// The undefined component key. /// - _ : Swift.Set /// The set of defined resident component keys. case undefinedScanComponentReference(Component.Key, Set) /// case JPEG.DecodingError.invalidScanSamplingVolume(_:) - /// An interleaved scan had a total component sampling volume greater + /// An interleaved scan had a total component sampling volume greater /// than 10. - /// - /// The total sampling volume is the sum of the products of the sampling + /// + /// The total sampling volume is the sum of the products of the sampling /// factors of each component encoded by the scan. - /// - _ : Swift.Int + /// - _ : Swift.Int /// The total sampling volume of the scan components. case invalidScanSamplingVolume(Int) /// case JPEG.DecodingError.undefinedScanHuffmanDCReference(_:) - /// A DC huffman table selector in a scan referenced a table + /// A DC huffman table selector in a scan referenced a table /// slot with no bound table. - /// - _ : JPEG.Table.HuffmanDC.Selector + /// - _ : JPEG.Table.HuffmanDC.Selector /// The table selector. case undefinedScanHuffmanDCReference(Table.HuffmanDC.Selector) /// case JPEG.DecodingError.undefinedScanHuffmanACReference(_:) - /// An AC huffman table selector in a scan referenced a table + /// An AC huffman table selector in a scan referenced a table /// slot with no bound table. - /// - _ : JPEG.Table.HuffmanAC.Selector + /// - _ : JPEG.Table.HuffmanAC.Selector /// The table selector. case undefinedScanHuffmanACReference(Table.HuffmanAC.Selector) /// case JPEG.DecodingError.undefinedScanQuantizationReference(_:) - /// A quantization table selector in the first scan for a particular + /// A quantization table selector in the first scan for a particular /// component referenced a table slot with no bound table. - /// - _ : JPEG.Table.Quantization.Selector + /// - _ : JPEG.Table.Quantization.Selector /// The table selector. case undefinedScanQuantizationReference(Table.Quantization.Selector) /// case JPEG.DecodingError.invalidScanQuantizationPrecision(_:) - /// A quantization table had the wrong precision mode for the image - /// color format. - /// - /// Only images with a bit depth greater than 8 should use a 16-bit + /// A quantization table had the wrong precision mode for the image + /// color format. + /// + /// Only images with a bit depth greater than 8 should use a 16-bit /// quantization table. - /// - _ : JPEG.Table.Quantization.Precision + /// - _ : JPEG.Table.Quantization.Precision /// The precision mode of the quantization table. case invalidScanQuantizationPrecision(Table.Quantization.Precision) - + /// case JPEG.DecodingError.missingStartOfImage(_:) - /// The first marker segment in the image was not a start-of-image marker. - /// - _ : JPEG.Marker - /// The type indicator of the first encountered marker segment. + /// The first marker segment in the image was not a start-of-image marker. + /// - _ : JPEG.Marker + /// The type indicator of the first encountered marker segment. case missingStartOfImage(Marker) - /// case JPEG.DecodingError.duplicateStartOfImage + /// case JPEG.DecodingError.duplicateStartOfImage /// The decoder encountered more than one start-of-image marker. case duplicateStartOfImage - /// case JPEG.DecodingError.duplicateFrameHeaderSegment - /// The decoder encountered more than one frame header segment. - /// - /// JPEG files using the hierarchical coding process can encode more - /// than one frame header. However, this coding process is not currently + /// case JPEG.DecodingError.duplicateFrameHeaderSegment + /// The decoder encountered more than one frame header segment. + /// + /// JPEG files using the hierarchical coding process can encode more + /// than one frame header. However, this coding process is not currently /// supported. case duplicateFrameHeaderSegment - /// case JPEG.DecodingError.prematureScanHeaderSegment - /// The decoder encountered a scan header segment before a frame header + /// case JPEG.DecodingError.prematureScanHeaderSegment + /// The decoder encountered a scan header segment before a frame header /// segment. case prematureScanHeaderSegment - /// case JPEG.DecodingError.missingHeightRedefinitionSegment - /// The decoder did not encounter the height redefinition segment that + /// case JPEG.DecodingError.missingHeightRedefinitionSegment + /// The decoder did not encounter the height redefinition segment that /// must follow the first scan of an image with a declared height of 0. case missingHeightRedefinitionSegment - /// case JPEG.DecodingError.prematureHeightRedefinitionSegment - /// The decoder encountered a height redefinition segment before the + /// case JPEG.DecodingError.prematureHeightRedefinitionSegment + /// The decoder encountered a height redefinition segment before the /// first image scan. case prematureHeightRedefinitionSegment - /// case JPEG.DecodingError.unexpectedHeightRedefinitionSegment - /// The decoder encountered a height redefinition segment after, but + /// case JPEG.DecodingError.unexpectedHeightRedefinitionSegment + /// The decoder encountered a height redefinition segment after, but /// not immediately after the first image scan. case unexpectedHeightRedefinitionSegment - /// case JPEG.DecodingError.unexpectedRestart - /// The decoder encountered a restart marker outside of an entropy-coded + /// case JPEG.DecodingError.unexpectedRestart + /// The decoder encountered a restart marker outside of an entropy-coded /// segment. case unexpectedRestart - /// case JPEG.DecodingError.prematureEndOfImage - /// The decoder encountered an end-of-image marker before encountering + /// case JPEG.DecodingError.prematureEndOfImage + /// The decoder encountered an end-of-image marker before encountering /// a frame header segment. case prematureEndOfImage - + /// case JPEG.DecodingError.unsupportedFrameCodingProcess(_:) - /// The image coding process was anything other than - /// [`(Process).baseline`], or [`(Process).extended(coding:differential:)`] + /// The image coding process was anything other than + /// [`(Process).baseline`], or [`(Process).extended(coding:differential:)`] /// and [`(Process).progressive(coding:differential:)`] with [`(Process.Coding).huffman`] /// coding and `differential` set to `false`. - /// - _ : JPEG.Process + /// - _ : JPEG.Process /// The coding process used by the image. case unsupportedFrameCodingProcess(Process) - /// case JPEG.DecodingError.unrecognizedColorFormat(_:_:_:) - /// A [`(Format).recognize(_:precision:)`] implementation failed to + /// case JPEG.DecodingError.unrecognizedColorFormat(_:_:_:) + /// A [`(Format).recognize(_:precision:)`] implementation failed to /// recognize the component set and bit precision in a frame header. /// - _ : Swift.Set /// The set of resident component keys read from the frame header. - /// - _ : Swift.Int + /// - _ : Swift.Int /// The bit precision read from the frame header. - /// - _ : Swift.Any.Type + /// - _ : Swift.Any.Type /// The [`Format`] type that tried to detect the color format. case unrecognizedColorFormat(Set, Int, Any.Type) - + /// static var JPEG.DecodingError.namespace: Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns the string `"decoding error"`. - public static - var namespace:String + public static + var namespace:String { - "decoding error" + "decoding error" } /// var JPEG.DecodingError.message : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a basic description of this decoding error. - public - var message:String + public + var message:String { - switch self + switch self { case .truncatedEntropyCodedSegment: return "truncated entropy coded segment bitstream" - + case .invalidSpectralSelectionProgression: return "invalid spectral selection progression" case .invalidSuccessiveApproximationProgression: return "invalid successive approximation progression" - + case .invalidRestartPhase: return "invalid restart phase" case .missingRestartIntervalSegment: return "missing restart interval segment" - + case .invalidCompositeValue: return "invalid composite value" case .invalidCompositeBlockRun: return "invalid composite end-of-band run length" - + case .undefinedScanComponentReference: return "undefined component reference" case .invalidScanSamplingVolume: @@ -712,7 +712,7 @@ extension JPEG return "undefined quantization table reference" case .invalidScanQuantizationPrecision: return "quantization table precision mismatch" - + case .missingStartOfImage: return "missing start-of-image marker" case .duplicateStartOfImage: @@ -731,7 +731,7 @@ extension JPEG return "unexpected restart marker" case .prematureEndOfImage: return "premature end-of-image marker" - + case .unsupportedFrameCodingProcess: return "unsupported encoding process" case .unrecognizedColorFormat: @@ -739,31 +739,31 @@ extension JPEG } } /// var JPEG.DecodingError.details : Swift.String? { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a detailed description of this decoding error, if available. - public - var details:String? + public + var details:String? { - switch self + switch self { case .truncatedEntropyCodedSegment: return "not enough data in entropy coded segment bitstream" - + case .invalidRestartPhase(let phase, expected: let expected): return "decoded restart phase (\(phase)) is not the expected phase (\(expected))" case .missingRestartIntervalSegment: return "encountered restart segments, but no restart interval has been defined" - + case .invalidSpectralSelectionProgression(let band, let ci): return "frequency band \(band.lowerBound) ..< \(band.upperBound) for component \(ci) is not allowed" case .invalidSuccessiveApproximationProgression(let bits, let a, z: let z, let ci): return "bits \(bits.lowerBound)\(bits.upperBound == .max ? "..." : " \(bits.upperBound)") for component \(ci) cannot refine bit \(a) of coefficient \(z)" - + case .invalidCompositeValue(let value, expected: let expected): return "magnitude-tail encoded value (\(value)) must be within \(expected.lowerBound) ... \(expected.upperBound)" case .invalidCompositeBlockRun(let value, expected: let expected): return "magnitude-tail encoded end-of-band run length (\(value)) must be within \(expected.lowerBound) ... \(expected.upperBound)" - + case .undefinedScanComponentReference(let ci, let defined): return "component with index (\(ci)) is not one of the components \(defined.sorted()) defined in frame header" case .invalidScanSamplingVolume(let volume): @@ -776,7 +776,7 @@ extension JPEG return "no quantization table has been installed at the location <\(String.init(selector: selector))>" case .invalidScanQuantizationPrecision(let precision): return "quantization table has invalid integer type (\(precision))" - + case .missingStartOfImage: return "start-of-image marker must be the first marker in image" case .duplicateStartOfImage: @@ -793,7 +793,7 @@ extension JPEG return "restart marker can only follow an entropy-coded segment" case .prematureEndOfImage: return "premature end-of-image marker" - + case .unsupportedFrameCodingProcess(let process): return "frame coding process (\(process)) is not supported" case .unrecognizedColorFormat(let components, let precision, let type): @@ -803,131 +803,131 @@ extension JPEG } } -extension JPEG +extension JPEG { /// enum JPEG.FormattingError - /// : JPEG.Error + /// : JPEG.Error /// A formatting error. /// # [See also](error-types) /// ## (error-types) /// ## (error-handling) - public - enum FormattingError:JPEG.Error + public + enum FormattingError:JPEG.Error { - /// case JPEG.FormattingError.invalidDestination + /// case JPEG.FormattingError.invalidDestination /// The formatter could not write data to its destination stream. case invalidDestination /// static var JPEG.FormattingError.namespace: Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns the string `"formatting error"`. - public static - var namespace:String + public static + var namespace:String { "formatting error" } /// var JPEG.FormattingError.message : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a basic description of this formatting error. - public - var message:String + public + var message:String { - switch self + switch self { case .invalidDestination: return "failed to write to destination" - } + } } /// var JPEG.FormattingError.details : Swift.String? { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a detailed description of this formatting error, if available. - public - var details:String? + public + var details:String? { - switch self + switch self { case .invalidDestination: return nil - } + } } } /// enum JPEG.SerializingError - /// : JPEG.Error + /// : JPEG.Error /// A serializing error. - /// + /// /// This enumeration currently has no cases. /// # [See also](error-types) /// ## (error-types) /// ## (error-handling) - public - enum SerializingError:JPEG.Error + public + enum SerializingError:JPEG.Error { /// static var JPEG.SerializingError.namespace: Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns the string `"serializing error"`. - public static - var namespace:String + public static + var namespace:String { "serializing error" } /// var JPEG.SerializingError.message : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a basic description of this serializing error. - public - var message:String + public + var message:String { - switch self + switch self { - } + } } /// var JPEG.SerializingError.details : Swift.String? { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a detailed description of this serializing error, if available. - public - var details:String? + public + var details:String? { - switch self + switch self { - } + } } } /// enum JPEG.EncodingError - /// : JPEG.Error + /// : JPEG.Error /// An encoding error. - /// + /// /// This enumeration currently has no cases. /// # [See also](error-types) /// ## (error-types) /// ## (error-handling) - public - enum EncodingError:JPEG.Error + public + enum EncodingError:JPEG.Error { /// static var JPEG.EncodingError.namespace : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns the string `"encoding error"`. - public static - var namespace:String + public static + var namespace:String { "encoding error" } /// var JPEG.EncodingError.message : Swift.String { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a basic description of this encoding error. - public - var message:String + public + var message:String { - switch self + switch self { - } + } } /// var JPEG.EncodingError.details : Swift.String? { get } - /// ?: JPEG.Error + /// ?: JPEG.Error /// Returns a detailed description of this encoding error, if available. - public - var details:String? + public + var details:String? { - switch self + switch self { - } + } } } } diff --git a/sources/jpeg/jpeg.swift b/Sources/JPEG/jpeg.swift similarity index 75% rename from sources/jpeg/jpeg.swift rename to Sources/JPEG/jpeg.swift index fd187554..d84832fe 100644 --- a/sources/jpeg/jpeg.swift +++ b/Sources/JPEG/jpeg.swift @@ -2,100 +2,100 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -/// module JPEG +/// module JPEG /// Decode, inspect, edit, and encode JPEG images. -/// +/// /// See example programs and library tutorials [here](https://github.com/kelvin13/jpeg/tree/master/examples). /// # [Top level namespaces](top-level-namespaces) /// protocol JPEG.Format -/// A color format, determined by the bit depth and set of component keys in -/// a frame header. -/// -/// The coding [`(JPEG).Process`] of an image may place restrictions on which +/// A color format, determined by the bit depth and set of component keys in +/// a frame header. +/// +/// The coding [`(JPEG).Process`] of an image may place restrictions on which /// combinations of component sets and bit precisions are valid. /// # [See also](color-protocols) /// ## (color-protocols) /// ## (0:color-space-apis) -public +public protocol _JPEGFormat { /// static func JPEG.Format.recognize(_:precision:) - /// required + /// required /// Detects this color format, given a set of component keys and a bit depth. - /// + /// /// - components : Swift.Set /// The set of given component keys. /// - precision : Swift.Int /// The given bit depth. /// - -> : Self? /// A color format instance. - static + static func recognize(_ components:Set, precision:Int) -> Self? - + /// var JPEG.Format.components : [JPEG.Component.Key] {get} - /// required - /// The set of component keys for this color format. - /// - /// The ordering is used to determine plane index assignments when initializing - /// an image layout. This property should never be empty. It is allowed - /// for this array to contain fewer components than were detected by the + /// required + /// The set of component keys for this color format. + /// + /// The ordering is used to determine plane index assignments when initializing + /// an image layout. This property should never be empty. It is allowed + /// for this array to contain fewer components than were detected by the /// [`(Format).recognize(_:precision:)`] constructor. var components:[JPEG.Component.Key] { - get + get } - + /// var JPEG.Format.precision : Swift.Int {get} - /// required + /// required /// The bit depth of each component in this color format. - var precision:Int + var precision:Int { - get + get } } -/// protocol JPEG.Color +/// protocol JPEG.Color /// A color target. /// # [See also](color-protocols) /// ## (color-protocols) /// ## (0:color-space-apis) -public +public protocol _JPEGColor { /// associatedtype JPEG.Color.Format : JPEG.Format - /// The color format associated with this color target. An image using + /// The color format associated with this color target. An image using /// any color format of this type will support rendering to this color target. - associatedtype Format:JPEG.Format - + associatedtype Format:JPEG.Format + /// static func JPEG.Color.unpack(_:of:) /// required /// Converts the given interleaved samples into an array of structured pixels. - /// + /// /// - interleaved : [Swift.UInt16] /// A flat array of interleaved component samples. /// - format : Format /// The color format of the interleaved input. /// - -> : [Self] /// An array of pixels of this color target type. - static + static func unpack(_ interleaved:[UInt16], of format:Format) -> [Self] - + /// static func JPEG.Color.pack(_:as:) /// required /// Converts the given array of structured pixels into an array of interleaved samples. - /// + /// /// - pixels : [Self] /// An array of pixels of this color target type. /// - format : Format /// The color format of the interleaved output. /// - -> : [Swift.UInt16] /// A flat array of interleaved component samples. - static + static func pack(_ pixels:[Self], as format:Format) -> [UInt16] } -/// enum JPEG -/// A namespace for JPEG-related functionality. +/// enum JPEG +/// A namespace for JPEG-related functionality. /// # [Color spaces](color-space-apis) /// # [Image metadata](metadata-types) /// # [Image headers](header-types-and-namespace) @@ -109,20 +109,20 @@ protocol _JPEGColor /// # [Error handling](error-handling) /// # [See also](top-level-namespaces) /// ## (0:top-level-namespaces) -public -enum JPEG +public +enum JPEG { - public + public typealias Format = _JPEGFormat - public + public typealias Color = _JPEGColor - + /// enum JPEG.Metadata /// A metadata record. /// # [Metadata types](metadata-types) /// ## (metadata-types) - public - enum Metadata + public + enum Metadata { /// case JPEG.Metadata.jfif(_:) /// A JFIF metadata record. @@ -142,106 +142,106 @@ enum JPEG /// case JPEG.Metadata.comment(data:) /// A comment segment. /// - data : Swift.Array - /// The raw contents of this comment segment. Often, but not always, + /// The raw contents of this comment segment. Often, but not always, /// this data is UTF-8-encoded text. case comment(data:[UInt8]) } - - /// struct JPEG.YCbCr + + /// struct JPEG.YCbCr /// : Swift.Hashable /// : JPEG.Color /// @ frozen - /// An 8-bit YCbCr color. - /// + /// An 8-bit YCbCr color. + /// /// This type is a color target for the built-in [`JPEG.Common`] color format. /// # [Color channels](JPEG-YCbCr-color-channels) /// # [See also](builtin-color-targets) /// ## (builtin-color-targets) /// ## (2:color-space-apis) - @frozen - public - struct YCbCr:Hashable + @frozen + public + struct YCbCr:Hashable { /// var JPEG.YCbCr.y : Swift.UInt8 - /// The luminance component of this color. + /// The luminance component of this color. /// ## (0:JPEG-YCbCr-color-channels) - public - var y:UInt8 + public + var y:UInt8 /// var JPEG.YCbCr.cb : Swift.UInt8 - /// The blue component of this color. + /// The blue component of this color. /// ## (1:JPEG-YCbCr-color-channels) - public - var cb:UInt8 + public + var cb:UInt8 /// var JPEG.YCbCr.cr : Swift.UInt8 - /// The red component of this color. + /// The red component of this color. /// ## (2:JPEG-YCbCr-color-channels) - public - var cr:UInt8 - + public + var cr:UInt8 + /// init JPEG.YCbCr.init(y:) /// Initializes this color to the given luminance level. - /// + /// /// The Cb and Cr channels will be initialized to 128. /// - y : Swift.UInt8 /// The given luminance level. - public - init(y:UInt8) + public + init(y:UInt8) { self.init(y: y, cb: 128, cr: 128) } - + /// init JPEG.YCbCr.init(y:cb:cr:) /// Initializes this color to the given YCbCr triplet. - /// + /// /// - y : Swift.UInt8 /// The given luminance component. /// - cb: Swift.UInt8 /// The given blue component. /// - cr: Swift.UInt8 /// The given red component. - public - init(y:UInt8, cb:UInt8, cr:UInt8) + public + init(y:UInt8, cb:UInt8, cr:UInt8) { - self.y = y - self.cb = cb - self.cr = cr + self.y = y + self.cb = cb + self.cr = cr } } - /// struct JPEG.RGB + /// struct JPEG.RGB /// : Swift.Hashable /// : JPEG.Color /// @ frozen - /// An 8-bit RGB color. - /// + /// An 8-bit RGB color. + /// /// This type is a color target for the built-in [`JPEG.Common`] color format. /// # [Color channels](JPEG-RGB-color-channels) /// # [See also](builtin-color-targets) /// ## (builtin-color-targets) /// ## (2:color-space-apis) @frozen - public - struct RGB:Hashable + public + struct RGB:Hashable { /// var JPEG.RGB.r : Swift.UInt8 - /// The red component of this color. + /// The red component of this color. /// ## (JPEG-RGB-color-channels) public var r:UInt8 /// var JPEG.RGB.g : Swift.UInt8 - /// The green component of this color. + /// The green component of this color. /// ## (JPEG-RGB-color-channels) public var g:UInt8 /// var JPEG.RGB.b : Swift.UInt8 - /// The blue component of this color. + /// The blue component of this color. /// ## (JPEG-RGB-color-channels) public var b:UInt8 - + /// init JPEG.RGB.init(_:) - /// Creates an opaque grayscale color with all color components set + /// Creates an opaque grayscale color with all color components set /// to the given value sample. - /// + /// /// - value : Swift.UInt8 /// The value to initialize all color components to. public @@ -249,10 +249,10 @@ enum JPEG { self.init(value, value, value) } - + /// init JPEG.RGB.init(_:_:_:) /// Creates an opaque color with the given color samples. - /// + /// /// - red : Swift.UInt8 /// The value to initialize the red component to. /// - green : Swift.UInt8 @@ -262,76 +262,76 @@ enum JPEG public init(_ red:UInt8, _ green:UInt8, _ blue:UInt8) { - self.r = red - self.g = green + self.r = red + self.g = green self.b = blue } - } + } } -// pixel accessors -extension JPEG +// pixel accessors +extension JPEG { - /// enum JPEG.Common - /// : JPEG.Format - /// A built-in color format which covers the JFIF/EXIF subset of the + /// enum JPEG.Common + /// : JPEG.Format + /// A built-in color format which covers the JFIF/EXIF subset of the /// [JPEG standard](https://www.w3.org/Graphics/JPEG/itu-t81.pdf). - /// + /// /// This color format is able to recognize conforming JFIF and EXIF images, /// which use the component key assignments *Y*\ =\ **1**, *Cb*\ =\ **2**, *Cr*\ =\ **3**. - /// To provide compatibility with older, faulty JPEG codecs, it is also - /// able to recognize non-standard component schemes as long as + /// To provide compatibility with older, faulty JPEG codecs, it is also + /// able to recognize non-standard component schemes as long as /// they have the correct arity and form a contiguously increasing sequence. /// # [Standardized formats](common-standard-formats) /// # [Compatibility formats](common-nonstandard-formats) /// # [See also](color-protocols) /// ## (color-protocols) /// ## (1:color-space-apis) - public - enum Common + public + enum Common { - /// case JPEG.Common.y8 + /// case JPEG.Common.y8 /// The standard JFIF 8-bit grayscale format. - /// - /// This color format uses the component key assignment *Y*\ =\ **1**. - /// Note that images using this format are compliant JFIF images, but + /// + /// This color format uses the component key assignment *Y*\ =\ **1**. + /// Note that images using this format are compliant JFIF images, but /// are *not* compliant EXIF images. /// # [See also](common-standard-formats) /// ## (common-standard-formats) case y8 - /// case JPEG.Common.ycc8 + /// case JPEG.Common.ycc8 /// The standard JFIF/EXIF 8-bit YCbCr format. - /// - /// This color format uses the component key assignments *Y*\ =\ **1**, + /// + /// This color format uses the component key assignments *Y*\ =\ **1**, /// *Cb*\ =\ **2**, *Cr*\ =\ **3**. /// # [See also](common-standard-formats) /// ## (common-standard-formats) case ycc8 /// case JPEG.Common.nonconforming1x8(_:) /// A non-standard 8-bit grayscale format. - /// - /// This color format can use any component key assignment of arity 1. - /// Note that images using this format are valid JPEG images, but are + /// + /// This color format can use any component key assignment of arity 1. + /// Note that images using this format are valid JPEG images, but are /// not compliant JFIF or EXIF images, and some viewers may not support them. - /// - _ : JPEG.Component.Key + /// - _ : JPEG.Component.Key /// The component key interpreted as the luminance component. /// # [See also](common-nonstandard-formats) /// ## (common-nonstandard-formats) case nonconforming1x8(JPEG.Component.Key) /// case JPEG.Common.nonconforming3x8(_:_:_:) /// A non-standard 8-bit YCbCr format. - /// - /// This color format can use any contiguously increasing sequence of - /// component key assignments of arity 3. For example, it can use the - /// assignments *Y*\ =\ **0**, *Cb*\ =\ **1**, *Cr*\ =\ **2**, or the assignments + /// + /// This color format can use any contiguously increasing sequence of + /// component key assignments of arity 3. For example, it can use the + /// assignments *Y*\ =\ **0**, *Cb*\ =\ **1**, *Cr*\ =\ **2**, or the assignments /// *Y*\ =\ **2**, *Cb*\ =\ **3**, *Cr*\ =\ **4**. - /// Note that images using this format are valid JPEG images, but are + /// Note that images using this format are valid JPEG images, but are /// not compliant JFIF or EXIF images, and some viewers may not support them. - /// - _ : JPEG.Component.Key + /// - _ : JPEG.Component.Key /// The component key interpreted as the luminance component. - /// - _ : JPEG.Component.Key + /// - _ : JPEG.Component.Key /// The component key interpreted as the blue component. - /// - _ : JPEG.Component.Key + /// - _ : JPEG.Component.Key /// The component key interpreted as the red component. /// # [See also](common-nonstandard-formats) /// ## (common-nonstandard-formats) @@ -340,72 +340,72 @@ extension JPEG } extension JPEG.Common:JPEG.Format { - fileprivate static + fileprivate static func clamp(_ x:Float, to _:T.Type) -> T where T:FixedWidthInteger { .init(max(.init(T.min), min(x, .init(T.max)))) } - fileprivate static + fileprivate static func clamp(_ x:SIMD3, to _:T.Type) -> SIMD3 where T:FixedWidthInteger { .init(x.clamped( - lowerBound: .init(repeating: .init(T.min)), + lowerBound: .init(repeating: .init(T.min)), upperBound: .init(repeating: .init(T.max)))) } - + /// static func JPEG.Common.recognize(_:precision:) - /// ?: JPEG.Format + /// ?: JPEG.Format /// Detects this color format, given a set of component keys and a bit depth. - /// - /// If this constructor detects a [`(Common).nonconforming3x8(_:_:_:)`] - /// color format, it will populate the associated values with the keys in + /// + /// If this constructor detects a [`(Common).nonconforming3x8(_:_:_:)`] + /// color format, it will populate the associated values with the keys in /// ascending order. /// - components : Swift.Set - /// Must be a numerically-contiguous set with one or three elements, or + /// Must be a numerically-contiguous set with one or three elements, or /// this constructor will return `nil`. - /// - precision : Swift.Int + /// - precision : Swift.Int /// Must be 8, or this constructor will return `nil`. /// - -> : Self? - public static - func recognize(_ components:Set, precision:Int) -> Self? + public static + func recognize(_ components:Set, precision:Int) -> Self? { let sorted:[JPEG.Component.Key] = components.sorted() - switch (sorted, precision) + switch (sorted, precision) { - case ([1], 8): + case ([1], 8): return .y8 - case ([1, 2, 3], 8): + case ([1, 2, 3], 8): return .ycc8 default: - break + break } - - if sorted.count == 1 + + if sorted.count == 1 { return .nonconforming1x8(sorted[0]) } - else if let base:Int = sorted.first?.value, - sorted.count == 3, + else if let base:Int = sorted.first?.value, + sorted.count == 3, sorted.map(\.value) == .init(base ..< base + 3) { return .nonconforming3x8(sorted[0], sorted[1], sorted[2]) } - else + else { return nil } } /// var JPEG.Common.components : [JPEG.Component.Key] {get} - /// ?: JPEG.Format - /// The set of component keys for this color format. - /// + /// ?: JPEG.Format + /// The set of component keys for this color format. + /// /// If this instance is a [`(Common).nonconforming3x8(_:_:_:)`] color format, - /// the array contains the component keys in the order they appear + /// the array contains the component keys in the order they appear /// in the instance’s associated values. - public - var components:[JPEG.Component.Key] + public + var components:[JPEG.Component.Key] { - switch self + switch self { case .y8: return [1] @@ -418,35 +418,35 @@ extension JPEG.Common:JPEG.Format } } /// var JPEG.Common.precision : Swift.Int {get} - /// ?: JPEG.Format - /// The bit depth of each component in this color format. - /// + /// ?: JPEG.Format + /// The bit depth of each component in this color format. + /// /// This value is always 8. - public - var precision:Int + public + var precision:Int { 8 } } extension JPEG.YCbCr { - /// var JPEG.YCbCr.rgb : JPEG.RGB - /// This color represented in the RGB color space. + /// var JPEG.YCbCr.rgb : JPEG.RGB + /// This color represented in the RGB color space. /// - /// This property applies the YCbCr-to-RGB conversion formula defined in - /// the [JFIF standard](https://www.w3.org/Graphics/JPEG/jfif3.pdf). Some - /// YCbCr colors are not representable in the RGB color space; such colors + /// This property applies the YCbCr-to-RGB conversion formula defined in + /// the [JFIF standard](https://www.w3.org/Graphics/JPEG/jfif3.pdf). Some + /// YCbCr colors are not representable in the RGB color space; such colors /// will be clipped to the acceptable range. - public + public var rgb:JPEG.RGB { - let matrix:(cb:SIMD3, cr:SIMD3) = + let matrix:(cb:SIMD3, cr:SIMD3) = ( .init( 0.00000, -0.34414, 1.77200), .init( 1.40200, -0.71414, 0.00000) ) - let x:SIMD3 = (.init(self.y) as Float ) + - (matrix.cb * (.init(self.cb) - 128) as SIMD3) + + let x:SIMD3 = (.init(self.y) as Float ) + + (matrix.cb * (.init(self.cb) - 128) as SIMD3) + (matrix.cr * (.init(self.cr) - 128) as SIMD3) let c:SIMD3 = JPEG.Common.clamp(x, to: UInt8.self) return .init(c.x, c.y, c.z) @@ -454,31 +454,31 @@ extension JPEG.YCbCr } extension JPEG.RGB { - /// var JPEG.RGB.ycc : JPEG.YCbCr - /// This color represented in the YCbCr color space. + /// var JPEG.RGB.ycc : JPEG.YCbCr + /// This color represented in the YCbCr color space. /// - /// This property applies the RGB-to-YCbCr conversion formula defined in + /// This property applies the RGB-to-YCbCr conversion formula defined in /// the [JFIF standard](https://www.w3.org/Graphics/JPEG/jfif3.pdf). - public + public var ycc:JPEG.YCbCr { - let matrix:(SIMD3, r:SIMD3, g:SIMD3, b:SIMD3) = + let matrix:(SIMD3, r:SIMD3, g:SIMD3, b:SIMD3) = ( .init( 0, 128, 128 ), .init( 0.2990, -0.1687, 0.5000), .init( 0.5870, -0.3313, -0.4187), .init( 0.1140, 0.5000, -0.0813) ) - let x:SIMD3 = matrix.0 + - (matrix.r * .init(self.r) as SIMD3) + - (matrix.g * .init(self.g) as SIMD3) + + let x:SIMD3 = matrix.0 + + (matrix.r * .init(self.r) as SIMD3) + + (matrix.g * .init(self.g) as SIMD3) + (matrix.b * .init(self.b) as SIMD3) let c:SIMD3 = JPEG.Common.clamp(x, to: UInt8.self) return .init(y: c.x, cb: c.y, cr: c.z) } } -extension JPEG.YCbCr:JPEG.Color +extension JPEG.YCbCr:JPEG.Color { /// static func JPEG.YCbCr.unpack(_:of:) /// ?: JPEG.Color @@ -489,54 +489,54 @@ extension JPEG.YCbCr:JPEG.Color /// The color format of the interleaved input. /// - -> : [Self] /// An array of YCbCr pixels. - public static + public static func unpack(_ interleaved:[UInt16], of format:JPEG.Common) -> [Self] { - // no need to clamp uint16 to uint8,, the idct should have already done - // this alongside the level shift - switch format + // no need to clamp uint16 to uint8,, the idct should have already done + // this alongside the level shift + switch format { case .y8, .nonconforming1x8: - return interleaved.map + return interleaved.map { Self.init(y: .init($0)) } - + case .ycc8, .nonconforming3x8: - return stride(from: 0, to: interleaved.count, by: 3).map + return stride(from: 0, to: interleaved.count, by: 3).map { Self.init( - y: .init(interleaved[$0 ]), - cb: .init(interleaved[$0 + 1]), + y: .init(interleaved[$0 ]), + cb: .init(interleaved[$0 + 1]), cr: .init(interleaved[$0 + 2])) } } } /// static func JPEG.YCbCr.pack(_:as:) - /// ?: JPEG.Color + /// ?: JPEG.Color /// Converts the given array of YCbCr pixels into an array of interleaved samples. - /// + /// /// - pixels : [Self] /// An array of YCbCr pixels. /// - format : JPEG.Common /// The color format of the interleaved output. /// - -> : [Swift.UInt16] /// A flat array of interleaved component samples. - public static + public static func pack(_ pixels:[Self], as format:JPEG.Common) -> [UInt16] { - switch format + switch format { case .y8, .nonconforming1x8: return pixels.map{ .init($0.y) } - + case .ycc8, .nonconforming3x8: return pixels.flatMap{ [ .init($0.y), .init($0.cb), .init($0.cr) ] } } } } -extension JPEG.RGB:JPEG.Color +extension JPEG.RGB:JPEG.Color { /// static func JPEG.RGB.unpack(_:of:) /// ?: JPEG.Color @@ -547,194 +547,194 @@ extension JPEG.RGB:JPEG.Color /// The color format of the interleaved input. /// - -> : [Self] /// An array of RGB pixels. - public static + public static func unpack(_ interleaved:[UInt16], of format:JPEG.Common) -> [Self] { - switch format + switch format { case .y8, .nonconforming1x8: - return interleaved.map + return interleaved.map { let ycc:JPEG.YCbCr = .init(y: .init($0)) - return ycc.rgb + return ycc.rgb } - + case .ycc8, .nonconforming3x8: - return stride(from: 0, to: interleaved.count, by: 3).map + return stride(from: 0, to: interleaved.count, by: 3).map { let ycc:JPEG.YCbCr = .init( - y: .init(interleaved[$0 ]), - cb: .init(interleaved[$0 + 1]), + y: .init(interleaved[$0 ]), + cb: .init(interleaved[$0 + 1]), cr: .init(interleaved[$0 + 2])) - return ycc.rgb + return ycc.rgb } } } /// static func JPEG.RGB.pack(_:as:) - /// ?: JPEG.Color + /// ?: JPEG.Color /// Converts the given array of RGB pixels into an array of interleaved samples. - /// + /// /// - pixels : [Self] /// An array of RGB pixels. /// - format : JPEG.Common /// The color format of the interleaved output. /// - -> : [Swift.UInt16] /// A flat array of interleaved component samples. - public static + public static func pack(_ pixels:[Self], as format:JPEG.Common) -> [UInt16] { - switch format + switch format { case .y8, .nonconforming1x8: return pixels.map{ .init($0.ycc.y) } - + case .ycc8, .nonconforming3x8: return pixels.flatMap { - (rgb:JPEG.RGB) -> [UInt16] in - let ycc:JPEG.YCbCr = rgb.ycc - return [ .init(ycc.y), .init(ycc.cb), .init(ycc.cr) ] + (rgb:JPEG.RGB) -> [UInt16] in + let ycc:JPEG.YCbCr = rgb.ycc + return [ .init(ycc.y), .init(ycc.cb), .init(ycc.cr) ] } as [UInt16] } } } -// compound types -extension JPEG +// compound types +extension JPEG { - /// enum JPEG.Process + /// enum JPEG.Process /// A coding process. - /// + /// /// The [JPEG standard](https://www.w3.org/Graphics/JPEG/itu-t81.pdf) /// specifies several subformats of the JPEG format known as *coding processes*. - /// The library can recognize images using any coding process, but - /// only supports encoding and decoding images using the [`(Process).baseline`], - /// [`(Process).extended(coding:differential:)`], or - /// [`(Process).progressive(coding:differential:)`] processes with - /// [`(Process.Coding).huffman`] entropy coding and the `differential` flag + /// The library can recognize images using any coding process, but + /// only supports encoding and decoding images using the [`(Process).baseline`], + /// [`(Process).extended(coding:differential:)`], or + /// [`(Process).progressive(coding:differential:)`] processes with + /// [`(Process.Coding).huffman`] entropy coding and the `differential` flag /// set to `false`. /// # [Coding processes](coding-processes) /// ## (4:lexing-and-formatting) - public + public enum Process:Sendable { - /// enum JPEG.Process.Coding + /// enum JPEG.Process.Coding /// An entropy coding method. - public + public enum Coding:Sendable { /// case JPEG.Process.Coding.huffman /// Huffman entropy coding. - case huffman + case huffman /// case JPEG.Process.Coding.arithmetic /// Arithmetic entropy coding. - case arithmetic + case arithmetic } - + /// case JPEG.Process.baseline - /// The baseline coding process. - /// - /// This is a sequential coding process. It allows up to two simultaneously - /// referenced tables of each type. It can only be used with color formats + /// The baseline coding process. + /// + /// This is a sequential coding process. It allows up to two simultaneously + /// referenced tables of each type. It can only be used with color formats /// with a bit [`(JPEG.Format).precision`] of 8. /// ## (coding-processes) - case baseline + case baseline /// case JPEG.Process.extended(coding:differential:) - /// The extended coding process. - /// - /// This is a sequential coding process. It allows up to four simultaneously - /// referenced tables of each type. It can only be used with color formats + /// The extended coding process. + /// + /// This is a sequential coding process. It allows up to four simultaneously + /// referenced tables of each type. It can only be used with color formats /// with a bit [`(JPEG.Format).precision`] of 8 or 12. - /// - coding : Coding + /// - coding : Coding /// The entropy coding used by this coding process. - /// - differential : Swift.Bool - /// Indicates whether the image frame using this coding process is a + /// - differential : Swift.Bool + /// Indicates whether the image frame using this coding process is a /// differential frame under the hierarchical mode of operations. /// ## (coding-processes) case extended(coding:Coding, differential:Bool) /// case JPEG.Process.progressive(coding:differential:) - /// The progressive coding process. - /// - /// This is a progressive coding process. It allows up to four simultaneously - /// referenced tables of each type. It can only be used with color formats - /// with a bit [`(JPEG.Format).precision`] of 8 or 12, and no more than + /// The progressive coding process. + /// + /// This is a progressive coding process. It allows up to four simultaneously + /// referenced tables of each type. It can only be used with color formats + /// with a bit [`(JPEG.Format).precision`] of 8 or 12, and no more than /// four components. - /// - coding : Coding + /// - coding : Coding /// The entropy coding used by this coding process. - /// - differential : Swift.Bool - /// Indicates whether the image frame using this coding process is a + /// - differential : Swift.Bool + /// Indicates whether the image frame using this coding process is a /// differential frame under the hierarchical mode of operations. /// ## (coding-processes) case progressive(coding:Coding, differential:Bool) /// case JPEG.Process.lossless(coding:differential:) /// The lossless coding process. - /// - coding : Coding + /// - coding : Coding /// The entropy coding used by this coding process. - /// - differential : Swift.Bool - /// Indicates whether the image frame using this coding process is a + /// - differential : Swift.Bool + /// Indicates whether the image frame using this coding process is a /// differential frame under the hierarchical mode of operations. /// ## (coding-processes) case lossless(coding:Coding, differential:Bool) } - - /// enum JPEG.Marker + + /// enum JPEG.Marker /// A marker type indicator. /// ## (3:lexing-and-formatting) - public + public enum Marker:Sendable { - /// case JPEG.Marker.start + /// case JPEG.Marker.start /// A start-of-image (SOI) marker. case start - /// case JPEG.Marker.end + /// case JPEG.Marker.end /// An end-of-image (EOI) marker. case end - /// case JPEG.Marker.quantization + /// case JPEG.Marker.quantization /// A quantization table definition (DQT) segment. - case quantization + case quantization /// case JPEG.Marker.huffman /// A huffman table definition (DHT) segment. - case huffman - + case huffman + /// case JPEG.Marker.application(_:) /// An application data (APP~*n*~) segment. - /// - _ : Swift.Int + /// - _ : Swift.Int /// The application segment type code. This value can be from 0 to 15. case application(Int) /// case JPEG.Marker.restart(_:) /// A restart (RST~*m*~) marker. - /// - _ : Swift.Int + /// - _ : Swift.Int /// The restart phase. It cycles through the values 0 through 7. case restart(Int) /// case JPEG.Marker.height /// A height redefinition (DNL) segment. - case height + case height /// case JPEG.Marker.interval - /// A restart interval definition (DRI) segment. - case interval + /// A restart interval definition (DRI) segment. + case interval /// case JPEG.Marker.comment - /// A comment (COM) segment. - case comment - + /// A comment (COM) segment. + case comment + /// case JPEG.Marker.frame(_:) - /// A frame header (SOF\*) segment. + /// A frame header (SOF\*) segment. /// - _ : JPEG.Process /// The coding process used by the image frame. case frame(Process) /// case JPEG.Marker.scan - /// A scan header (SOS) segment. - case scan - + /// A scan header (SOS) segment. + case scan + /// case JPEG.Marker.arithmeticCodingCondition case arithmeticCodingCondition /// case JPEG.Marker.hierarchical - case hierarchical + case hierarchical /// case JPEG.Marker.expandReferenceComponents case expandReferenceComponents - - init?(code:UInt8) + + init?(code:UInt8) { - switch code + switch code { case 0xc0: self = .frame(.baseline) @@ -742,69 +742,69 @@ extension JPEG self = .frame(.extended (coding: .huffman, differential: false)) case 0xc2: self = .frame(.progressive(coding: .huffman, differential: false)) - + case 0xc3: self = .frame(.lossless (coding: .huffman, differential: false)) - + case 0xc4: self = .huffman - + case 0xc5: self = .frame(.extended (coding: .huffman, differential: true)) case 0xc6: self = .frame(.progressive(coding: .huffman, differential: true)) case 0xc7: self = .frame(.lossless (coding: .huffman, differential: true)) - + case 0xc8: // reserved - return nil - + return nil + case 0xc9: self = .frame(.extended (coding: .arithmetic, differential: false)) case 0xca: self = .frame(.progressive(coding: .arithmetic, differential: false)) case 0xcb: self = .frame(.lossless (coding: .arithmetic, differential: false)) - + case 0xcc: - self = .arithmeticCodingCondition - + self = .arithmeticCodingCondition + case 0xcd: self = .frame(.extended (coding: .arithmetic, differential: true)) case 0xce: self = .frame(.progressive(coding: .arithmetic, differential: true)) case 0xcf: self = .frame(.lossless (coding: .arithmetic, differential: true)) - + case 0xd0 ... 0xd7: self = .restart(.init(code & 0x0f)) - + case 0xd8: - self = .start + self = .start case 0xd9: - self = .end + self = .end case 0xda: - self = .scan + self = .scan case 0xdb: self = .quantization case 0xdc: - self = .height + self = .height case 0xdd: - self = .interval + self = .interval case 0xde: self = .hierarchical case 0xdf: - self = .expandReferenceComponents - + self = .expandReferenceComponents + case 0xe0 ... 0xef: self = .application(.init(code & 0x0f)) case 0xf0 ... 0xfd: - return nil + return nil case 0xfe: - self = .comment - + self = .comment + default: - return nil + return nil } } } @@ -814,456 +814,456 @@ extension JPEG /// Functionality common to all table types. /// # [See also](table-types-and-protocols) /// ## (table-types-and-protocols) -public -protocol _JPEGAnyTable +public +protocol _JPEGAnyTable { - /// associatedtype JPEG.AnyTable.Delegate + /// associatedtype JPEG.AnyTable.Delegate /// required /// A type representing a table instance while it is bound to a table slot. - associatedtype Delegate + associatedtype Delegate /// typealias JPEG.AnyTable.Slots = (Delegate?, Delegate?, Delegate?, Delegate?) - /// Four table slots. - /// - /// JPEG images are always limited to four table slots for each table type. + /// Four table slots. + /// + /// JPEG images are always limited to four table slots for each table type. /// Some coding processes may limit further the number of available table slots. typealias Slots = (Delegate?, Delegate?, Delegate?, Delegate?) /// typealias JPEG.AnyTable.Selector = Swift.WritableKeyPath /// A table selector. typealias Selector = WritableKeyPath } -extension JPEG +extension JPEG { - /// enum JPEG.Header + /// enum JPEG.Header /// A namespace for header types. - /// - /// In general, the fields in these types can be assumed to be valid with - /// respect to the other fields in the structure, but not necessarily with + /// + /// In general, the fields in these types can be assumed to be valid with + /// respect to the other fields in the structure, but not necessarily with /// respect to the JPEG file as a whole. /// # [Header types](header-types-and-namespace) /// ## (0:header-types-and-namespace) - public - enum Header + public + enum Header { - /// struct JPEG.Header.HeightRedefinition - /// A height redefinition header. - /// - /// This structure is the parsed form of a [`(JPEG.Marker).height`] - /// marker segment. + /// struct JPEG.Header.HeightRedefinition + /// A height redefinition header. + /// + /// This structure is the parsed form of a [`(JPEG.Marker).height`] + /// marker segment. /// ## (header-types-and-namespace) - public - struct HeightRedefinition + public + struct HeightRedefinition { - /// let JPEG.Header.HeightRedefinition.height : Swift.Int - /// The visible image height, in pixels. - /// + /// let JPEG.Header.HeightRedefinition.height : Swift.Int + /// The visible image height, in pixels. + /// /// This value is always positive. - public - let height:Int + public + let height:Int /// init JPEG.Header.HeightRedefinition.init(height:) /// Creates a height redefinition header. - /// - height : Swift.Int - /// The visible image height, in pixels. Passing a negative value + /// - height : Swift.Int + /// The visible image height, in pixels. Passing a negative value /// will result in a precondition failure. - public + public init(height:Int) { precondition(height > 0, "height must be positive") self.height = height } } - /// struct JPEG.Header.RestartInterval - /// A restart interval definition header. - /// - /// This structure is the parsed form of an [`(JPEG.Marker).interval`] - /// marker segment. It can modify or clear the restart interval of an + /// struct JPEG.Header.RestartInterval + /// A restart interval definition header. + /// + /// This structure is the parsed form of an [`(JPEG.Marker).interval`] + /// marker segment. It can modify or clear the restart interval of an /// image. /// ## (header-types-and-namespace) - public - struct RestartInterval + public + struct RestartInterval { /// let JPEG.Header.RestartInterval.interval : Swift.Int? - /// The restart interval, in minimum-coded units, or `nil` if the + /// The restart interval, in minimum-coded units, or `nil` if the /// header is meant to disable restart markers. - /// + /// /// This value is always positive or `nil`. - public - let interval:Int? + public + let interval:Int? /// init JPEG.Header.RestartInterval.init(interval:) - /// Creates a restart interval definition header. - /// - interval : Swift.Int? - /// The restart interval, in minimum-coded units, or `nil` to - /// disable restart markers. Passing a negative or zero value + /// Creates a restart interval definition header. + /// - interval : Swift.Int? + /// The restart interval, in minimum-coded units, or `nil` to + /// disable restart markers. Passing a negative or zero value /// will result in a precondition failure. - public + public init(interval:Int?) { precondition(interval.map{ $0 > 0 } ?? true, "interval must be positive or `nil`") self.interval = interval } } - /// struct JPEG.Header.Frame + /// struct JPEG.Header.Frame /// A frame header. - /// - /// This structure is the parsed form of a [`(JPEG.Marker).frame(_:)`] - /// marker segment. In non-hierarchical mode images, it defines global - /// image parameters. It contains some of the information needed to + /// + /// This structure is the parsed form of a [`(JPEG.Marker).frame(_:)`] + /// marker segment. In non-hierarchical mode images, it defines global + /// image parameters. It contains some of the information needed to /// fully-define an image [`(JPEG).Layout`]. /// ## (1:header-types-and-namespace) - public - struct Frame + public + struct Frame { - /// let JPEG.Header.Frame.process : JPEG.Process - /// The coding process used by the image. - public + /// let JPEG.Header.Frame.process : JPEG.Process + /// The coding process used by the image. + public let process:Process, - /// let JPEG.Header.Frame.precision : Swift.Int + /// let JPEG.Header.Frame.precision : Swift.Int /// The bit precision of this image. - precision:Int, + precision:Int, /// let JPEG.Header.Frame.size : (x:Swift.Int, y:Swift.Int) - /// The visible size of this image, in pixels. - /// - /// The width is always positive. The height can be either positive - /// or zero, if the height is to be defined later by a + /// The visible size of this image, in pixels. + /// + /// The width is always positive. The height can be either positive + /// or zero, if the height is to be defined later by a /// [`(JPEG.Header).HeightRedefinition`] header. size:(x:Int, y:Int) /// let JPEG.Header.Frame.components: [JPEG.Component.Key: JPEG.Component] /// The components in this image. - /// + /// /// This dictionary will always have at least one element. - public + public let components:[Component.Key: Component] } - /// struct JPEG.Header.Scan - /// A scan header. - /// - /// This structure is the parsed form of a [`(JPEG.Marker).scan`] - /// marker segment. It defines scan-level image parameters. The library - /// validates these structures against the global image parameters to + /// struct JPEG.Header.Scan + /// A scan header. + /// + /// This structure is the parsed form of a [`(JPEG.Marker).scan`] + /// marker segment. It defines scan-level image parameters. The library + /// validates these structures against the global image parameters to /// create the [`JPEG.Scan`] structures elsewhere in the library API. /// # [Creating sequential scans](scan-header-creation-sequential) /// # [Creating progressive scans](scan-header-creation-progressive) /// ## (1:header-types-and-namespace) - public + public struct Scan { /// let JPEG.Header.Scan.band : Swift.Range - /// The frequency band encoded by this scan. - /// + /// The frequency band encoded by this scan. + /// /// This property specifies a range of zigzag-indexed frequency coefficients. - /// It is always within the interval of 0 to 64. - public - let band:Range, - /// let JPEG.Header.Scan.bits : Swift.Range - /// The bit range encoded by this scan. - /// + /// It is always within the interval of 0 to 64. + public + let band:Range, + /// let JPEG.Header.Scan.bits : Swift.Range + /// The bit range encoded by this scan. + /// /// This range is always non-negative. - bits:Range, - /// let JPEG.Header.Scan.components : [JPEG.Scan.Component] - /// The color components in this scan, in the order in which their - /// data units are interleaved. - /// + bits:Range, + /// let JPEG.Header.Scan.components : [JPEG.Scan.Component] + /// The color components in this scan, in the order in which their + /// data units are interleaved. + /// /// This array always contains at least one element. - components:[JPEG.Scan.Component] + components:[JPEG.Scan.Component] } } - - public - typealias AnyTable = _JPEGAnyTable - /// enum JPEG.Table + + public + typealias AnyTable = _JPEGAnyTable + /// enum JPEG.Table /// A namespace for table types. /// # [Table types](table-types-and-protocols) /// ## (0:table-types-and-protocols) - public - enum Table + public + enum Table { /// typealias JPEG.Table.HuffmanDC = Huffman /// A DC huffman table. /// # [See also](huffman-table-types) /// ## (huffman-table-types) - public + public typealias HuffmanDC = Huffman /// typealias JPEG.Table.HuffmanAC = Huffman /// An AC huffman table. /// # [See also](huffman-table-types) /// ## (huffman-table-types) - public + public typealias HuffmanAC = Huffman - /// struct JPEG.Table.Huffman - /// : JPEG.AnyTable + /// struct JPEG.Table.Huffman + /// : JPEG.AnyTable /// where Symbol:JPEG.Bitstream.AnySymbol - /// A huffman table. + /// A huffman table. /// # [See also](huffman-table-types) /// ## (1:table-types-and-protocols) - public + public struct Huffman:AnyTable where Symbol:Bitstream.AnySymbol { - /// typealias JPEG.Table.Huffman.Delegate = Self + /// typealias JPEG.Table.Huffman.Delegate = Self /// ?: JPEG.AnyTable - public - typealias Delegate = Self - + public + typealias Delegate = Self + let symbols:[[Symbol]] var target:Selector - - // these are size parameters generated by the structural validator. - // we store them here as proof of tree validity, so that the - // constructor for the huffman Decoder type can just read it from here + + // these are size parameters generated by the structural validator. + // we store them here as proof of tree validity, so that the + // constructor for the huffman Decoder type can just read it from here let size:(n:Int, z:Int) } - /// struct JPEG.Table.Quantization - /// : JPEG.AnyTable + /// struct JPEG.Table.Quantization + /// : JPEG.AnyTable /// A quantization table. - /// - /// Quantization tables store 64 coefficient quanta. The quantum values - /// can be accessed using either a zigzag index with the [`(Quantization).[z:]`] + /// + /// Quantization tables store 64 coefficient quanta. The quantum values + /// can be accessed using either a zigzag index with the [`(Quantization).[z:]`] /// subscript, or grid indices with the [`(Quantization).[k:h:]`] subscript. /// ## (1:table-types-and-protocols) - public + public struct Quantization:AnyTable { - /// struct JPEG.Table.Quantization.Key - /// : Swift.Hashable - /// : Swift.Comparable + /// struct JPEG.Table.Quantization.Key + /// : Swift.Hashable + /// : Swift.Comparable /// : Swift.ExpressibleByIntegerLiteral /// A unique identifier assigned to each quantization table in an image. - /// - /// Quanta keys are numeric values ranging from [`Swift.Int`min`] - /// to [`Swift.Int`max`]. In these reference pages, quanta keys + /// + /// Quanta keys are numeric values ranging from [`Swift.Int`min`] + /// to [`Swift.Int`max`]. In these reference pages, quanta keys /// in their numerical representation are written in **boldface**. /// # [See also](key-types) /// ## (key-types) /// ## (0:image-quality) - public + public struct Key:Hashable, Comparable, Sendable { - let value:Int - - init(_ value:I) where I:BinaryInteger + let value:Int + + init(_ value:I) where I:BinaryInteger { self.value = .init(value) } - - public static - func < (lhs:Self, rhs:Self) -> Bool + + public static + func < (lhs:Self, rhs:Self) -> Bool { lhs.value < rhs.value } } - - /// typealias JPEG.Table.Quantization.Delegate = (q:Swift.Int, qi:JPEG.Table.Quantization.Key) + + /// typealias JPEG.Table.Quantization.Delegate = (q:Swift.Int, qi:JPEG.Table.Quantization.Key) /// ?: JPEG.AnyTable - public + public typealias Delegate = (q:Int, qi:Table.Quantization.Key) - /// enum JPEG.Table.Quantization.Precision + /// enum JPEG.Table.Quantization.Precision /// The integer width of the quantum values in this quantization table. - public + public enum Precision:Sendable { - /// case JPEG.Table.Quantization.Precision.uint8 + /// case JPEG.Table.Quantization.Precision.uint8 /// The quantum values are encoded as 8-bit unsigned integers. case uint8 - /// case JPEG.Table.Quantization.Precision.uint16 + /// case JPEG.Table.Quantization.Precision.uint16 /// The quantum values are encoded as big endian 16-bit unsigned integers. case uint16 } - - var storage:[UInt16], + + var storage:[UInt16], target:Selector let precision:Precision } } } -// layout -extension JPEG +// layout +extension JPEG { - /// struct JPEG.Component + /// struct JPEG.Component /// A color channel in an image. /// ## (1:image-structure-and-decomposition) - public + public struct Component { /// let JPEG.Component.factor : (x:Swift.Int, y:Swift.Int) /// The horizontal and vertical sampling factors for this component. - public + public let factor:(x:Int, y:Int) - /// let JPEG.Component.selector : JPEG.Table.Quantization.Selector + /// let JPEG.Component.selector : JPEG.Table.Quantization.Selector /// The table selector of the quantization table associated with this component. - public - let selector:Table.Quantization.Selector - /// struct JPEG.Component.Key - /// : Swift.Hashable - /// : Swift.Comparable + public + let selector:Table.Quantization.Selector + /// struct JPEG.Component.Key + /// : Swift.Hashable + /// : Swift.Comparable /// : Swift.ExpressibleByIntegerLiteral /// A unique identifier assigned to each color component in an image. - /// - /// Component keys are numeric values ranging from 0 to 255. In - /// these reference pages, component keys in their numerical + /// + /// Component keys are numeric values ranging from 0 to 255. In + /// these reference pages, component keys in their numerical /// representation are written in **boldface**. /// # [See also](key-types) /// ## (key-types) /// ## (2:image-structure-and-decomposition) - public + public struct Key:Hashable, Comparable, Sendable { - let value:Int - - init(_ value:I) where I:BinaryInteger + let value:Int + + init(_ value:I) where I:BinaryInteger { self.value = .init(value) } - - public static - func < (lhs:Self, rhs:Self) -> Bool + + public static + func < (lhs:Self, rhs:Self) -> Bool { lhs.value < rhs.value } } } - - /// struct JPEG.Scan - /// An image scan. - /// - /// Depending on the coding process used by the image, a scan may encode + + /// struct JPEG.Scan + /// An image scan. + /// + /// Depending on the coding process used by the image, a scan may encode /// a select frequency band, range of bits, and subset of color components. - /// - /// This type contains essentially the same information as [`(JPEG).Header.Scan`], - /// but has been validated against the global image parameters and has its + /// + /// This type contains essentially the same information as [`(JPEG).Header.Scan`], + /// but has been validated against the global image parameters and has its /// component keys pre-resolved to integer indices. /// ## (4:image-structure-and-decomposition) - public + public struct Scan { - /// struct JPEG.Scan.Component - /// A descriptor for a component encoded within a scan. + /// struct JPEG.Scan.Component + /// A descriptor for a component encoded within a scan. /// ## (5:image-structure-and-decomposition) - public - struct Component + public + struct Component { - /// let JPEG.Scan.Component.ci : JPEG.Component.Key + /// let JPEG.Scan.Component.ci : JPEG.Component.Key /// The key specifying the image component referenced by this descriptor. - public + public let ci:JPEG.Component.Key /// let JPEG.Scan.Component.selector : (dc:JPEG.Table.HuffmanDC.Selector, ac:JPEG.Table.HuffmanAC.Selector) - /// The table selectors for the huffman tables associated with this + /// The table selectors for the huffman tables associated with this /// component in the context of this scan. /// - /// An image component may use different huffman - /// tables in different scans. (In contrast, quantization - /// table assignments are global to the file.) The DC table is - /// used to encode or decode coefficient zero; the AC table is used - /// for all other frequency coefficients. Depending on the band - /// and bit range encoded by the image scan, one or both of the - /// huffman table selectors may be unused, and therefore may not + /// An image component may use different huffman + /// tables in different scans. (In contrast, quantization + /// table assignments are global to the file.) The DC table is + /// used to encode or decode coefficient zero; the AC table is used + /// for all other frequency coefficients. Depending on the band + /// and bit range encoded by the image scan, one or both of the + /// huffman table selectors may be unused, and therefore may not /// need to reference valid tables. - public + public let selector:(dc:Table.HuffmanDC.Selector, ac:Table.HuffmanAC.Selector) } - - /// let JPEG.Scan.band : Swift.Range - /// The frequency band encoded by this scan. - /// + + /// let JPEG.Scan.band : Swift.Range + /// The frequency band encoded by this scan. + /// /// This property specifies a range of zigzag-indexed frequency coefficients. - /// It is always within the interval of 0 to 64. If the image coding - /// process is not [`(Process).progressive(coding:differential:)`], + /// It is always within the interval of 0 to 64. If the image coding + /// process is not [`(Process).progressive(coding:differential:)`], /// this value will be `0 ..< 64`. - public - let band:Range, - /// let JPEG.Scan.bits : Swift.Range - /// The bit range encoded by this scan. - /// - /// This property specifies a range of bit indices, where bit zero is - /// the least significant bit. The upper range bound is always either + public + let band:Range, + /// let JPEG.Scan.bits : Swift.Range + /// The bit range encoded by this scan. + /// + /// This property specifies a range of bit indices, where bit zero is + /// the least significant bit. The upper range bound is always either /// infinity ([`Swift.Int`max`]) or one greater than the lower bound. - /// If the image coding process is not [`(Process).progressive(coding:differential:)`], + /// If the image coding process is not [`(Process).progressive(coding:differential:)`], /// this value will be `0 ..< .max`. - bits:Range, - /// let JPEG.Scan.components : [(c:Swift.Int, component:Component)] - /// The color components in this scan, in the order in which their - /// data units are interleaved. - /// - /// The component descriptors are paired with resolved component indices - /// which are equivalent to the index of the image plane storing that + bits:Range, + /// let JPEG.Scan.components : [(c:Swift.Int, component:Component)] + /// The color components in this scan, in the order in which their + /// data units are interleaved. + /// + /// The component descriptors are paired with resolved component indices + /// which are equivalent to the index of the image plane storing that /// color channel. This array will always have at least one element. - components:[(c:Int, component:Component)] + components:[(c:Int, component:Component)] // restrict access for synthesized init - fileprivate - init(band:Range, bits:Range, components:[(c:Int, component:Component)]) + fileprivate + init(band:Range, bits:Range, components:[(c:Int, component:Component)]) { - self.band = band - self.bits = bits - self.components = components + self.band = band + self.bits = bits + self.components = components } } - - /// struct JPEG.Layout - /// where Format:JPEG.Format - /// A specification of the components, coding process, table assignments, + + /// struct JPEG.Layout + /// where Format:JPEG.Format + /// A specification of the components, coding process, table assignments, /// and scan progression of an image. /// - /// This structure records both the *recognized components* and - /// the *resident components* in an image. We draw this distinction because - /// the [`(Layout).planes`] property is allowed to include definitions for components + /// This structure records both the *recognized components* and + /// the *resident components* in an image. We draw this distinction because + /// the [`(Layout).planes`] property is allowed to include definitions for components /// that are not part of [`(Layout).format``(Format).components`]. - /// Such components will not recieve a plane in the [`(JPEG).Data`] types, - /// but will be ignored by the scan decoder without errors. + /// Such components will not recieve a plane in the [`(JPEG).Data`] types, + /// but will be ignored by the scan decoder without errors. /// - /// Non-recognized components can only occur in images decoded from JPEG files, - /// and only when using a custom [`(JPEG).Format`] type, as the built-in - /// [`JPEG.Common`] color format will never accept any component declaration - /// in a frame header that it does not also recognize. When encoding images to JPEG + /// Non-recognized components can only occur in images decoded from JPEG files, + /// and only when using a custom [`(JPEG).Format`] type, as the built-in + /// [`JPEG.Common`] color format will never accept any component declaration + /// in a frame header that it does not also recognize. When encoding images to JPEG /// files, all declared resident components must also be recognized components. /// # [Creating a layout](layout-creation) /// # [Image modes](layout-image-format) /// # [Component membership](layout-component-membership) /// # [Image structure](layout-image-structure) /// ## (0:image-structure-and-decomposition) - public - struct Layout where Format:JPEG.Format + public + struct Layout where Format:JPEG.Format { - /// let JPEG.Layout.format : Format + /// let JPEG.Layout.format : Format /// The color format of the image. /// ## (layout-image-format) - public - let format:Format - /// let JPEG.Layout.process : JPEG.Process + public + let format:Format + /// let JPEG.Layout.process : JPEG.Process /// The coding process used by the image. /// ## (layout-image-format) - public + public let process:Process - + /// let JPEG.Layout.residents : [JPEG.Component.Key: Swift.Int] - /// The set of color components declared (or to-be-declared) in the + /// The set of color components declared (or to-be-declared) in the /// image frame header. - /// - /// The dictionary values are indices to be used with the [`planes`] property + /// + /// The dictionary values are indices to be used with the [`planes`] property /// on this type. /// # [See also](layout-component-membership) /// ## (layout-component-membership) - public + public let residents:[Component.Key: Int] /// var JPEG.Layout.recognized : [JPEG.Component.Key] { get } - /// The set of color components in the color format of this image. + /// The set of color components in the color format of this image. /// This set is always a subset of the resident components in the image. /// # [See also](layout-component-membership) /// ## (layout-component-membership) - public - var recognized:[Component.Key] + public + var recognized:[Component.Key] { - self.format.components + self.format.components } /// var JPEG.Layout.planes : [(component:JPEG.Component, qi:JPEG.Table.Quantization.Key)] { get } - /// The descriptor array for the planes in the image. + /// The descriptor array for the planes in the image. /// - /// Each descriptor consists of a [`JPEG.Component`] instance and a - /// quantization table key. On layout initialization, the library will + /// Each descriptor consists of a [`JPEG.Component`] instance and a + /// quantization table key. On layout initialization, the library will /// automatically assign table keys to table selectors. /// - /// The ordering of the first *k* array elements follows the order that - /// the component keys appear in the [`recognized`] property, where - /// *k* is the number of components in the image. Any - /// non-recognized resident components will occur at the end of this - /// array, can can be indexed using the values of the [`residents`] + /// The ordering of the first *k* array elements follows the order that + /// the component keys appear in the [`recognized`] property, where + /// *k* is the number of components in the image. Any + /// non-recognized resident components will occur at the end of this + /// array, can can be indexed using the values of the [`residents`] /// dictionary. /// ## (layout-image-structure) public internal(set) @@ -1271,229 +1271,229 @@ extension JPEG /// var JPEG.Layout.definitions : [(quanta:[JPEG.Table.Quantization.Key], scans:[JPEG.Scan])] { get } /// The sequence of scan and table definitions in the image file. /// - /// The definitions in this property are given as alternating runs - /// of quantization tables and image scans. (Image layouts do not specify - /// huffman table definitions, as the library encodes them on a per-scan + /// The definitions in this property are given as alternating runs + /// of quantization tables and image scans. (Image layouts do not specify + /// huffman table definitions, as the library encodes them on a per-scan /// basis.) /// ## (layout-image-structure) public private(set) var definitions:[(quanta:[Table.Quantization.Key], scans:[Scan])] } } -extension JPEG.Layout -{ - private - init(format:Format, - process:JPEG.Process, +extension JPEG.Layout +{ + private + init(format:Format, + process:JPEG.Process, components combined: [ JPEG.Component.Key: (component:JPEG.Component, qi:JPEG.Table.Quantization.Key) ]) { - self.format = format - self.process = process - - var planes:[(component:JPEG.Component, qi:JPEG.Table.Quantization.Key)] = - format.components.map + self.format = format + self.process = process + + var planes:[(component:JPEG.Component, qi:JPEG.Table.Quantization.Key)] = + format.components.map { - guard let value:(component:JPEG.Component, qi:JPEG.Table.Quantization.Key) = + guard let value:(component:JPEG.Component, qi:JPEG.Table.Quantization.Key) = combined[$0] - else + else { preconditionFailure("missing definition for component \($0) in format '\(format)'") } - - return value + + return value } - - var residents:[JPEG.Component.Key: Int] = + + var residents:[JPEG.Component.Key: Int] = .init(uniqueKeysWithValues: zip(format.components, planes.indices)) for (ci, value): ( JPEG.Component.Key, (component:JPEG.Component, qi:JPEG.Table.Quantization.Key) - ) in combined + ) in combined { - guard residents[ci] == nil - else + guard residents[ci] == nil + else { - continue + continue } - + residents[ci] = planes.endIndex planes.append(value) } - + self.residents = residents self.planes = planes self.definitions = [] } - - init(format:Format, - process:JPEG.Process, + + init(format:Format, + process:JPEG.Process, components:[JPEG.Component.Key: JPEG.Component]) { - self.init(format: format, process: process, components: components.mapValues + self.init(format: format, process: process, components: components.mapValues { ($0, -1) }) } /// init JPEG.Layout.init(format:process:components:scans:) /// Creates an image layout given image parameters and a scan decomposition. - /// - /// If the image coding process is a sequential process, the given scan headers - /// should be constructed using the [`(JPEG.Header.Scan).sequential(...:)`] - /// constructor. If the coding process is progressive, the scan headers - /// should be constructed with the [`(JPEG.Header.Scan).progressive(...:bits:)`], - /// [`(JPEG.Header.Scan).progressive(...:bit:)`], + /// + /// If the image coding process is a sequential process, the given scan headers + /// should be constructed using the [`(JPEG.Header.Scan).sequential(...:)`] + /// constructor. If the coding process is progressive, the scan headers + /// should be constructed with the [`(JPEG.Header.Scan).progressive(...:bits:)`], + /// [`(JPEG.Header.Scan).progressive(...:bit:)`], /// [`(JPEG.Header.Scan).progressive(_:band:bits:)`], or /// [`(JPEG.Header.Scan).progressive(_:band:bit:)`] constructors. - /// - /// This initializer will validate the scan progression and attempt to - /// generate a sequence of table definitions to implement the - /// quantization table relationships specified by the `components` parameter. - /// It will suffer a precondition failure if the scan progression is invalid, or - /// if it is impossible to implement the specified table relationships with + /// + /// This initializer will validate the scan progression and attempt to + /// generate a sequence of table definitions to implement the + /// quantization table relationships specified by the `components` parameter. + /// It will suffer a precondition failure if the scan progression is invalid, or + /// if it is impossible to implement the specified table relationships with /// the number of table selectors available for the given coding process. - /// + /// /// This initializer will normalize all scan headers passed to the `scans` - /// argument. It will strip non-recognized components from the headers, + /// argument. It will strip non-recognized components from the headers, /// and rearrange their component descriptors in ascending numeric order. - /// It will also validate the scan headers against the `process` argument + /// It will also validate the scan headers against the `process` argument /// with [`(Header.Scan).validate(process:band:bits:components:)`]. /// Passing invalid scan headers will result in a precondition failure. /// See the [advanced encoding](https://github.com/kelvin13/jpeg/tree/master/examples#advanced-encoding) /// library tutorial to learn more about the validation rules. /// - format : Format /// The color format of the image. - /// - process : JPEG.Process + /// - process : JPEG.Process /// The coding process used by the image. /// - components: [JPEG.Component.Key: (factor:(x:Swift.Int, y:Swift.Int), qi:JPEG.Table.Quantization.Key)] /// The sampling factors and quantization table key for each component in the image. - /// This dictionary must contain an entry for every component in the + /// This dictionary must contain an entry for every component in the /// given color `format`. /// - scans : [JPEG.Header.Scan] - /// The scan progression of the image. + /// The scan progression of the image. /// ## (layout-creation) - public - init(format:Format, - process:JPEG.Process, - components:[JPEG.Component.Key: - (factor:(x:Int, y:Int), qi:JPEG.Table.Quantization.Key)], + public + init(format:Format, + process:JPEG.Process, + components:[JPEG.Component.Key: + (factor:(x:Int, y:Int), qi:JPEG.Table.Quantization.Key)], scans:[JPEG.Header.Scan]) { - // to assign quantization table selectors, we first need to determine - // the first and last scans for each component, which then tells us + // to assign quantization table selectors, we first need to determine + // the first and last scans for each component, which then tells us // how long each quantization table needs to be activated // q -> lifetime var lifetimes:[JPEG.Table.Quantization.Key: (start:Int, end:Int)] = [:] - for (i, descriptor):(Int, JPEG.Header.Scan) in zip(scans.indices, scans) + for (i, descriptor):(Int, JPEG.Header.Scan) in zip(scans.indices, scans) { - for component:JPEG.Scan.Component in descriptor.components + for component:JPEG.Scan.Component in descriptor.components { - guard let qi:JPEG.Table.Quantization.Key = components[component.ci]?.qi - else + guard let qi:JPEG.Table.Quantization.Key = components[component.ci]?.qi + else { - // this scan is referencing a component that’s not in the - // `components` dictionary. we strip out unrecognized - // scan components later on anyway, so we ignore it here - continue + // this scan is referencing a component that’s not in the + // `components` dictionary. we strip out unrecognized + // scan components later on anyway, so we ignore it here + continue } - + lifetimes[qi, default: (i, i)].end = i + 1 } } - - var slots:[(selector:JPEG.Table.Quantization.Selector, time:Int)] - switch process + + var slots:[(selector:JPEG.Table.Quantization.Selector, time:Int)] + switch process { case .baseline: slots = [(\.0, 0), (\.1, 0)] default: slots = [(\.0, 0), (\.1, 0), (\.2, 0), (\.3, 0)] } - - // q -> selector - let mappings:[JPEG.Table.Quantization.Key: JPEG.Table.Quantization.Selector] = - lifetimes.mapValues + + // q -> selector + let mappings:[JPEG.Table.Quantization.Key: JPEG.Table.Quantization.Selector] = + lifetimes.mapValues { - (lifetime:(start:Int, end:Int)) in - - guard let free:Int = + (lifetime:(start:Int, end:Int)) in + + guard let free:Int = slots.firstIndex(where: { lifetime.start >= $0.time }) - else + else { fatalError("not enough free quantization table slots") } - - slots[free].time = lifetime.end + + slots[free].time = lifetime.end return slots[free].selector } - - self.init(format: format, process: process, components: components.mapValues + + self.init(format: format, process: process, components: components.mapValues { - // if `q` is not in the mappings dictionary, that means that there - // were no scans, even ones with unrecognized components, that - // referenced it. (this is a problem, because all `q` values are associated - // with at least one component, and every component needs to be covered - // by the scan progression). for now, since it has a lifetime of 0, it does + // if `q` is not in the mappings dictionary, that means that there + // were no scans, even ones with unrecognized components, that + // referenced it. (this is a problem, because all `q` values are associated + // with at least one component, and every component needs to be covered + // by the scan progression). for now, since it has a lifetime of 0, it does // not matter which selector we assign to it (.init(factor: $0.factor, selector: mappings[$0.qi] ?? \.0), $0.qi) }) - - // store scan information - let intrusions:[(start:Int, quanta:[JPEG.Table.Quantization.Key])] = - Dictionary.init(grouping: lifetimes.map{ (start: $0.value.start, quanta: $0.key) }) + + // store scan information + let intrusions:[(start:Int, quanta:[JPEG.Table.Quantization.Key])] = + Dictionary.init(grouping: lifetimes.map{ (start: $0.value.start, quanta: $0.key) }) { - $0.start + $0.start } .map { (start: $0.key, quanta: $0.value.map(\.quanta)) } - .sorted + .sorted { $0.start < $1.start } - + var progression:Progression = .init(format.components) let recognized:Set = .init(format.components) - - self.definitions = zip(intrusions.indices, intrusions).map + + self.definitions = zip(intrusions.indices, intrusions).map { let (g, (start, quanta)):(Int, (Int, [JPEG.Table.Quantization.Key])) = $0 - + let end:Int = intrusions.dropFirst(g + 1).first?.start ?? scans.endIndex - let group:[JPEG.Scan] = scans[start ..< end].map + let group:[JPEG.Scan] = scans[start ..< end].map { - do + do { try progression.update($0) - - // strip non-recognized components from the scan header. we - // also have to sort them so that their ordering matches the - // order in the generated frame header later on. this will + + // strip non-recognized components from the scan header. we + // also have to sort them so that their ordering matches the + // order in the generated frame header later on. this will // also validate process-dependent constraints. - return try self.push(scan: try .validate(process: process, - band: $0.band, - bits: $0.bits, + return try self.push(scan: try .validate(process: process, + band: $0.band, + bits: $0.bits, components: $0.components.filter - { - recognized.contains($0.ci) + { + recognized.contains($0.ci) } - .sorted + .sorted { $0.ci < $1.ci })) } - catch let error as JPEG.ParsingError // validation error + catch let error as JPEG.ParsingError // validation error { preconditionFailure(error.message) } - catch let error as JPEG.DecodingError // invalid progression + catch let error as JPEG.DecodingError // invalid progression { preconditionFailure(error.message) } - catch + catch { fatalError("unreachable") } @@ -1501,378 +1501,378 @@ extension JPEG.Layout return (quanta, group) } } - - mutating + + mutating func push(scan header:JPEG.Header.Scan) throws -> JPEG.Scan { - var volume:Int = 0 - let components:[(c:Int, component:JPEG.Scan.Component)] = - try header.components.map + var volume:Int = 0 + let components:[(c:Int, component:JPEG.Scan.Component)] = + try header.components.map { // validate sampling factor sum, and component residency guard let c:Int = self.residents[$0.ci] - else + else { throw JPEG.DecodingError.undefinedScanComponentReference( $0.ci, .init(residents.keys)) } - + let (x, y):(Int, Int) = self.planes[c].component.factor volume += x * y - + return (c, $0) } - + guard 0 ... 10 ~= volume || components.count == 1 - else + else { throw JPEG.DecodingError.invalidScanSamplingVolume(volume) } - - let passed:JPEG.Scan = + + let passed:JPEG.Scan = .init(band: header.band, bits: header.bits, components: components) - - // the ordering in the stored scan may be different since it has to match + + // the ordering in the stored scan may be different since it has to match // the ordering in the frame header - let stored:JPEG.Scan = - .init(band: header.band, bits: header.bits, components: components.sorted + let stored:JPEG.Scan = + .init(band: header.band, bits: header.bits, components: components.sorted { $0.component.ci < $1.component.ci }) - - if self.definitions.endIndex - 1 >= self.definitions.startIndex + + if self.definitions.endIndex - 1 >= self.definitions.startIndex { self.definitions[self.definitions.endIndex - 1].scans.append(stored) } - else + else { - // this shouldn’t happen, and will trigger an error later on when - // the dequantize function runs + // this shouldn’t happen, and will trigger an error later on when + // the dequantize function runs self.definitions.append(([], [stored])) } return passed } - mutating - func push(qi:JPEG.Table.Quantization.Key) + mutating + func push(qi:JPEG.Table.Quantization.Key) { - if self.definitions.last?.scans.isEmpty ?? false + if self.definitions.last?.scans.isEmpty ?? false { self.definitions[self.definitions.endIndex - 1].quanta.append(qi) } - else + else { self.definitions.append(([qi], [])) } } - - func index(ci:JPEG.Component.Key) -> Int? + + func index(ci:JPEG.Component.Key) -> Int? { guard let c:Int = self.residents[ci], self.recognized.indices ~= c - else + else { - return nil + return nil } return c } } // high-level state handling -extension JPEG.Layout +extension JPEG.Layout { - struct Progression + struct Progression { - private + private var approximations:[JPEG.Component.Key: [Int]] } } extension JPEG.Layout.Progression { - init(_ components:S) where S:Sequence, S.Element == JPEG.Component.Key + init(_ components:S) where S:Sequence, S.Element == JPEG.Component.Key { self.approximations = .init(uniqueKeysWithValues: components.map - { - ($0, .init(repeating: .max, count: 64)) + { + ($0, .init(repeating: .max, count: 64)) }) } - - mutating - func update(_ scan:JPEG.Header.Scan) throws + + mutating + func update(_ scan:JPEG.Header.Scan) throws { - for component:JPEG.Scan.Component in scan.components + for component:JPEG.Scan.Component in scan.components { guard var approximation:[Int] = self.approximations[component.ci] - else + else { - continue + continue } - - // preempt an array copy - self.approximations[component.ci] = nil - - // first scan must be a dc scan - guard approximation[0] < .max || scan.band.lowerBound == 0 - else + + // preempt an array copy + self.approximations[component.ci] = nil + + // first scan must be a dc scan + guard approximation[0] < .max || scan.band.lowerBound == 0 + else { throw JPEG.DecodingError.invalidSpectralSelectionProgression( scan.band, component.ci) } - // we need to check this because even though the scan header - // parser enforces bit-range constraints, it doesn’t enforce ordering + // we need to check this because even though the scan header + // parser enforces bit-range constraints, it doesn’t enforce ordering for (z, a):(Int, Int) in zip(scan.band, approximation[scan.band]) { - guard scan.bits.upperBound == a, scan.bits.lowerBound < a - else + guard scan.bits.upperBound == a, scan.bits.lowerBound < a + else { throw JPEG.DecodingError.invalidSuccessiveApproximationProgression( scan.bits, a, z: z, component.ci) } - + approximation[z] = scan.bits.lowerBound } - + self.approximations[component.ci] = approximation } } } -// this is an extremely boilerplatey api but i consider it necessary to avoid -// having to provide huge amounts of (visually noisy) extraneous information +// this is an extremely boilerplatey api but i consider it necessary to avoid +// having to provide huge amounts of (visually noisy) extraneous information // in the constructor (ie. huffman table selectors for refining dc scans) -extension JPEG.Header.Scan +extension JPEG.Header.Scan { - // these constructors bypass the validator. this is fine because the validator + // these constructors bypass the validator. this is fine because the validator // runs when the scan headers get compiled into the layout struct. - // it is possible for users to construct a process-inconsistent scan header - // using these apis, but this is also possible with the validating constructor, + // it is possible for users to construct a process-inconsistent scan header + // using these apis, but this is also possible with the validating constructor, // by simply passing a fake value for `process` - + /// static func JPEG.Header.Scan.sequential(_:) /// Creates a sequential scan descriptor. - /// - /// This constructor bypasses normal scan header validation checks. It is + /// + /// This constructor bypasses normal scan header validation checks. It is /// otherwise equivalent to calling [`(Header.Scan).validate(process:band:bits:components:)`] - /// with the `band` argument set to `0 ..< 64` and the `bits` argument set + /// with the `band` argument set to `0 ..< 64` and the `bits` argument set /// to `0 ..< .max`. /// - components:[(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector, ac:JPEG.Table.HuffmanAC.Selector)] - /// The components encoded by this scan, and associated DC and AC huffman - /// table selectors. For each type of huffman table, components with the - /// same table selector will share the same huffman table. Huffman tables - /// will not persist in between different scans. Passing an empty array + /// The components encoded by this scan, and associated DC and AC huffman + /// table selectors. For each type of huffman table, components with the + /// same table selector will share the same huffman table. Huffman tables + /// will not persist in between different scans. Passing an empty array /// will result in a precondition failure. - /// - -> :Self + /// - -> :Self /// An unvalidated sequential scan header. /// # [See also](scan-header-creation-sequential) /// ## (scan-header-creation-sequential) - public static + public static func sequential(_ components: [( - ci:JPEG.Component.Key, - dc:JPEG.Table.HuffmanDC.Selector, + ci:JPEG.Component.Key, + dc:JPEG.Table.HuffmanDC.Selector, ac:JPEG.Table.HuffmanAC.Selector - )]) -> Self + )]) -> Self { precondition(!components.isEmpty, "components array cannot be empty") - return .init(band: 0 ..< 64, bits: 0 ..< .max, + return .init(band: 0 ..< 64, bits: 0 ..< .max, components: components.map{ .init(ci: $0.ci, selector: ($0.dc, $0.ac))}) } /// static func JPEG.Header.Scan.sequential(...:) - /// Creates a sequential scan descriptor. - /// + /// Creates a sequential scan descriptor. + /// /// This function is variadic sugar for [`(Scan).sequential(_:)`]. /// - components:(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector, ac:JPEG.Table.HuffmanAC.Selector) - /// - -> :Self + /// - -> :Self /// # [See also](scan-header-creation-sequential) /// ## (scan-header-creation-sequential) - public static + public static func sequential(_ components: ( - ci:JPEG.Component.Key, - dc:JPEG.Table.HuffmanDC.Selector, + ci:JPEG.Component.Key, + dc:JPEG.Table.HuffmanDC.Selector, ac:JPEG.Table.HuffmanAC.Selector - )...) -> Self + )...) -> Self { .sequential(components) } /// static func JPEG.Header.Scan.progressive(_:bits:) /// Creates a progressive initial DC scan descriptor. - /// - /// This constructor bypasses normal scan header validation checks. It is + /// + /// This constructor bypasses normal scan header validation checks. It is /// otherwise equivalent to calling [`(Header.Scan).validate(process:band:bits:components:)`] /// with the `band` argument set to `0 ..< 1`. /// - /// Initial DC scans only use DC huffman tables, so no AC table selectors + /// Initial DC scans only use DC huffman tables, so no AC table selectors /// need to be specified. /// - components:[(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector)] - /// The components encoded by this scan, and associated DC huffman + /// The components encoded by this scan, and associated DC huffman /// table selectors. Components with the same table selector will share - /// the same huffman table. Huffman tables will not persist in between + /// the same huffman table. Huffman tables will not persist in between /// different scans. Passing an empty array will result in a precondition failure. /// - bits :Swift.PartialRangeFrom - /// The range of DC bits encoded by this scan. Setting this argument to - /// `0...` disables spectral selection. Passing a range with a negative + /// The range of DC bits encoded by this scan. Setting this argument to + /// `0...` disables spectral selection. Passing a range with a negative /// lower bound will result in a precondition failure. - /// - -> :Self + /// - -> :Self /// An unvalidated initial DC scan header. /// # [See also](scan-header-creation-progressive) /// ## (scan-header-creation-progressive) - public static - func progressive(_ - components:[(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector)], - bits:PartialRangeFrom) -> Self + public static + func progressive(_ + components:[(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector)], + bits:PartialRangeFrom) -> Self { precondition(!components.isEmpty, "components array cannot be empty") precondition(bits.lowerBound >= 0, "lower bound of bit range cannot be negative") - return .init(band: 0 ..< 1, bits: bits.lowerBound ..< .max, + return .init(band: 0 ..< 1, bits: bits.lowerBound ..< .max, components: components.map{ .init(ci: $0.ci, selector: ($0.dc, \.0))}) } /// static func JPEG.Header.Scan.progressive(...:bits:) /// Creates a progressive initial DC scan descriptor. - /// + /// /// This function is variadic sugar for [`(Scan).progressive(_:bits:)`]. /// - components:(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector) /// - bits :Swift.PartialRangeFrom - /// - -> :Self + /// - -> :Self /// # [See also](scan-header-creation-progressive) /// ## (scan-header-creation-progressive) - public static - func progressive(_ - components:(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector)..., - bits:PartialRangeFrom) -> Self + public static + func progressive(_ + components:(ci:JPEG.Component.Key, dc:JPEG.Table.HuffmanDC.Selector)..., + bits:PartialRangeFrom) -> Self { .progressive(components, bits: bits) } /// static func JPEG.Header.Scan.progressive(_:bit:) /// Creates a progressive refining DC scan descriptor. - /// - /// This constructor bypasses normal scan header validation checks. It is + /// + /// This constructor bypasses normal scan header validation checks. It is /// otherwise equivalent to calling [`(Header.Scan).validate(process:band:bits:components:)`] - /// with the `band` argument set to `0 ..< 1` and the `bits` argument set + /// with the `band` argument set to `0 ..< 1` and the `bits` argument set /// to `bit ..< bit + 1`. - /// - /// Refining DC scans do not use entropy-coding, so no huffman table selectors + /// + /// Refining DC scans do not use entropy-coding, so no huffman table selectors /// need to be specified. /// - components:[JPEG.Component.Key] - /// The components encoded by this scan. Passing an empty array will + /// The components encoded by this scan. Passing an empty array will /// result in a precondition failure. /// - bit :Swift.Int /// The index of the bit refined by this scan. Passing a negative index /// will result in a precondition failure. - /// - -> :Self + /// - -> :Self /// An unvalidated refining DC scan header. /// # [See also](scan-header-creation-progressive) /// ## (scan-header-creation-progressive) - public static - func progressive(_ - components:[JPEG.Component.Key], - bit:Int) -> Self + public static + func progressive(_ + components:[JPEG.Component.Key], + bit:Int) -> Self { precondition(!components.isEmpty, "components array cannot be empty") precondition(bit >= 0, "bit index cannot be negative") - return .init(band: 0 ..< 1, bits: bit ..< bit + 1, + return .init(band: 0 ..< 1, bits: bit ..< bit + 1, components: components.map{ .init(ci: $0, selector: (\.0, \.0))}) } /// static func JPEG.Header.Scan.progressive(...:bit:) /// Creates a progressive refining DC scan descriptor. - /// + /// /// This function is variadic sugar for [`(Scan).progressive(_:bit:)`]. /// - components:JPEG.Component.Key /// - bit :Swift.Int - /// - -> :Self + /// - -> :Self /// # [See also](scan-header-creation-progressive) /// ## (scan-header-creation-progressive) - public static - func progressive(_ - components:JPEG.Component.Key..., - bit:Int) -> Self + public static + func progressive(_ + components:JPEG.Component.Key..., + bit:Int) -> Self { .progressive(components, bit: bit) } /// static func JPEG.Header.Scan.progressive(_:band:bits:) /// Creates a progressive initial AC scan descriptor. - /// - /// This constructor bypasses normal scan header validation checks. It is + /// + /// This constructor bypasses normal scan header validation checks. It is /// otherwise equivalent to calling [`(Header.Scan).validate(process:band:bits:components:)`]. /// - /// AC scans only use AC huffman tables, so no DC table selectors + /// AC scans only use AC huffman tables, so no DC table selectors /// need to be specified. /// - component :(ci:JPEG.Component.Key, ac:JPEG.Table.HuffmanAC.Selector) - /// The component encoded by this scan, and its associated AC huffman - /// table selector. Huffman tables will not persist in between + /// The component encoded by this scan, and its associated AC huffman + /// table selector. Huffman tables will not persist in between /// different scans. /// - band :Swift.Range - /// The frequency band encoded by this scan, in zigzag indices. This range + /// The frequency band encoded by this scan, in zigzag indices. This range /// will be clamped to the interval `1 ..< 64`. /// - bits :Swift.PartialRangeFrom - /// The range of AC bits encoded by this scan. Setting this argument to - /// `0...` disables spectral selection. Passing a range with a negative + /// The range of AC bits encoded by this scan. Setting this argument to + /// `0...` disables spectral selection. Passing a range with a negative /// lower bound will result in a precondition failure. - /// - -> :Self + /// - -> :Self /// An unvalidated initial AC scan header. /// # [See also](scan-header-creation-progressive) /// ## (scan-header-creation-progressive) - public static - func progressive(_ - component:(ci:JPEG.Component.Key, ac:JPEG.Table.HuffmanAC.Selector), - band:Range, bits:PartialRangeFrom) -> Self + public static + func progressive(_ + component:(ci:JPEG.Component.Key, ac:JPEG.Table.HuffmanAC.Selector), + band:Range, bits:PartialRangeFrom) -> Self { precondition(bits.lowerBound >= 0, "lower bound of bit range cannot be negative") - return .init(band: band.clamped(to: 1 ..< 64), bits: bits.lowerBound ..< .max, + return .init(band: band.clamped(to: 1 ..< 64), bits: bits.lowerBound ..< .max, components: [.init(ci: component.ci, selector: (\.0, component.ac))]) } /// static func JPEG.Header.Scan.progressive(_:band:bit:) /// Creates a progressive refining AC scan descriptor. - /// - /// This constructor bypasses normal scan header validation checks. It is - /// otherwise equivalent to calling [`(Header.Scan).validate(process:band:bits:components:)`] + /// + /// This constructor bypasses normal scan header validation checks. It is + /// otherwise equivalent to calling [`(Header.Scan).validate(process:band:bits:components:)`] /// with the `bits` argument set to `bit ..< bit + 1`. /// - /// AC scans only use AC huffman tables, so no DC table selectors + /// AC scans only use AC huffman tables, so no DC table selectors /// need to be specified. /// - component :(ci:JPEG.Component.Key, ac:JPEG.Table.HuffmanAC.Selector) - /// The component encoded by this scan, and its associated AC huffman - /// table selector. Huffman tables will not persist in between + /// The component encoded by this scan, and its associated AC huffman + /// table selector. Huffman tables will not persist in between /// different scans. /// - band :Swift.Range - /// The frequency band encoded by this scan, in zigzag indices. This range + /// The frequency band encoded by this scan, in zigzag indices. This range /// will be clamped to the interval `1 ..< 64`. /// - bit :Swift.Int /// The index of the bit refined by this scan. Passing a negative index /// will result in a precondition failure. - /// - -> :Self + /// - -> :Self /// An unvalidated refining AC scan header. /// # [See also](scan-header-creation-progressive) /// ## (scan-header-creation-progressive) - public static - func progressive(_ - component:(ci:JPEG.Component.Key, ac:JPEG.Table.HuffmanAC.Selector), - band:Range, bit:Int) -> Self + public static + func progressive(_ + component:(ci:JPEG.Component.Key, ac:JPEG.Table.HuffmanAC.Selector), + band:Range, bit:Int) -> Self { precondition(bit >= 0, "bit index cannot be negative") - return .init(band: band.clamped(to: 1 ..< 64), bits: bit ..< bit + 1, + return .init(band: band.clamped(to: 1 ..< 64), bits: bit ..< bit + 1, components: [.init(ci: component.ci, selector: (\.0, component.ac))]) } } -// bitstream -extension JPEG +// bitstream +extension JPEG { - /// struct JPEG.Bitstream + /// struct JPEG.Bitstream /// : Swift.ExpressibleByArrayLiteral /// A padded bitstream. /// # [Symbol types](entropy-coding-symbols) /// ## (0:entropy-coding) - public - struct Bitstream + public + struct Bitstream { - private + private var atoms:[UInt16] private(set) var count:Int } } -extension JPEG.Bitstream +extension JPEG.Bitstream { init(_ data:[UInt8]) { - // convert byte array to big-endian UInt16 array + // convert byte array to big-endian UInt16 array var atoms:[UInt16] = stride(from: 0, to: data.count - 1, by: 2).map { UInt16.init(data[$0]) << 8 | .init(data[$0 | 1]) @@ -1882,27 +1882,27 @@ extension JPEG.Bitstream { atoms.append(.init(data[data.count - 1]) << 8 | 0x00ff) } - + // insert a `0xffff` atom to serve as a barrier atoms.append(0xffff) - + self.atoms = atoms self.count = 8 * data.count } - + // single bit (0 or 1) subscript(i:Int, as _:I.Type) -> I where I:BinaryInteger { - let a:Int = i >> 4, + let a:Int = i >> 4, b:Int = i & 0x0f let shift:Int = UInt16.bitWidth &- 1 &- b let single:UInt16 = (self.atoms[a] &>> shift) & 1 return .init(single) } - + subscript(i:Int, count c:Int) -> UInt16 { - let a:Int = i >> 4, + let a:Int = i >> 4, b:Int = i & 0x0f // w.0 w.1 // |<-- c = 16 -->| @@ -1914,44 +1914,44 @@ extension JPEG.Bitstream let front:UInt16 = self.atoms[a] &<< b | self.atoms[a &+ 1] >> (UInt16.bitWidth &- b) return front &>> (UInt16.bitWidth - c) } - - // integer is 1 or 0 (ignoring higher bits), we avoid using `Bool` here + + // integer is 1 or 0 (ignoring higher bits), we avoid using `Bool` here // since this is not semantically a logic parameter - mutating - func append(bit:I) where I:BinaryInteger + mutating + func append(bit:I) where I:BinaryInteger { - let a:Int = self.count >> 4, + let a:Int = self.count >> 4, b:Int = self.count & 0x0f - + guard a < self.atoms.count - else + else { self.atoms.append(0xffff) self.append(bit: bit) - return + return } - + let shift:Int = UInt16.bitWidth &- 1 &- b let inverted:UInt16 = ~(.init(~bit & 1) &<< shift) - // all bits at and beyond bit index `self.count` should be `1`-bits - self.atoms[a] &= inverted + // all bits at and beyond bit index `self.count` should be `1`-bits + self.atoms[a] &= inverted self.count += 1 } - // relevant bits in the least significant positions - mutating - func append(_ bits:UInt16, count:Int) + // relevant bits in the least significant positions + mutating + func append(_ bits:UInt16, count:Int) { - let a:Int = self.count >> 4, + let a:Int = self.count >> 4, b:Int = self.count & 0x0f - + guard a + 1 < self.atoms.count - else + else { self.atoms.append(0xffff) self.append(bits, count: count) - return + return } - + // w.0 w.1 // [x:x:x:x:x|x:x:x] // b = 6 @@ -1959,69 +1959,69 @@ extension JPEG.Bitstream // [ : : :x:x:x:x:x|x:x:x: : : : : ] // |<-- c = 16 -->| - // invert bits because we use `1`-bits as the “background”, and shift + // invert bits because we use `1`-bits as the “background”, and shift // operator will only extend with `0`-bits // must use >> and << and not &>> and &<< to correctly handle shift of 16 let trimmed:UInt16 = bits &<< (UInt16.bitWidth &- count) | .max >> count - let inverted:(UInt16, UInt16) = + let inverted:(UInt16, UInt16) = ( - ~trimmed &>> b, + ~trimmed &>> b, ~trimmed << (UInt16.bitWidth &- b) ) - + self.atoms[a ] &= ~inverted.0 self.atoms[a + 1] &= ~inverted.1 - self.count += count + self.count += count } - + func bytes(escaping escaped:UInt8, with sequence:(UInt8, UInt8)) -> [UInt8] { let unescaped:[UInt8] = .init(unsafeUninitializedCapacity: 2 * self.atoms.count) { - for (i, atom):(Int, UInt16) in self.atoms.enumerated() + for (i, atom):(Int, UInt16) in self.atoms.enumerated() { $0[i << 1 ] = .init(atom >> 8) $0[i << 1 | 1] = .init(atom & 0xff) } - + $1 = 2 * self.atoms.count } - // figure out which of the bytes are actually part of the bitstream + // figure out which of the bytes are actually part of the bitstream let count:Int = self.count >> 3 + (self.count & 0x07 != 0 ? 1 : 0) var bytes:[UInt8] = [] bytes.reserveCapacity(count) - for byte:UInt8 in unescaped.prefix(count) + for byte:UInt8 in unescaped.prefix(count) { - if byte == escaped + if byte == escaped { bytes.append(sequence.0) bytes.append(sequence.1) } - else + else { bytes.append(byte) } } - + return bytes } } -extension JPEG.Bitstream:ExpressibleByArrayLiteral +extension JPEG.Bitstream:ExpressibleByArrayLiteral { /// init JPEG.Bitstream.init(arrayLiteral...:) - /// ?: Swift.ExpressibleByArrayLiteral + /// ?: Swift.ExpressibleByArrayLiteral /// Creates a bitstream from the given array literal. /// - /// This type stores the bitstream in 16-bit atoms. If the array literal - /// does not contain an even number of bytes, the last atom is padded + /// This type stores the bitstream in 16-bit atoms. If the array literal + /// does not contain an even number of bytes, the last atom is padded /// with 1-bits. /// - arrayLiteral : Swift.UInt8 - /// The raw bytes making up the bitstream. The more significant bits in - /// each byte come first in the bitstream. If the bitstream does not - /// correspond to a whole number of bytes, the least significant bits + /// The raw bytes making up the bitstream. The more significant bits in + /// each byte come first in the bitstream. If the bitstream does not + /// correspond to a whole number of bytes, the least significant bits /// in the last byte should be padded with 1-bits. - public - init(arrayLiteral:UInt8...) + public + init(arrayLiteral:UInt8...) { self.init(arrayLiteral) } diff --git a/sources/jpeg/metadata.swift b/Sources/JPEG/metadata.swift similarity index 74% rename from sources/jpeg/metadata.swift rename to Sources/JPEG/metadata.swift index 099e9041..0e6959e2 100644 --- a/sources/jpeg/metadata.swift +++ b/Sources/JPEG/metadata.swift @@ -2,203 +2,203 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -extension JPEG +extension JPEG { - /// struct JPEG.JFIF + /// struct JPEG.JFIF /// A JFIF metadata record. /// # [See also](metadata-types) /// ## (metadata-types) - public + public struct JFIF { - /// enum JPEG.JFIF.Unit + /// enum JPEG.JFIF.Unit /// A unit of measurement. - public + public enum Unit { - /// case JPEG.JFIF.Unit.inches + /// case JPEG.JFIF.Unit.inches /// The unit is inches. - case inches + case inches /// case JPEG.JFIF.Unit.centimeters /// The unit is centimeters. case centimeters } - /// enum JPEG.JFIF.Version + /// enum JPEG.JFIF.Version /// A version of the [JFIF standard](https://www.w3.org/Graphics/JPEG/jfif3.pdf). - public - enum Version + public + enum Version { - /// case JPEG.JFIF.Version.v1_0 + /// case JPEG.JFIF.Version.v1_0 /// JFIF version 1.0. - + /// case JPEG.JFIF.Version.v1_1 /// JFIF version 1.1. - + /// case JPEG.JFIF.Version.v1_2 /// JFIF version 1.2. case v1_0, v1_1, v1_2 } - + /// let JPEG.JFIF.version : Version /// The JFIF version of this metadata record. - public + public let version:Version, /// let JPEG.JFIF.density : (x:Swift.Int, y:Swift.Int, unit:Unit?) - /// The sampling density of the image. - /// - /// The `x` and `y` fields specify the number of physical samples along - /// each dimension per `unit` length. For example, the value + /// The sampling density of the image. + /// + /// The `x` and `y` fields specify the number of physical samples along + /// each dimension per `unit` length. For example, the value /// `(x: 72, y: 72, unit: `[`Unit.inches`]`)` indicates a density of 72\ dpi. /// If the `unit` field is `nil`, the units are unknown. density:(x:Int, y:Int, unit:Unit?) - + // initializer has to live here due to compiler issue - + /// init JPEG.JFIF.init(version:density:) /// Creates a metadata record. - /// - version : Version + /// - version : Version /// The JFIF version of the metadata record. - /// - density : (x:Swift.Int, y:Swift.Int, unit:Unit?) + /// - density : (x:Swift.Int, y:Swift.Int, unit:Unit?) /// The sampling density of the image. - public + public init(version:Version, density:(x:Int, y:Int, unit:Unit?)) { - self.version = version + self.version = version self.density = density } } - /// struct JPEG.EXIF + /// struct JPEG.EXIF /// An EXIF metadata record. - /// + /// /// This type will index the root, *EXIF* (tag 34665), and *GPS* (tag 34853) /// field groups, if they are present and well-linked in the record. - /// - /// This framework only supports basic field retrieval from EXIF data. + /// + /// This framework only supports basic field retrieval from EXIF data. /// It does not support constructing or editing EXIF records. Because this metadata format - /// relies extensively on internal file pointers, it is easy to accidentally - /// corrupt an EXIF segment. To perform more sophisticated operations, + /// relies extensively on internal file pointers, it is easy to accidentally + /// corrupt an EXIF segment. To perform more sophisticated operations, /// use a dedicated library, such as [Carpaccio](https://github.com/mz2/Carpaccio). /// # [See also](metadata-types) /// ## (metadata-types) - public - struct EXIF + public + struct EXIF { - /// enum JPEG.EXIF.Endianness - /// An endianness mode. - public - enum Endianness + /// enum JPEG.EXIF.Endianness + /// An endianness mode. + public + enum Endianness { - /// case JPEG.EXIF.Endianness.bigEndian + /// case JPEG.EXIF.Endianness.bigEndian /// Multibyte integers are stored most-significant-byte first. - /// - /// For historical reasons, the [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// For historical reasons, the [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this mode as *motorola byte order*. - case bigEndian + case bigEndian /// case JPEG.EXIF.Endianness.littleEndian - /// Multibyte integers are stored least-significant-byte first. - /// - /// For historical reasons, the [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// Multibyte integers are stored least-significant-byte first. + /// + /// For historical reasons, the [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this mode as *intel byte order*. - case littleEndian + case littleEndian } - - /// enum JPEG.EXIF.FieldType + + /// enum JPEG.EXIF.FieldType /// A variant field type. - /// # [Field types](exif-field-types) - public - enum FieldType + /// # [Field types](exif-field-types) + public + enum FieldType { - /// case JPEG.EXIF.FieldType.ascii + /// case JPEG.EXIF.FieldType.ascii /// A [`Swift.Unicode.ASCII.CodeUnit`] field. /// ## (exif-field-types) - case ascii + case ascii /// case JPEG.EXIF.FieldType.uint8 /// A [`Swift.UInt8`] field. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as a *byte*. /// ## (exif-field-types) case uint8 /// case JPEG.EXIF.FieldType.uint16 /// A [`Swift.UInt16`] field. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as a *short*. /// ## (exif-field-types) - case uint16 + case uint16 /// case JPEG.EXIF.FieldType.uint32 /// A [`Swift.UInt32`] field. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as a *long*. /// ## (exif-field-types) - case uint32 + case uint32 /// case JPEG.EXIF.FieldType.int32 /// An [`Swift.Int32`] field. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as a *slong*. /// ## (exif-field-types) - case int32 + case int32 /// case JPEG.EXIF.FieldType.urational /// An unsigned fraction field, represented as two [`Swift.UInt32`]s. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as a *rational*. /// ## (exif-field-types) - case urational + case urational /// case JPEG.EXIF.FieldType.rational /// A signed fraction field, represented as two [`Swift.Int32`]s. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as a *srational*. /// ## (exif-field-types) case rational /// case JPEG.EXIF.FieldType.raw /// A raw byte. - /// - /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) + /// + /// The [EXIF standard](https://www.exif.org/Exif2-2.PDF) /// refers to this variant type as an *undefined*. /// ## (exif-field-types) case raw - + /// case JPEG.EXIF.FieldType.other(code:) /// A field of unknown type. - /// - code : Swift.UInt16 + /// - code : Swift.UInt16 /// The type indicator code. /// ## (exif-field-types) case other(code:UInt16) } - /// struct JPEG.EXIF.Box - /// An abstracted field value. - /// - /// Depending on the size of the field data, the field value may either - /// be stored inline within this instance, or be stored indirectly + /// struct JPEG.EXIF.Box + /// An abstracted field value. + /// + /// Depending on the size of the field data, the field value may either + /// be stored inline within this instance, or be stored indirectly /// elsewhere in the mantle of this metadata segment. - public - struct Box + public + struct Box { /// let JPEG.EXIF.Box.contents : (Swift.UInt8, Swift.UInt8, Swift.UInt8, Swift.UInt8) /// The contents of this field box. - /// - /// Depending on the field, the four bytes that make up this property - /// may encode the field value directly, or encode a 32-bit internal + /// + /// Depending on the field, the four bytes that make up this property + /// may encode the field value directly, or encode a 32-bit internal /// file address pointing to the field value. /// # [See also](exif-box-as-offset) public - let contents:(UInt8, UInt8, UInt8, UInt8), - /// let JPEG.EXIF.Box.endianness : JPEG.EXIF.Endianness + let contents:(UInt8, UInt8, UInt8, UInt8), + /// let JPEG.EXIF.Box.endianness : JPEG.EXIF.Endianness /// The byte order of the contents of this field box. - /// + /// /// This is always the same as the byte order of the entire metadata record. endianness:Endianness - + /// var JPEG.EXIF.Box.asOffset : Swift.Int { get } - /// The contents of this field box, interpreted as a 32-bit internal + /// The contents of this field box, interpreted as a 32-bit internal /// file pointer, and extended to the width of an [`Swift.Int`]. /// ## (exif-box-as-offset) - public - var asOffset:Int + public + var asOffset:Int { switch self.endianness { @@ -206,63 +206,63 @@ extension JPEG return .init(contents.3) << 24 | .init(contents.2) << 24 | .init(contents.1) << 24 | - .init(contents.0) + .init(contents.0) case .bigEndian: return .init(contents.0) << 24 | .init(contents.1) << 24 | .init(contents.2) << 24 | - .init(contents.3) + .init(contents.3) } } /// init JPEG.EXIF.Box.init(_:_:_:_:endianness:) /// Creates a field box instance. /// - b0 : Swift.UInt8 - /// The zeroth content byte. Depending on the `endianness`, this + /// The zeroth content byte. Depending on the `endianness`, this /// is either the most- or least-significant byte. /// - b1 : Swift.UInt8 - /// The first content byte. + /// The first content byte. /// - b2 : Swift.UInt8 - /// The second content byte. + /// The second content byte. /// - b3 : Swift.UInt8 - /// The third content byte. Depending on the `endianness`, this + /// The third content byte. Depending on the `endianness`, this /// is either the least- or most-significant byte. /// - endianness : JPEG.EXIF.Endianness /// The byte order of the content bytes. - public - init(_ b0:UInt8, _ b1:UInt8, _ b2:UInt8, _ b3:UInt8, endianness:Endianness) + public + init(_ b0:UInt8, _ b1:UInt8, _ b2:UInt8, _ b3:UInt8, endianness:Endianness) { self.contents = (b0, b1, b2, b3) self.endianness = endianness } } - - /// let JPEG.EXIF.endianness : JPEG.EXIF.Endianness + + /// let JPEG.EXIF.endianness : JPEG.EXIF.Endianness /// The byte order of this metadata record. - public - let endianness:Endianness + public + let endianness:Endianness /// var JPEG.EXIF.tags : [Swift.UInt16: Swift.Int] { get } - /// The index of tags and fields defined in this metadata record. - /// - /// The tag codes are listed in the [EXIF standard](https://www.exif.org/Exif2-2.PDF). - /// The dictionary values are internal file pointers that can be - /// dereferenced manually from the [`storage`]; alternatively, the - /// [`[tag:]`] subscript can perform both the lookup and the dereferencing + /// The index of tags and fields defined in this metadata record. + /// + /// The tag codes are listed in the [EXIF standard](https://www.exif.org/Exif2-2.PDF). + /// The dictionary values are internal file pointers that can be + /// dereferenced manually from the [`storage`]; alternatively, the + /// [`[tag:]`] subscript can perform both the lookup and the dereferencing /// in one step. public private(set) - var tags:[UInt16: Int], + var tags:[UInt16: Int], /// var JPEG.EXIF.storage : [Swift.UInt8] { get } /// The raw contents of this metadata record. - /// - /// A file pointer with a value of 0 points to the beginning of this + /// + /// A file pointer with a value of 0 points to the beginning of this /// array. - storage:[UInt8] + storage:[UInt8] } } -// jfif segment parsing -extension JPEG.JFIF.Version +// jfif segment parsing +extension JPEG.JFIF.Version { - static + static func parse(code:(UInt8, UInt8)) -> Self? { switch (major: code.0, minor: code.1) @@ -274,63 +274,63 @@ extension JPEG.JFIF.Version case (major: 1, minor: 2): return .v1_2 default: - return nil + return nil } } } -extension JPEG.JFIF.Unit +extension JPEG.JFIF.Unit { - static + static func parse(code:UInt8) -> Self?? { - switch code + switch code { case 0: - return .some(nil) + return .some(nil) case 1: - return .inches + return .inches case 2: return .centimeters default: - return nil + return nil } } } -extension JPEG.JFIF +extension JPEG.JFIF { - static + static let signature:[UInt8] = [0x4a, 0x46, 0x49, 0x46, 0x00] /// static func JPEG.JFIF.parse(_:) - /// throws + /// throws /// Parses a JFIF segment into a metadata record. - /// - /// If the given data does not parse to a valid metadata record, - /// this function will throw a [`(JPEG).ParsingError`]. - /// + /// + /// If the given data does not parse to a valid metadata record, + /// this function will throw a [`(JPEG).ParsingError`]. + /// /// This parser ignores embedded thumbnails. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - -> : Self + /// - -> : Self /// The parsed metadata record. - public static + public static func parse(_ data:[UInt8]) throws -> Self { guard data.count >= 14 else { - throw JPEG.ParsingError.mismatched(marker: .application(0), + throw JPEG.ParsingError.mismatched(marker: .application(0), count: data.count, minimum: 14) } - + // look for 'JFIF\0' signature guard data[0 ..< 5] == Self.signature[...] - else + else { throw JPEG.ParsingError.invalidJFIFSignature(.init(data[0 ..< 5])) } guard let version:Version = .parse(code: (data[5], data[6])) - else + else { throw JPEG.ParsingError.invalidJFIFVersionCode((data[5], data[6])) } @@ -341,22 +341,22 @@ extension JPEG.JFIF throw JPEG.ParsingError.invalidJFIFDensityUnitCode(data[7]) } - let density:(x:Int, y:Int) = + let density:(x:Int, y:Int) = ( - data.load(bigEndian: UInt16.self, as: Int.self, at: 8), + data.load(bigEndian: UInt16.self, as: Int.self, at: 8), data.load(bigEndian: UInt16.self, as: Int.self, at: 10) ) - + // we ignore the thumbnail data return .init(version: version, density: (density.x, density.y, unit)) } } -extension JPEG.JFIF.Version +extension JPEG.JFIF.Version { - var serialized:(UInt8, UInt8) + var serialized:(UInt8, UInt8) { - switch self + switch self { case .v1_0: return (1, 0) @@ -367,11 +367,11 @@ extension JPEG.JFIF.Version } } } -extension JPEG.JFIF.Unit +extension JPEG.JFIF.Unit { - var serialized:UInt8 + var serialized:UInt8 { - switch self + switch self { case .inches: return 1 @@ -380,98 +380,98 @@ extension JPEG.JFIF.Unit } } } -extension JPEG.JFIF +extension JPEG.JFIF { - /// func JPEG.JFIF.serialized() + /// func JPEG.JFIF.serialized() /// Serializes this metadata record as segment data. - /// + /// /// This method is the inverse of [`parse(_:)`]. /// - -> : [Swift.UInt8] - /// A marker segment body. This array does not include the marker type + /// A marker segment body. This array does not include the marker type /// indicator, or the marker segment length field. - public - func serialized() -> [UInt8] + public + func serialized() -> [UInt8] { - var bytes:[UInt8] = Self.signature + var bytes:[UInt8] = Self.signature bytes.append(self.version.serialized.0) bytes.append(self.version.serialized.1) bytes.append(self.density.unit?.serialized ?? 0) bytes.append(contentsOf: [UInt8].store(self.density.x, asBigEndian: UInt16.self)) bytes.append(contentsOf: [UInt8].store(self.density.y, asBigEndian: UInt16.self)) - // no thumbnail - bytes.append(0) + // no thumbnail + bytes.append(0) bytes.append(0) return bytes } } -extension JPEG.EXIF.FieldType +extension JPEG.EXIF.FieldType { - static + static func parse(code:UInt16) -> Self { - switch code + switch code { case 1: - return .uint8 + return .uint8 case 2: - return .ascii + return .ascii case 3: return .uint16 case 4: - return .uint32 + return .uint32 case 5: - return .urational + return .urational case 7: return .raw case 9: - return .int32 + return .int32 case 10: - return .rational + return .rational default: - return .other(code: code) + return .other(code: code) } } } -extension JPEG.EXIF +extension JPEG.EXIF { - static + static let signature:[UInt8] = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00] /// static func JPEG.EXIF.parse(_:) - /// throws + /// throws /// Parses an EXIF segment into a metadata record. - /// - /// If the given data does not parse to a valid metadata record, - /// this function will throw a [`(JPEG).ParsingError`]. - /// - /// This constructor will attempt to index the root, *EXIF* (tag 34665), - /// and *GPS* (tag 34853) field groups, if they are present and well-linked + /// + /// If the given data does not parse to a valid metadata record, + /// this function will throw a [`(JPEG).ParsingError`]. + /// + /// This constructor will attempt to index the root, *EXIF* (tag 34665), + /// and *GPS* (tag 34853) field groups, if they are present and well-linked /// in the segment data. This parser ignores embedded thumbnails. /// - data : [Swift.UInt8] /// The segment data to parse. - /// - -> : Self + /// - -> : Self /// The parsed metadata record. - public static - func parse(_ data:[UInt8]) throws -> Self + public static + func parse(_ data:[UInt8]) throws -> Self { - guard data.count >= 14 - else + guard data.count >= 14 + else { - throw JPEG.ParsingError.mismatched(marker: .application(1), + throw JPEG.ParsingError.mismatched(marker: .application(1), count: data.count, minimum: 14) } - + // look for 'Exif\0\0' signature - guard data[0 ..< 6] == Self.signature[...] - else + guard data[0 ..< 6] == Self.signature[...] + else { throw JPEG.ParsingError.invalidEXIFSignature(.init(data[0 ..< 6])) } - - // determine endianness + + // determine endianness let endianness:Endianness - switch (data[6], data[7], data[8], data[9]) + switch (data[6], data[7], data[8], data[9]) { case (0x49, 0x49, 0x2a, 0x00): endianness = .littleEndian @@ -481,138 +481,138 @@ extension JPEG.EXIF throw JPEG.ParsingError.invalidEXIFEndiannessCode( (data[6], data[7], data[8], data[9])) } - - var exif:Self = .init(endianness: endianness, tags: [:], + + var exif:Self = .init(endianness: endianness, tags: [:], storage: .init(data.dropFirst(6))) - + exif.index(ifd: .init(exif[4, as: UInt32.self])) - // exif ifd - if let (type, count, box):(FieldType, Int, Box) = exif[tag: 34665], + // exif ifd + if let (type, count, box):(FieldType, Int, Box) = exif[tag: 34665], case .uint32 = type, count == 1 { exif.index(ifd: box.asOffset) } - // gps ifd - if let (type, count, box):(FieldType, Int, Box) = exif[tag: 34853], + // gps ifd + if let (type, count, box):(FieldType, Int, Box) = exif[tag: 34853], case .uint32 = type, count == 1 { exif.index(ifd: box.asOffset) } - + return exif } - - private mutating - func index(ifd:Int) + + private mutating + func index(ifd:Int) { - guard ifd + 2 <= self.storage.count - else + guard ifd + 2 <= self.storage.count + else { - return + return } - + let count:Int = .init(self[ifd, as: UInt16.self]) - for i:Int in 0 ..< count + for i:Int in 0 ..< count { let offset:Int = ifd + 2 + i * 12 - guard offset + 12 <= self.storage.count - else + guard offset + 12 <= self.storage.count + else { - continue + continue } - + self.tags[self[offset, as: UInt16.self]] = offset } } /// subscript JPEG.EXIF[tag:] { get } - /// Returns the field descriptor and boxed value for the given tag, + /// Returns the field descriptor and boxed value for the given tag, /// if it exists in this metadata record. - /// - tag : Swift.UInt16 - /// A tag code. The various EXIF tag codes are listed in the - /// [EXIF standard](https://www.exif.org/Exif2-2.PDF). The [`tags`] - /// dictionary contains an index of the known, defined tags in this metadata + /// - tag : Swift.UInt16 + /// A tag code. The various EXIF tag codes are listed in the + /// [EXIF standard](https://www.exif.org/Exif2-2.PDF). The [`tags`] + /// dictionary contains an index of the known, defined tags in this metadata /// record. /// - -> : (type:FieldType, count:Swift.Int, box:Box)? /// The field descriptor and boxed value, if it exists, otherwise `nil`. - public + public subscript(tag tag:UInt16) -> (type:FieldType, count:Int, box:Box)? { - guard let offset:Int = self.tags[tag] - else + guard let offset:Int = self.tags[tag] + else { - return nil + return nil } - + let type:FieldType = .parse(code: self[offset + 2, as: UInt16.self]) - + let count:Int = .init(self[offset + 4, as: UInt32.self]) let box:Box = .init( - self[offset + 8 , as: UInt8.self], - self[offset + 9 , as: UInt8.self], - self[offset + 10, as: UInt8.self], - self[offset + 11, as: UInt8.self], + self[offset + 8 , as: UInt8.self], + self[offset + 9 , as: UInt8.self], + self[offset + 10, as: UInt8.self], + self[offset + 11, as: UInt8.self], endianness: self.endianness) return (type, count, box) } /// subscript JPEG.EXIF[_:as:] { get } /// Loads the [`(FieldType).uint8`] value at the given address. - /// - offset: Swift.Int - /// The address of the value to access. This pointer must be within the + /// - offset: Swift.Int + /// The address of the value to access. This pointer must be within the /// index range of [`storage`]. - /// - _ : Swift.UInt8.Type + /// - _ : Swift.UInt8.Type /// This parameter must be set to [`Swift.UInt8`self`]. - /// - -> : Swift.UInt8 + /// - -> : Swift.UInt8 /// The [`(FieldType).uint8`] value. - public - subscript(offset:Int, as _:UInt8.Type) -> UInt8 + public + subscript(offset:Int, as _:UInt8.Type) -> UInt8 { self.storage[offset] } /// subscript JPEG.EXIF[_:as:] { get } /// Loads the [`(FieldType).uint16`] value at the given address. - /// - offset: Swift.Int - /// The address of the value to access. This pointer, and the address after + /// - offset: Swift.Int + /// The address of the value to access. This pointer, and the address after /// it must be within the index range of [`storage`]. - /// - _ : Swift.UInt16.Type + /// - _ : Swift.UInt16.Type /// This parameter must be set to [`Swift.UInt16`self`]. - /// - -> : Swift.UInt16 + /// - -> : Swift.UInt16 /// The [`(FieldType).uint16`] value, loaded according to the [`endianness`] /// of this metadata record. - public - subscript(offset:Int, as _:UInt16.Type) -> UInt16 + public + subscript(offset:Int, as _:UInt16.Type) -> UInt16 { - switch self.endianness + switch self.endianness { case .littleEndian: - return .init(self[offset + 1, as: UInt8.self]) << 8 | + return .init(self[offset + 1, as: UInt8.self]) << 8 | .init(self[offset , as: UInt8.self]) case .bigEndian: - return .init(self[offset , as: UInt8.self]) << 8 | + return .init(self[offset , as: UInt8.self]) << 8 | .init(self[offset + 1, as: UInt8.self]) } } /// subscript JPEG.EXIF[_:as:] { get } /// Loads the [`(FieldType).uint32`] value at the given address. - /// - offset: Swift.Int - /// The address of the value to access. This pointer, and the next three + /// - offset: Swift.Int + /// The address of the value to access. This pointer, and the next three /// addresses after it must be within the index range of [`storage`]. - /// - _ : Swift.UInt32.Type + /// - _ : Swift.UInt32.Type /// This parameter must be set to [`Swift.UInt32`self`]. - /// - -> : Swift.UInt32 + /// - -> : Swift.UInt32 /// The [`(FieldType).uint32`] value, loaded according to the [`endianness`] /// of this metadata record. - public - subscript(offset:Int, as _:UInt32.Type) -> UInt32 + public + subscript(offset:Int, as _:UInt32.Type) -> UInt32 { - switch self.endianness + switch self.endianness { case .littleEndian: - return .init(self[offset + 3, as: UInt8.self]) << 24 | + return .init(self[offset + 3, as: UInt8.self]) << 24 | .init(self[offset + 2, as: UInt8.self]) << 16 | .init(self[offset + 1, as: UInt8.self]) << 8 | .init(self[offset , as: UInt8.self]) case .bigEndian: - return .init(self[offset , as: UInt8.self]) << 24 | + return .init(self[offset , as: UInt8.self]) << 24 | .init(self[offset + 1, as: UInt8.self]) << 16 | .init(self[offset + 2, as: UInt8.self]) << 8 | .init(self[offset + 3, as: UInt8.self]) @@ -620,11 +620,11 @@ extension JPEG.EXIF } } -extension JPEG.EXIF.Endianness +extension JPEG.EXIF.Endianness { - func serialized() -> [UInt8] + func serialized() -> [UInt8] { - switch self + switch self { case .littleEndian: return [0x49, 0x49, 0x2a, 0x00] @@ -633,19 +633,19 @@ extension JPEG.EXIF.Endianness } } } -extension JPEG.EXIF +extension JPEG.EXIF { - /// func JPEG.EXIF.serialized() + /// func JPEG.EXIF.serialized() /// Serializes this metadata record as segment data. - /// + /// /// This method is the inverse of [`parse(_:)`]. /// - -> : [Swift.UInt8] - /// A marker segment body. This array does not include the marker type + /// A marker segment body. This array does not include the marker type /// indicator, or the marker segment length field. - public - func serialized() -> [UInt8] + public + func serialized() -> [UInt8] { - var bytes:[UInt8] = Self.signature + var bytes:[UInt8] = Self.signature bytes.append(contentsOf: self.endianness.serialized()) bytes.append(contentsOf: self.storage.dropFirst(4)) return bytes diff --git a/sources/jpeg/os.swift b/Sources/JPEG/os.swift similarity index 80% rename from sources/jpeg/os.swift rename to Sources/JPEG/os.swift index 6704fd1f..cebf9eef 100644 --- a/sources/jpeg/os.swift +++ b/Sources/JPEG/os.swift @@ -12,27 +12,27 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #if os(macOS) || os(Linux) -/// enum System +/// enum System /// A namespace for platform-dependent functionality. -/// -/// These APIs are only available on MacOS and Linux. However, the rest of the +/// +/// These APIs are only available on MacOS and Linux. However, the rest of the /// framework is pure Swift and should support all Swift platforms. /// # [File IO](system-file-io) /// # [See also](top-level-namespaces) /// ## (2:top-level-namespaces) -public -enum System +public +enum System { - /// enum System.File + /// enum System.File /// A namespace for file IO functionality. /// ## (system-file-io) public enum File { typealias Descriptor = UnsafeMutablePointer - + /// struct System.File.Source - /// : JPEG.Bytestream.Source + /// : JPEG.Bytestream.Source /// A type for reading data from files on disk. /// ## (system-file-io) public @@ -41,15 +41,15 @@ enum System private let descriptor:Descriptor } - + /// struct System.File.Destination /// : JPEG.Bytestream.Destination /// A type for writing data to files on disk. /// ## (system-file-io) - public - struct Destination + public + struct Destination { - private + private let descriptor:Descriptor } } @@ -57,22 +57,22 @@ enum System extension System.File.Source { /// static func System.File.Source.open(path:_:) - /// rethrows + /// rethrows /// Calls a closure with an interface for reading from the specified file. - /// + /// /// This method automatically closes the file when its closure argument returns. - /// - path : Swift.String + /// - path : Swift.String /// The path to the file to open. /// - body : (inout Self) throws -> R /// A closure with a [`Source`] parameter from which data in /// the specified file can be read. This interface is only valid /// for the duration of the method’s execution. The closure is /// only executed if the specified file could be successfully - /// opened, otherwise this method will return `nil`. If `body` has a - /// return value and the specified file could be opened, this method + /// opened, otherwise this method will return `nil`. If `body` has a + /// return value and the specified file could be opened, this method /// returns the return value of the closure. /// - -> : R? - /// The return value of the closure argument, or `nil` if the specified + /// The return value of the closure argument, or `nil` if the specified /// file could not be opened. public static func open(path:String, _ body:(inout Self) throws -> R) @@ -94,12 +94,12 @@ extension System.File.Source } /// func System.File.Source.read(count:) - /// ?: JPEG.Bytestream.Source + /// ?: JPEG.Bytestream.Source /// Reads the specified number of bytes from this file interface. - /// + /// /// This method only returns an array if the exact number of bytes /// specified could be read. This method advances the file pointer. - /// - capacity : Swift.Int + /// - capacity : Swift.Int /// The number of bytes to read. /// - -> : [Swift.UInt8]? /// An array containing the read data, or `nil` if the specified @@ -124,65 +124,65 @@ extension System.File.Source return buffer } /// var System.File.Source.count : Swift.Int? { get } - /// The size of the file, in bytes, or `nil` if the file is not a regular + /// The size of the file, in bytes, or `nil` if the file is not a regular /// file or a link to a file. - /// + /// /// This property queries the file size using `stat`. - public - var count:Int? + public + var count:Int? { let descriptor:Int32 = fileno(self.descriptor) - guard descriptor != -1 - else + guard descriptor != -1 + else { - return nil + return nil } - - guard let status:stat = + + guard let status:stat = ({ var status:stat = .init() - guard fstat(descriptor, &status) == 0 - else + guard fstat(descriptor, &status) == 0 + else { - return nil + return nil } - return status + return status }()) - else + else { - return nil + return nil } - - switch status.st_mode & S_IFMT + + switch status.st_mode & S_IFMT { case S_IFREG, S_IFLNK: - break + break default: - return nil + return nil } - + return Int.init(status.st_size) - } + } } extension System.File.Destination { /// static func System.File.Destination.open(path:_:) - /// rethrows + /// rethrows /// Calls a closure with an interface for writing to the specified file. - /// + /// /// This method automatically closes the file when its closure argument returns. - /// - path : Swift.String + /// - path : Swift.String /// The path to the file to open. /// - body : (inout Self) throws -> R /// A closure with a [`Destination`] parameter representing /// the specified file to which data can be written to. This /// interface is only valid for the duration of the method’s - /// execution. The closure is only executed if the specified file could + /// execution. The closure is only executed if the specified file could /// be successfully opened, otherwise this method will return `nil`. - /// If `body` has a return value and the specified file could be opened, + /// If `body` has a return value and the specified file could be opened, /// this method returns the return value of the closure. - /// - -> : R? - /// The return value of the closure argument, or `nil` if the specified + /// - -> : R? + /// The return value of the closure argument, or `nil` if the specified /// file could not be opened. public static func open(path:String, _ body:(inout Self) throws -> R) @@ -202,16 +202,16 @@ extension System.File.Destination return try body(&file) } - + /// func System.File.Destination.write(_:) /// ?: JPEG.Bytestream.Destination /// Write the bytes in the given array to this file interface. - /// + /// /// This method only returns `()` if the entire array argument could /// be written. This method advances the file pointer. - /// - buffer : [Swift.UInt8] + /// - buffer : [Swift.UInt8] /// The data to write. - /// - -> : Swift.Void? + /// - -> : Swift.Void? /// A [`Swift.Void`] tuple if the entire array argument could be written, /// or `nil` otherwise. public @@ -234,182 +234,182 @@ extension System.File.Destination } // declare conformance (as a formality) -extension System.File.Source:JPEG.Bytestream.Source +extension System.File.Source:JPEG.Bytestream.Source { } -extension System.File.Destination:JPEG.Bytestream.Destination +extension System.File.Destination:JPEG.Bytestream.Destination { } // file-based encoding and decoding apis -extension JPEG.Data.Spectral +extension JPEG.Data.Spectral { - /// static func JPEG.Data.Spectral.decompress(path:) - /// throws + /// static func JPEG.Data.Spectral.decompress(path:) + /// throws /// Decompresses a spectral image from the given file path. - /// + /// /// Calling this function is equivalent to calling [`System.File.Source.open(path:_:)`] /// with the closure parameter set to [`Self``decompress(stream:)`]. /// /// This function is only available on MacOS and Linux platforms. - /// - path : Swift.String + /// - path : Swift.String /// A file path. /// - -> : Self? - /// The decompressed image, or `nil` if the file could not be opened at + /// The decompressed image, or `nil` if the file could not be opened at /// the given file path. /// # [See also](spectral-create-image) /// ## (2:spectral-create-image) - public static - func decompress(path:String) throws -> Self? + public static + func decompress(path:String) throws -> Self? { return try System.File.Source.open(path: path, Self.decompress(stream:)) } - /// func JPEG.Data.Spectral.compress(path:) - /// throws - /// Compresses a spectral image to the given file path. - /// - /// All metadata records in this image will be emitted at the beginning of + /// func JPEG.Data.Spectral.compress(path:) + /// throws + /// Compresses a spectral image to the given file path. + /// + /// All metadata records in this image will be emitted at the beginning of /// the outputted file, in the order they appear in the [`metadata`] array. /// /// Calling this function is equivalent to calling [`System.File.Destination.open(path:_:)`] /// with the closure parameter set to [`Self``compress(stream:)`]. - /// + /// /// This function is only available on MacOS and Linux platforms. - /// - path : Swift.String + /// - path : Swift.String /// A file path. /// - -> : Swift.Void? - /// A [`Swift.Void`] tuple, or `nil` if the file could not be opened at + /// A [`Swift.Void`] tuple, or `nil` if the file could not be opened at /// the given file path. /// # [See also](spectral-save-image) /// ## (2:spectral-save-image) - public + public func compress(path:String) throws -> Void? { return try System.File.Destination.open(path: path, self.compress(stream:)) } } -extension JPEG.Data.Planar +extension JPEG.Data.Planar { - /// static func JPEG.Data.Planar.decompress(path:) - /// throws + /// static func JPEG.Data.Planar.decompress(path:) + /// throws /// Decompresses a planar image from the given file path. - /// - /// This function is a convenience function which calls [`Spectral.decompress(path:)`] - /// to obtain a spectral image, and then calls [`(Spectral).idct()`] on the + /// + /// This function is a convenience function which calls [`Spectral.decompress(path:)`] + /// to obtain a spectral image, and then calls [`(Spectral).idct()`] on the /// output to return a planar image. - /// + /// /// This function is only available on MacOS and Linux platforms. - /// - path : Swift.String + /// - path : Swift.String /// A file path. /// - -> : Self? - /// The decompressed image, or `nil` if the file could not be opened at + /// The decompressed image, or `nil` if the file could not be opened at /// the given file path. /// # [See also](planar-create-image) /// ## (3:planar-create-image) - public static + public static func decompress(path:String) throws -> Self? { guard let spectral:JPEG.Data.Spectral = try .decompress(path: path) - else + else { - return nil + return nil } return spectral.idct() } - /// func JPEG.Data.Planar.compress(path:quanta:) - /// throws - /// Compresses a planar image to the given file path. - /// - /// All metadata records in this image will be emitted at the beginning of + /// func JPEG.Data.Planar.compress(path:quanta:) + /// throws + /// Compresses a planar image to the given file path. + /// + /// All metadata records in this image will be emitted at the beginning of /// the outputted file, in the order they appear in the [`metadata`] array. - /// + /// /// This function is a convenience function which calls [`fdct(quanta:)`] - /// to obtain a spectral image, and then calls [`(Spectral).compress(path:)`] + /// to obtain a spectral image, and then calls [`(Spectral).compress(path:)`] /// on the output. - /// + /// /// This function is only available on MacOS and Linux platforms. - /// - path : Swift.String + /// - path : Swift.String /// A file path. /// - quanta: [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by this image’s [`layout`], - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by this image’s [`layout`], + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width /// determined by this image’s [`layout``(Layout).format``(JPEG.Format).precision`], /// and all the values must be in the correct range for that bit width. /// - -> : Swift.Void? - /// A [`Swift.Void`] tuple, or `nil` if the file could not be opened at + /// A [`Swift.Void`] tuple, or `nil` if the file could not be opened at /// the given file path. /// # [See also](planar-save-image) /// ## (1:planar-save-image) - public - func compress(path:String, quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws + public + func compress(path:String, quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws -> Void? { return try self.fdct(quanta: quanta).compress(path: path) } } -extension JPEG.Data.Rectangular +extension JPEG.Data.Rectangular { - /// static func JPEG.Data.Rectangular.decompress(path:cosite:) - /// throws + /// static func JPEG.Data.Rectangular.decompress(path:cosite:) + /// throws /// Decompresses a rectangular image from the given file path. - /// - /// This function is a convenience function which calls [`Planar.decompress(path:)`] - /// to obtain a planar image, and then calls [`(Planar).interleaved(cosite:)`] + /// + /// This function is a convenience function which calls [`Planar.decompress(path:)`] + /// to obtain a planar image, and then calls [`(Planar).interleaved(cosite:)`] /// on the output to return a rectangular image. - /// + /// /// This function is only available on MacOS and Linux platforms. - /// - path : Swift.String + /// - path : Swift.String /// A file path. - /// - cosited : Swift.Bool - /// The upsampling method to use. Setting this parameter to `true` co-sites + /// - cosited : Swift.Bool + /// The upsampling method to use. Setting this parameter to `true` co-sites /// the samples; setting it to `false` centers them instead. - /// + /// /// The default value is `false`. /// - -> : Self? - /// The decompressed image, or `nil` if the file could not be opened at + /// The decompressed image, or `nil` if the file could not be opened at /// the given file path. /// # [See also](rectangular-create-image) /// ## (4:rectangular-create-image) - public static - func decompress(path:String, cosite cosited:Bool = false) throws -> Self? + public static + func decompress(path:String, cosite cosited:Bool = false) throws -> Self? { - guard let planar:JPEG.Data.Planar = try .decompress(path: path) - else + guard let planar:JPEG.Data.Planar = try .decompress(path: path) + else { - return nil + return nil } - + return planar.interleaved(cosite: cosited) } - /// func JPEG.Data.Rectangular.compress(path:quanta:) - /// throws - /// Compresses a rectangular image to the given file path. - /// - /// All metadata records in this image will be emitted at the beginning of + /// func JPEG.Data.Rectangular.compress(path:quanta:) + /// throws + /// Compresses a rectangular image to the given file path. + /// + /// All metadata records in this image will be emitted at the beginning of /// the outputted file, in the order they appear in the [`metadata`] array. - /// + /// /// This function is a convenience function which calls [`decomposed()`] - /// to obtain a planar image, and then calls [`(Planar).compress(path:quanta:)`] + /// to obtain a planar image, and then calls [`(Planar).compress(path:quanta:)`] /// on the output. /// /// This function is only available on MacOS and Linux platforms. - /// - path : Swift.String + /// - path : Swift.String /// A file path. /// - quanta: [JPEG.Table.Quantization.Key: [Swift.UInt16]] - /// The quantum values for each quanta key used by this image’s [`layout`], - /// including quanta keys used only by non-recognized components. Each - /// array of quantum values must have exactly 64 elements. The quantization + /// The quantum values for each quanta key used by this image’s [`layout`], + /// including quanta keys used only by non-recognized components. Each + /// array of quantum values must have exactly 64 elements. The quantization /// tables created from these values will be encoded using integers with a bit width /// determined by this image’s [`layout``(Layout).format``(JPEG.Format).precision`], /// and all the values must be in the correct range for that bit width. /// - -> : Swift.Void? - /// A [`Swift.Void`] tuple, or `nil` if the file could not be opened at + /// A [`Swift.Void`] tuple, or `nil` if the file could not be opened at /// the given file path. /// # [See also](rectangular-save-image) /// ## (1:rectangular-save-image) - public - func compress(path:String, quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws + public + func compress(path:String, quanta:[JPEG.Table.Quantization.Key: [UInt16]]) throws -> Void? { try self.decomposed().compress(path: path, quanta: quanta) diff --git a/Sources/JPEGInspection/Highlight.swift b/Sources/JPEGInspection/Highlight.swift new file mode 100644 index 00000000..a6fd467a --- /dev/null +++ b/Sources/JPEGInspection/Highlight.swift @@ -0,0 +1,114 @@ +public +enum Highlight +{ + public + static let bold:String = "\u{1B}[1m" + public + static let reset:String = "\u{1B}[0m" + + public + static func fg(_ color:(r:UInt8, g:UInt8, b:UInt8)?) -> String + { + if let color:(r:UInt8, g:UInt8, b:UInt8) = color + { + return "\u{1B}[38;2;\(color.r);\(color.g);\(color.b)m" + } + else + { + return "\u{1B}[39m" + } + } + public + static func bg(_ color:(r:UInt8, g:UInt8, b:UInt8)?) -> String + { + if let color:(r:UInt8, g:UInt8, b:UInt8) = color + { + return "\u{1B}[48;2;\(color.r);\(color.g);\(color.b)m" + } + else + { + return "\u{1B}[49m" + } + } + + public + static func quantize(_ color:(r:F, g:F, b:F)) -> (r:UInt8, g:UInt8, b:UInt8) + where F:BinaryFloatingPoint + { + let r:UInt8 = .init((.init(UInt8.max) * max(0, min(color.r, 1))).rounded()), + g:UInt8 = .init((.init(UInt8.max) * max(0, min(color.g, 1))).rounded()), + b:UInt8 = .init((.init(UInt8.max) * max(0, min(color.b, 1))).rounded()) + return (r, g, b) + } + public + static func color(_ string:String, _ color:(r:F, g:F, b:F)) -> String + where F:BinaryFloatingPoint + { + return Self.color(string, Self.quantize(color)) + } + public + static func color(_ string:String, _ fg:(r:UInt8, g:UInt8, b:UInt8)) -> String + { + return "\(Self.fg(fg))\(string)\(Self.fg(nil))" + } + + public + static func highlight(_ string:String, _ color:(r:F, g:F, b:F)) -> String + where F:BinaryFloatingPoint + { + return Self.highlight(string, Self.quantize(color)) + } + public + static func highlight(_ string:String, _ bg:(r:UInt8, g:UInt8, b:UInt8)) -> String + { + let fg:(r:UInt8, g:UInt8, b:UInt8) = + (bg.r / 3 + bg.g / 3 + bg.b / 3) < 128 ? (.max, .max, .max) : (0, 0, 0) + + return "\(Self.bg(bg))\(Self.fg(fg))\(string)\(Self.fg(nil))\(Self.bg(nil))" + } + public + static func swatch(_ color:(r:F, g:F, b:F)) -> String + where F:BinaryFloatingPoint + { + let v:(String, String, String) = + ( + String.pad("\(color.r)", left: 3), + String.pad("\(color.g)", left: 3), + String.pad("\(color.b)", left: 3) + ) + return Self.highlight(" \(v.0)\(v.1)\(v.2) ", color) + } + public + static func square(_ color:(r:F, g:F, b:F)) -> String + where F:BinaryFloatingPoint + { + return Self.highlight(" ", color) + } + public + static func square(_ color:(r:UInt8, g:UInt8, b:UInt8)) -> String + { + return Self.highlight(" ", color) + } + + public + static func bits(_ x:I) -> String where I:FixedWidthInteger + { + return (0 ..< I.bitWidth).reversed().map + { + (x >> $0) & 1 == 0 ? Self.highlight("0", (0.2, 0.2, 0.2)) : Self.highlight("1", (1, 1, 1)) + }.joined(separator: "") + } + + public + static func print(_ string:String, highlight color:(r:F, g:F, b:F)) + where F:BinaryFloatingPoint + { + Swift.print(Self.highlight(string, color)) + } + public + static func print(_ string:String, color:(r:F, g:F, b:F)) + where F:BinaryFloatingPoint + { + Swift.print(Self.color(string, color)) + } +} diff --git a/Sources/JPEGInspection/String (ext).swift b/Sources/JPEGInspection/String (ext).swift new file mode 100644 index 00000000..e4219052 --- /dev/null +++ b/Sources/JPEGInspection/String (ext).swift @@ -0,0 +1,13 @@ +extension String +{ + public + static func pad(_ string:String, left count:Int) -> Self + { + .init(repeating: " ", count: count - string.count) + string + } + public + static func pad(_ string:String, right count:Int) -> Self + { + string + .init(repeating: " ", count: count - string.count) + } +} diff --git a/examples/custom-color/main.swift b/examples/custom-color/main.swift index eb45da62..593814e2 100644 --- a/examples/custom-color/main.swift +++ b/examples/custom-color/main.swift @@ -1,18 +1,18 @@ -import JPEG +import JPEG -extension JPEG +extension JPEG { - enum Deep + enum Deep { case rgba12 } - - struct RGB12 + + struct RGB12 { var r:UInt16 var g:UInt16 var b:UInt16 - + init(_ r:UInt16, _ g:UInt16, _ b:UInt16) { self.r = r @@ -20,14 +20,14 @@ extension JPEG self.b = b } } - - struct RGBA12 + + struct RGBA12 { var r:UInt16 var g:UInt16 var b:UInt16 var a:UInt16 - + init(_ r:UInt16, _ g:UInt16, _ b:UInt16, _ a:UInt16) { self.r = r @@ -38,52 +38,52 @@ extension JPEG } } -extension JPEG.Deep:JPEG.Format +extension JPEG.Deep:JPEG.Format { - static + static func recognize(_ components:Set, precision:Int) -> Self? { switch (components.sorted(), precision) { case ([4, 5, 6, 7], 12): - return .rgba12 + return .rgba12 default: - return nil + return nil } } - - // the ordering here is used to determine planar indices + + // the ordering here is used to determine planar indices var components:[JPEG.Component.Key] { [4, 5, 6, 7] } - var precision:Int + var precision:Int { - 12 + 12 } } -extension JPEG.RGB12:JPEG.Color +extension JPEG.RGB12:JPEG.Color { - static + static func unpack(_ interleaved:[UInt16], of format:JPEG.Deep) -> [Self] { - switch format + switch format { case .rgba12: - return stride(from: interleaved.startIndex, to: interleaved.endIndex, by: 4).map + return stride(from: interleaved.startIndex, to: interleaved.endIndex, by: 4).map { - (base:Int) -> Self in + (base:Int) -> Self in .init( - interleaved[base ], - interleaved[base + 1], + interleaved[base ], + interleaved[base + 1], interleaved[base + 2]) } } } - static + static func pack(_ pixels:[Self], as format:JPEG.Deep) -> [UInt16] { - switch format + switch format { case .rgba12: return pixels.flatMap @@ -93,29 +93,29 @@ extension JPEG.RGB12:JPEG.Color } } } -extension JPEG.RGBA12:JPEG.Color +extension JPEG.RGBA12:JPEG.Color { - static + static func unpack(_ interleaved:[UInt16], of format:JPEG.Deep) -> [Self] { - switch format + switch format { case .rgba12: - return stride(from: interleaved.startIndex, to: interleaved.endIndex, by: 4).map + return stride(from: interleaved.startIndex, to: interleaved.endIndex, by: 4).map { - (base:Int) -> Self in + (base:Int) -> Self in .init( - interleaved[base ], - interleaved[base + 1], - interleaved[base + 2], + interleaved[base ], + interleaved[base + 1], + interleaved[base + 2], interleaved[base + 3]) } } } - static + static func pack(_ pixels:[Self], as format:JPEG.Deep) -> [UInt16] { - switch format + switch format { case .rgba12: return pixels.flatMap @@ -127,45 +127,45 @@ extension JPEG.RGBA12:JPEG.Color } -func sin(_ x:Double) -> UInt16 +func sin(_ x:Double) -> UInt16 { .init(0x0fff * (_sin(2.0 * .pi * x) * 0.5 + 0.5)) -} -let gradient:[JPEG.RGBA12] = stride(from: 0.0, to: 1.0, by: 0.005).flatMap +} +let gradient:[JPEG.RGBA12] = stride(from: 0.0, to: 1.0, by: 0.005).flatMap { - (phase:Double) -> [JPEG.RGBA12] in + (phase:Double) -> [JPEG.RGBA12] in stride(from: 0.0, to: 1.0, by: 0.001).map { .init(sin(phase + $0 - 0.15), sin(phase + $0), sin(phase + $0 + 0.15), 0x0fff) } } -let format:JPEG.Deep = .rgba12 +let format:JPEG.Deep = .rgba12 let R:JPEG.Component.Key = format.components[0], G:JPEG.Component.Key = format.components[1], B:JPEG.Component.Key = format.components[2], A:JPEG.Component.Key = format.components[3] let layout:JPEG.Layout = .init( - format: format, - process: .progressive(coding: .huffman, differential: false), - components: + format: format, + process: .progressive(coding: .huffman, differential: false), + components: [ R: (factor: (2, 2), qi: 0), G: (factor: (2, 2), qi: 0), B: (factor: (2, 2), qi: 0), A: (factor: (1, 1), qi: 1), - ], - scans: + ], + scans: [ .progressive((G, \.0), (A, \.1), bits: 0...), .progressive((R, \.0), (B, \.1), bits: 0...), - + .progressive((R, \.0), band: 1 ..< 64, bits: 1...), .progressive((G, \.0), band: 1 ..< 64, bits: 1...), .progressive((B, \.0), band: 1 ..< 64, bits: 1...), .progressive((A, \.0), band: 1 ..< 64, bits: 1...), - + .progressive((R, \.0), band: 1 ..< 64, bit: 0), .progressive((G, \.0), band: 1 ..< 64, bit: 0), .progressive((B, \.0), band: 1 ..< 64, bit: 0), @@ -173,16 +173,16 @@ let layout:JPEG.Layout = .init( ]) let path:String = "examples/custom-color/output.jpg" -let image:JPEG.Data.Rectangular = +let image:JPEG.Data.Rectangular = .pack(size: (1000, 200), layout: layout, metadata: [], pixels: gradient) -try image.compress(path: path, quanta: +try image.compress(path: path, quanta: [ 0: [1, 2, 2, 3, 3, 3] + .init(repeating: 10, count: 58), 1: [1] + .init(repeating: 100, count: 63), ]) guard let saved:JPEG.Data.Rectangular = try .decompress(path: path) -else +else { fatalError("failed to open file '\(path)'") } @@ -191,18 +191,18 @@ let rgb12:[JPEG.RGB12] = image.unpack(as: JPEG.RGB12.self) guard let _:Void = (System.File.Destination.open(path: "\(path).rgb") { guard let _:Void = $0.write(rgb12.flatMap - { + { [ - .init($0.r >> 4 as UInt16), .init(($0.r << 4 as UInt16) & 0xff), - .init($0.g >> 4 as UInt16), .init(($0.g << 4 as UInt16) & 0xff), - .init($0.b >> 4 as UInt16), .init(($0.b << 4 as UInt16) & 0xff), - ] + .init($0.r >> 4 as UInt16), .init(($0.r << 4 as UInt16) & 0xff), + .init($0.g >> 4 as UInt16), .init(($0.g << 4 as UInt16) & 0xff), + .init($0.b >> 4 as UInt16), .init(($0.b << 4 as UInt16) & 0xff), + ] }) - else + else { fatalError("failed to write to file '\(path).rgb'") } -}) +}) else { fatalError("failed to open file '\(path).rgb'") diff --git a/examples/decode-advanced/main.swift b/examples/decode-advanced/main.swift index 6a3fab32..8c607337 100644 --- a/examples/decode-advanced/main.swift +++ b/examples/decode-advanced/main.swift @@ -1,14 +1,14 @@ import JPEG -extension String +extension String { - static - func pad(_ string:String, left count:Int) -> Self + static + func pad(_ string:String, left count:Int) -> Self { .init(repeating: " ", count: count - string.count) + string } - static - func pad(_ string:String, right count:Int) -> Self + static + func pad(_ string:String, right count:Int) -> Self { string + .init(repeating: " ", count: count - string.count) } @@ -16,7 +16,7 @@ extension String let path:String = "examples/decode-advanced/karlie-2019.jpg" guard let spectral:JPEG.Data.Spectral = try .decompress(path: path) -else +else { fatalError("failed to open file '\(path)'") } @@ -27,18 +27,18 @@ print(""" size : (\(spectral.size.x), \(spectral.size.y)) process : \(spectral.layout.process) precision : \(spectral.layout.format.precision) - components : + components : [ - \(spectral.layout.residents.sorted(by: { $0.key < $1.key }).map + \(spectral.layout.residents.sorted(by: { $0.key < $1.key }).map { - let (component, qi):(JPEG.Component, JPEG.Table.Quantization.Key) = + let (component, qi):(JPEG.Component, JPEG.Table.Quantization.Key) = spectral.layout.planes[$0.value] return "\($0.key): (\(component.factor.x), \(component.factor.y), qi: \(qi))" }.joined(separator: "\n ")) ] - scans : + scans : [ - \(spectral.layout.scans.map + \(spectral.layout.scans.map { "[band: \($0.band), bits: \($0.bits)]: \($0.components.map(\.ci))" }.joined(separator: "\n ")) @@ -48,13 +48,13 @@ print(""" for metadata:JPEG.Metadata in spectral.metadata { - switch metadata + switch metadata { case .application(let a, data: let data): Swift.print("metadata (application \(a), \(data.count) bytes)") case .comment(data: let data): Swift.print(""" - comment + comment { '\(String.init(decoding: data, as: Unicode.UTF8.self))' } @@ -66,7 +66,7 @@ for metadata:JPEG.Metadata in spectral.metadata if let (type, count, box):(JPEG.EXIF.FieldType, Int, JPEG.EXIF.Box) = exif[tag: 315], case .ascii = type { - let artist:String = .init(decoding: (0 ..< count).map + let artist:String = .init(decoding: (0 ..< count).map { exif[box.asOffset + $0, as: UInt8.self] }, as: Unicode.ASCII.self) @@ -76,20 +76,20 @@ for metadata:JPEG.Metadata in spectral.metadata } let keys:Set = .init(spectral.layout.planes.map(\.qi)) -for qi:JPEG.Table.Quantization.Key in keys.sorted() +for qi:JPEG.Table.Quantization.Key in keys.sorted() { - let q:Int = spectral.quanta.index(forKey: qi) + let q:Int = spectral.quanta.index(forKey: qi) let table:JPEG.Table.Quantization = spectral.quanta[q] print("quantization table \(qi):") print(""" ┌ \(String.init(repeating: " ", count: 4 * 8)) ┐ - \((0 ..< 8).map + \((0 ..< 8).map { - (h:Int) in + (h:Int) in """ - │ \((0 ..< 8).map + │ \((0 ..< 8).map { - (k:Int) in + (k:Int) in String.pad("\(table[z: JPEG.Table.Quantization.z(k: k, h: h)]) ", left: 4) }.joined()) │ """ @@ -103,27 +103,27 @@ let planar:JPEG.Data.Planar = spectral.idct() for (p, plane):(Int, JPEG.Data.Planar.Plane) in planar.enumerated() { print(""" - plane \(p) + plane \(p) { size: (\(plane.size.x), \(plane.size.y)) } """) - - let samples:[UInt8] = plane.indices.map + + let samples:[UInt8] = plane.indices.map { - (i:(x:Int, y:Int)) in + (i:(x:Int, y:Int)) in .init(clamping: plane[x: i.x, y: i.y]) } - + let planepath:String = "\(path)-\(p).\(plane.size.x)x\(plane.size.y).gray" guard let _:Void = (System.File.Destination.open(path: planepath) { guard let _:Void = $0.write(samples) - else + else { fatalError("failed to write to file '\(planepath)'") } - }) + }) else { fatalError("failed to open file '\(planepath)'") @@ -135,12 +135,12 @@ let rgb:[JPEG.RGB] = rectangular.unpack(as: JPEG.RGB.self) guard let _:Void = (System.File.Destination.open(path: "\(path).rgb") { guard let _:Void = $0.write(rgb.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { fatalError("failed to write to file '\(path).rgb'") } -}) +}) else { fatalError("failed to open file '\(path).rgb'") -} +} diff --git a/examples/decode-basic/main.swift b/examples/decode-basic/main.swift index 18b90132..dc75800f 100644 --- a/examples/decode-basic/main.swift +++ b/examples/decode-basic/main.swift @@ -3,7 +3,7 @@ import JPEG let path:String = "examples/decode-basic/karlie-kwk-2019.jpg" guard let image:JPEG.Data.Rectangular = try .decompress(path: path) -else +else { fatalError("failed to open file '\(path)'") } @@ -12,11 +12,11 @@ let rgb:[JPEG.RGB] = image.unpack(as: JPEG.RGB.self) guard let _:Void = (System.File.Destination.open(path: "\(path).rgb") { guard let _:Void = $0.write(rgb.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { fatalError("failed to write to file '\(path).rgb'") } -}) +}) else { fatalError("failed to open file '\(path).rgb'") diff --git a/examples/decode-online/main.swift b/examples/decode-online/main.swift index 39de4e88..eeef191c 100644 --- a/examples/decode-online/main.swift +++ b/examples/decode-online/main.swift @@ -1,46 +1,46 @@ -import JPEG +import JPEG -struct Stream +struct Stream { private(set) - var data:[UInt8], - position:Int, - available:Int + var data:[UInt8], + position:Int, + available:Int } extension Stream:JPEG.Bytestream.Source { - init(_ data:[UInt8]) + init(_ data:[UInt8]) { - self.data = data + self.data = data self.position = data.startIndex self.available = data.startIndex } - - mutating - func read(count:Int) -> [UInt8]? + + mutating + func read(count:Int) -> [UInt8]? { - guard self.position + count <= data.endIndex - else + guard self.position + count <= data.endIndex + else { - return nil + return nil } - guard self.position + count < self.available - else + guard self.position + count < self.available + else { self.available += 4096 - return nil + return nil } - - defer + + defer { - self.position += count + self.position += count } - + return .init(self.data[self.position ..< self.position + count]) } - - mutating - func reset(position:Int) + + mutating + func reset(position:Int) { precondition(self.data.indices ~= position) self.position = position @@ -48,205 +48,205 @@ extension Stream:JPEG.Bytestream.Source } let path:String = "examples/decode-online/karlie-oscars-2017.jpg" -guard let data:[UInt8] = (System.File.Source.open(path: path) +guard let data:[UInt8] = (System.File.Source.open(path: path) { (source:inout System.File.Source) -> [UInt8]? in - + guard let count:Int = source.count - else + else { - return nil + return nil } return source.read(count: count) } ?? nil) -else +else { fatalError("failed to open or read file '\(path)'") } var stream:Stream = .init(data) -func waitSegment(stream:inout Stream) throws -> (JPEG.Marker, [UInt8]) +func waitSegment(stream:inout Stream) throws -> (JPEG.Marker, [UInt8]) { let position:Int = stream.position - while true + while true { - do + do { return try stream.segment() } - catch JPEG.LexingError.truncatedMarkerSegmentType + catch JPEG.LexingError.truncatedMarkerSegmentType { stream.reset(position: position) - continue + continue } - catch JPEG.LexingError.truncatedMarkerSegmentHeader + catch JPEG.LexingError.truncatedMarkerSegmentHeader { stream.reset(position: position) - continue + continue } - catch JPEG.LexingError.truncatedMarkerSegmentBody + catch JPEG.LexingError.truncatedMarkerSegmentBody { stream.reset(position: position) - continue + continue } - catch JPEG.LexingError.truncatedEntropyCodedSegment + catch JPEG.LexingError.truncatedEntropyCodedSegment { stream.reset(position: position) - continue + continue } } } func waitSegmentPrefix(stream:inout Stream) throws -> ([UInt8], (JPEG.Marker, [UInt8])) { let position:Int = stream.position - while true + while true { - do + do { return try stream.segment(prefix: true) } - catch JPEG.LexingError.truncatedMarkerSegmentType + catch JPEG.LexingError.truncatedMarkerSegmentType { stream.reset(position: position) - continue + continue } - catch JPEG.LexingError.truncatedMarkerSegmentHeader + catch JPEG.LexingError.truncatedMarkerSegmentHeader { stream.reset(position: position) - continue + continue } - catch JPEG.LexingError.truncatedMarkerSegmentBody + catch JPEG.LexingError.truncatedMarkerSegmentBody { stream.reset(position: position) - continue + continue } - catch JPEG.LexingError.truncatedEntropyCodedSegment + catch JPEG.LexingError.truncatedEntropyCodedSegment { stream.reset(position: position) - continue + continue } } } -func decodeOnline(stream:inout Stream, _ capture:(JPEG.Data.Spectral) throws -> ()) +func decodeOnline(stream:inout Stream, _ capture:(JPEG.Data.Spectral) throws -> ()) throws { - var marker:(type:JPEG.Marker, data:[UInt8]) + var marker:(type:JPEG.Marker, data:[UInt8]) - // start of image + // start of image marker = try waitSegment(stream: &stream) - guard case .start = marker.type - else + guard case .start = marker.type + else { fatalError() } marker = try waitSegment(stream: &stream) - var dc:[JPEG.Table.HuffmanDC] = [], - ac:[JPEG.Table.HuffmanAC] = [], + var dc:[JPEG.Table.HuffmanDC] = [], + ac:[JPEG.Table.HuffmanAC] = [], quanta:[JPEG.Table.Quantization] = [] - var interval:JPEG.Header.RestartInterval?, + var interval:JPEG.Header.RestartInterval?, frame:JPEG.Header.Frame? definitions: - while true + while true { - switch marker.type + switch marker.type { case .frame(let process): frame = try .parse(marker.data, process: process) marker = try waitSegment(stream: &stream) break definitions - + case .quantization: - let parsed:[JPEG.Table.Quantization] = + let parsed:[JPEG.Table.Quantization] = try JPEG.Table.parse(quantization: marker.data) quanta.append(contentsOf: parsed) - + case .huffman: - let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = + let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = try JPEG.Table.parse(huffman: marker.data) dc.append(contentsOf: parsed.dc) ac.append(contentsOf: parsed.ac) - + case .interval: interval = try .parse(marker.data) - - // ignore + + // ignore case .application, .comment: - break - - // unexpected + break + + // unexpected case .scan, .height, .end, .start, .restart: fatalError() - - // unsupported + + // unsupported case .arithmeticCodingCondition, .hierarchical, .expandReferenceComponents: - break + break } - + marker = try waitSegment(stream: &stream) } // can use `!` here, previous loop cannot exit without initializing `frame` var context:JPEG.Context = try .init(frame: frame!) - for table:JPEG.Table.HuffmanDC in dc + for table:JPEG.Table.HuffmanDC in dc { context.push(dc: table) } - for table:JPEG.Table.HuffmanAC in ac + for table:JPEG.Table.HuffmanAC in ac { context.push(ac: table) } - for table:JPEG.Table.Quantization in quanta + for table:JPEG.Table.Quantization in quanta { try context.push(quanta: table) } - if let interval:JPEG.Header.RestartInterval = interval + if let interval:JPEG.Header.RestartInterval = interval { context.push(interval: interval) } var first:Bool = true scans: - while true + while true { - switch marker.type + switch marker.type { - // ignore + // ignore case .application, .comment: - break + break // unexpected case .frame, .start, .restart, .height: fatalError() - // unsupported + // unsupported case .arithmeticCodingCondition, .hierarchical, .expandReferenceComponents: - break - + break + case .quantization: - for table:JPEG.Table.Quantization in + for table:JPEG.Table.Quantization in try JPEG.Table.parse(quantization: marker.data) { try context.push(quanta: table) } - + case .huffman: - let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = + let parsed:(dc:[JPEG.Table.HuffmanDC], ac:[JPEG.Table.HuffmanAC]) = try JPEG.Table.parse(huffman: marker.data) - for table:JPEG.Table.HuffmanDC in parsed.dc + for table:JPEG.Table.HuffmanDC in parsed.dc { context.push(dc: table) } - for table:JPEG.Table.HuffmanAC in parsed.ac + for table:JPEG.Table.HuffmanAC in parsed.ac { context.push(ac: table) } - + case .interval: context.push(interval: try .parse(marker.data)) - + case .scan: - let scan:JPEG.Header.Scan = try .parse(marker.data, + let scan:JPEG.Header.Scan = try .parse(marker.data, process: context.spectral.layout.process) var ecss:[[UInt8]] = [] for index:Int in 0... @@ -255,13 +255,13 @@ func decodeOnline(stream:inout Stream, _ capture:(JPEG.Data.Spectral = $0.idct().interleaved() let rgb:[JPEG.RGB] = image.unpack(as: JPEG.RGB.self) - let difference:[JPEG.RGB] = - zip(last + .init(repeating: .init(0, 0, 0), count: rgb.count - last.count), rgb).map + let difference:[JPEG.RGB] = + zip(last + .init(repeating: .init(0, 0, 0), count: rgb.count - last.count), rgb).map { - let d:(r:Int, g:Int, b:Int) = + let d:(r:Int, g:Int, b:Int) = ( abs(.init($1.r) - .init($0.r)), abs(.init($1.g) - .init($0.g)), @@ -316,34 +316,34 @@ try decodeOnline(stream: &stream) ) return .init(.init(clamping: 50 * d.r), .init(clamping: 50 * d.g), .init(clamping: 50 * d.b)) } - + last = rgb - + guard let _:Void = (System.File.Destination.open(path: "\(path)-\(counter).rgb") { guard let _:Void = $0.write(rgb.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { fatalError("failed to write to file '\(path)-\(counter).rgb'") } - }) + }) else { fatalError("failed to open file '\(path)-\(counter).rgb'") - } - + } + guard let _:Void = (System.File.Destination.open(path: "\(path)-difference-\(counter).rgb") { guard let _:Void = $0.write(difference.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { fatalError("failed to write to file '\(path)-difference-\(counter).rgb'") } - }) + }) else { fatalError("failed to open file '\(path)-difference-\(counter).rgb'") - } - + } + counter += 1 } diff --git a/examples/encode-advanced/main.swift b/examples/encode-advanced/main.swift index 27aa44b6..c6ec9b6a 100644 --- a/examples/encode-advanced/main.swift +++ b/examples/encode-advanced/main.swift @@ -5,17 +5,17 @@ let path:String = "examples/encode-advanced/karlie-cfdas-2011.png.rgb", guard let rgb:[JPEG.RGB] = (System.File.Source.open(path: path) { guard let data:[UInt8] = $0.read(count: 3 * size.x * size.y) - else + else { fatalError("failed to read from file '\(path)'") } - return (0 ..< size.x * size.y).map + return (0 ..< size.x * size.y).map { (i:Int) -> JPEG.RGB in .init(data[3 * i], data[3 * i + 1], data[3 * i + 2]) } -}) +}) else { fatalError("failed to open file '\(path)'") @@ -29,46 +29,46 @@ let Y:JPEG.Component.Key = format.components[0], let layout:JPEG.Layout = .init( format: format, - process: .progressive(coding: .huffman, differential: false), - components: + process: .progressive(coding: .huffman, differential: false), + components: [ Y: (factor: (2, 1), qi: 0), // 4:2:2 subsampling - Cb: (factor: (1, 1), qi: 1), + Cb: (factor: (1, 1), qi: 1), Cr: (factor: (1, 1), qi: 1), - ], - scans: + ], + scans: [ .progressive((Y, \.0), (Cb, \.1), (Cr, \.1), bits: 2...), .progressive( Y, Cb, Cr , bit: 1 ), .progressive( Y, Cb, Cr , bit: 0 ), - - .progressive((Y, \.0), band: 1 ..< 64, bits: 1...), - - .progressive((Cb, \.0), band: 1 ..< 6, bits: 1...), - .progressive((Cr, \.0), band: 1 ..< 6, bits: 1...), - - .progressive((Cb, \.0), band: 6 ..< 64, bits: 1...), - .progressive((Cr, \.0), band: 6 ..< 64, bits: 1...), - - .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), - .progressive((Cb, \.0), band: 1 ..< 64, bit: 0 ), - .progressive((Cr, \.0), band: 1 ..< 64, bit: 0 ), + + .progressive((Y, \.0), band: 1 ..< 64, bits: 1...), + + .progressive((Cb, \.0), band: 1 ..< 6, bits: 1...), + .progressive((Cr, \.0), band: 1 ..< 6, bits: 1...), + + .progressive((Cb, \.0), band: 6 ..< 64, bits: 1...), + .progressive((Cr, \.0), band: 6 ..< 64, bits: 1...), + + .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), + .progressive((Cb, \.0), band: 1 ..< 64, bit: 0 ), + .progressive((Cr, \.0), band: 1 ..< 64, bit: 0 ), ]) -for (tables, scans):([JPEG.Table.Quantization.Key], [JPEG.Scan]) in layout.definitions +for (tables, scans):([JPEG.Table.Quantization.Key], [JPEG.Scan]) in layout.definitions { print(""" - define quantization tables: + define quantization tables: [ \(tables.map(String.init(describing:)).joined(separator: "\n ")) ] """) print(""" - scans: \(scans.count) scans + scans: \(scans.count) scans """) } -for (c, (component, qi)):(Int, (component:JPEG.Component, qi:JPEG.Table.Quantization.Key)) in layout.planes.enumerated() +for (c, (component, qi)):(Int, (component:JPEG.Component, qi:JPEG.Table.Quantization.Key)) in layout.planes.enumerated() { print(""" plane \(c) @@ -81,18 +81,18 @@ for (c, (component, qi)):(Int, (component:JPEG.Component, qi:JPEG.Table.Quantiza } let comment:[UInt8] = .init("the way u say ‘important’ is important".utf8) -let rectangular:JPEG.Data.Rectangular = +let rectangular:JPEG.Data.Rectangular = .pack(size: size, layout: layout, metadata: [.comment(data: comment)], pixels: rgb) let planar:JPEG.Data.Planar = rectangular.decomposed() -let spectral:JPEG.Data.Spectral = planar.fdct(quanta: +let spectral:JPEG.Data.Spectral = planar.fdct(quanta: [ 0: [1, 2, 2, 3, 3, 3] + .init(repeating: 4, count: 58), 1: [1, 2, 2, 5, 5, 5] + .init(repeating: 30, count: 58), ]) guard let _:Void = try spectral.compress(path: "\(path).jpg") -else +else { fatalError("failed to open file '\(path).jpg'") } diff --git a/examples/encode-basic/main.swift b/examples/encode-basic/main.swift index 5644d5cf..8c9718ea 100644 --- a/examples/encode-basic/main.swift +++ b/examples/encode-basic/main.swift @@ -1,21 +1,21 @@ import JPEG -let path:String = "examples/encode-basic/karlie-milan-sp12-2011", +let path:String = "examples/encode-basic/karlie-milan-sp12-2011", size:(x:Int, y:Int) = (400, 665) guard let rgb:[JPEG.RGB] = (System.File.Source.open(path: "\(path).rgb") { guard let data:[UInt8] = $0.read(count: 3 * size.x * size.y) - else + else { fatalError("failed to read from file '\(path).rgb'") } - - return (0 ..< size.x * size.y).map + + return (0 ..< size.x * size.y).map { (i:Int) -> JPEG.RGB in .init(data[3 * i], data[3 * i + 1], data[3 * i + 2]) } -}) +}) else { fatalError("failed to open file '\(path).rgb'") @@ -31,25 +31,25 @@ for factor:(luminance:(Int, Int), chrominance:(Int, Int), name:String) in { let layout:JPEG.Layout = .init( format: .ycc8, - process: .baseline, - components: + process: .baseline, + components: [ - 1: (factor: factor.luminance, qi: 0 as JPEG.Table.Quantization.Key), - 2: (factor: factor.chrominance, qi: 1 as JPEG.Table.Quantization.Key), + 1: (factor: factor.luminance, qi: 0 as JPEG.Table.Quantization.Key), + 2: (factor: factor.chrominance, qi: 1 as JPEG.Table.Quantization.Key), 3: (factor: factor.chrominance, qi: 1 as JPEG.Table.Quantization.Key), - ], - scans: + ], + scans: [ .sequential((1, \.0, \.0)), .sequential((2, \.1, \.1), (3, \.1, \.1)) ]) let jfif:JPEG.JFIF = .init(version: .v1_2, density: (1, 1, .centimeters)) - let image:JPEG.Data.Rectangular = + let image:JPEG.Data.Rectangular = .pack(size: size, layout: layout, metadata: [.jfif(jfif)], pixels: rgb) - - for level:Double in [0.0, 0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0] + + for level:Double in [0.0, 0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0] { - try image.compress(path: "\(path)-\(factor.name)-\(level).jpg", quanta: + try image.compress(path: "\(path)-\(factor.name)-\(level).jpg", quanta: [ 0: JPEG.CompressionLevel.luminance( level).quanta, 1: JPEG.CompressionLevel.chrominance(level).quanta diff --git a/examples/in-memory/main.swift b/examples/in-memory/main.swift index 6a0d6235..68f2a499 100644 --- a/examples/in-memory/main.swift +++ b/examples/in-memory/main.swift @@ -1,94 +1,94 @@ -import JPEG +import JPEG -extension System +extension System { - struct Blob + struct Blob { private(set) - var data:[UInt8], - position:Int + var data:[UInt8], + position:Int } } -extension System.Blob:JPEG.Bytestream.Source, JPEG.Bytestream.Destination +extension System.Blob:JPEG.Bytestream.Source, JPEG.Bytestream.Destination { - init(_ data:[UInt8]) + init(_ data:[UInt8]) { - self.data = data + self.data = data self.position = data.startIndex } - - mutating - func read(count:Int) -> [UInt8]? + + mutating + func read(count:Int) -> [UInt8]? { - guard self.position + count <= data.endIndex - else + guard self.position + count <= data.endIndex + else { - return nil + return nil } - - defer + + defer { - self.position += count + self.position += count } - + return .init(self.data[self.position ..< self.position + count]) } - - mutating - func write(_ bytes:[UInt8]) -> Void? + + mutating + func write(_ bytes:[UInt8]) -> Void? { - self.data.append(contentsOf: bytes) + self.data.append(contentsOf: bytes) return () } } let path:String = "examples/in-memory/karlie-2011.jpg" -guard let data:[UInt8] = (System.File.Source.open(path: path) +guard let data:[UInt8] = (System.File.Source.open(path: path) { (source:inout System.File.Source) -> [UInt8]? in - + guard let count:Int = source.count - else + else { - return nil + return nil } return source.read(count: count) } ?? nil) -else +else { fatalError("failed to open or read file '\(path)'") } var blob:System.Blob = .init(data) -// read from blob +// read from blob let spectral:JPEG.Data.Spectral = try .decompress(stream: &blob) let image:JPEG.Data.Rectangular = spectral.idct().interleaved() let rgb:[JPEG.RGB] = image.unpack(as: JPEG.RGB.self) guard let _:Void = (System.File.Destination.open(path: "\(path).rgb") { guard let _:Void = $0.write(rgb.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { fatalError("failed to write to file '\(path).rgb'") } -}) +}) else { fatalError("failed to open file '\(path).rgb'") -} +} -// write to blob +// write to blob blob = .init([]) try spectral.compress(stream: &blob) guard let _:Void = (System.File.Destination.open(path: "\(path).jpg") { guard let _:Void = $0.write(blob.data) - else + else { fatalError("failed to write to file '\(path).jpg'") } -}) +}) else { fatalError("failed to open file '\(path).jpg'") -} +} diff --git a/examples/recompress/main.swift b/examples/recompress/main.swift index 234920a9..53749d59 100644 --- a/examples/recompress/main.swift +++ b/examples/recompress/main.swift @@ -2,7 +2,7 @@ import JPEG let path:String = "examples/recompress/original.jpg" guard let original:JPEG.Data.Spectral = try .decompress(path: path) -else +else { fatalError("failed to open file '\(path)'") } @@ -14,40 +14,40 @@ let Y:JPEG.Component.Key = format.components[0], let layout:JPEG.Layout = .init( format: format, - process: .progressive(coding: .huffman, differential: false), - components: original.layout.components, - scans: + process: .progressive(coding: .huffman, differential: false), + components: original.layout.components, + scans: [ .progressive((Y, \.0), (Cb, \.1), (Cr, \.1), bits: 0...), - - .progressive((Y, \.0), band: 1 ..< 64, bits: 0...), - .progressive((Cb, \.0), band: 1 ..< 64, bits: 0...), + + .progressive((Y, \.0), band: 1 ..< 64, bits: 0...), + .progressive((Cb, \.0), band: 1 ..< 64, bits: 0...), .progressive((Cr, \.0), band: 1 ..< 64, bits: 0...) ]) var recompressed:JPEG.Data.Spectral = .init( - size: original.size, - layout: layout, - metadata: + size: original.size, + layout: layout, + metadata: [ .jfif(.init(version: .v1_2, density: (1, 1, .centimeters))), - ], + ], quanta: original.quanta.mapValues { [$0[0]] + $0.dropFirst().map{ min($0 * 3 as UInt16, 255) } }) -for ci:JPEG.Component.Key in recompressed.layout.recognized +for ci:JPEG.Component.Key in recompressed.layout.recognized { original.read(ci: ci) { - (plane:JPEG.Data.Spectral.Plane, quanta:JPEG.Table.Quantization) in - - recompressed.with(ci: ci) + (plane:JPEG.Data.Spectral.Plane, quanta:JPEG.Table.Quantization) in + + recompressed.with(ci: ci) { for b:((x:Int, y:Int), (x:Int, y:Int)) in zip(plane.indices, $0.indices) { - for z:Int in 0 ..< 64 + for z:Int in 0 ..< 64 { let coefficient:Int16 = .init(quanta[z: z]) * plane[x: b.0.x, y: b.0.y, z: z] let rescaled:Double = .init(coefficient) / .init($1[z: z]) @@ -56,6 +56,6 @@ for ci:JPEG.Component.Key in recompressed.layout.recognized } } } -} - +} + try recompressed.compress(path: "examples/recompress/recompressed-requantized.jpg") diff --git a/examples/rotate/main.swift b/examples/rotate/main.swift index dc81256b..b24c0e85 100644 --- a/examples/rotate/main.swift +++ b/examples/rotate/main.swift @@ -1,6 +1,6 @@ import JPEG -enum Parameter:String +enum Parameter:String { case rotation } @@ -9,65 +9,65 @@ enum Rotation:String case ii, iii, iv } -enum Block +enum Block { typealias Coefficient = (z:Int, multiplier:Int16) - - static + + static func transpose(_ input:[Coefficient]) -> [Coefficient] { - (0 ..< 8).flatMap + (0 ..< 8).flatMap { - (y:Int) in - (0 ..< 8).map + (y:Int) in + (0 ..< 8).map { - (x:Int) in + (x:Int) in input[8 * x + y] } } } - static + static func reflectVertical(_ input:[Coefficient]) -> [Coefficient] { - (0 ..< 8).flatMap + (0 ..< 8).flatMap { - (y:Int) -> [Coefficient] in - (0 ..< 8).map + (y:Int) -> [Coefficient] in + (0 ..< 8).map { - (x:Int) -> Coefficient in + (x:Int) -> Coefficient in ( - input[8 * y + x].z, + input[8 * y + x].z, input[8 * y + x].multiplier * (1 - 2 * (.init(y) & 1)) ) } } } - static + static func reflectHorizontal(_ input:[Coefficient]) -> [Coefficient] { - (0 ..< 8).flatMap + (0 ..< 8).flatMap { - (y:Int) -> [Coefficient] in - (0 ..< 8).map + (y:Int) -> [Coefficient] in + (0 ..< 8).map { - (x:Int) -> Coefficient in + (x:Int) -> Coefficient in ( - input[8 * y + x].z, + input[8 * y + x].z, input[8 * y + x].multiplier * (1 - 2 * (.init(x) & 1)) ) } } } - - static + + static func transform(_ body:([Coefficient]) -> [Coefficient]) -> [Coefficient] { - let blank:[Coefficient] = (0 ..< 8).flatMap + let blank:[Coefficient] = (0 ..< 8).flatMap { - (y:Int) -> [Coefficient] in - (0 ..< 8).map + (y:Int) -> [Coefficient] in + (0 ..< 8).map { - (x:Int) in + (x:Int) in (JPEG.Table.Quantization.z(k: x, h: y), 1) } } @@ -76,7 +76,7 @@ enum Block { for h:Int in 0 ..< 8 { - for k:Int in 0 ..< 8 + for k:Int in 0 ..< 8 { let z:Int = JPEG.Table.Quantization.z(k: k, h: h) $0[z] = result[8 * h + k] @@ -88,157 +88,157 @@ enum Block } } -func rotate(_ rotation:Rotation, input:String, output:String) throws +func rotate(_ rotation:Rotation, input:String, output:String) throws { guard var original:JPEG.Data.Spectral = try .decompress(path: input) - else + else { fatalError("failed to open file '\(input)'") - } - - let scale:(x:Int, y:Int) = original.layout.scale - + } + + let scale:(x:Int, y:Int) = original.layout.scale + let mapping:[Block.Coefficient] let matrix:(x:(x:Int, y:Int), y:(x:Int, y:Int)) let size:(x:Int, y:Int) - switch rotation + switch rotation { case .ii: original.set(width: original.size.x - original.size.x % (8 * scale.x)) size = (original.size.y, original.size.x) - mapping = Block.transform + mapping = Block.transform { Block.reflectVertical(Block.transpose($0)) } - matrix = + matrix = ( - ( 0, 1), + ( 0, 1), (-1, 0) ) - + case .iii: original.set(width: original.size.x - original.size.x % (8 * scale.x)) original.set(height: original.size.y - original.size.y % (8 * scale.y)) - size = original.size - mapping = Block.transform + size = original.size + mapping = Block.transform { Block.reflectVertical(Block.reflectHorizontal($0)) } - matrix = + matrix = ( - (-1, 0), + (-1, 0), ( 0, -1) ) case .iv: original.set(height: original.size.y - original.size.y % (8 * scale.y)) size = (original.size.y, original.size.x) - mapping = Block.transform + mapping = Block.transform { Block.reflectHorizontal(Block.transpose($0)) } - matrix = + matrix = ( - ( 0, -1), + ( 0, -1), ( 1, 0) ) } - + var rotated:JPEG.Data.Spectral = .init( - size: size, - layout: original.layout, - metadata: original.metadata, - quanta: original.quanta.mapValues + size: size, + layout: original.layout, + metadata: original.metadata, + quanta: original.quanta.mapValues { - (old:[UInt16]) in + (old:[UInt16]) in .init(unsafeUninitializedCapacity: 64) { - for z:Int in 0 ..< 64 + for z:Int in 0 ..< 64 { $0[z] = old[mapping[z].z] } - + $1 = 64 } - }) - - // loop through planes + }) + + // loop through planes for p:(Int, Int) in zip(original.indices, rotated.indices) { let period:(x:Int, y:Int) = original[p.0].units - let offset:(x:Int, y:Int) = + let offset:(x:Int, y:Int) = ( (matrix.x.x < 0 ? period.x - 1 : 0) + (matrix.x.y < 0 ? period.y - 1 : 0), (matrix.y.x < 0 ? period.x - 1 : 0) + (matrix.y.y < 0 ? period.y - 1 : 0) ) - // loop through blocks - for s:(x:Int, y:Int) in original[p.0].indices + // loop through blocks + for s:(x:Int, y:Int) in original[p.0].indices { - let d:(x:Int, y:Int) = + let d:(x:Int, y:Int) = ( - offset.x + matrix.x.x * s.x + matrix.x.y * s.y, + offset.x + matrix.x.x * s.x + matrix.x.y * s.y, offset.y + matrix.y.x * s.x + matrix.y.y * s.y ) - - // loop through coefficients - for z:Int in 0 ..< 64 + + // loop through coefficients + for z:Int in 0 ..< 64 { - rotated[p.1][x: d.x, y: d.y, z: z] = + rotated[p.1][x: d.x, y: d.y, z: z] = original[p.0][x: s.x, y: s.y, z: mapping[z].z] * mapping[z].multiplier } } } - + guard let _:Void = try rotated.compress(path: output) - else + else { fatalError("failed to open file '\(output)'") - } + } } -var parameter:Parameter? = nil +var parameter:Parameter? = nil var rotation:Rotation = .ii -var input:String? = nil, - output:String? = nil +var input:String? = nil, + output:String? = nil for argument:String in CommandLine.arguments.dropFirst() { if argument.prefix(2) == "--" { guard let p:Parameter = Parameter.init(rawValue: .init(argument.dropFirst(2))) - else + else { fatalError("unrecognized parameter '\(argument)'") } - - parameter = p + + parameter = p } - else + else { - switch parameter + switch parameter { case nil: - if input == nil + if input == nil { - input = argument + input = argument } - else + else { - output = argument + output = argument } - + case .rotation?: guard let r:Rotation = Rotation.init(rawValue: argument) - else + else { fatalError("'\(argument)' is not a valid rotation specifier (must be 'ii', 'iii', or 'iv')") } - + rotation = r } } } -if let input:String = input, - let output:String = output +if let input:String = input, + let output:String = output { try rotate(rotation, input: input, output: output) } diff --git a/sources/common/output.swift b/sources/common/output.swift deleted file mode 100644 index ebbbd53d..00000000 --- a/sources/common/output.swift +++ /dev/null @@ -1,126 +0,0 @@ -extension String -{ - static - func pad(_ string:String, left count:Int) -> Self - { - .init(repeating: " ", count: count - string.count) + string - } - static - func pad(_ string:String, right count:Int) -> Self - { - string + .init(repeating: " ", count: count - string.count) - } -} -enum Highlight -{ - static - let bold:String = "\u{1B}[1m" - static - let reset:String = "\u{1B}[0m" - - static - func fg(_ color:(r:UInt8, g:UInt8, b:UInt8)?) -> String - { - if let color:(r:UInt8, g:UInt8, b:UInt8) = color - { - return "\u{1B}[38;2;\(color.r);\(color.g);\(color.b)m" - } - else - { - return "\u{1B}[39m" - } - } - static - func bg(_ color:(r:UInt8, g:UInt8, b:UInt8)?) -> String - { - if let color:(r:UInt8, g:UInt8, b:UInt8) = color - { - return "\u{1B}[48;2;\(color.r);\(color.g);\(color.b)m" - } - else - { - return "\u{1B}[49m" - } - } - - static - func quantize(_ color:(r:F, g:F, b:F)) -> (r:UInt8, g:UInt8, b:UInt8) - where F:BinaryFloatingPoint - { - let r:UInt8 = .init((.init(UInt8.max) * max(0, min(color.r, 1))).rounded()), - g:UInt8 = .init((.init(UInt8.max) * max(0, min(color.g, 1))).rounded()), - b:UInt8 = .init((.init(UInt8.max) * max(0, min(color.b, 1))).rounded()) - return (r, g, b) - } - static - func color(_ string:String, _ color:(r:F, g:F, b:F)) -> String - where F:BinaryFloatingPoint - { - return Self.color(string, Self.quantize(color)) - } - static - func color(_ string:String, _ fg:(r:UInt8, g:UInt8, b:UInt8)) -> String - { - return "\(Self.fg(fg))\(string)\(Self.fg(nil))" - } - - static - func highlight(_ string:String, _ color:(r:F, g:F, b:F)) -> String - where F:BinaryFloatingPoint - { - return Self.highlight(string, Self.quantize(color)) - } - static - func highlight(_ string:String, _ bg:(r:UInt8, g:UInt8, b:UInt8)) -> String - { - let fg:(r:UInt8, g:UInt8, b:UInt8) = - (bg.r / 3 + bg.g / 3 + bg.b / 3) < 128 ? (.max, .max, .max) : (0, 0, 0) - - return "\(Self.bg(bg))\(Self.fg(fg))\(string)\(Self.fg(nil))\(Self.bg(nil))" - } - static - func swatch(_ color:(r:F, g:F, b:F)) -> String - where F:BinaryFloatingPoint - { - let v:(String, String, String) = - ( - String.pad("\(color.r)", left: 3), - String.pad("\(color.g)", left: 3), - String.pad("\(color.b)", left: 3) - ) - return Self.highlight(" \(v.0)\(v.1)\(v.2) ", color) - } - static - func square(_ color:(r:F, g:F, b:F)) -> String - where F:BinaryFloatingPoint - { - return Self.highlight(" ", color) - } - static - func square(_ color:(r:UInt8, g:UInt8, b:UInt8)) -> String - { - return Self.highlight(" ", color) - } - - static - func bits(_ x:I) -> String where I:FixedWidthInteger - { - return (0 ..< I.bitWidth).reversed().map - { - (x >> $0) & 1 == 0 ? Self.highlight("0", (0.2, 0.2, 0.2)) : Self.highlight("1", (1, 1, 1)) - }.joined(separator: "") - } - - static - func print(_ string:String, highlight color:(r:F, g:F, b:F)) - where F:BinaryFloatingPoint - { - Swift.print(Self.highlight(string, color)) - } - static - func print(_ string:String, color:(r:F, g:F, b:F)) - where F:BinaryFloatingPoint - { - Swift.print(Self.color(string, color)) - } -} diff --git a/tests/common/result.swift b/tests/common/result.swift index b33613a9..3b36dc37 100644 --- a/tests/common/result.swift +++ b/tests/common/result.swift @@ -1,11 +1,13 @@ -enum Test +import JPEGInspection + +enum Test { - struct Failure:Swift.Error + struct Failure:Swift.Error { - let message:String + let message:String } - - enum Function + + enum Function { case void( () -> Result) case string_int2((String, (x:Int, y:Int)) -> Result, [(String, (x:Int, y:Int))]) @@ -18,7 +20,7 @@ func test(_ function:Test.Function, name:String) -> Void? { var successes:Int = 0 var failures:[(name:String?, message:String)] = [] - switch function + switch function { case .void(let function): switch function() @@ -29,7 +31,7 @@ func test(_ function:Test.Function, name:String) -> Void? failures.append((nil, failure.message)) } case .string_int2(let function, let cases): - for arguments:(String, (x:Int, y:Int)) in cases + for arguments:(String, (x:Int, y:Int)) in cases { switch function(arguments.0, arguments.1) { @@ -40,7 +42,7 @@ func test(_ function:Test.Function, name:String) -> Void? } } case .string(let function, let cases): - for argument:String in cases + for argument:String in cases { switch function(argument) { @@ -51,7 +53,7 @@ func test(_ function:Test.Function, name:String) -> Void? } } case .int(let function, let cases): - for argument:Int in cases + for argument:Int in cases { switch function(argument) { @@ -62,8 +64,8 @@ func test(_ function:Test.Function, name:String) -> Void? } } } - - var width:Int + + var width:Int { 80 } @@ -86,17 +88,17 @@ func test(_ function:Test.Function, name:String) -> Void? case (let succeeded, let failed): Highlight.print(.pad(" test '\(name)' failed (\(succeeded + failed) cases, \(failed) failed)", right: width), highlight: red) } - for (i, failure):(Int, (name:String?, message:String)) in failures.enumerated() + for (i, failure):(Int, (name:String?, message:String)) in failures.enumerated() { - if let name:String = failure.name + if let name:String = failure.name { Highlight.print(" [\(String.pad("\(i)", left: 2))] case '\(name)' failed: \(failure.message)", color: red) } - else + else { Highlight.print(" [\(String.pad("\(i)", left: 2))]: \(failure.message)", color: red) } } - + return failures.count > 0 ? nil : () } diff --git a/tests/compare/main.swift b/tests/compare/main.swift index 21f731fb..f8047868 100644 --- a/tests/compare/main.swift +++ b/tests/compare/main.swift @@ -1,15 +1,16 @@ -import JPEG +import JPEG +import JPEGInspection -func discrepancy(jpeg:String, reference:String) +func discrepancy(jpeg:String, reference:String) throws -> (average:Double, max:Double) { - guard let rectangular:JPEG.Data.Rectangular = + guard let rectangular:JPEG.Data.Rectangular = try .decompress(path: jpeg) else { fatalError("failed to open file '\(jpeg)'") } - + let output:[JPEG.YCbCr] = rectangular.unpack(as: JPEG.YCbCr.self) guard let expected:[JPEG.YCbCr] = (System.File.Source.open(path: reference) { @@ -26,149 +27,149 @@ func discrepancy(jpeg:String, reference:String) cr:UInt8 = data[$0 * 3 + 2] return .init(y: y, cb: cb, cr: cr) } - }) + }) else { fatalError("failed to open file '\(reference)'") } - - // terminal output - for i:Int in 0 ..< rectangular.size.y + + // terminal output + for i:Int in 0 ..< rectangular.size.y { - func gradientRed(_ x:Double) -> (r:Double, g:Double, b:Double) + func gradientRed(_ x:Double) -> (r:Double, g:Double, b:Double) { (x, 2 * (x - 0.5), 1 * (x - 0.4)) } - func gradientBlue(_ x:Double) -> (r:Double, g:Double, b:Double) + func gradientBlue(_ x:Double) -> (r:Double, g:Double, b:Double) { (0.8 * (x - 0.4), 0, x) } - - let line1:String = (0 ..< rectangular.size.x).map + + let line1:String = (0 ..< rectangular.size.x).map { - (j:Int) in - + (j:Int) in + let c:JPEG.RGB = output[j + i * rectangular.size.x].rgb return Highlight.square((c.r, c.g, c.b)) }.joined(separator: "") - let line2:String = (0 ..< rectangular.size.x).map + let line2:String = (0 ..< rectangular.size.x).map { - (j:Int) in - + (j:Int) in + let c:JPEG.RGB = expected[j + i * rectangular.size.x].rgb return Highlight.square((c.r, c.g, c.b)) }.joined(separator: "") - let line3:String = (0 ..< rectangular.size.x).map + let line3:String = (0 ..< rectangular.size.x).map { - (j:Int) in - - let y:(UInt8, UInt8) = + (j:Int) in + + let y:(UInt8, UInt8) = ( output[j + i * rectangular.size.x].y, expected[j + i * rectangular.size.x].y ) - + let d:Int = Int.init(y.0) - Int.init(y.1) - if d < 0 + if d < 0 { return Highlight.square(gradientBlue(.init(-d) / 10)) } - else + else { return Highlight.square(gradientRed(.init(d) / 10)) } }.joined(separator: "") print("\(line1) \(line2) \(line3)") - } + } for i:Int in 0 ..< rectangular.size.y { - for j:Int in 0 ..< rectangular.size.x + for j:Int in 0 ..< rectangular.size.x { - let y:(UInt8, UInt8) = + let y:(UInt8, UInt8) = ( output[j + i * rectangular.size.x].y, expected[j + i * rectangular.size.x].y ) - - if abs(Int.init(y.0) - Int.init(y.1)) > 1 + + if abs(Int.init(y.0) - Int.init(y.1)) > 1 { print("output = \(y.0), expected = \(y.1)") } } } - - var total:Int = 0, + + var total:Int = 0, max:Int = 0 - - for (a, b):(JPEG.YCbCr, JPEG.YCbCr) in zip(output, expected) + + for (a, b):(JPEG.YCbCr, JPEG.YCbCr) in zip(output, expected) { let difference:Int = abs(.init(a.y) - .init(b.y)) - - total += difference + + total += difference max = Swift.max(max, difference) } - + return (average: .init(total) / .init(output.count), max: .init(max)) } func bin(_ value:Double, into histogram:inout [Int], range:Range) { - let u:Double = (value - .init(range.lowerBound)) / + let u:Double = (value - .init(range.lowerBound)) / .init(range.count) * .init(histogram.count) let b:Int = max(histogram.startIndex, min(.init(u), histogram.endIndex - 1)) histogram[b] += 1 } -func print(histogram:[Int], range:Range, width:Int) +func print(histogram:[Int], range:Range, width:Int) { - func gradient(_ x:Double) -> (r:Double, g:Double, b:Double) + func gradient(_ x:Double) -> (r:Double, g:Double, b:Double) { (1, 1 - 1.5 * (x - 0.4), 1 - x) - } - + } + let max:Int = histogram.max() ?? 1 for (i, count):(Int, Int) in zip(histogram.indices, histogram) { - let a:(Double, Double) = + let a:(Double, Double) = ( - .init(range.lowerBound), + .init(range.lowerBound), .init(range.upperBound) ) - let u:(Double, Double) = + let u:(Double, Double) = ( - .init(i ) / .init(histogram.count), + .init(i ) / .init(histogram.count), .init(i + 1) / .init(histogram.count) ) - let x:(Double, Double) = + let x:(Double, Double) = ( a.0 * (1 - u.0) + a.1 * u.0, a.0 * (1 - u.1) + a.1 * u.1 ) - + let rgb:(r:Double, g:Double, b:Double) = gradient(u.0) - + let left:String = Highlight.highlight( " \(String.pad("\(x.0)", left: 8)) ..< \(String.pad("\(x.1)", left: 8))", rgb) let label:String = .pad("\(count)", left: 5) let right:String = .init(repeating: "█", count: width * count / max) - + print("\(left) \(label) \(right)") } } -let range:(average:Range, max:Range) = +let range:(average:Range, max:Range) = ( 0 ..< 1, 0 ..< 32 ) -var histogram:(average:[Int], max:[Int]) = +var histogram:(average:[Int], max:[Int]) = ( .init(repeating: 0, count: 64), .init(repeating: 0, count: 32) ) for argument:String in CommandLine.arguments.dropFirst() { - let (average, max):(Double, Double) = + let (average, max):(Double, Double) = try discrepancy(jpeg: argument, reference: "\(argument).ycc") bin(average, into: &histogram.average, range: range.average) bin(max, into: &histogram.max, range: range.max) diff --git a/tests/compare/output.swift b/tests/compare/output.swift deleted file mode 120000 index 7501e3cf..00000000 --- a/tests/compare/output.swift +++ /dev/null @@ -1 +0,0 @@ -../../sources/common/output.swift \ No newline at end of file diff --git a/tests/fuzz/main.swift b/tests/fuzz/main.swift index c71bf652..93bc9737 100644 --- a/tests/fuzz/main.swift +++ b/tests/fuzz/main.swift @@ -1,4 +1,5 @@ -import JPEG +import JPEG +import JPEGInspection func fuzz(rng:inout RNG, path:String) throws where RNG:RandomNumberGenerator { @@ -6,56 +7,56 @@ func fuzz(rng:inout RNG, path:String) throws where RNG:RandomNumberGenerato let Y:JPEG.Component.Key = format.components[0], Cb:JPEG.Component.Key = format.components[1], Cr:JPEG.Component.Key = format.components[2] - + let layout:JPEG.Layout = .init( format: format, - process: .progressive(coding: .huffman, differential: false), - components: + process: .progressive(coding: .huffman, differential: false), + components: [ - Y: (factor: (1, 1), qi: 0), - Cb: (factor: (1, 1), qi: 1), + Y: (factor: (1, 1), qi: 0), + Cb: (factor: (1, 1), qi: 1), Cr: (factor: (1, 1), qi: 1), - ], - scans: + ], + scans: [ .progressive((Y, \.0), (Cb, \.1), (Cr, \.1), bits: 2...), .progressive( Y, Cb, Cr , bit: 1 ), .progressive( Y, Cb, Cr , bit: 0 ), - - .progressive((Y, \.0), band: 1 ..< 64, bits: 1...), - - .progressive((Cb, \.0), band: 1 ..< 6, bits: 1...), - .progressive((Cr, \.0), band: 1 ..< 6, bits: 1...), - - .progressive((Cb, \.0), band: 6 ..< 64, bits: 1...), - .progressive((Cr, \.0), band: 6 ..< 64, bits: 1...), - - .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), - .progressive((Cb, \.0), band: 1 ..< 64, bit: 0 ), - .progressive((Cr, \.0), band: 1 ..< 64, bit: 0 ), + + .progressive((Y, \.0), band: 1 ..< 64, bits: 1...), + + .progressive((Cb, \.0), band: 1 ..< 6, bits: 1...), + .progressive((Cr, \.0), band: 1 ..< 6, bits: 1...), + + .progressive((Cb, \.0), band: 6 ..< 64, bits: 1...), + .progressive((Cr, \.0), band: 6 ..< 64, bits: 1...), + + .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), + .progressive((Cb, \.0), band: 1 ..< 64, bit: 0 ), + .progressive((Cr, \.0), band: 1 ..< 64, bit: 0 ), ]) - let quanta:([UInt16], [UInt16]) = + let quanta:([UInt16], [UInt16]) = ( .init(repeating: 1, count: 64), .init(repeating: 1, count: 64) ) - + var planar:JPEG.Data.Planar = .init( - size: (8, 8), - layout: layout, - metadata: + size: (8, 8), + layout: layout, + metadata: [ .jfif(.init(version: .v1_2, density: (1, 1, .centimeters))), ]) - - let colors:[JPEG.YCbCr] = ((0 as UInt8) ..< (8 * 8 as UInt8)).map + + let colors:[JPEG.YCbCr] = ((0 as UInt8) ..< (8 * 8 as UInt8)).map { let rgb:JPEG.RGB = .init( - UInt8.random(in: 5 ... 250), + UInt8.random(in: 5 ... 250), 128 + ($0 & 0x38) - 32, 128 + ($0 << 3 & 0x38) - 32) - return rgb.ycc + return rgb.ycc } planar.with(ci: Y) { @@ -78,32 +79,32 @@ func fuzz(rng:inout RNG, path:String) throws where RNG:RandomNumberGenerato $0[x: x, y: y] = .init(colors[8 * y + x].cr) } } - - let spectral:JPEG.Data.Spectral = planar.fdct(quanta: + + let spectral:JPEG.Data.Spectral = planar.fdct(quanta: [ 0: quanta.0, 1: quanta.1, ]) - + guard let _:Void = try spectral.compress(path: path) - else + else { fatalError("failed to open file '\(path)'") - } + } } -func print(histogram:[Int], width:Int) +func print(histogram:[Int], width:Int) { - // print histogram + // print histogram let max:Int = histogram.max() ?? 1 for (y, count):(Int, Int) in zip(histogram.indices, histogram) { - let value:UInt8 = .init(y), + let value:UInt8 = .init(y), rgb:(UInt8, UInt8, UInt8) = (value, value, value) let left:String = Highlight.highlight(" y = \(String.pad("\(y)", left: 3)) ", rgb) let label:String = .pad("\(count)", left: 5) let right:String = .init(repeating: "█", count: width * count / max) - + print("\(left) \(label) \(right)") } } @@ -112,81 +113,81 @@ func generate(count:Int, prefix:String) throws { var rng:SystemRandomNumberGenerator = .init() var histogram:[Int] = .init(repeating: 0, count: 256) - for i:Int in 0 ..< count + for i:Int in 0 ..< count { let path:String = "\(prefix)/\(i).jpg" try fuzz(rng: &rng, path: path) - - guard let rectangular:JPEG.Data.Rectangular = + + guard let rectangular:JPEG.Data.Rectangular = try .decompress(path: path) else { fatalError("failed to open file '\(path)'") } - - // merge into histogram + + // merge into histogram let ycc:[JPEG.YCbCr] = rectangular.unpack(as: JPEG.YCbCr.self) - for pixel:JPEG.YCbCr in ycc + for pixel:JPEG.YCbCr in ycc { histogram[.init(pixel.y)] += 1 } - - // terminal output + + // terminal output print(path) let image:[JPEG.RGB] = rectangular.unpack(as: JPEG.RGB.self) - for i:Int in 0 ..< rectangular.size.y + for i:Int in 0 ..< rectangular.size.y { - let line:String = (0 ..< rectangular.size.x).map + let line:String = (0 ..< rectangular.size.x).map { - (j:Int) in - + (j:Int) in + let c:JPEG.RGB = image[j + i * rectangular.size.x] return Highlight.square((c.r, c.g, c.b)) }.joined(separator: "") print(line) - } + } } print(histogram: histogram, width: 80) } -enum Parameter:String +enum Parameter:String { case count - case path + case path } -var parameter:Parameter? = nil -var count:Int = 16 +var parameter:Parameter? = nil +var count:Int = 16 var prefix:String = "tests/fuzz/data/jpeg" for argument:String in CommandLine.arguments.dropFirst() { if argument.prefix(2) == "--" { guard let p:Parameter = Parameter.init(rawValue: .init(argument.dropFirst(2))) - else + else { fatalError("unrecognized parameter '\(argument)'") } - - parameter = p + + parameter = p } - else + else { - switch parameter + switch parameter { case nil: fatalError("no parameter name given before argument value '\(argument)'") - + case .count?: - guard let n:Int = Int.init(argument) - else + guard let n:Int = Int.init(argument) + else { fatalError("could not convert argument '\(argument)' to Int") } count = n - + case .path: - prefix = argument + prefix = argument } } } diff --git a/tests/fuzz/output.swift b/tests/fuzz/output.swift deleted file mode 120000 index 7501e3cf..00000000 --- a/tests/fuzz/output.swift +++ /dev/null @@ -1 +0,0 @@ -../../sources/common/output.swift \ No newline at end of file diff --git a/tests/integration/main.swift b/tests/integration/main.swift index 694a708a..bc24b646 100644 --- a/tests/integration/main.swift +++ b/tests/integration/main.swift @@ -1,13 +1,13 @@ import func Foundation.exit -var failed = false -for (name, function):(String, Test.Function) in Test.cases +var failed = false +for (name, function):(String, Test.Function) in Test.cases { guard let _:Void = test(function, name: name) - else + else { - failed = true - continue + failed = true + continue } } diff --git a/tests/integration/output.swift b/tests/integration/output.swift deleted file mode 120000 index 7501e3cf..00000000 --- a/tests/integration/output.swift +++ /dev/null @@ -1 +0,0 @@ -../../sources/common/output.swift \ No newline at end of file diff --git a/tests/integration/tests.swift b/tests/integration/tests.swift index d1eea6ca..e362f733 100644 --- a/tests/integration/tests.swift +++ b/tests/integration/tests.swift @@ -1,76 +1,77 @@ import JPEG +import JPEGInspection -extension Test +extension Test { - static - var cases:[(name:String, function:Function)] + static + var cases:[(name:String, function:Function)] { [ - ("color-sequential-robustness", .string(Self.decode(_:), + ("color-sequential-robustness", .string(Self.decode(_:), [ "tests/integration/decode/color-sequential-1.jpg", "tests/integration/decode/color-sequential-2.jpg", "tests/integration/decode/color-sequential-3.jpg", "tests/integration/decode/color-sequential-4.jpg", ])), - ("grayscale-sequential-robustness", .string(Self.decode(_:), + ("grayscale-sequential-robustness", .string(Self.decode(_:), [ "tests/integration/decode/grayscale-sequential-1.jpg", "tests/integration/decode/grayscale-sequential-2.jpg", ])), - ("color-progressive-robustness", .string(Self.decode(_:), + ("color-progressive-robustness", .string(Self.decode(_:), [ "tests/integration/decode/color-progressive-1.jpg", "tests/integration/decode/color-progressive-2.jpg", "tests/integration/decode/color-progressive-3.jpg", "tests/integration/decode/color-progressive-4.jpg", ])), - ("grayscale-progressive-robustness", .string(Self.decode(_:), + ("grayscale-progressive-robustness", .string(Self.decode(_:), [ "tests/integration/decode/grayscale-progressive-1.jpg", "tests/integration/decode/grayscale-progressive-2.jpg", ])), - ("restart-interval-robustness", .string(Self.decode(_:), + ("restart-interval-robustness", .string(Self.decode(_:), [ "tests/integration/decode/color-sequential-restart.jpg", "tests/integration/decode/color-progressive-restart.jpg", "tests/integration/decode/grayscale-sequential-restart.jpg", "tests/integration/decode/grayscale-progressive-restart.jpg", ])), - - ("color-sequential-encoding-robustness", .string_int2(Self.encodeColorSequential(_:_:), + + ("color-sequential-encoding-robustness", .string_int2(Self.encodeColorSequential(_:_:), [ ("tests/integration/encode/karlie-kloss-1", (640, 320)) ])), - ("grayscale-sequential-encoding-robustness", .string_int2(Self.encodeGrayscaleSequential(_:_:), + ("grayscale-sequential-encoding-robustness", .string_int2(Self.encodeGrayscaleSequential(_:_:), [ ("tests/integration/encode/karlie-kloss-1", (640, 320)) ])), - ("color-progressive-encoding-robustness", .string_int2(Self.encodeColorProgressive(_:_:), + ("color-progressive-encoding-robustness", .string_int2(Self.encodeColorProgressive(_:_:), [ ("tests/integration/encode/karlie-kloss-1", (640, 320)) ])), - ("grayscale-progressive-encoding-robustness", .string_int2(Self.encodeGrayscaleProgressive(_:_:), + ("grayscale-progressive-encoding-robustness", .string_int2(Self.encodeGrayscaleProgressive(_:_:), [ ("tests/integration/encode/karlie-kloss-1", (640, 320)) ])), ] } - - private static - func print(image rgb:[JPEG.RGB], size:(x:Int, y:Int)) + + private static + func print(image rgb:[JPEG.RGB], size:(x:Int, y:Int)) { for i:Int in stride(from: 0, to: size.y, by: 8) { - let line:String = stride(from: 0, to: size.x, by: 8).map + let line:String = stride(from: 0, to: size.x, by: 8).map { - (j:Int) in - - // downsampling - var r:Int = 0, - g:Int = 0, - b:Int = 0 - for y:Int in i ..< min(i + 8, size.y) + (j:Int) in + + // downsampling + var r:Int = 0, + g:Int = 0, + b:Int = 0 + for y:Int in i ..< min(i + 8, size.y) { for x:Int in j ..< min(j + 8, size.x) { @@ -80,9 +81,9 @@ extension Test b += .init(c.b) } } - + let count:Int = (min(i + 8, size.y) - i) * (min(j + 8, size.x) - j) - let c:(r:Float, g:Float, b:Float) = + let c:(r:Float, g:Float, b:Float) = ( .init(r) / (255 * .init(count)), .init(g) / (255 * .init(count)), @@ -91,42 +92,42 @@ extension Test return Highlight.square(c) }.joined(separator: "") Swift.print(line) - } + } } - - // this test only tries to decode the image without errors, it does not check + + // this test only tries to decode the image without errors, it does not check // for content accuracy - static - func decode(_ path:String) -> Result + static + func decode(_ path:String) -> Result { - do + do { guard let image:JPEG.Data.Rectangular = try .decompress(path: path) - else + else { return .failure(.init(message: "failed to open file '\(path)'")) } - + Swift.print( """ - + \(Highlight.bold)\(path)\(Highlight.reset) (\(image.layout.format)) { size : (\(image.size.x), \(image.size.y)) process : \(image.layout.process) precision : \(image.layout.format.precision) - components : + components : [ - \(image.layout.residents.sorted(by: { $0.key < $1.key }).map + \(image.layout.residents.sorted(by: { $0.key < $1.key }).map { - let (x, y):(Int, Int) = + let (x, y):(Int, Int) = image.layout.planes[$0.value].component.factor return "\($0.key): (\(x), \(y))" }.joined(separator: "\n ")) ] - scans : + scans : [ - \(image.layout.scans.map + \(image.layout.scans.map { "[band: \(String.pad("\($0.band.lowerBound)", left: 2)) ..< \(String.pad("\($0.band.upperBound)", left: 2)), bits: \(String.pad("\($0.bits.lowerBound)", left: 2)) \(String.pad($0.bits.upperBound == .max ? "..." : "..< \("\(String.pad("\($0.bits.upperBound)", left: 2))")", right: 6))]: \($0.components.map(\.ci))" }.joined(separator: "\n ")) @@ -135,7 +136,7 @@ extension Test """) for metadata:JPEG.Metadata in image.metadata { - switch metadata + switch metadata { case .jfif(let jfif): Swift.print(jfif) @@ -145,50 +146,50 @@ extension Test Swift.print("metadata (application \(a), \(data.count) bytes)") case .comment(data: let data): Swift.print(""" - comment + comment { '\(String.init(decoding: data, as: Unicode.UTF8.self))' } """) } } - + let rgb:[JPEG.RGB] = image.unpack(as: JPEG.RGB.self) - // write to rgb file + // write to rgb file guard let _:Void = try (System.File.Destination.open(path: "\(path).rgb") { guard let _:Void = $0.write(rgb.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { throw Failure.init(message: "failed to write to file '\(path).rgb'") } - }) + }) else { throw Failure.init(message: "failed to open file '\(path).rgb'") } - - // terminal output + + // terminal output Self.print(image: rgb, size: image.size) } - catch + catch { - if let error:JPEG.Error = error as? JPEG.Error + if let error:JPEG.Error = error as? JPEG.Error { return .failure(.init(message: error.message)) } - else + else { return .failure(.init(message: "\(error)")) } } - + return .success(()) } - - static - func encodeColorSequential(_ path:String, _ size:(x:Int, y:Int)) - -> Result + + static + func encodeColorSequential(_ path:String, _ size:(x:Int, y:Int)) + -> Result { let format:JPEG.Common = .ycc8 let Y:JPEG.Component.Key = format.components[0], @@ -196,41 +197,41 @@ extension Test Cr:JPEG.Component.Key = format.components[2] let layout:JPEG.Layout = .init( format: format, - process: .baseline, - components: + process: .baseline, + components: [ - Y: (factor: (1, 1), qi: 0), - Cb: (factor: (1, 1), qi: 1), + Y: (factor: (1, 1), qi: 0), + Cb: (factor: (1, 1), qi: 1), Cr: (factor: (1, 1), qi: 1), - ], - scans: + ], + scans: [ .sequential((Y, \.0, \.0), (Cb, \.1, \.1), (Cr, \.1, \.1)) ]) return Self.encode(path, suffix: "-color-sequential", size: size, layout: layout) } - static - func encodeGrayscaleSequential(_ path:String, _ size:(x:Int, y:Int)) - -> Result + static + func encodeGrayscaleSequential(_ path:String, _ size:(x:Int, y:Int)) + -> Result { let format:JPEG.Common = .y8 let Y:JPEG.Component.Key = format.components[0] let layout:JPEG.Layout = .init( format: format, - process: .baseline, - components: + process: .baseline, + components: [ - Y: (factor: (1, 1), qi: 0), - ], - scans: + Y: (factor: (1, 1), qi: 0), + ], + scans: [ .sequential((Y, \.0, \.0)) ]) return Self.encode(path, suffix: "-grayscale-sequential", size: size, layout: layout) } - static - func encodeColorProgressive(_ path:String, _ size:(x:Int, y:Int)) - -> Result + static + func encodeColorProgressive(_ path:String, _ size:(x:Int, y:Int)) + -> Result { let format:JPEG.Common = .ycc8 let Y:JPEG.Component.Key = format.components[0], @@ -238,65 +239,65 @@ extension Test Cr:JPEG.Component.Key = format.components[2] let layout:JPEG.Layout = .init( format: format, - process: .progressive(coding: .huffman, differential: false), - components: + process: .progressive(coding: .huffman, differential: false), + components: [ - Y: (factor: (1, 1), qi: 0), - Cb: (factor: (1, 1), qi: 1), + Y: (factor: (1, 1), qi: 0), + Cb: (factor: (1, 1), qi: 1), Cr: (factor: (1, 1), qi: 1), - ], - scans: + ], + scans: [ .progressive((Y, \.0), (Cb, \.1), (Cr, \.2), bits: 2...), .progressive( Y, Cb, Cr , bit: 1 ), .progressive( Y, Cb, Cr , bit: 0 ), - - .progressive((Y, \.0), band: 1 ..< 64, bits: 2...), - - .progressive((Cb, \.0), band: 1 ..< 6, bits: 1...), - .progressive((Cr, \.0), band: 1 ..< 6, bits: 1...), - - .progressive((Cb, \.0), band: 6 ..< 64, bits: 1...), - .progressive((Cr, \.0), band: 6 ..< 64, bits: 1...), - - .progressive((Y, \.0), band: 1 ..< 64, bit: 1 ), - .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), - .progressive((Cb, \.0), band: 1 ..< 64, bit: 0 ), - .progressive((Cr, \.0), band: 1 ..< 64, bit: 0 ), + + .progressive((Y, \.0), band: 1 ..< 64, bits: 2...), + + .progressive((Cb, \.0), band: 1 ..< 6, bits: 1...), + .progressive((Cr, \.0), band: 1 ..< 6, bits: 1...), + + .progressive((Cb, \.0), band: 6 ..< 64, bits: 1...), + .progressive((Cr, \.0), band: 6 ..< 64, bits: 1...), + + .progressive((Y, \.0), band: 1 ..< 64, bit: 1 ), + .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), + .progressive((Cb, \.0), band: 1 ..< 64, bit: 0 ), + .progressive((Cr, \.0), band: 1 ..< 64, bit: 0 ), ]) return Self.encode(path, suffix: "-color-progressive", size: size, layout: layout) } - static - func encodeGrayscaleProgressive(_ path:String, _ size:(x:Int, y:Int)) - -> Result + static + func encodeGrayscaleProgressive(_ path:String, _ size:(x:Int, y:Int)) + -> Result { let format:JPEG.Common = .y8 let Y:JPEG.Component.Key = format.components[0] let layout:JPEG.Layout = .init( format: format, - process: .progressive(coding: .huffman, differential: false), - components: + process: .progressive(coding: .huffman, differential: false), + components: [ Y: (factor: (1, 1), qi: 0) - ], - scans: + ], + scans: [ .progressive((Y, \.0), bits: 2...), .progressive( Y, bit: 1 ), .progressive( Y, bit: 0 ), - - .progressive((Y, \.0), band: 1 ..< 6, bits: 2...), - .progressive((Y, \.0), band: 6 ..< 64, bits: 2...), - .progressive((Y, \.0), band: 1 ..< 64, bit: 1 ), - .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), + + .progressive((Y, \.0), band: 1 ..< 6, bits: 2...), + .progressive((Y, \.0), band: 6 ..< 64, bits: 2...), + .progressive((Y, \.0), band: 1 ..< 64, bit: 1 ), + .progressive((Y, \.0), band: 1 ..< 64, bit: 0 ), ]) return Self.encode(path, suffix: "-grayscale-progressive", size: size, layout: layout) } - private static - func encode(_ path:String, suffix:String, size:(x:Int, y:Int), layout:JPEG.Layout) - -> Result + private static + func encode(_ path:String, suffix:String, size:(x:Int, y:Int), layout:JPEG.Layout) + -> Result { - do + do { guard let rgb:[JPEG.RGB] = try (System.File.Source.open(path: "\(path).rgb") { @@ -308,34 +309,34 @@ extension Test return (0 ..< size.x * size.y).map { - (i:Int) -> JPEG.RGB in + (i:Int) -> JPEG.RGB in .init(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]) } }) - else + else { throw Failure.init(message: "failed to open file '\(path).rgb'") } - + var planar:JPEG.Data.Planar = .init( - size: size, - layout: layout, - metadata: + size: size, + layout: layout, + metadata: [ .jfif(.init(version: .v1_2, density: (1, 1, .centimeters))), ]) - + let ycc:[JPEG.YCbCr] = rgb.map(\.ycc) - for (ci, p):(JPEG.Component.Key, KeyPath) in + for (ci, p):(JPEG.Component.Key, KeyPath) in zip(layout.format.components, [\.y, \.cb, \.cr]) { - guard planar.index(forKey: ci) != nil - else + guard planar.index(forKey: ci) != nil + else { - continue + continue } - - planar.with(ci: ci) + + planar.with(ci: ci) { for (x, y):(Int, Int) in $0.indices { @@ -343,43 +344,43 @@ extension Test } } } - - let quanta:([UInt16], [UInt16]) = + + let quanta:([UInt16], [UInt16]) = ( (1 ... 64).map{ 1 + $0 >> 1 }, (1 ... 64).map{ 1 + 2 * ($0 & 0xfffe) } ) - + let spectral:JPEG.Data.Spectral = planar.fdct( - quanta: + quanta: [ 0: quanta.0, 1: quanta.1, ]) - + guard let _:Void = try spectral.compress(path: "\(path)\(suffix).jpg") - else + else { fatalError("failed to open file '\(path)\(suffix).jpg'") - } - - // terminal output - let rectangular:JPEG.Data.Rectangular = + } + + // terminal output + let rectangular:JPEG.Data.Rectangular = spectral.idct().interleaved() Self.print(image: rectangular.unpack(as: JPEG.RGB.self), size: size) } - catch + catch { - if let error:JPEG.Error = error as? JPEG.Error + if let error:JPEG.Error = error as? JPEG.Error { return .failure(.init(message: error.message)) } - else + else { return .failure(.init(message: "\(error)")) } } - + return .success(()) } } diff --git a/tests/regression/main.swift b/tests/regression/main.swift index 694a708a..bc24b646 100644 --- a/tests/regression/main.swift +++ b/tests/regression/main.swift @@ -1,13 +1,13 @@ import func Foundation.exit -var failed = false -for (name, function):(String, Test.Function) in Test.cases +var failed = false +for (name, function):(String, Test.Function) in Test.cases { guard let _:Void = test(function, name: name) - else + else { - failed = true - continue + failed = true + continue } } diff --git a/tests/regression/output.swift b/tests/regression/output.swift deleted file mode 120000 index 7501e3cf..00000000 --- a/tests/regression/output.swift +++ /dev/null @@ -1 +0,0 @@ -../../sources/common/output.swift \ No newline at end of file diff --git a/tests/regression/tests.swift b/tests/regression/tests.swift index ce044510..b75c822f 100644 --- a/tests/regression/tests.swift +++ b/tests/regression/tests.swift @@ -1,54 +1,54 @@ import JPEG -extension Test +extension Test { - static - var cases:[(name:String, function:Function)] + static + var cases:[(name:String, function:Function)] { [ - ("color-sequential-regression", .string(Self.test(_:), + ("color-sequential-regression", .string(Self.test(_:), [ "tests/regression/gold/color-sequential-1.jpg", "tests/regression/gold/color-sequential-2.jpg", "tests/regression/gold/color-sequential-3.jpg", "tests/regression/gold/color-sequential-4.jpg", ])), - ("grayscale-sequential-regression", .string(Self.test(_:), + ("grayscale-sequential-regression", .string(Self.test(_:), [ "tests/regression/gold/grayscale-sequential-1.jpg", "tests/regression/gold/grayscale-sequential-2.jpg", ])), - ("color-progressive-regression", .string(Self.test(_:), + ("color-progressive-regression", .string(Self.test(_:), [ "tests/regression/gold/color-progressive-1.jpg", "tests/regression/gold/color-progressive-2.jpg", "tests/regression/gold/color-progressive-3.jpg", "tests/regression/gold/color-progressive-4.jpg", ])), - ("grayscale-progressive-regression", .string(Self.test(_:), + ("grayscale-progressive-regression", .string(Self.test(_:), [ "tests/regression/gold/grayscale-progressive-1.jpg", "tests/regression/gold/grayscale-progressive-2.jpg", ])), ] } - - // this test attempts to decode the given image, and compares it to the golden + + // this test attempts to decode the given image, and compares it to the golden // outputs in the same directory - static - func test(_ path:String) -> Result + static + func test(_ path:String) -> Result { - do + do { guard let image:JPEG.Data.Rectangular = try .decompress(path: path) - else + else { throw Failure.init(message: "failed to open file '\(path)'") } - - let output:(ycc:[JPEG.YCbCr], rgb:[JPEG.RGB]) = + + let output:(ycc:[JPEG.YCbCr], rgb:[JPEG.RGB]) = ( - image.unpack(as: JPEG.YCbCr.self), + image.unpack(as: JPEG.YCbCr.self), image.unpack(as: JPEG.RGB.self) ) guard let ycc:[JPEG.YCbCr] = try (System.File.Source.open(path: "\(path).ycc") @@ -61,10 +61,10 @@ extension Test return (0 ..< output.ycc.count).map { - (i:Int) -> JPEG.YCbCr in + (i:Int) -> JPEG.YCbCr in .init(y: data[i * 3], cb: data[i * 3 + 1], cr: data[i * 3 + 2]) } - }), + }), let rgb:[JPEG.RGB] = try (System.File.Source.open(path: "\(path).rgb") { guard let data:[UInt8] = $0.read(count: 3 * output.rgb.count) @@ -75,62 +75,62 @@ extension Test return (0 ..< output.rgb.count).map { - (i:Int) -> JPEG.RGB in + (i:Int) -> JPEG.RGB in .init(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]) } }) else { - // write new golden output if there is none at the given location + // write new golden output if there is none at the given location guard let _:Void = try (System.File.Destination.open(path: "\(path).ycc") { guard let _:Void = $0.write(output.ycc.flatMap{ [$0.y, $0.cb, $0.cr] }) - else + else { throw Failure.init(message: "failed to write to file '\(path).ycc'") } - }) + }) else { throw Failure.init(message: "failed to open file '\(path).ycc'") } - + guard let _:Void = try (System.File.Destination.open(path: "\(path).rgb") { guard let _:Void = $0.write(output.rgb.flatMap{ [$0.r, $0.g, $0.b] }) - else + else { throw Failure.init(message: "failed to write to file '\(path).rgb'") } - }) + }) else { throw Failure.init(message: "failed to open file '\(path).rgb'") } - - throw Failure.init(message: + + throw Failure.init(message: "no golden output for '\(path)' (new golden output written to '\(path).ycc', '\(path).rgb')") } - + guard output.ycc == ycc, output.rgb == rgb - else + else { throw Failure.init(message: "decoded output does not match golden output") } - + return .success(()) } - catch + catch { - if let error:Failure = error as? Failure + if let error:Failure = error as? Failure { return .failure(error) } - else if let error:JPEG.Error = error as? JPEG.Error + else if let error:JPEG.Error = error as? JPEG.Error { return .failure(.init(message: error.message)) } - else + else { return .failure(.init(message: "\(error)")) } diff --git a/tests/unit/main.swift b/tests/unit/main.swift index 694a708a..bc24b646 100644 --- a/tests/unit/main.swift +++ b/tests/unit/main.swift @@ -1,13 +1,13 @@ import func Foundation.exit -var failed = false -for (name, function):(String, Test.Function) in Test.cases +var failed = false +for (name, function):(String, Test.Function) in Test.cases { guard let _:Void = test(function, name: name) - else + else { - failed = true - continue + failed = true + continue } } diff --git a/tests/unit/output.swift b/tests/unit/output.swift deleted file mode 120000 index 7501e3cf..00000000 --- a/tests/unit/output.swift +++ /dev/null @@ -1 +0,0 @@ -../../sources/common/output.swift \ No newline at end of file diff --git a/tests/unit/tests.swift b/tests/unit/tests.swift index 93f3bebd..dcf7ec93 100644 --- a/tests/unit/tests.swift +++ b/tests/unit/tests.swift @@ -1,26 +1,26 @@ -@testable import JPEG +@testable import JPEG extension JPEG.Bitstream.Symbol.DC:ExpressibleByIntegerLiteral { - public - init(integerLiteral:UInt8) + public + init(integerLiteral:UInt8) { self.init(integerLiteral) } } extension JPEG.Bitstream.Symbol.AC:ExpressibleByIntegerLiteral { - public - init(integerLiteral:UInt8) + public + init(integerLiteral:UInt8) { self.init(integerLiteral) } } -extension Test +extension Test { - static - var cases:[(name:String, function:Function)] + static + var cases:[(name:String, function:Function)] { [ ("zigzag-ordering", .void(Self.zigzagOrdering)), @@ -31,123 +31,123 @@ extension Test ("huffman-table-coding-symmetric", .int (Self.huffmanCodingSymmetric(_:), [16, 256, 4096, 65536])), ] } - - static - func zigzagOrdering() -> Result + + static + func zigzagOrdering() -> Result { - let indices:[[Int]] = + let indices:[[Int]] = [ [ 0, 1, 5, 6, 14, 15, 27, 28], [ 2, 4, 7, 13, 16, 26, 29, 42], [ 3, 8, 12, 17, 25, 30, 41, 43], [ 9, 11, 18, 24, 31, 40, 44, 53], - [10, 19, 23, 32, 39, 45, 52, 54], - [20, 22, 33, 38, 46, 51, 55, 60], - [21, 34, 37, 47, 50, 56, 59, 61], + [10, 19, 23, 32, 39, 45, 52, 54], + [20, 22, 33, 38, 46, 51, 55, 60], + [21, 34, 37, 47, 50, 56, 59, 61], [35, 36, 48, 49, 57, 58, 62, 63] ] - for (y, row):(Int, [Int]) in indices.enumerated() + for (y, row):(Int, [Int]) in indices.enumerated() { - for (x, expected):(Int, Int) in row.enumerated() + for (x, expected):(Int, Int) in row.enumerated() { let z:Int = JPEG.Table.Quantization.z(k: x, h: y) - guard z == expected - else + guard z == expected + else { - return .failure(.init(message: + return .failure(.init(message: "zig-zag transform mapped index (\(x), \(y)) to zig-zag index \(z) (expected \(expected))")) } } } - + return .success(()) } - - // tests a few “known” cases in one direction - static - func amplitudeCoding() -> Result + + // tests a few “known” cases in one direction + static + func amplitudeCoding() -> Result { - for (binade, tail, expected):(Int, UInt16, Int32) in + for (binade, tail, expected):(Int, UInt16, Int32) in [ (1, 0, -1), - (1, 1, 1), - - (2, 0, -3), - (2, 1, -2), - (2, 2, 2), + (1, 1, 1), + + (2, 0, -3), + (2, 1, -2), + (2, 2, 2), (2, 3, 3), - - (5, 0, -31), - (5, 1, -30), - (5, 14, -17), - (5, 15, -16), - (5, 16, 16), - (5, 17, 17), - (5, 30, 30), - (5, 31, 31), - - (11, 0, -2047), - (11, 1, -2046), - (11, 1023, -1024), - (11, 1024, 1024), - (11, 2046, 2046), - (11, 2047, 2047), - - (15, 0, -32767), - (15, 1, -32766), - (15, 16383, -16384), - (15, 16384, 16384), - (15, 32766, 32766), - (15, 32767, 32767), + + (5, 0, -31), + (5, 1, -30), + (5, 14, -17), + (5, 15, -16), + (5, 16, 16), + (5, 17, 17), + (5, 30, 30), + (5, 31, 31), + + (11, 0, -2047), + (11, 1, -2046), + (11, 1023, -1024), + (11, 1024, 1024), + (11, 2046, 2046), + (11, 2047, 2047), + + (15, 0, -32767), + (15, 1, -32766), + (15, 16383, -16384), + (15, 16384, 16384), + (15, 32766, 32766), + (15, 32767, 32767), ] { let result:Int32 = JPEG.Bitstream.extend(binade: binade, tail, as: Int32.self) - guard result == expected - else + guard result == expected + else { - return .failure(.init(message: + return .failure(.init(message: "amplitude decoder mapped composite bits {\(binade); \(tail)} incorrectly (expected \(expected), got \(result))")) } } - + return .success(()) } - + // exhaustively tests reversibility of extend and compact - static - func amplitudeCodingSymmetric() -> Result + static + func amplitudeCodingSymmetric() -> Result { // JPEG.Bitstream.extend(binade:_:as:) not defined for x = 0 - // JPEG.Bitstream.compact(_:) not defined for x = Int16.min + // JPEG.Bitstream.compact(_:) not defined for x = Int16.min for x:Int32 in -1 << 15 + 1 ..< 1 >> 15 where x != 0 { let (binade, tail):(Int, UInt16) = JPEG.Bitstream.compact(x) let xp:Int32 = JPEG.Bitstream.extend(binade: binade, tail, as: Int32.self) - - guard x == xp - else + + guard x == xp + else { - return .failure(.init(message: + return .failure(.init(message: "amplitude coder failed to round-trip value {\(x)} -> {\(binade); \(tail)} -> {\(xp)}")) } } - + return .success(()) } - - // tries to construct the example AC huffman table from the JPEG standard annex, - // and decode individual codewords - static + + // tries to construct the example AC huffman table from the JPEG standard annex, + // and decode individual codewords + static func huffmanBuilding() -> Result { - let counts:[Int] = + let counts:[Int] = [ 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, ] - let values:[UInt8] = + let values:[UInt8] = [ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, @@ -161,207 +161,207 @@ extension Test 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA ] - - guard let table:JPEG.Table.HuffmanAC = + + guard let table:JPEG.Table.HuffmanAC = JPEG.Table.HuffmanAC.init(counts: counts, values: values, target: \.0) - else + else { return .failure(.init(message: "failed to initialize huffman table")) } - + let decoder:JPEG.Table.HuffmanAC.Decoder = table.decoder() var expected:UInt8 = 0 - for (length, codeword):(Int, UInt16) in + for (length, codeword):(Int, UInt16) in [ - (4, 0b1010), - (2, 0b00), - (2, 0b01), - (3, 0b100), - (4, 0b1011), - (5, 0b11010), - (7, 0b1111000), - (8, 0b11111000), - (10, 0b1111110110), - (16, 0b1111111110000010), - (16, 0b1111111110000011), - (4, 0b1100), - (5, 0b11011), - (7, 0b1111001), - (9, 0b111110110), - (11, 0b11111110110), - (16, 0b1111111110000100), - (16, 0b1111111110000101), - (16, 0b1111111110000110), - (16, 0b1111111110000111), - (16, 0b1111111110001000), - (5, 0b11100), - (8, 0b11111001), - (10, 0b1111110111), - (12, 0b111111110100), - (16, 0b1111111110001001), - (16, 0b1111111110001010), - (16, 0b1111111110001011), - (16, 0b1111111110001100), - (16, 0b1111111110001101), - (16, 0b1111111110001110), - (6, 0b111010), - (9, 0b111110111), - (12, 0b111111110101), - (16, 0b1111111110001111), - (16, 0b1111111110010000), - (16, 0b1111111110010001), - (16, 0b1111111110010010), - (16, 0b1111111110010011), - (16, 0b1111111110010100), - (16, 0b1111111110010101), - (6, 0b111011), - (10, 0b1111111000), - (16, 0b1111111110010110), - (16, 0b1111111110010111), - (16, 0b1111111110011000), - (16, 0b1111111110011001), - (16, 0b1111111110011010), - (16, 0b1111111110011011), - (16, 0b1111111110011100), - (16, 0b1111111110011101), - (7, 0b1111010), - (11, 0b11111110111), - (16, 0b1111111110011110), - (16, 0b1111111110011111), - (16, 0b1111111110100000), - (16, 0b1111111110100001), - (16, 0b1111111110100010), - (16, 0b1111111110100011), - (16, 0b1111111110100100), - (16, 0b1111111110100101), - (7, 0b1111011), - (12, 0b111111110110), - (16, 0b1111111110100110), - (16, 0b1111111110100111), - (16, 0b1111111110101000), - (16, 0b1111111110101001), - (16, 0b1111111110101010), - (16, 0b1111111110101011), - (16, 0b1111111110101100), - (16, 0b1111111110101101), - (8, 0b11111010), - (12, 0b111111110111), - (16, 0b1111111110101110), - (16, 0b1111111110101111), - (16, 0b1111111110110000), - (16, 0b1111111110110001), - (16, 0b1111111110110010), - (16, 0b1111111110110011), - (16, 0b1111111110110100), - (16, 0b1111111110110101), - (9, 0b111111000), - (15, 0b111111111000000), - (16, 0b1111111110110110), - (16, 0b1111111110110111), - (16, 0b1111111110111000), - (16, 0b1111111110111001), - (16, 0b1111111110111010), - (16, 0b1111111110111011), - (16, 0b1111111110111100), - (16, 0b1111111110111101), - (9, 0b111111001), - (16, 0b1111111110111110), - (16, 0b1111111110111111), - (16, 0b1111111111000000), - (16, 0b1111111111000001), - (16, 0b1111111111000010), - (16, 0b1111111111000011), - (16, 0b1111111111000100), - (16, 0b1111111111000101), - (16, 0b1111111111000110), - (9, 0b111111010), - (16, 0b1111111111000111), - (16, 0b1111111111001000), - (16, 0b1111111111001001), - (16, 0b1111111111001010), - (16, 0b1111111111001011), - (16, 0b1111111111001100), - (16, 0b1111111111001101), - (16, 0b1111111111001110), - (16, 0b1111111111001111), - (10, 0b1111111001), - (16, 0b1111111111010000), - (16, 0b1111111111010001), - (16, 0b1111111111010010), - (16, 0b1111111111010011), - (16, 0b1111111111010100), - (16, 0b1111111111010101), - (16, 0b1111111111010110), - (16, 0b1111111111010111), - (16, 0b1111111111011000), - (10, 0b1111111010), - (16, 0b1111111111011001), - (16, 0b1111111111011010), - (16, 0b1111111111011011), - (16, 0b1111111111011100), - (16, 0b1111111111011101), - (16, 0b1111111111011110), - (16, 0b1111111111011111), - (16, 0b1111111111100000), - (16, 0b1111111111100001), - (11, 0b11111111000), - (16, 0b1111111111100010), - (16, 0b1111111111100011), - (16, 0b1111111111100100), - (16, 0b1111111111100101), - (16, 0b1111111111100110), - (16, 0b1111111111100111), - (16, 0b1111111111101000), - (16, 0b1111111111101001), - (16, 0b1111111111101010), - (16, 0b1111111111101011), - (16, 0b1111111111101100), - (16, 0b1111111111101101), - (16, 0b1111111111101110), - (16, 0b1111111111101111), - (16, 0b1111111111110000), - (16, 0b1111111111110001), - (16, 0b1111111111110010), - (16, 0b1111111111110011), - (16, 0b1111111111110100), - (11, 0b11111111001), - (16, 0b1111111111110101), - (16, 0b1111111111110110), - (16, 0b1111111111110111), - (16, 0b1111111111111000), - (16, 0b1111111111111001), - (16, 0b1111111111111010), - (16, 0b1111111111111011), - (16, 0b1111111111111100), - (16, 0b1111111111111101), + (4, 0b1010), + (2, 0b00), + (2, 0b01), + (3, 0b100), + (4, 0b1011), + (5, 0b11010), + (7, 0b1111000), + (8, 0b11111000), + (10, 0b1111110110), + (16, 0b1111111110000010), + (16, 0b1111111110000011), + (4, 0b1100), + (5, 0b11011), + (7, 0b1111001), + (9, 0b111110110), + (11, 0b11111110110), + (16, 0b1111111110000100), + (16, 0b1111111110000101), + (16, 0b1111111110000110), + (16, 0b1111111110000111), + (16, 0b1111111110001000), + (5, 0b11100), + (8, 0b11111001), + (10, 0b1111110111), + (12, 0b111111110100), + (16, 0b1111111110001001), + (16, 0b1111111110001010), + (16, 0b1111111110001011), + (16, 0b1111111110001100), + (16, 0b1111111110001101), + (16, 0b1111111110001110), + (6, 0b111010), + (9, 0b111110111), + (12, 0b111111110101), + (16, 0b1111111110001111), + (16, 0b1111111110010000), + (16, 0b1111111110010001), + (16, 0b1111111110010010), + (16, 0b1111111110010011), + (16, 0b1111111110010100), + (16, 0b1111111110010101), + (6, 0b111011), + (10, 0b1111111000), + (16, 0b1111111110010110), + (16, 0b1111111110010111), + (16, 0b1111111110011000), + (16, 0b1111111110011001), + (16, 0b1111111110011010), + (16, 0b1111111110011011), + (16, 0b1111111110011100), + (16, 0b1111111110011101), + (7, 0b1111010), + (11, 0b11111110111), + (16, 0b1111111110011110), + (16, 0b1111111110011111), + (16, 0b1111111110100000), + (16, 0b1111111110100001), + (16, 0b1111111110100010), + (16, 0b1111111110100011), + (16, 0b1111111110100100), + (16, 0b1111111110100101), + (7, 0b1111011), + (12, 0b111111110110), + (16, 0b1111111110100110), + (16, 0b1111111110100111), + (16, 0b1111111110101000), + (16, 0b1111111110101001), + (16, 0b1111111110101010), + (16, 0b1111111110101011), + (16, 0b1111111110101100), + (16, 0b1111111110101101), + (8, 0b11111010), + (12, 0b111111110111), + (16, 0b1111111110101110), + (16, 0b1111111110101111), + (16, 0b1111111110110000), + (16, 0b1111111110110001), + (16, 0b1111111110110010), + (16, 0b1111111110110011), + (16, 0b1111111110110100), + (16, 0b1111111110110101), + (9, 0b111111000), + (15, 0b111111111000000), + (16, 0b1111111110110110), + (16, 0b1111111110110111), + (16, 0b1111111110111000), + (16, 0b1111111110111001), + (16, 0b1111111110111010), + (16, 0b1111111110111011), + (16, 0b1111111110111100), + (16, 0b1111111110111101), + (9, 0b111111001), + (16, 0b1111111110111110), + (16, 0b1111111110111111), + (16, 0b1111111111000000), + (16, 0b1111111111000001), + (16, 0b1111111111000010), + (16, 0b1111111111000011), + (16, 0b1111111111000100), + (16, 0b1111111111000101), + (16, 0b1111111111000110), + (9, 0b111111010), + (16, 0b1111111111000111), + (16, 0b1111111111001000), + (16, 0b1111111111001001), + (16, 0b1111111111001010), + (16, 0b1111111111001011), + (16, 0b1111111111001100), + (16, 0b1111111111001101), + (16, 0b1111111111001110), + (16, 0b1111111111001111), + (10, 0b1111111001), + (16, 0b1111111111010000), + (16, 0b1111111111010001), + (16, 0b1111111111010010), + (16, 0b1111111111010011), + (16, 0b1111111111010100), + (16, 0b1111111111010101), + (16, 0b1111111111010110), + (16, 0b1111111111010111), + (16, 0b1111111111011000), + (10, 0b1111111010), + (16, 0b1111111111011001), + (16, 0b1111111111011010), + (16, 0b1111111111011011), + (16, 0b1111111111011100), + (16, 0b1111111111011101), + (16, 0b1111111111011110), + (16, 0b1111111111011111), + (16, 0b1111111111100000), + (16, 0b1111111111100001), + (11, 0b11111111000), + (16, 0b1111111111100010), + (16, 0b1111111111100011), + (16, 0b1111111111100100), + (16, 0b1111111111100101), + (16, 0b1111111111100110), + (16, 0b1111111111100111), + (16, 0b1111111111101000), + (16, 0b1111111111101001), + (16, 0b1111111111101010), + (16, 0b1111111111101011), + (16, 0b1111111111101100), + (16, 0b1111111111101101), + (16, 0b1111111111101110), + (16, 0b1111111111101111), + (16, 0b1111111111110000), + (16, 0b1111111111110001), + (16, 0b1111111111110010), + (16, 0b1111111111110011), + (16, 0b1111111111110100), + (11, 0b11111111001), + (16, 0b1111111111110101), + (16, 0b1111111111110110), + (16, 0b1111111111110111), + (16, 0b1111111111111000), + (16, 0b1111111111111001), + (16, 0b1111111111111010), + (16, 0b1111111111111011), + (16, 0b1111111111111100), + (16, 0b1111111111111101), (16, 0b1111111111111110) ] { let entry:JPEG.Table.HuffmanAC.Decoder.Entry = decoder[codeword << (16 - length)] - - guard entry.symbol.value == expected, + + guard entry.symbol.value == expected, entry.length == length - else + else { - return .failure(.init(message: + return .failure(.init(message: "codeword decoded incorrectly (\(entry.symbol.value), expected \(expected))")) } - - if expected & 0x0f < 0x0a + + if expected & 0x0f < 0x0a { expected = expected & 0xf0 | (expected & 0x0f &+ 1) } - else + else { expected = (expected & 0xf0 &+ 0x10) | (expected & 0xf0 == 0xe0 ? 0 : 1) } } - + return .success(()) } - + // tries to decode bitstreams into byte arrays using simple known huffman trees - static + static func huffmanCoding() -> Result { let trees:[[[JPEG.Bitstream.Symbol.DC]]] = @@ -374,16 +374,16 @@ extension Test // 110 -> 0x64 // 1110 -> 0x65 [ - [ ], + [ ], [0x61, 0x62, 0x63], [0x64], [0x65], - + [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], ], - // 16-height degenerate, double-layered tree + // 16-height degenerate, double-layered tree // // 0 -> 0x61 // 10 -> 0x62 @@ -398,7 +398,7 @@ extension Test [0x69], [0x6a], [0x6b], [0x6c], [0x6d], [0x6e], [0x6f], [0x70], ], - // 4-height degenerate, single-layered tree + // 4-height degenerate, single-layered tree // // 0 -> 0x61 // 10 -> 0x62 @@ -411,94 +411,94 @@ extension Test [ ], [ ], [ ], [ ], ], ] - let pairs:[(encoded:JPEG.Bitstream, decoded:[UInt8])] = + let pairs:[(encoded:JPEG.Bitstream, decoded:[UInt8])] = [ ( - [0b110_1110__0,0b0_01____10____110 ], + [0b110_1110__0,0b0_01____10____110 ], [0x64, 0x65, 0x61, 0x62, 0x63, 0x64] ), ( - [0b11111111,0b110_0_____1111,0b11111111,0b1110_1110], + [0b11111111,0b110_0_____1111,0b11111111,0b1110_1110], [0x6b, 0x61, 0x70, 0x64] ), ( // test codewords that do not correspond to encoded symbols - // (decoder should return a null byte rather than crashing, and + // (decoder should return a null byte rather than crashing, and // skip 16 bits) - [0b11110110,0b11111110, 0b10__10____1110, 0b11111111,0b11111110], + [0b11110110,0b11111110, 0b10__10____1110, 0b11111111,0b11111110], [0x00, 0x62, 0x62, 0x64, 0x00] ), ] for (symbols, (encoded, expected)): ([[JPEG.Bitstream.Symbol.DC]], (JPEG.Bitstream, [UInt8])) in zip(trees, pairs) { - guard let table:JPEG.Table.HuffmanDC = + guard let table:JPEG.Table.HuffmanDC = JPEG.Table.HuffmanDC.init(symbols, target: \.0) - else + else { return .failure(.init(message: "failed to initialize huffman table")) } - + let decoder:JPEG.Table.HuffmanDC.Decoder = table.decoder() var decoded:[UInt8] = [] var b:Int = 0 - while b < encoded.count + while b < encoded.count { let entry:JPEG.Table.HuffmanDC.Decoder.Entry = decoder[encoded[b, count: 16]] decoded.append(entry.symbol.value) b += entry.length } - - guard decoded == expected - else + + guard decoded == expected + else { - return .failure(.init(message: + return .failure(.init(message: "message decoded incorrectly (expected \(expected), got \(decoded))")) } } - + return .success(()) } - - static + + static func huffmanCodingSymmetric(_ count:Int) -> Result { - let symbols:[JPEG.Bitstream.Symbol.AC] = (0 ..< count).map + let symbols:[JPEG.Bitstream.Symbol.AC] = (0 ..< count).map { _ in - // biases the distribution so that values around 128 are more common + // biases the distribution so that values around 128 are more common .init(UInt8.random(in: 0 ..< 128) + UInt8.random(in: 0 ... 128)) } - + let frequencies:[Int] = JPEG.Bitstream.Symbol.AC.frequencies(of: \.self, in: symbols) let table:JPEG.Table.HuffmanAC = .init(frequencies: frequencies, target: \.0) let encoder:JPEG.Table.HuffmanAC.Encoder = table.encoder() - + var bits:JPEG.Bitstream = [] - for symbol:JPEG.Bitstream.Symbol.AC in symbols + for symbol:JPEG.Bitstream.Symbol.AC in symbols { let codeword:JPEG.Table.HuffmanAC.Encoder.Codeword = encoder[symbol] bits.append(codeword.bits, count: codeword.length) } - + let decoder:JPEG.Table.HuffmanAC.Decoder = table.decoder() - + var b:Int = 0 var decoded:[JPEG.Bitstream.Symbol.AC] = [] - while b < bits.count + while b < bits.count { let entry:JPEG.Table.Huffman.Decoder.Entry = decoder[bits[b, count: 16]] decoded.append(entry.symbol) - b += entry.length + b += entry.length } - - guard symbols == decoded - else + + guard symbols == decoded + else { - return .failure(.init(message: + return .failure(.init(message: "huffman coder failed to round-trip symbolic sequence (\(symbols.count) symbols)")) } - + return .success(()) } } diff --git a/utils/examples b/utils/examples index 6aef6794..48f025d8 100755 --- a/utils/examples +++ b/utils/examples @@ -2,7 +2,7 @@ DEFAULT_BUILD_CONFIGURATION="debug" SCRIPT_NAME="examples" -usage() +usage() { echo "usage: utils/$SCRIPT_NAME [OPTION...] -c, --configuration swift build configuration mode, default '$DEFAULT_BUILD_CONFIGURATION'" @@ -10,14 +10,14 @@ usage() error() { - echo $1 + echo $1 exit 1 } check() { message=$1 - shift + shift echo $@ "$@" || error "$message" } @@ -25,78 +25,78 @@ check() build_configuration=$DEFAULT_BUILD_CONFIGURATION while [ "$1" != "" ] ; do - case $1 in - -c | --configuration ) + case $1 in + -c | --configuration ) shift build_configuration=$1 ;; * ) - usage + usage exit 1 - esac - shift -done + esac + shift +done -for path in examples/*/ ; do +for path in examples/*/ ; do product=$(basename $path) check "error: swift build failed" \ swift build -c $build_configuration --product $product - + binaries=".build/$build_configuration" if ! [ -f $binaries/$product ]; then error "error: missing '$product' product" fi -done +done # run `decode-basic` example check "error: runtime error" \ - .build/$build_configuration/decode-basic + .build/$build_configuration/decode-basic -for file in examples/decode-basic/*.jpg ; do +for file in examples/decode-basic/*.jpg ; do size=$(identify -format "%wx%h" jpg:$file) convert -depth 8 -size $size "rgb:$file.rgb" "png:$file.rgb.png" -done +done # run `encode-basic` example check "error: runtime error" \ - .build/$build_configuration/encode-basic + .build/$build_configuration/encode-basic # run `decode-advanced` example check "error: runtime error" \ - .build/$build_configuration/decode-advanced + .build/$build_configuration/decode-advanced -for file in examples/decode-advanced/*.gray ; do +for file in examples/decode-advanced/*.gray ; do size=$(echo $file | sed -r 's/.*\.([0-9]+)x([0-9]+)\.gray/\1x\2/') convert -depth 8 -size $size "gray:$file" "png:$file.png" -done -for file in examples/decode-advanced/*.jpg ; do +done +for file in examples/decode-advanced/*.jpg ; do size=$(identify -format "%wx%h" jpg:$file) convert -depth 8 -size $size "rgb:$file.rgb" "png:$file.rgb.png" -done +done -# run `encode-advanced` example +# run `encode-advanced` example check "error: runtime error" \ - .build/$build_configuration/encode-advanced + .build/$build_configuration/encode-advanced -# run `in-memory` example +# run `in-memory` example check "error: runtime error" \ - .build/$build_configuration/in-memory -for file in examples/in-memory/*.jpg ; do + .build/$build_configuration/in-memory +for file in examples/in-memory/*.jpg ; do if [ -f $file.rgb ]; then size=$(identify -format "%wx%h" jpg:$file) convert -depth 8 -size $size "rgb:$file.rgb" "png:$file.rgb.png" fi -done +done -# run `decode-online` example +# run `decode-online` example check "error: runtime error" \ .build/$build_configuration/decode-online -for file in examples/decode-online/*.jpg ; do +for file in examples/decode-online/*.jpg ; do size=$(identify -format "%wx%h" jpg:$file) -done -for file in examples/decode-online/*.rgb ; do +done +for file in examples/decode-online/*.rgb ; do convert -depth 8 -size $size "rgb:$file" "png:$file.png" -done +done # run `recompress` example check "error: runtime error" \ @@ -113,7 +113,7 @@ check "error: runtime error" \ # run `custom-color` example check "error: runtime error" \ .build/$build_configuration/custom-color -for file in examples/custom-color/*.rgb ; do +for file in examples/custom-color/*.rgb ; do convert -depth 16 -endian msb -size 1000x200 rgb:$file -depth 16 png:$file-16.png convert -depth 16 -endian msb -size 1000x200 rgb:$file -depth 8 png:$file-8.png -done +done diff --git a/utils/fuzz-test b/utils/fuzz-test index 6e16311d..af927bf2 100755 --- a/utils/fuzz-test +++ b/utils/fuzz-test @@ -3,7 +3,7 @@ DEFAULT_BUILD_CONFIGURATION="debug" DEFAULT_COUNT="16" DEFAULT_PREFIX="tests/fuzz/data" -usage() +usage() { echo "usage: utils/fuzz-test [OPTION...] -c, --configuration swift build configuration mode, default '$DEFAULT_BUILD_CONFIGURATION' @@ -13,14 +13,14 @@ usage() error() { - echo $1 + echo $1 exit 1 } check() { message=$1 - shift + shift echo $@ "$@" || error "$message" } @@ -37,31 +37,31 @@ prefix=$DEFAULT_PREFIX count=$DEFAULT_COUNT build_configuration=$DEFAULT_BUILD_CONFIGURATION -JPEG_DIRECTORY_NAME="jpeg" -YCC_DIRECTORY_NAME="ycc" -JPEG_EXTENSION="jpg" +JPEG_DIRECTORY_NAME="jpeg" +YCC_DIRECTORY_NAME="ycc" +JPEG_EXTENSION="jpg" YCC_EXTENSION="ycc" while [ "$1" != "" ] ; do - case $1 in - -c | --configuration ) + case $1 in + -c | --configuration ) shift build_configuration=$1 ;; - -n | --count ) - shift + -n | --count ) + shift count=$1 ;; - -p | --prefix ) - shift + -p | --prefix ) + shift prefix=$1 ;; * ) - usage + usage exit 1 - esac - shift -done + esac + shift +done jpeg_pattern=$prefix/*.$JPEG_EXTENSION ycc_pattern=$prefix/*.$YCC_EXTENSION @@ -84,9 +84,9 @@ check "error: test image generation failed" \ rm $ycc_pattern &> /dev/null -for file in $jpeg_pattern ; do +for file in $jpeg_pattern ; do check "error: system jpeg-to-data conversion failed" \ convert -depth 8 -define jpeg:dct-method=float "jpeg:$file" "ycbcr:$file.$YCC_EXTENSION" -done +done $binaries/comparator $jpeg_pattern diff --git a/utils/generate-documentation b/utils/generate-documentation deleted file mode 100755 index 287821a4..00000000 --- a/utils/generate-documentation +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -TOOL_NAME=$0 -usage() -{ - echo "usage: $TOOL_NAME [OPTION...] - -l, --local generate local html pages (as opposed to a deployable website)" -} - -error() -{ - echo $1 - exit 1 -} - -check() -{ - message=$1 - shift - echo $@ - "$@" || error "$message" -} - -local= -while [ "$1" != "" ] ; do - case $1 in - -l | --local ) - shift - local="local" - ;; - * ) - usage - exit 1 - esac - shift -done - -base=$PWD - -mkdir -p .entrapta/ -cd .entrapta/ - -if ! [ -d entrapta ]; then - git clone https://github.com/kelvin13/entrapta -else - cd entrapta - git pull - cd .. -fi - -cd entrapta/ - -# lock to entrapta 0.1.0 -git checkout tags/0.1.0 - -swift build -c release - -if [ -z $local ]; then - .build/release/entrapta ../../sources/jpeg/*.swift --directory ../../documentation/ --url-prefix https://kelvin13.github.io/jpeg --github https://github.com/kelvin13/jpeg --project Swift\ JPEG\ Documentation -else - .build/release/entrapta ../../sources/jpeg/*.swift --directory ../../documentation/ --url-prefix $base/documentation --url-suffix /index.html --github https://github.com/kelvin13/jpeg --project Swift\ JPEG\ Documentation -fi - -cd ../../ diff --git a/utils/integration-test b/utils/integration-test index 16369de6..c910e1f9 100755 --- a/utils/integration-test +++ b/utils/integration-test @@ -2,7 +2,7 @@ DEFAULT_BUILD_CONFIGURATION="debug" TOOL_NAME="integration-test" -usage() +usage() { echo "usage: utils/$TOOL_NAME [OPTION...] -c, --configuration swift build configuration mode, default '$DEFAULT_BUILD_CONFIGURATION'" @@ -10,14 +10,14 @@ usage() error() { - echo $1 + echo $1 exit 1 } check() { message=$1 - shift + shift echo $@ "$@" || error "$message" } @@ -25,17 +25,17 @@ check() build_configuration=$DEFAULT_BUILD_CONFIGURATION while [ "$1" != "" ] ; do - case $1 in - -c | --configuration ) + case $1 in + -c | --configuration ) shift build_configuration=$1 ;; * ) - usage + usage exit 1 - esac - shift -done + esac + shift +done check "error: swift build failed" \ swift build -c $build_configuration --product $TOOL_NAME @@ -49,9 +49,9 @@ check "error: test failures" \ $binaries/$TOOL_NAME data=(tests/integration/decode/*.jpg) -for file in ${data[@]} ; do +for file in ${data[@]} ; do size=$(identify -format "%wx%h" jpg:$file) convert -depth 8 -size $size "rgb:$file.rgb" "png:$file.rgb.png" convert -depth 8 -size $size -define jpeg:dct-method=float "jpeg:$file" rgb:- |\ convert -depth 8 -size $size rgb:- "png:$file.png" -done +done diff --git a/utils/regression-test b/utils/regression-test index 1324beb7..f4a2f545 100755 --- a/utils/regression-test +++ b/utils/regression-test @@ -2,7 +2,7 @@ DEFAULT_BUILD_CONFIGURATION="debug" TOOL_NAME="regression-test" -usage() +usage() { echo "usage: utils/$TOOL_NAME [OPTION...] -c, --configuration swift build configuration mode, default '$DEFAULT_BUILD_CONFIGURATION' @@ -11,14 +11,14 @@ usage() error() { - echo $1 + echo $1 exit 1 } check() { message=$1 - shift + shift echo $@ "$@" || error "$message" } @@ -26,29 +26,29 @@ check() build_configuration=$DEFAULT_BUILD_CONFIGURATION while [ "$1" != "" ] ; do - case $1 in - -c | --configuration ) + case $1 in + -c | --configuration ) shift build_configuration=$1 ;; - -u | --update ) + -u | --update ) shift gold=(tests/regression/gold/*.jpg.ycc) echo "the following ${#gold[@]} golden outputs will be removed and regenerated:" for file in ${gold[@]} ; do echo " $file" - done + done echo "note: regression tests will fail. regression tests will pass on the next run." echo -n "press enter to confirm>" read confirmation rm ${gold[@]} ;; * ) - usage + usage exit 1 - esac - shift -done + esac + shift +done check "error: swift build failed" \ swift build -c $build_configuration --product $TOOL_NAME diff --git a/utils/unit-test b/utils/unit-test index 8f009541..a6c61c71 100755 --- a/utils/unit-test +++ b/utils/unit-test @@ -2,20 +2,20 @@ TOOL_NAME="unit-test" error() { - echo $1 + echo $1 exit 1 } check() { message=$1 - shift + shift echo $@ "$@" || error "$message" } check "error: swift build failed" \ - swift build -c debug --product $TOOL_NAME + swift build -c debug --product $TOOL_NAME binaries=".build/debug" if ! [ -f $binaries/$TOOL_NAME ]; then