├── .github
├── FUNDING.yml
└── workflows
│ ├── ci.yml
│ └── swiftlint.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── WebPKit.podspec
├── WebPKit.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── WebPKit-iOS.xcscheme
│ ├── WebPKit-macOS.xcscheme
│ ├── WebPKit-tvOS.xcscheme
│ ├── WebPKit-watchOS.xcscheme
│ ├── WebPKitExample-iOS.xcscheme
│ ├── WebPKitExample-macOS.xcscheme
│ ├── WebPKitExample-tvOS.xcscheme
│ ├── WebPKitExample-watchOS WatchKit App (Complication).xcscheme
│ ├── WebPKitExample-watchOS.xcscheme
│ ├── WebPKitTestHost.xcscheme
│ └── WebPKitTests.xcscheme
├── WebPKit
├── Decoding
│ ├── Core
│ │ └── CGImage+WebPDecoding.swift
│ ├── NSImage+WebPDecoding.swift
│ ├── UIImage+WebPDecoding.swift
│ └── Utilities
│ │ ├── Data+WebPDecoding.swift
│ │ └── URL+WebPDecoding.swift
├── Encoding
│ ├── Core
│ │ └── CGImage+WebPEncoding.swift
│ ├── NSImage+WebPEncoding.swift
│ ├── UIImage+WebPEncoding.swift
│ └── Utilities
│ │ └── CGImage+PixelFormat.swift
└── Supporting
│ ├── Info.plist
│ └── WebPKit.h
├── WebPKitExample-iOS
├── .gitignore
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
├── Sources
│ └── AppDelegate.swift
├── Supporting
│ ├── Info.plist
│ └── WebPKitExample-iOS.entitlements
├── ViewController.swift
├── WebP.xcframework
│ └── .gitignore
├── WebPKitLogo.webp
└── WebPKitLogoOpaque.webp
├── WebPKitExample-macOS
├── .gitignore
├── ContentView.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── Base.lproj
│ │ └── Main.storyboard
├── Sources
│ └── AppDelegate.swift
├── Supporting
│ ├── Info.plist
│ └── WebPKitExample_macOS.entitlements
└── WebP.xcframework
│ └── .gitignore
├── WebPKitExample-tvOS
├── .gitignore
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── App Icon & Top Shelf Image.brandassets
│ │ │ ├── App Icon - App Store.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Middle.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ ├── App Icon.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Middle.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Top Shelf Image Wide.imageset
│ │ │ │ └── Contents.json
│ │ │ └── Top Shelf Image.imageset
│ │ │ │ └── Contents.json
│ │ └── Contents.json
│ └── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
├── Sources
│ └── AppDelegate.swift
├── Supporting
│ └── Info.plist
├── ViewController.swift
└── WebP.xcframework
│ └── .gitignore
├── WebPKitExample-watchOS
├── .gitignore
├── App
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── Base.lproj
│ │ └── Interface.storyboard
├── Extension
│ ├── Assets.xcassets
│ │ ├── Complication.complicationset
│ │ │ ├── Circular.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Extra Large.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Graphic Bezel.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Graphic Circular.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Graphic Corner.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Graphic Extra Large.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Graphic Large Rectangular.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Modular.imageset
│ │ │ │ └── Contents.json
│ │ │ └── Utilitarian.imageset
│ │ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ComplicationController.swift
│ ├── ExtensionDelegate.swift
│ ├── Info.plist
│ └── InterfaceController.swift
└── WebP.xcframework
│ └── .gitignore
├── WebPKitTests
├── Decoding
│ ├── WebPDecodingCGImageTests.swift
│ ├── WebPDecodingDataTests.swift
│ ├── WebPDecodingTests.swift
│ ├── WebPDecodingUIImageTests.swift
│ └── WebPDecodingURLTests.swift
├── Encoding
│ ├── WebPEncodingCGBitmapInfoTests.swift
│ ├── WebPEncodingCGImageTests.swift
│ ├── WebPEncodingTests.swift
│ └── WebPEncodingUIImageTests.swift
├── Resources
│ ├── circle-grayscale-transparent.png
│ ├── circle-index-transparent.png
│ ├── circle-opaque.jpg
│ ├── circle-opaque.png
│ ├── circle-transparent.png
│ ├── logo-lossless.webp
│ ├── logo-lossy.WEBP
│ ├── rect-horizontal.webp
│ └── rect-vertical.webp
└── Supporting
│ ├── HostApp
│ └── iOS
│ │ ├── AppDelegate.swift
│ │ └── Info.plist
│ └── Info.plist
├── download.sh
└── screenshot.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: timoliver
2 | custom: https://tim.dev/paypal
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macos-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Run unit tests
13 | run: 'xcodebuild -scheme WebPKitTests -project ./WebPKit.xcodeproj -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 12" clean build test'
14 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/swiftlint.yml'
7 | - '.swiftlint.yml'
8 | - '**/*.swift'
9 |
10 | jobs:
11 | SwiftLint:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v1
17 |
18 | - name: Run SwiftLint on PR
19 | uses: norio-nomura/action-swiftlint@3.2.1
20 | env:
21 | DIFF_BASE: ${{ github.base_ref }}
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | # CocoaPods
34 | #
35 | # We recommend against adding the Pods directory to your .gitignore. However
36 | # you should judge for yourself, the pros and cons are mentioned at:
37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
38 | #
39 | # Pods/
40 | #
41 | # Add this line if you want to avoid checking in source code from the Xcode workspace
42 | # *.xcworkspace
43 |
44 | # Carthage
45 | #
46 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
47 | # Carthage/Checkouts
48 |
49 | Carthage/Build/
50 |
51 | # fastlane
52 | #
53 | # It is recommended to not store the screenshots in the git repo.
54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
55 | # For more information about the recommended setup visit:
56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
57 |
58 | fastlane/report.xml
59 | fastlane/Preview.html
60 | fastlane/screenshots/**/*.png
61 | fastlane/test_output
62 |
63 | # Code Injection
64 | #
65 | # After new code Injection tools there's a generated folder /iOSInjectionProject
66 | # https://github.com/johnno1962/injectionforxcode
67 |
68 | iOSInjectionProject/
69 | /build*/
70 |
71 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | x.y.z Release Notes (yyyy-MM-dd)
2 | =============================================================
3 |
4 | 1.0.0 Release Notes (2022-04-19)
5 | =============================================================
6 |
7 | ## Breaking Changes
8 |
9 | * Renamed `Data.isWebPFormat` and `URL.isWebPFile` to `isWebP`
10 |
11 | ## Fixes
12 |
13 | * Added an explicit check that `URL.isWebP` only tries to load data from local on-disk files.
14 | * A compile-time error when trying to use the WebP encoder via CocoaPods.
15 |
16 | 0.0.1 Release Notes (2020-10-17)
17 | =============================================================
18 |
19 | * Initial Release! 🎉
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Tim Oliver
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebPKit
2 |
3 |
4 |
5 | [](https://github.com/TimOliver/WebPKit/actions?query=workflow%3ACI)
6 | [](http://cocoadocs.org/docsets/TOCropViewController)
7 | [](https://github.com/Carthage/Carthage)
8 | [](https://raw.githubusercontent.com/TimOliver/WebPKit/main/LICENSE)
9 | [](http://cocoadocs.org/docsets/WebPKit)
10 |
11 | `WebPKit` is an open source Swift framework that wraps around Google's [WebP library](https://developers.google.com/speed/webp) to provide a native-feeling Cocoa API for working with WebP image files on all of Apple's platforms.
12 |
13 | `WebPKit` works by extending certain Cocoa classes to enable decoding WebP image data from disk, or encoding WebP image data from memory. It also provides additional functionality such as being able to verify the contents of a WebP file before decoding, as well as using WebP's decoding features to enable custom sizing.
14 |
15 |
16 | # Features
17 | * Read image files in the WebP format.
18 | * Write in-memory images to the WebP format.
19 | * Supports *all* of Apple's platforms (including Mac Catalyst).
20 | * Additional convenience methods for quickly verifying WebP data before decoding.
21 | * Decode WebP images directly to custom sizes (Good for saving memory)
22 | * 100% Swift, and fully unit-tested.
23 |
24 | # Requirements
25 |
26 | * **iOS:** 9.0 and above
27 | * **macOS:** 10.9 and above
28 | * **tvOS:** 9.0 and above
29 | * **watchOS:** 2.0 and above
30 |
31 | When installing manually, you will also need Google's `WebP` C library as well. Precompiled static binaries are available at [the Cocoa-WebP repo](https://github.com/TimOliver/WebP-Cocoa).
32 |
33 | # Usage
34 |
35 | `WebPKit` provides extensions to a variety of popular Cocoa classes in order to natively provide WebP format support.
36 |
37 | ### Verifying a WebP image file
38 |
39 | `WebPKit` provides a variety of methods to check the contents of a file or some data to see if it is a valid WebP file.
40 |
41 | ```swift
42 | import WebPKit
43 |
44 | // Check a `Data` object to see if it contains WebP data
45 | let webpData = Data(...)
46 | print(webpData.isWebP)
47 |
48 | // Check a file on disk to see if it is a WebP file
49 | let url = URL(...)
50 | print(url.isWebP)
51 |
52 | // Retrieve the pixel size of the image without decoding it
53 | let size = CGImage.sizeOfWebP(with: Data())
54 | ```
55 |
56 | ### Decoding a WebP image
57 |
58 | `WebPKit` performs decoding of WebP image data at the `CGImage` level, and then provides convenience initialisers for `UIImage` and `NSImage` on iOS-based platforms and macOS respectively.
59 |
60 | #### iOS / iPadOS / tvOS / watchOS
61 |
62 | ```swift
63 | import WebPKit
64 |
65 | // Load from data in memory
66 | let webpImage = UIImage(webpData: Data())
67 |
68 | // Load from disk
69 | let webpImage = UIImage(contentsOfWebPFile: URL())
70 |
71 | // Load from resource bundle
72 | let webpImage = UIImage.webpNamed("MyWebPImage")
73 | ```
74 |
75 | #### macOS
76 |
77 | ```swift
78 | import WebPKit
79 |
80 | // Load from data in memory
81 | let webpImage = NSImage(webpData: Data())
82 |
83 | // Load from disk
84 | let webpImage = NSImage(contentsOfWebPFile: URL())
85 |
86 | // Load from resource bundle
87 | let webpImage = NSImage.webpNamed("MyWebPImage")
88 | ```
89 |
90 | # Installation
91 |
92 |
93 | CocoaPods
94 |
95 | Add the following to your `Podfile`:
96 |
97 | ```
98 | pod 'WebPKit'
99 | ```
100 |
101 |
102 |
103 |
104 | Carthage
105 |
106 | Carthage support is coming soon. Stay tuned!
107 |
108 |
109 |
110 | Swift Package Manager
111 |
112 | SPM support is coming soon. Stay tuned!
113 |
114 |
115 |
116 | Manual Installation
117 |
118 | 1. Download this repository.
119 | 2. Copy the `WebPKit` folder to your Xcode project.
120 | 3. Download the precompiled WebP binary from [the Cocoa-WebP repo](https://github.com/TimOliver/WebP-Cocoa) for your desired platform.
121 | 4. Drag that framework into your Xcode project.
122 |
123 |
124 |
125 | # Why Build This?
126 |
127 | Support for WebP image files had been a growing feature request in my comic reader app [iComics](http://icomics/co) for a number of years. With iComics being in Objective-C, I was able to use [one of the many libraries](https://github.com/mattt/WebPImageSerialization) on GitHub out there to easily support it.
128 |
129 | But while that was the case for Objective-C, while I've been working on iComics 2, I started to realise that there still wasn't a great amount of WebP support for Swift, as well as Apple's modern platforms and features in general.
130 |
131 | Google's own precompiled WebP binaries don't support either Swift or Mac Catalyst, and while I found a few different WebP Swift libraries out there, none covered all of the requirements that I was hoping for (Things like CocoaPods support, accurate alpha channel control, and decoding at intermediate sizes).
132 |
133 | For a feature that will be an extremely fundamental pillar in iComics 2, I decided that it would be worth the time and investment to go back and make a *really* good WebP framework from scratch, that followed the API design of Apple's frameworks for easy integration, but also gave me the control to include all of the advanced features I need from the start.
134 |
135 | I'm incrdedibly happy with how this framework turned out. It turns out it was no where near as much code as I was expecting, and it all fit nicely as extensions on existing Apple classes. Moving forward, I hope this framework can be valuable for other Apple developers as well.
136 |
137 | # Credits
138 |
139 | `WebPKit` was created by [Tim Oliver](http://twitter.com/TimOliverAU).
140 |
141 | A huge shout-out also goes to the `SDWebImage` team for [maintaining CocoaPods and Carthage releases](https://github.com/SDWebImage/libwebp-Xcode) for WebP all this time as well.
142 |
143 | # License
144 |
145 | `WebPKit` is licensed under the MIT License. Please see the [LICENSE](LICENSE) file for more details.
146 |
--------------------------------------------------------------------------------
/WebPKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'WebPKit'
3 | s.version = '1.0.0'
4 | s.license = { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'A framework that implements encoding and decoding WebP files on all of Apple\'s platforms.'
6 | s.homepage = 'https://github.com/TimOliver/WebPKit'
7 | s.author = 'Tim Oliver'
8 | s.source = { :git => 'https://github.com/TimOliver/WebPKit.git', :tag => s.version }
9 | s.source_files = 'WebPKit/**/*.{swift}'
10 | s.swift_version = '5.0'
11 |
12 | s.ios.deployment_target = '9.0'
13 | s.osx.deployment_target = '10.9'
14 | s.watchos.deployment_target = '2.0'
15 | s.tvos.deployment_target = '9.0'
16 |
17 | s.dependency 'libwebp'
18 | end
19 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKit-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
61 |
62 |
68 |
69 |
75 |
76 |
77 |
78 |
80 |
81 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKit-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
61 |
62 |
68 |
69 |
75 |
76 |
77 |
78 |
80 |
81 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKit-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
61 |
62 |
68 |
69 |
75 |
76 |
77 |
78 |
80 |
81 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKit-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
61 |
62 |
68 |
69 |
75 |
76 |
77 |
78 |
80 |
81 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitExample-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
51 |
57 |
58 |
59 |
60 |
61 |
71 |
73 |
79 |
80 |
81 |
82 |
88 |
90 |
96 |
97 |
98 |
99 |
101 |
102 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitExample-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
51 |
57 |
58 |
59 |
60 |
61 |
71 |
73 |
79 |
80 |
81 |
82 |
88 |
90 |
96 |
97 |
98 |
99 |
101 |
102 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitExample-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
61 |
63 |
69 |
70 |
71 |
72 |
78 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitExample-watchOS WatchKit App (Complication).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
58 |
60 |
66 |
67 |
68 |
69 |
76 |
78 |
84 |
85 |
86 |
87 |
89 |
90 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitExample-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
47 |
53 |
54 |
55 |
56 |
57 |
62 |
63 |
64 |
65 |
75 |
77 |
83 |
84 |
85 |
86 |
92 |
94 |
100 |
101 |
102 |
103 |
105 |
106 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitTestHost.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
61 |
63 |
69 |
70 |
71 |
72 |
78 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/WebPKit.xcodeproj/xcshareddata/xcschemes/WebPKitTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
51 |
57 |
58 |
59 |
60 |
61 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/WebPKit/Decoding/Core/CGImage+WebPDecoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGImage+WebPDecoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 | import CoreGraphics
25 |
26 | #if canImport(WebP)
27 | import WebP.Decoder
28 | #elseif canImport(libwebp)
29 | import libwebp
30 | #else
31 | #error("libwebp couldn't be found")
32 | #endif
33 |
34 | /// Errors that can potentially occur when
35 | /// tying to decode WebP data
36 | public enum WebPDecodingError: UInt32, Error {
37 | // VP8_STATUS errors
38 | case none=0
39 | case outOfMemory
40 | case invalidParam
41 | case bitstreamError
42 | case unsupportedFeature
43 | case suspended
44 | case userAbort
45 | case notEnoughData
46 |
47 | // CGImage decode errors
48 | case invalidHeader=100
49 | case initConfigFailed
50 | case imageRenderFailed
51 | }
52 |
53 | /// The different scaling behaviours available
54 | /// when optionally decoding WebP images to custom sizes.
55 | public enum WebPScalingMode {
56 | case aspectFit // Scaled to fit the size, preserving aspect ratio
57 | case aspectFill // Scaled to fill the size, preserving aspect ratio
58 | case scale // Scaled to fill the size, disregarding aspect ratio
59 | }
60 |
61 | /// Extends CGImage with the ability
62 | /// to decode images from the WebP file format
63 | public extension CGImage {
64 |
65 | /// Reads the header of a WebP image file and extracts
66 | /// the pixel resolution of the image without performing a full decode.
67 | /// - Parameter url: The file URL of the WebP image
68 | /// - Returns: The size of the image, or nil if it failed
69 | static func sizeOfWebP(at url: URL) -> CGSize? {
70 | guard let data = try? Data(contentsOf: url, options: .alwaysMapped) else {
71 | return nil
72 | }
73 | return CGImage.sizeOfWebP(with: data)
74 | }
75 |
76 | /// Reads the header of a WebP image file and extracts
77 | /// the pixel resolution of the image without performing a full decode.
78 | /// - Parameter data: The WebP image data
79 | /// - Returns: The size of the image, or nil if it failed
80 | static func sizeOfWebP(with data: Data) -> CGSize? {
81 | var width: Int32 = 0, height: Int32 = 0
82 |
83 | if !data.withUnsafeBytes({ bytes -> Bool in
84 | guard let boundPtr = bytes.baseAddress?
85 | .assumingMemoryBound(to: UInt8.self) else { return false }
86 | return (WebPGetInfo(boundPtr, bytes.count, &width, &height) != 0)
87 | }) { return nil }
88 |
89 | return CGSize(width: Int(width), height: Int(height))
90 | }
91 |
92 | /// Decode a WebP image from a file on disk and return it as a CGImage
93 | /// - Parameter url: The URL path to the file
94 | /// - Throws: If the data was unabled to be decoded
95 | /// - Returns: The decoded image as a CGImage
96 | static func webpImage(contentsOfFile url: URL,
97 | width: CGFloat? = nil,
98 | height: CGFloat? = nil,
99 | scalingMode: WebPScalingMode = .aspectFit) throws -> CGImage {
100 | let data = try Data(contentsOf: url, options: .alwaysMapped)
101 | return try CGImage.webpImage(data: data, width: width,
102 | height: height, scalingMode: scalingMode)
103 | }
104 |
105 | /// Decode a WebP image from memory and return it as a CGImage
106 | /// - Parameter data: The data to decode
107 | /// - Throws: If the data was unabled to be decoded
108 | /// - Returns: The decoded image as a CGImage
109 | static func webpImage(data: Data,
110 | width: CGFloat? = nil,
111 | height: CGFloat? = nil,
112 | scalingMode: WebPScalingMode = .aspectFit) throws -> CGImage {
113 | // Check the header before proceeding to ensure this is a valid WebP file
114 | guard data.isWebP else { throw WebPDecodingError.invalidHeader }
115 |
116 | // Get properties of WebP image so we can configure the decoding as needed
117 | var features = WebPBitstreamFeatures()
118 | var status = data.withUnsafeBytes { ptr -> VP8StatusCode in
119 | guard let boundPtr = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
120 | return VP8_STATUS_INVALID_PARAM
121 | }
122 | return WebPGetFeatures(boundPtr, ptr.count, &features)
123 | }
124 | guard status == VP8_STATUS_OK else { throw WebPDecodingError(rawValue: status.rawValue)! }
125 |
126 | // Init the config
127 | var config = WebPDecoderConfig()
128 | guard WebPInitDecoderConfig(&config) != 0 else { throw WebPDecodingError.initConfigFailed }
129 | config.output.colorspace = MODE_rgbA // Pre-multipled alpha (Alpha channel is disregarded for opaque images)
130 | config.options.bypass_filtering = 1
131 | config.options.no_fancy_upsampling = 1
132 | config.options.use_threads = 1
133 |
134 | // If a custom width or height was provided, configure the struct to decode the image to that size
135 | configureScaledSize(width: width, height: height, scalingMode: scalingMode, features: features, config: &config)
136 |
137 | // Decode the image
138 | status = data.withUnsafeBytes { ptr -> VP8StatusCode in
139 | guard let boundPtr = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
140 | return VP8_STATUS_INVALID_PARAM
141 | }
142 | return WebPDecode(boundPtr, ptr.count, &config)
143 | }
144 | guard status == VP8_STATUS_OK else { throw WebPDecodingError(rawValue: status.rawValue)! }
145 |
146 | // Convert the decoded pixel data to a CGImage
147 | let releaseData: CGDataProviderReleaseDataCallback = {_, data, _ in data.deallocate() }
148 | let bytesPerRow = 4
149 | let dataProvider = CGDataProvider(dataInfo: &config, data: config.output.u.RGBA.rgba,
150 | size: Int(config.output.width * config.output.height) * bytesPerRow,
151 | releaseData: releaseData)
152 |
153 | // Configure the rendering information for the image
154 | var bitmapInfo = CGBitmapInfo.byteOrder32Big.rawValue
155 | bitmapInfo |= features.has_alpha == 1 ? CGImageAlphaInfo.premultipliedLast.rawValue :
156 | CGImageAlphaInfo.noneSkipLast.rawValue
157 | let renderingIntent = CGColorRenderingIntent.defaultIntent
158 |
159 | // Render the image
160 | guard let imageRef = CGImage(width: Int(config.output.width),
161 | height: Int(config.output.height),
162 | bitsPerComponent: 8,
163 | bitsPerPixel: 32,
164 | bytesPerRow: bytesPerRow * Int(config.output.width),
165 | space: CGColorSpaceCreateDeviceRGB(),
166 | bitmapInfo: CGBitmapInfo(rawValue: bitmapInfo),
167 | provider: dataProvider!,
168 | decode: nil,
169 | shouldInterpolate: false,
170 | intent: renderingIntent) else { throw WebPDecodingError.imageRenderFailed }
171 |
172 | return imageRef
173 | }
174 |
175 | /// Updates the WebP config struct to decode this image to a custom size
176 | /// - Parameters:
177 | /// - width: The width to scale this image to
178 | /// - height: The height to scale this image to
179 | /// - scalingMode: The scaling mode to fit the image to the provided size
180 | /// - features: The features struct of the current WebP file
181 | /// - config: The output config struct where the final size is configured
182 | static private func configureScaledSize(width: CGFloat?,
183 | height: CGFloat?,
184 | scalingMode: WebPScalingMode,
185 | features: WebPBitstreamFeatures,
186 | config: inout WebPDecoderConfig) {
187 | // Exit out if no custom size was provided
188 | guard width != nil || height != nil else { return }
189 |
190 | // Fetch the size of the image so we can calculate aspect ratio
191 | let originalSize = CGSize(width: Int(features.width), height: Int(features.height))
192 |
193 | // Configure the target size, using the original size as default
194 | var size = CGSize.zero
195 | if scalingMode == .aspectFit { // Shrink the image to fit inside the provided size
196 | let scaleSize = CGSize(width: width ?? originalSize.width, height: height ?? originalSize.height)
197 | let scale = min(1.0, min(scaleSize.width/originalSize.width, scaleSize.height/originalSize.height))
198 | size.width = originalSize.width * scale
199 | size.height = originalSize.height * scale
200 | } else if scalingMode == .aspectFill { // Shrink the image to completely fill the provided size
201 | let scaleSize = CGSize(width: min(originalSize.width, width ?? 0),
202 | height: min(originalSize.height, height ?? 0))
203 | let scale = max(scaleSize.width/originalSize.width, scaleSize.height/originalSize.height)
204 | size.width = originalSize.width * scale
205 | size.height = originalSize.height * scale
206 | }
207 |
208 | // Set the config to use custom scale decoding,
209 | // and supply the calculated sizes
210 | config.options.use_scaling = 1
211 | config.options.scaled_width = Int32(size.width)
212 | config.options.scaled_height = Int32(size.height)
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/WebPKit/Decoding/NSImage+WebPDecoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSImage+WebPDecoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 |
25 | #if !targetEnvironment(macCatalyst) && canImport(AppKit)
26 | import AppKit
27 |
28 | public extension NSImage {
29 |
30 | /// Create a new image object by the decoding
31 | /// data in the WebP format
32 | /// - Parameters:
33 | /// - webpData: The WebP encoded data to decode
34 | /// - scale: The scale factor to scale the content to.
35 | /// If nil is specified, the screen scale is used
36 | convenience init?(webpData: Data, width: CGFloat? = nil, height: CGFloat? = nil,
37 | scalingMode: WebPScalingMode = .aspectFit) {
38 | guard let cgImage = try? CGImage.webpImage(data: webpData,
39 | width: width,
40 | height: height,
41 | scalingMode: scalingMode) else { return nil }
42 | self.init(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))
43 | }
44 |
45 | /// Create a new image object by the decoding
46 | /// data in the WebP format on disk
47 | /// - Parameters:
48 | /// - url: The WebP file to decode
49 | /// - scale: The scale factor to scale the content to.
50 | /// If nil is specified, the screen scale is used
51 | convenience init?(contentsOfWebPFile url: URL, width: CGFloat? = nil, height: CGFloat? = nil,
52 | scalingMode: WebPScalingMode = .aspectFit) {
53 | guard let cgImage = try? CGImage.webpImage(contentsOfFile: url,
54 | width: width,
55 | height: height,
56 | scalingMode: scalingMode) else { return nil }
57 | self.init(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))
58 | }
59 |
60 | /// Load a WebP image file from this app's resources bundle.
61 | /// If successfully loaded, the image is cached so it can be re-used
62 | /// on subsequent calls
63 | /// - Parameters:
64 | /// - name: The WebP image's name in the resources bundle
65 | /// - bundle: Optionally, the bundle to target (By default, the main bundle is used)
66 | /// - Returns: The decoded image if successful, or nil if not
67 | static func webpNamed(_ name: String, bundle: Bundle = Bundle.main) -> NSImage? {
68 | // Find the non-retina version of the image in the resource bundle
69 | guard let url = bundle.url(forResource: name,
70 | withExtension: URL.webpFileExtension) else { return nil }
71 |
72 | // Get the size of the non-retina image
73 | guard let size = CGImage.sizeOfWebP(at: url) else { return nil }
74 |
75 | // Configure a dynamic block to render the image to the supplied size
76 | let image = NSImage(size: size, flipped: false) { rect -> Bool in
77 | // Try and decode the image
78 | guard let cgImage = try? CGImage.webpImage(contentsOfFile: url) else {
79 | return false
80 | }
81 |
82 | // Load the image and draw
83 | let image = NSImage(cgImage: cgImage, size: size)
84 | image.draw(in: rect)
85 | return true
86 | }
87 |
88 | return image
89 | }
90 | }
91 |
92 | #endif
93 |
--------------------------------------------------------------------------------
/WebPKit/Decoding/UIImage+WebPDecoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+WebPDecoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 |
25 | #if canImport(UIKit)
26 | import UIKit
27 |
28 | #if canImport(WatchKit)
29 | import WatchKit
30 | #endif
31 |
32 | /// A global image cache that stores and serves images
33 | /// created with "webpNamed". The value is weak referenced so that
34 | /// instances will free themselves when no external objects are retaining them.
35 | let imageCache = NSMapTable(keyOptions: .strongMemory,
36 | valueOptions: .weakMemory)
37 |
38 | public extension UIImage {
39 |
40 | /// Create a new image object by the decoding
41 | /// data in the WebP format
42 | /// - Parameters:
43 | /// - webpData: The WebP encoded data to decode
44 | /// - scale: The scale factor to scale the content to.
45 | /// If nil is specified, the screen scale is used.
46 | /// - width: Optionally, a custom width to decode the image to.
47 | /// - height: Optionally, a custom height to decode the image to.
48 | /// - scalingMode: When decoding to a custom size, the type of scaling that will be applied.
49 | convenience init?(webpData: Data, scale: CGFloat? = 1.0, width: CGFloat? = nil,
50 | height: CGFloat? = nil, scalingMode: WebPScalingMode = .aspectFit) {
51 |
52 | // Depending on platform, retrieve the screen scale
53 | #if os(watchOS)
54 | let imageScale = scale ?? WKInterfaceDevice.current().screenScale
55 | #else
56 | let imageScale = scale ?? UIScreen.main.scale
57 | #endif
58 |
59 | // Decode the WebP image from memory
60 | guard let cgImage = try? CGImage.webpImage(data: webpData,
61 | width: (width != nil) ? width! * imageScale : nil,
62 | height: (height != nil) ? height! * imageScale : nil,
63 | scalingMode: scalingMode) else { return nil }
64 |
65 | // Initialize the UIImage
66 | self.init(cgImage: cgImage, scale: imageScale, orientation: .up)
67 | }
68 |
69 | /// Create a new image object by the decoding
70 | /// data in the WebP format on disk
71 | /// - Parameters:
72 | /// - url: The WebP file to decode
73 | /// - scale: The scale factor to scale the content to.
74 | /// If nil is specified, the screen scale is used
75 | /// - width: Optionally, a custom width to decode the image to.
76 | /// - height: Optionally, a custom height to decode the image to.
77 | /// - scalingMode: When decoding to a custom size, the type of scaling that will be applied.
78 | convenience init?(contentsOfWebPFile url: URL, scale: CGFloat? = 1.0, width: CGFloat? = nil,
79 | height: CGFloat? = nil, scalingMode: WebPScalingMode = .aspectFit) {
80 | // Depending on platform, retrieve the screen scale
81 | #if os(watchOS)
82 | let imageScale = scale ?? WKInterfaceDevice.current().screenScale
83 | #else
84 | let imageScale = scale ?? UIScreen.main.scale
85 | #endif
86 |
87 | // Decode the WebP image from disk
88 | guard let cgImage = try? CGImage.webpImage(contentsOfFile: url,
89 | width: (width != nil) ? width! * imageScale : nil,
90 | height: (height != nil) ? height! * imageScale : nil,
91 | scalingMode: scalingMode) else { return nil }
92 |
93 | // Initialize the UIImage
94 | self.init(cgImage: cgImage, scale: imageScale, orientation: .up)
95 | }
96 |
97 | /// Load a WebP image file from this app's resources bundle.
98 | /// If successfully loaded, the image is cached so it can be re-used
99 | /// on subsequent calls
100 | /// - Parameters:
101 | /// - name: The WebP image's name in the resources bundle
102 | /// - bundle: Optionally, the bundle to target (By default, the main bundle is used)
103 | /// - Returns: The decoded image if successful, or nil if not
104 | static func webpNamed(_ name: String, bundle: Bundle = Bundle.main) -> UIImage? {
105 | // Retrieve the scale of the screen
106 | #if os(watchOS)
107 | let scale = Int(WKInterfaceDevice.current().screenScale)
108 | #else
109 | let scale = Int(UIScreen.main.scale)
110 | #endif
111 |
112 | // If a scale was discovered, configure the default file name
113 | var scaleSuffix = ""
114 | if scale > 1 {
115 | scaleSuffix = "@\(scale)x"
116 | }
117 |
118 | // Work out the file extension
119 | var pathExtension = (name as NSString).pathExtension
120 | if pathExtension.isEmpty { pathExtension = URL.webpFileExtension }
121 |
122 | // Extract the file name minus the extension
123 | let fileName = (name as NSString).deletingPathExtension
124 |
125 | // Format the name to include the Retina scale suffix
126 | let scaleName = "\(fileName)\(scaleSuffix)" // eg 'image@2x.webp'
127 |
128 | // Query to see if we have a stored image in the cache
129 | if let image = imageCache.object(forKey: NSString(string: scaleName)) { return image }
130 | if let image = imageCache.object(forKey: NSString(string: fileName)) { return image }
131 |
132 | // Check both the scale name and regular name
133 | let names = [scaleName, fileName]
134 | for name in names {
135 | // If we discovered an image, load it, and save it to the cache
136 | if let url = bundle.resourceURL?.appendingPathComponent("\(name).\(pathExtension)"),
137 | let image = UIImage(contentsOfWebPFile: url) {
138 | imageCache.setObject(image, forKey: NSString(string: name))
139 | return image
140 | }
141 | }
142 |
143 | // Return the newly created image
144 | return nil
145 | }
146 | }
147 |
148 | #endif
149 |
--------------------------------------------------------------------------------
/WebPKit/Decoding/Utilities/Data+WebPDecoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+WebPDecoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 |
25 | /// Extends the Foundation Data class with
26 | /// functionality for identifying WebP formatted data.
27 | public extension Data {
28 |
29 | /// Checks the contents of the data to see if the
30 | /// header matches that of the WebP file format.
31 | var isWebP: Bool {
32 | // Ensure the size of the data is large enough
33 | // for us to properly check.
34 | guard self.count >= 12 else {
35 | return false
36 | }
37 |
38 | return withUnsafeBytes { bytes in
39 | // The first 4 bytes are the ASCII letters "RIFF"
40 | // Skipping 4 bytes for the file size, the next 4 bytes after
41 | // that should read "WEBP"
42 | if String(decoding: bytes[0..<4], as: UTF8.self) != "RIFF" ||
43 | String(decoding: bytes[8..<12], as: UTF8.self) != "WEBP" {
44 | return false
45 | }
46 |
47 | return true
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/WebPKit/Decoding/Utilities/URL+WebPDecoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL+WebPDecoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 |
25 | /// Extends the Foundation URL class with
26 | /// functionality for identifying WebP files.
27 | public extension URL {
28 |
29 | /// Returns the standard file extension for the WEBP format as a string
30 | static var webpFileExtension: String { return "webp" }
31 |
32 | /// Returns whether this file URL points to a WebP image file.
33 | /// It initially checks the file name to see if it contains the WebP file extension,
34 | /// but if that files, it will check the contents of the file for the WebP format magic number.
35 | var isWebP: Bool { isWebP(ignoringFileExtension: false) }
36 |
37 | /// Returns whether this file URL points to a WebP image file.
38 | /// It initially checks the file name to see if it contains the WebP file extension,
39 | /// but if that files, it will check the contents of the file for the WebP format magic number.
40 | /// - Parameter ignoringFileExtension: Skip checking the file extension and directly scan the file first
41 | /// - Returns: Whether the file is in the WebP format or not
42 | func isWebP(ignoringFileExtension: Bool = false) -> Bool {
43 |
44 | // If desired, check the file format extension
45 | if !ignoringFileExtension, self.pathExtension.lowercased() == URL.webpFileExtension {
46 | return true
47 | }
48 |
49 | // Load the file as mapped memory, and check the header
50 | // Ensure we only try this on URLs representing local files on disk.
51 | if isFileURL, let data = try? Data(contentsOf: self, options: .alwaysMapped) {
52 | return data.isWebP
53 | }
54 |
55 | return false
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/WebPKit/Encoding/Core/CGImage+WebPEncoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGImage+WebPEncoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 | import CoreGraphics
25 | import Accelerate
26 |
27 | /// Presets that can be used to help
28 | /// configure how an image is encoded to WebP
29 | public enum WebPEncodePreset: UInt32 {
30 | case `default`=0 // The default preset
31 | case picture // Digital pictures, like portraits, or inner shots
32 | case photo // Outdoor photographs, with natural lighting
33 | case drawing // Hand or line drawing, with high-contrast details
34 | case icon // Small-sized colorful images
35 | case text // Text based images
36 | }
37 |
38 | /// The list of possible errors that can occur
39 | /// when trying to encode to WebP
40 | public enum WebPEncodingError: UInt32, Error {
41 | case outOfMemory=0
42 | case bitstreamOutOfMemory
43 | case nullParameter
44 | case invalidConfiguration
45 | case badDimension
46 | case partition0Overflow
47 | case partitionOverflow
48 | case badWrite
49 | case fileTooBig
50 | case userAbort
51 |
52 | // CGImage encode errors
53 | case initConfigFailed=100
54 | case initPictureFailed
55 | case configPresetFailed
56 | case imageRenderFailed
57 | case invalidPictureData
58 | case nilImage
59 | }
60 |
61 | #if canImport(WebP)
62 | import WebP
63 | #elseif canImport(libwebp)
64 | import libwebp
65 | #else
66 | #error("libwebp couldn't be found")
67 | #endif
68 |
69 | /// Extends CGImage with the ability
70 | /// to write WebP images
71 | extension CGImage {
72 |
73 | /// Returns the image as a lossy WebP file.
74 | /// - Parameters:
75 | /// - preset: A preset that helps configure the encoder for the type of picture.
76 | /// - quality: The encoding quality of the picture. (Between 0-100)
77 | /// - Returns: An encoded WebP image file
78 | public func webpData(preset: WebPEncodePreset = .default, quality: Float = 100) throws -> Data {
79 |
80 | // Create and initialize an encoding config
81 | var config = WebPConfig()
82 | if WebPConfigInit(&config) == 0 {
83 | throw WebPEncodingError.initConfigFailed
84 | }
85 |
86 | // Set up the config with a preset and target quality
87 | if WebPConfigPreset(&config, WebPPreset(rawValue: preset.rawValue), quality) == 0 {
88 | throw WebPEncodingError.invalidConfiguration
89 | }
90 |
91 | // Compress the image
92 | return try webpData(config: &config)
93 | }
94 |
95 | /// Returns the image as a lossless WebP file.
96 | /// - Parameters:
97 | /// - losslessLevel: The desired efficiency level of the image encoding.
98 | /// Between 0 (fastest, lowest compression) and 9 (slower, best compression).
99 | /// A good default level is '6', providing a fair tradeoff between compression
100 | /// speed and final compressed size.
101 | /// - Returns: An encoded WebP image file
102 | public func webpLosslessData(level: Int = 6) throws -> Data {
103 |
104 | // Create and initialize an encoding config
105 | var config = WebPConfig()
106 | if WebPConfigInit(&config) == 0 {
107 | throw WebPEncodingError.initConfigFailed
108 | }
109 |
110 | // Set up the config as a lossless image with the provided level
111 | if WebPConfigLosslessPreset(&config, Int32(level)) == 0 {
112 | throw WebPEncodingError.initConfigFailed
113 | }
114 |
115 | // Compress the image
116 | return try webpData(config: &config)
117 | }
118 |
119 | /// Returns the image as a WebP file, as described in the provided config object.
120 | /// - Parameter config: A properly configured WebPConfig object to control the encoding.
121 | /// - Returns: An encoded WebP image file
122 | private func webpData(config: inout WebPConfig) throws -> Data {
123 |
124 | // Verify the configuration isn't invalid
125 | if WebPValidateConfig(&config) == 0 { throw WebPEncodingError.invalidConfiguration }
126 |
127 | // Create a picture object to hold the image data
128 | var picture = WebPPicture()
129 | if WebPPictureInit(&picture) == 0 { throw WebPEncodingError.initPictureFailed }
130 | defer { WebPPictureFree(&picture) }
131 |
132 | // Configure the picture with this image size
133 | picture.width = Int32(width)
134 | picture.height = Int32(height)
135 |
136 | // Try to use WebP's capabilities to import our pixel data
137 | // into the picture struct.
138 | if !importPixelData(into: &picture) {
139 | // If the data was in a color format WebP can't convert as-is,
140 | // perform our own color conversion and then import the converted data.
141 | if !importConvertedPixelData(into: &picture) {
142 | throw WebPEncodingError.invalidPictureData
143 | }
144 | }
145 |
146 | // Create a memory writer as a destination for the encoded data
147 | var writer = WebPMemoryWriter()
148 | WebPMemoryWriterInit(&writer)
149 | picture.writer = WebPMemoryWrite
150 | withUnsafePointer(to: &writer) { ptr in
151 | picture.custom_ptr = UnsafeMutableRawPointer(mutating: ptr)
152 | }
153 |
154 | // Perform the conversion to WebP
155 | if WebPEncode(&config, &picture) == 0 {
156 | throw WebPEncodingError(rawValue: picture.error_code.rawValue) ??
157 | WebPEncodingError.imageRenderFailed
158 | }
159 |
160 | // Wrap the data in a Data object that will be in
161 | // charge of freeing the data when done.
162 | return Data(bytesNoCopy: writer.mem,
163 | count: writer.size,
164 | deallocator: .free)
165 | }
166 | }
167 |
168 | // MARK: Private Members
169 |
170 | private extension CGImage {
171 |
172 | // If this image is already in a compatible pixel format,
173 | // import it into the provided WebPPicture struct using libwebp's
174 | // color space conversion capabilities.
175 | func importPixelData(into picture: inout WebPPicture) -> Bool {
176 | // WebP can only deal with combinations of RGB color values
177 | guard colorSpace?.numberOfComponents ?? 0 == 3 else { return false }
178 |
179 | // Check to see if this image is in a compatible pixel format before we
180 | // do a potentially heavy copy operation.
181 | let availablePixelFormats: [PixelFormat] = [.rgb, .rgba, .rgbx, .bgra, .bgrx]
182 | guard let pixelFormat = pixelFormat,
183 | availablePixelFormats.contains(pixelFormat) else { return false }
184 |
185 | // Now we've confirmed it's in a format we can support, fetch the data
186 | guard let data = dataProvider?.data else { return false }
187 | let bytePointer = CFDataGetBytePtr(data)
188 | let stride = Int32(bytesPerRow)
189 |
190 | // Perform the import operation
191 | if pixelFormat == .rgb {
192 | return WebPPictureImportRGB(&picture, bytePointer, stride) != 0
193 | } else if pixelFormat == .rgba {
194 | return WebPPictureImportRGBA(&picture, bytePointer, stride) != 0
195 | } else if pixelFormat == .rgbx {
196 | return WebPPictureImportRGBX(&picture, bytePointer, stride) != 0
197 | } else if pixelFormat == .bgra {
198 | return WebPPictureImportBGRA(&picture, bytePointer, stride) != 0
199 | } else if pixelFormat == .bgrx {
200 | return WebPPictureImportBGRX(&picture, bytePointer, stride) != 0
201 | }
202 |
203 | // Default to false if it failed
204 | return false
205 | }
206 |
207 | // If the image was in an unsupported format that WebP can't presently work with,
208 | // perform a color conversion in Core Graphics, and then import the converted data.
209 | func importConvertedPixelData(into picture: inout WebPPicture) -> Bool {
210 | // Capture whether we need to allocate for an alpha channel
211 | let hasAlpha = !(alphaInfo == .none || alphaInfo == .noneSkipFirst || alphaInfo == .noneSkipLast)
212 |
213 | // Configure the buffer settings
214 | let colorSpace = CGColorSpaceCreateDeviceRGB()
215 | let bytesPerPixel = 4
216 | let bytesPerRow = width * bytesPerPixel
217 |
218 | // Allocate the pixel buffer
219 | let pixels = UnsafeMutablePointer.allocate(capacity: bytesPerRow * height)
220 | defer { pixels.deallocate() }
221 |
222 | // Configure the byte order, and include alpha if this image has it
223 | var bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Big.rawValue
224 | bitmapInfo |= hasAlpha ? CGImageAlphaInfo.premultipliedLast.rawValue : CGImageAlphaInfo.noneSkipLast.rawValue
225 |
226 | // Create the context
227 | guard let context = CGContext(data: pixels,
228 | width: width,
229 | height: height,
230 | bitsPerComponent: 8,
231 | bytesPerRow: bytesPerRow,
232 | space: colorSpace,
233 | bitmapInfo: bitmapInfo) else { return false }
234 |
235 | // Draw the image into this new context
236 | context.draw(self, in: CGRect(origin: .zero, size: CGSize(width: width, height: height)))
237 |
238 | // Import the image into the WebP Picture struct
239 | if hasAlpha {
240 | return WebPPictureImportRGBA(&picture, pixels, Int32(bytesPerRow)) != 0
241 | } else {
242 | return WebPPictureImportRGBX(&picture, pixels, Int32(bytesPerRow)) != 0
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/WebPKit/Encoding/NSImage+WebPEncoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSImage+WebPEncoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 |
25 | #if canImport(AppKit)
26 | import AppKit
27 |
28 | /// Extends NSImage with the ability to write WebP images
29 | public extension NSImage {
30 |
31 | /// Returns the image as a lossy WebP file.
32 | /// - Parameters:
33 | /// - preset: A preset that helps configure the encoder for the type of picture.
34 | /// - quality: The encoding quality of the picture. (Between 0-100)
35 | /// - Returns: An encoded WebP image file
36 | func webpData(preset: WebPEncodePreset = .default, quality: Float = 100) throws -> Data {
37 | guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else {
38 | throw WebPEncodingError.nilImage
39 | }
40 | return try cgImage.webpData(preset: preset, quality: quality)
41 | }
42 |
43 | /// Returns the image as a lossless WebP file.
44 | /// - Parameters:
45 | /// - losslessLevel: The desired efficiency level of the image encoding.
46 | /// Between 0 (fastest, lowest compression) and 9 (slower, best compression).
47 | /// A good default level is '6', providing a fair tradeoff between compression
48 | /// speed and final compressed size.
49 | /// - Returns: An encoded WebP image file
50 | func webpLosslessData(level: Int = 6) throws -> Data {
51 | guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else {
52 | throw WebPEncodingError.nilImage
53 | }
54 | return try cgImage.webpLosslessData(level: level)
55 | }
56 | }
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/WebPKit/Encoding/UIImage+WebPEncoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+WebPEncoding.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import Foundation
24 |
25 | #if canImport(UIKit)
26 | import UIKit
27 |
28 | /// Extends UIImage with the ability to write WebP images
29 | public extension UIImage {
30 |
31 | /// Returns the image as a lossy WebP file.
32 | /// - Parameters:
33 | /// - preset: A preset that helps configure the encoder for the type of picture.
34 | /// - quality: The encoding quality of the picture. (Between 0-100)
35 | /// - Returns: An encoded WebP image file
36 | func webpData(preset: WebPEncodePreset = .default, quality: Float = 100) throws -> Data {
37 | guard let cgImage = cgImage else { throw WebPEncodingError.nilImage }
38 | return try cgImage.webpData(preset: preset, quality: quality)
39 | }
40 |
41 | /// Returns the image as a lossless WebP file.
42 | /// - Parameters:
43 | /// - losslessLevel: The desired efficiency level of the image encoding.
44 | /// Between 0 (fastest, lowest compression) and 9 (slower, best compression).
45 | /// A good default level is '6', providing a fair tradeoff between compression
46 | /// speed and final compressed size.
47 | /// - Returns: An encoded WebP image file
48 | func webpLosslessData(level: Int = 6) throws -> Data {
49 | guard let cgImage = cgImage else { throw WebPEncodingError.nilImage }
50 | return try cgImage.webpLosslessData(level: level)
51 | }
52 | }
53 |
54 | #endif
55 |
--------------------------------------------------------------------------------
/WebPKit/Encoding/Utilities/CGImage+PixelFormat.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGImage+PixelFormat.swift
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | import CoreFoundation
24 | import CoreGraphics
25 |
26 | extension CGImage {
27 |
28 | /// The different types of color pixel combinations
29 | /// that this image may be composed by
30 | public enum PixelFormat {
31 | case grayscale // Grayscale
32 | case grayscaleAlpha // Grayscale with alpha
33 | case rgb // Red, Green, Blue
34 | case bgr // Blue, Green, Red
35 | case abgr // Alpha, Blue, Green, Red
36 | case argb // Alpha, Red, Green, Blue
37 | case rgba // Red, Green, Blue, Alpha
38 | case rgbx // Red, Green, Blue, Alpha skipped
39 | case bgra // Blue, Green, Red, Alpha
40 | case bgrx // Blue, Green, Red, Alpha skipped
41 | }
42 |
43 | /// Returns the pixel format for this image
44 | public var pixelFormat: PixelFormat? {
45 |
46 | // See how many colors is encoded in this image
47 | guard let numberOfColorComponents = colorSpace?.numberOfComponents else { return nil }
48 |
49 | // HasAlpha - if it has any usable alpha data (eg, skipped alpha channels don't count)
50 | let hasAlpha: Bool = !(alphaInfo == .none || alphaInfo == .noneSkipFirst || alphaInfo == .noneSkipLast)
51 |
52 | // AlphaFirst – the alpha channel is next to the red channel, argb and bgra are both alpha first formats.
53 | let alphaFirst: Bool = alphaInfo == .premultipliedFirst || alphaInfo == .first || alphaInfo == .noneSkipFirst
54 |
55 | // AlphaLast – the alpha channel is next to the blue channel, rgba and abgr are both alpha last formats.
56 | let alphaLast: Bool = alphaInfo == .premultipliedLast || alphaInfo == .last || alphaInfo == .noneSkipLast
57 |
58 | // LittleEndian – blue comes before red, bgra and abgr are little endian formats.
59 | // Little endian ordered pixels are BGR (BGRX, XBGR, BGRA, ABGR, BGR).
60 | // BigEndian – red comes before blue, argb and rgba are big endian formats.
61 | // Big endian ordered pixels are RGB (XRGB, RGBX, ARGB, RGBA, RGB).
62 | let endianLittle: Bool = bitmapInfo.contains(.byteOrder32Little)
63 |
64 | // Check if this image only has 1 component (Either grayscale or alpha)
65 | if numberOfColorComponents == 1 { return hasAlpha ? .grayscaleAlpha : .grayscale }
66 |
67 | // Anything other than 3 colors is unsupported at this time
68 | if numberOfColorComponents != 3 { return nil }
69 |
70 | // Determine the pixel format of this image, with alpha
71 | if alphaFirst && endianLittle {
72 | return hasAlpha ? .bgra : .bgrx
73 | } else if alphaFirst {
74 | return .argb
75 | } else if alphaLast && endianLittle {
76 | return .abgr
77 | } else if alphaLast {
78 | return hasAlpha ? .rgba : .rgbx
79 | } else if hasAlpha == false {
80 | return endianLittle ? .bgr : .rgb
81 | }
82 |
83 | // The format isn't recognized
84 | return nil
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/WebPKit/Supporting/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | WebPKit
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/WebPKit/Supporting/WebPKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // WebPKit.h
3 | //
4 | // Copyright 2020-2022 Timothy Oliver. All rights reserved.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to
8 | // deal in the Software without restriction, including without limitation the
9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | // sell copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | #import
24 |
25 | FOUNDATION_EXPORT double WebPKitVersionNumber;
26 | FOUNDATION_EXPORT const unsigned char WebPKitVersionString[];
27 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/.gitignore:
--------------------------------------------------------------------------------
1 | WebP.xcframework
2 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Resources/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WebPKitExample-iOS
4 | //
5 | // Created by Tim Oliver on 12/10/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | public var window: UIWindow?
14 |
15 | func application(_ application: UIApplication,
16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Supporting/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSupportsIndirectInputEvents
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/Supporting/WebPKitExample-iOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // WebPKitExample-iOS
4 | //
5 | // Created by Tim Oliver on 12/10/20.
6 | //
7 |
8 | import UIKit
9 | import WebPKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var imageView: UIImageView!
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | // Locate the file on disk
19 | guard let url = Bundle.main.url(forResource: "WebPKitLogo",
20 | withExtension: "webp") else { return }
21 |
22 | // Work out the smallest dimension of this window so we can scale to it
23 | let scale = UIScreen.main.scale
24 | let width = min(view.frame.width * scale, view.frame.height * scale)
25 |
26 | // Decode a copy of the image scaled to the size of the screen
27 | imageView.image = UIImage(contentsOfWebPFile: url, width: width)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/WebP.xcframework/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | */
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/WebPKitExample-iOS/WebPKitLogo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitExample-iOS/WebPKitLogo.webp
--------------------------------------------------------------------------------
/WebPKitExample-iOS/WebPKitLogoOpaque.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitExample-iOS/WebPKitLogoOpaque.webp
--------------------------------------------------------------------------------
/WebPKitExample-macOS/.gitignore:
--------------------------------------------------------------------------------
1 | WebP.xcframework
2 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // WebPKitExample-macOS
4 | //
5 | // Created by Tim Oliver on 16/10/20.
6 | //
7 |
8 | import SwiftUI
9 | import WebPKit
10 |
11 | struct ContentView: View {
12 | var webpLogo: NSImage {
13 | return NSImage.webpNamed("WebPKitLogo")!
14 | }
15 |
16 | var body: some View {
17 | Image(nsImage: webpLogo)
18 | .resizable()
19 | .scaledToFit()
20 | .padding()
21 | }
22 | }
23 |
24 | struct ContentView_Previews: PreviewProvider {
25 | static var previews: some View {
26 | ContentView()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WebPKitExample-macOS
4 | //
5 | // Created by Tim Oliver on 16/10/20.
6 | //
7 |
8 | import Cocoa
9 | import SwiftUI
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | var window: NSWindow!
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Create the SwiftUI view that provides the window contents.
18 | let contentView = ContentView()
19 |
20 | // Create the window and set the content view.
21 | window = NSWindow(
22 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
23 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
24 | backing: .buffered, defer: false)
25 | window.isReleasedWhenClosed = false
26 | window.center()
27 | window.setFrameAutosaveName("Main Window")
28 | window.contentView = NSHostingView(rootView: contentView)
29 | window.makeKeyAndOrderFront(nil)
30 | }
31 |
32 | func applicationWillTerminate(_ aNotification: Notification) {
33 | // Insert code here to tear down your application
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/Supporting/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSMainStoryboardFile
26 | Main
27 | NSPrincipalClass
28 | NSApplication
29 |
30 |
31 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/Supporting/WebPKitExample_macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/WebPKitExample-macOS/WebP.xcframework/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | */
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/.gitignore:
--------------------------------------------------------------------------------
1 | WebP.xcframework
2 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Resources/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WebPKitExample-tvOS
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication,
16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/Supporting/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | arm64
30 |
31 | UIUserInterfaceStyle
32 | Automatic
33 |
34 |
35 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // WebPKitExample-tvOS
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import UIKit
9 | import WebPKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var backgroundView: UIView!
14 | @IBOutlet weak var imageView: UIImageView!
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 |
19 | // Decode and set a WebP image to
20 | // the image view in this view controller
21 | imageView.image = UIImage.webpNamed("WebPKitLogo")
22 |
23 | // As this is a large image, enable trilinear filtering
24 | // to allow better re-sampling at smaller screen sizes
25 | imageView.layer.minificationFilter = .trilinear
26 | }
27 |
28 | override func viewDidLayoutSubviews() {
29 | super.viewDidLayoutSubviews()
30 |
31 | // Set the rounding of the background view to
32 | backgroundView.layer.cornerRadius = view.bounds.height * 0.2
33 |
34 | // Set the rounding to be squircular
35 | if #available(tvOS 13.0, *) {
36 | backgroundView.layer.cornerCurve = .continuous
37 | }
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/WebPKitExample-tvOS/WebP.xcframework/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | */
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/.gitignore:
--------------------------------------------------------------------------------
1 | WebP.xcframework
2 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/App/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "role" : "notificationCenter",
6 | "scale" : "2x",
7 | "size" : "24x24",
8 | "subtype" : "38mm"
9 | },
10 | {
11 | "idiom" : "watch",
12 | "role" : "notificationCenter",
13 | "scale" : "2x",
14 | "size" : "27.5x27.5",
15 | "subtype" : "42mm"
16 | },
17 | {
18 | "idiom" : "watch",
19 | "role" : "companionSettings",
20 | "scale" : "2x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "watch",
25 | "role" : "companionSettings",
26 | "scale" : "3x",
27 | "size" : "29x29"
28 | },
29 | {
30 | "idiom" : "watch",
31 | "role" : "notificationCenter",
32 | "scale" : "2x",
33 | "size" : "33x33",
34 | "subtype" : "45mm"
35 | },
36 | {
37 | "idiom" : "watch",
38 | "role" : "appLauncher",
39 | "scale" : "2x",
40 | "size" : "40x40",
41 | "subtype" : "38mm"
42 | },
43 | {
44 | "idiom" : "watch",
45 | "role" : "appLauncher",
46 | "scale" : "2x",
47 | "size" : "44x44",
48 | "subtype" : "40mm"
49 | },
50 | {
51 | "idiom" : "watch",
52 | "role" : "appLauncher",
53 | "scale" : "2x",
54 | "size" : "46x46",
55 | "subtype" : "41mm"
56 | },
57 | {
58 | "idiom" : "watch",
59 | "role" : "appLauncher",
60 | "scale" : "2x",
61 | "size" : "50x50",
62 | "subtype" : "44mm"
63 | },
64 | {
65 | "idiom" : "watch",
66 | "role" : "appLauncher",
67 | "scale" : "2x",
68 | "size" : "51x51",
69 | "subtype" : "45mm"
70 | },
71 | {
72 | "idiom" : "watch",
73 | "role" : "quickLook",
74 | "scale" : "2x",
75 | "size" : "86x86",
76 | "subtype" : "38mm"
77 | },
78 | {
79 | "idiom" : "watch",
80 | "role" : "quickLook",
81 | "scale" : "2x",
82 | "size" : "98x98",
83 | "subtype" : "42mm"
84 | },
85 | {
86 | "idiom" : "watch",
87 | "role" : "quickLook",
88 | "scale" : "2x",
89 | "size" : "108x108",
90 | "subtype" : "44mm"
91 | },
92 | {
93 | "idiom" : "watch",
94 | "role" : "quickLook",
95 | "scale" : "2x",
96 | "size" : "117x117",
97 | "subtype" : "45mm"
98 | },
99 | {
100 | "idiom" : "watch-marketing",
101 | "scale" : "1x",
102 | "size" : "1024x1024"
103 | }
104 | ],
105 | "info" : {
106 | "author" : "xcode",
107 | "version" : 1
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/App/Base.lproj/Interface.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : "<=145"
11 | },
12 | {
13 | "idiom" : "watch",
14 | "scale" : "2x",
15 | "screen-width" : ">183"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "auto-scaling" : "auto"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "Circular.imageset",
5 | "idiom" : "watch",
6 | "role" : "circular"
7 | },
8 | {
9 | "filename" : "Extra Large.imageset",
10 | "idiom" : "watch",
11 | "role" : "extra-large"
12 | },
13 | {
14 | "filename" : "Graphic Bezel.imageset",
15 | "idiom" : "watch",
16 | "role" : "graphic-bezel"
17 | },
18 | {
19 | "filename" : "Graphic Circular.imageset",
20 | "idiom" : "watch",
21 | "role" : "graphic-circular"
22 | },
23 | {
24 | "filename" : "Graphic Corner.imageset",
25 | "idiom" : "watch",
26 | "role" : "graphic-corner"
27 | },
28 | {
29 | "filename" : "Graphic Extra Large.imageset",
30 | "idiom" : "watch",
31 | "role" : "graphic-extra-large"
32 | },
33 | {
34 | "filename" : "Graphic Large Rectangular.imageset",
35 | "idiom" : "watch",
36 | "role" : "graphic-large-rectangular"
37 | },
38 | {
39 | "filename" : "Modular.imageset",
40 | "idiom" : "watch",
41 | "role" : "modular"
42 | },
43 | {
44 | "filename" : "Utilitarian.imageset",
45 | "idiom" : "watch",
46 | "role" : "utilitarian"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : "<=145"
11 | },
12 | {
13 | "idiom" : "watch",
14 | "scale" : "2x",
15 | "screen-width" : ">183"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "auto-scaling" : "auto"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : ">183"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | },
17 | "properties" : {
18 | "auto-scaling" : "auto"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : ">183"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | },
17 | "properties" : {
18 | "auto-scaling" : "auto"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : ">183"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | },
17 | "properties" : {
18 | "auto-scaling" : "auto"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : "<=145"
11 | },
12 | {
13 | "idiom" : "watch",
14 | "scale" : "2x",
15 | "screen-width" : ">183"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "auto-scaling" : "auto"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : ">183"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | },
17 | "properties" : {
18 | "auto-scaling" : "auto"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : "<=145"
11 | },
12 | {
13 | "idiom" : "watch",
14 | "scale" : "2x",
15 | "screen-width" : ">183"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "auto-scaling" : "auto"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x"
6 | },
7 | {
8 | "idiom" : "watch",
9 | "scale" : "2x",
10 | "screen-width" : "<=145"
11 | },
12 | {
13 | "idiom" : "watch",
14 | "scale" : "2x",
15 | "screen-width" : ">183"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "auto-scaling" : "auto"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/ComplicationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComplicationController.swift
3 | // WebPKitExample-watchOS WatchKit Extension
4 | //
5 | // Created by Tim Oliver on 20/4/2022.
6 | //
7 |
8 | import ClockKit
9 |
10 | class ComplicationController: NSObject, CLKComplicationDataSource {
11 |
12 | // MARK: - Complication Configuration
13 |
14 | func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
15 | let descriptors = [
16 | CLKComplicationDescriptor(identifier: "complication",
17 | displayName: "WebPKit",
18 | supportedFamilies: CLKComplicationFamily.allCases)
19 | // Multiple complication support can be added here with more descriptors
20 | ]
21 |
22 | // Call the handler with the currently supported complication descriptors
23 | handler(descriptors)
24 | }
25 |
26 | func handleSharedComplicationDescriptors(_ complicationDescriptors: [CLKComplicationDescriptor]) {
27 | // Do any necessary work to support these newly shared complication descriptors
28 | }
29 |
30 | // MARK: - Timeline Configuration
31 |
32 | func getTimelineEndDate(for complication: CLKComplication,
33 | withHandler handler: @escaping (Date?) -> Void) {
34 | // Call the handler with the last entry date you can currently provide
35 | // or nil if you can't support future timelines
36 | handler(nil)
37 | }
38 |
39 | func getPrivacyBehavior(for complication: CLKComplication,
40 | withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
41 | // Call the handler with your desired behavior when the device is locked
42 | handler(.showOnLockScreen)
43 | }
44 |
45 | // MARK: - Timeline Population
46 |
47 | func getCurrentTimelineEntry(for complication: CLKComplication,
48 | withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
49 | // Call the handler with the current timeline entry
50 | handler(nil)
51 | }
52 |
53 | func getTimelineEntries(for complication: CLKComplication,
54 | after date: Date,
55 | limit: Int,
56 | withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
57 | // Call the handler with the timeline entries after the given date
58 | handler(nil)
59 | }
60 |
61 | // MARK: - Sample Templates
62 |
63 | func getLocalizableSampleTemplate(for complication: CLKComplication,
64 | withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
65 | // This method will be called once per supported complication, and the results will be cached
66 | handler(nil)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/ExtensionDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionDelegate.swift
3 | // WebPKitExample-watchOS WatchKit Extension
4 | //
5 | // Created by Tim Oliver on 20/4/2022.
6 | //
7 |
8 | import WatchKit
9 |
10 | class ExtensionDelegate: NSObject, WKExtensionDelegate {
11 |
12 | func handle(_ backgroundTasks: Set) {
13 | // Sent when the system needs to launch the application in the background to process tasks.
14 | // Tasks arrive in a set, so loop through and process each one.
15 | for task in backgroundTasks {
16 | // Use a switch statement to check the task type
17 | switch task {
18 | case let backgroundTask as WKApplicationRefreshBackgroundTask:
19 | // Be sure to complete the background task once you’re done.
20 | backgroundTask.setTaskCompletedWithSnapshot(false)
21 | case let snapshotTask as WKSnapshotRefreshBackgroundTask:
22 | // Snapshot tasks have a unique completion call, make sure to set your expiration date
23 | snapshotTask.setTaskCompleted(restoredDefaultState: true,
24 | estimatedSnapshotExpiration: Date.distantFuture,
25 | userInfo: nil)
26 | case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
27 | // Be sure to complete the connectivity task once you’re done.
28 | connectivityTask.setTaskCompletedWithSnapshot(false)
29 | case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
30 | // Be sure to complete the URL session task once you’re done.
31 | urlSessionTask.setTaskCompletedWithSnapshot(false)
32 | case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
33 | // Be sure to complete the relevant-shortcut task once you're done.
34 | relevantShortcutTask.setTaskCompletedWithSnapshot(false)
35 | case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
36 | // Be sure to complete the intent-did-run task once you're done.
37 | intentDidRunTask.setTaskCompletedWithSnapshot(false)
38 | default:
39 | // make sure to complete unhandled task types
40 | task.setTaskCompletedWithSnapshot(false)
41 | }
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionAttributes
8 |
9 | WKAppBundleIdentifier
10 | dev.tim.WebPKitExample-watchOS.watchkitapp
11 |
12 | NSExtensionPointIdentifier
13 | com.apple.watchkit
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/Extension/InterfaceController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InterfaceController.swift
3 | // WebPKitExample-watchOS WatchKit Extension
4 | //
5 | // Created by Tim Oliver on 20/4/2022.
6 | //
7 |
8 | import WatchKit
9 | import Foundation
10 | import UIKit
11 | import WebPKit
12 |
13 | class InterfaceController: WKInterfaceController {
14 |
15 | @IBOutlet weak var interfaceImage: WKInterfaceImage!
16 |
17 | override func awake(withContext context: Any?) {
18 | // Locate the file on disk
19 | guard let url = Bundle.main.url(forResource: "WebPKitLogo",
20 | withExtension: "webp") else { return }
21 |
22 | // Decode a copy of the image scaled to the size of the screen
23 | let scale = WKInterfaceDevice.current().screenScale
24 | let webpImage = UIImage(contentsOfWebPFile: url,
25 | width: contentFrame.width * scale)
26 | interfaceImage.setImage(webpImage)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WebPKitExample-watchOS/WebP.xcframework/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | */
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/WebPKitTests/Decoding/WebPDecodingCGImageTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPDecodingImageTests.swift
3 | // WebPKitTests
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import XCTest
9 |
10 | class WebPDecodingCGImageTests: WebPDecodingTests {
11 |
12 | // Test retrieving the size of the images
13 | func testDecodingImageSizes() {
14 | let losslessSize = CGImage.sizeOfWebP(at: losslessWebPFileURL)
15 | XCTAssertNotNil(losslessSize)
16 | XCTAssert(losslessSize!.width > 0 && losslessSize!.height > 0)
17 |
18 | let lossySize = CGImage.sizeOfWebP(at: lossyWebPFileURL)
19 | XCTAssertNotNil(lossySize)
20 | XCTAssert(lossySize!.width > 0 && lossySize!.height > 0)
21 | }
22 |
23 | // Test checking the file's path extension
24 | func testDecodingImages() throws {
25 | _ = try CGImage.webpImage(contentsOfFile: losslessWebPFileURL)
26 | _ = try CGImage.webpImage(contentsOfFile: lossyWebPFileURL)
27 | }
28 |
29 | // Test decoding the image with aspect fit
30 | func testDecodingImagesAspectFitSizing() throws {
31 | // Test aspect horizontal scaling by width alone
32 | let firstImage = try CGImage.webpImage(contentsOfFile: horizontalRectWebPFileURL, width: 64)
33 | XCTAssertEqual(firstImage.width, 64)
34 | XCTAssertEqual(firstImage.height, 32)
35 |
36 | // Test aspect horizontal scaling by height alone
37 | let secondImage = try CGImage.webpImage(contentsOfFile: horizontalRectWebPFileURL, height: 32)
38 | XCTAssertEqual(secondImage.width, 64)
39 | XCTAssertEqual(secondImage.height, 32)
40 |
41 | // Test aspect vertical scaling by width alone
42 | let thirdImage = try CGImage.webpImage(contentsOfFile: verticalRectWebPFileURL, width: 32)
43 | XCTAssertEqual(thirdImage.width, 32)
44 | XCTAssertEqual(thirdImage.height, 64)
45 |
46 | // Test aspect vertical scaling by height alone
47 | let fourthImage = try CGImage.webpImage(contentsOfFile: verticalRectWebPFileURL, height: 64)
48 | XCTAssertEqual(fourthImage.width, 32)
49 | XCTAssertEqual(fourthImage.height, 64)
50 |
51 | // Test horizontal with 2 values
52 | let fifthImage = try CGImage.webpImage(contentsOfFile: horizontalRectWebPFileURL, width: 75, height: 32)
53 | XCTAssertEqual(fifthImage.height, 32)
54 | XCTAssertEqual(fifthImage.width, 64)
55 |
56 | // Test vertical with 2 values
57 | let sixthImage = try CGImage.webpImage(contentsOfFile: verticalRectWebPFileURL, width: 75, height: 32)
58 | XCTAssertEqual(sixthImage.height, 32)
59 | XCTAssertEqual(sixthImage.width, 16)
60 | }
61 |
62 | // Test decoding the image with aspect fit
63 | func testDecodingImagesAspectFillSizing() throws {
64 | // Test aspect horizontal scaling by width alone
65 | let firstImage = try CGImage.webpImage(contentsOfFile: horizontalRectWebPFileURL,
66 | width: 64, scalingMode: .aspectFill)
67 | XCTAssertEqual(firstImage.width, 64)
68 | XCTAssertEqual(firstImage.height, 32)
69 |
70 | // Test aspect horizontal scaling by height alone
71 | let secondImage = try CGImage.webpImage(contentsOfFile: horizontalRectWebPFileURL,
72 | height: 32, scalingMode: .aspectFill)
73 | XCTAssertEqual(secondImage.width, 64)
74 | XCTAssertEqual(secondImage.height, 32)
75 |
76 | // Test aspect vertical scaling by width alone
77 | let thirdImage = try CGImage.webpImage(contentsOfFile: verticalRectWebPFileURL,
78 | width: 32, scalingMode: .aspectFill)
79 | XCTAssertEqual(thirdImage.width, 32)
80 | XCTAssertEqual(thirdImage.height, 64)
81 |
82 | // Test aspect vertical scaling by height alone
83 | let fourthImage = try CGImage.webpImage(contentsOfFile: verticalRectWebPFileURL,
84 | height: 64, scalingMode: .aspectFill)
85 | XCTAssertEqual(fourthImage.width, 32)
86 | XCTAssertEqual(fourthImage.height, 64)
87 |
88 | // Test horizontal with 2 values
89 | let fifthImage = try CGImage.webpImage(contentsOfFile: horizontalRectWebPFileURL,
90 | width: 75, height: 32, scalingMode: .aspectFill)
91 | XCTAssertEqual(fifthImage.height, 37)
92 | XCTAssertEqual(fifthImage.width, 75)
93 |
94 | // Test vertical with 2 values
95 | let sixthImage = try CGImage.webpImage(contentsOfFile: verticalRectWebPFileURL,
96 | width: 64, height: 32, scalingMode: .aspectFill)
97 | XCTAssertEqual(sixthImage.height, 128)
98 | XCTAssertEqual(sixthImage.width, 64)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/WebPKitTests/Decoding/WebPDecodingDataTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPDecodingDataTests.swift
3 | // WebPKit
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import XCTest
9 |
10 | class WebPDecodingDataTests: WebPDecodingTests {
11 |
12 | // Test checking the file's path extension
13 | func testDataContentsCheck() {
14 | XCTAssertTrue(losslessWebPFileData.isWebP)
15 | XCTAssertTrue(lossyWebPFileData.isWebP)
16 | }
17 |
18 | // Test an incorrect data stream
19 | func testInvalidData() {
20 | let data = "InvalidData".data(using: .ascii)!
21 | XCTAssertFalse(data.isWebP)
22 | }
23 |
24 | // Test an incorrect data stream which is shorter than the WebP magic
25 | func testTooShortData() {
26 | let data = "Tom".data(using: .ascii)!
27 | XCTAssertFalse(data.isWebP)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WebPKitTests/Decoding/WebPDecodingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPDecodingTests.swift
3 | // WebPKit
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import XCTest
9 |
10 | public class WebPDecodingTests: XCTestCase {
11 |
12 | /// The bundle hosting these tests
13 | public lazy var testBundle: Bundle = {
14 | return Bundle(for: type(of: self))
15 | }()
16 |
17 | /// A file URL to a lossless WebP test file
18 | public lazy var losslessWebPFileURL: URL = {
19 | return testBundle.url(forResource: "logo-lossless", withExtension: "webp")!
20 | }()
21 |
22 | /// A file URL to a lossless WebP test file
23 | public lazy var lossyWebPFileURL: URL = {
24 | return testBundle.url(forResource: "logo-lossy", withExtension: "WEBP")!
25 | }()
26 |
27 | /// A file URL to a lossless rectangle
28 | public lazy var horizontalRectWebPFileURL: URL = {
29 | return testBundle.url(forResource: "rect-horizontal", withExtension: "webp")!
30 | }()
31 |
32 | /// A file URL to a lossless rectangle
33 | public lazy var verticalRectWebPFileURL: URL = {
34 | return testBundle.url(forResource: "rect-vertical", withExtension: "webp")!
35 | }()
36 |
37 | /// A file URL to a lossless WebP test file
38 | public lazy var losslessWebPFileData: Data = {
39 | let url = testBundle.url(forResource: "logo-lossless", withExtension: "webp")!
40 | guard let data = try? Data(contentsOf: url, options: .alwaysMapped) else {
41 | fatalError("Unable to load image data from test bundle")
42 | }
43 | return data
44 | }()
45 |
46 | /// A file URL to a lossless WebP test file
47 | public lazy var lossyWebPFileData: Data = {
48 | let url = testBundle.url(forResource: "logo-lossy", withExtension: "WEBP")!
49 | guard let data = try? Data(contentsOf: url, options: .alwaysMapped) else {
50 | fatalError("Unable to load image data from test bundle")
51 | }
52 | return data
53 | }()
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/WebPKitTests/Decoding/WebPDecodingUIImageTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPDecodingUIImageTests.swift
3 | // WebPKit
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import XCTest
9 |
10 | #if canImport(UIKit)
11 | import UIKit
12 |
13 | class WebPDecodingUIImageTests: WebPDecodingTests {
14 |
15 | // Test loading WebP files from disk
16 | func testLoadingUIImageFromFile() {
17 | XCTAssertNotNil(UIImage(contentsOfWebPFile: losslessWebPFileURL))
18 | XCTAssertNotNil(UIImage(contentsOfWebPFile: lossyWebPFileURL))
19 | }
20 |
21 | // Test loading WebP files from memory
22 | func testLoadingUIImageFromData() {
23 | XCTAssertNotNil(UIImage(webpData: losslessWebPFileData))
24 | XCTAssertNotNil(UIImage(webpData: lossyWebPFileData))
25 | }
26 |
27 | // Test loading WebP files from the resource bundle
28 | func testLoadingUIImageFromResourceBundle() {
29 | // Check lossy image
30 | let lossyImage = UIImage.webpNamed("logo-lossy.WEBP", bundle: testBundle)
31 | XCTAssertNotNil(lossyImage)
32 |
33 | // Compare to system WebP on iOS 14
34 | let lossyImageSystem = UIImage(named: "logo-lossy.WEBP", in: testBundle, with: nil)
35 | XCTAssertNotNil(lossyImageSystem)
36 |
37 | // Check both have a valid and equal size
38 | XCTAssertEqual(lossyImage!.size, lossyImageSystem!.size)
39 |
40 | // Check lossless image
41 | let losslessImage = UIImage.webpNamed("logo-lossless", bundle: testBundle)
42 | XCTAssertNotNil(losslessImage)
43 |
44 | let losslessImageSystem = UIImage(named: "logo-lossless.webp", in: testBundle, with: nil)
45 | XCTAssertNotNil(losslessImageSystem)
46 |
47 | XCTAssertEqual(losslessImage!.size, losslessImageSystem!.size)
48 | }
49 |
50 | // Test that images loaded multiple times point to the same instance
51 | func testLoadingUIImageFromResourceCache() {
52 | let firstImage = UIImage.webpNamed("logo-lossy.WEBP", bundle: testBundle)
53 | let secondImage = UIImage.webpNamed("logo-lossy.WEBP", bundle: testBundle)
54 | XCTAssertTrue(firstImage === secondImage)
55 |
56 | let thirdImage = UIImage.webpNamed("logo-lossless", bundle: testBundle)
57 | let fourthImage = UIImage.webpNamed("logo-lossless", bundle: testBundle)
58 | XCTAssertTrue(thirdImage === fourthImage)
59 | }
60 | }
61 |
62 | #endif
63 |
--------------------------------------------------------------------------------
/WebPKitTests/Decoding/WebPDecodingURLTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPKitDecodingTests.swift
3 | // WebPKitTests
4 | //
5 | // Created by Tim Oliver on 15/10/20.
6 | //
7 |
8 | import XCTest
9 |
10 | class WebPDecodingURLTests: WebPDecodingTests {
11 |
12 | // Test checking the file's path extension
13 | func testFileExtensionCheck() {
14 | XCTAssertTrue(losslessWebPFileURL.isWebP)
15 | XCTAssertTrue(lossyWebPFileURL.isWebP)
16 | }
17 |
18 | // Test checking the header of each file
19 | func testFileContentsCheck() {
20 | XCTAssertTrue(losslessWebPFileURL.isWebP(ignoringFileExtension: true))
21 | XCTAssertTrue(lossyWebPFileURL.isWebP(ignoringFileExtension: true))
22 | }
23 |
24 | // Test different types of invalid URLs
25 | func testInvalidURLValues() {
26 | XCTAssertFalse(URL(string: "~/image.webp")!.isWebP(ignoringFileExtension: true))
27 | XCTAssertFalse(URL(string: "~/image.jpeg")!.isWebP)
28 | XCTAssertFalse(URL(string: "http://google.com/image.jpg")!.isWebP)
29 | XCTAssertFalse(URL(string: "http://google.com/image.webp")!.isWebP(ignoringFileExtension: true))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/WebPKitTests/Encoding/WebPEncodingCGBitmapInfoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPEncodingCGBitmapInfoTests.swift
3 | // WebPKitTests
4 | //
5 | // Created by Tim Oliver on 20/10/20.
6 | //
7 |
8 | import XCTest
9 |
10 | class WebPEncodingCGBitmapInfoTests: WebPEncodingTests {
11 | func testRGBAImages() throws {
12 | XCTAssertEqual(grayscaleTransparentPNGImage.pixelFormat, .grayscaleAlpha)
13 | XCTAssertEqual(opaqueJPEGImage.pixelFormat, .rgbx)
14 | XCTAssertEqual(opaquePNGImage.pixelFormat, .rgbx)
15 | XCTAssertEqual(transparentPNGImage.pixelFormat, .rgba)
16 | XCTAssertEqual(indexedTransparentPNGImage.pixelFormat, .rgba)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/WebPKitTests/Encoding/WebPEncodingCGImageTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPEncodingCGImageTests.swift
3 | // WebPKitTests
4 | //
5 | // Created by Tim Oliver on 18/10/20.
6 | //
7 |
8 | import XCTest
9 | import CoreGraphics
10 |
11 | class WebPEncodingCGImageTests: WebPEncodingTests {
12 |
13 | func testEncodingLossyWebP() throws {
14 | var data = try opaqueJPEGImage.webpData()
15 | XCTAssertNotNil(data)
16 |
17 | data = try transparentPNGImage.webpData(preset: .text, quality: 50)
18 | XCTAssertNotNil(data)
19 | }
20 |
21 | func testEncodingLosslessWebP() throws {
22 | var data = try opaqueJPEGImage.webpLosslessData()
23 | XCTAssertNotNil(data)
24 |
25 | data = try grayscaleTransparentPNGImage.webpLosslessData(level: 1)
26 | XCTAssertNotNil(data)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WebPKitTests/Encoding/WebPEncodingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPEncodingTests.swift
3 | // WebPKitTests
4 | //
5 | // Created by Tim Oliver on 20/10/20.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 |
11 | class WebPEncodingTests: XCTestCase {
12 | /// The bundle hosting these tests
13 | public lazy var testBundle: Bundle = {
14 | return Bundle(for: type(of: self))
15 | }()
16 |
17 | /// Loads a transparent PNG as a CGImage
18 | public lazy var transparentPNGImage: CGImage = {
19 | return image(named: "circle-transparent", withExtension: "png")
20 | }()
21 |
22 | /// Loads a transparent PNG as a CGImage
23 | public lazy var grayscaleTransparentPNGImage: CGImage = {
24 | return image(named: "circle-grayscale-transparent", withExtension: "png")
25 | }()
26 |
27 | /// Loads a transparent indexed PNG as a CGImage
28 | public lazy var indexedTransparentPNGImage: CGImage = {
29 | return image(named: "circle-index-transparent", withExtension: "png")
30 | }()
31 |
32 | /// Loads a opaque PNG as a CGImage
33 | public lazy var opaquePNGImage: CGImage = {
34 | return image(named: "circle-opaque", withExtension: "png")
35 | }()
36 |
37 | /// Loads a opaque JPEG as a CGImage
38 | public lazy var opaqueJPEGImage: CGImage = {
39 | return image(named: "circle-opaque", withExtension: "jpg")
40 | }()
41 | }
42 |
43 | extension WebPEncodingTests {
44 |
45 | /// Return a CGImage from the image bundle
46 | private func image(named name: String, withExtension fileExtension: String) -> CGImage {
47 | let url = testBundle.url(forResource: name, withExtension: fileExtension)!
48 | guard let data = try? Data(contentsOf: url, options: .alwaysMapped) else {
49 | fatalError("Could not locate file from test bundle")
50 | }
51 | let dataProvider = CGDataProvider(data: data as CFData)!
52 | if fileExtension == "jpg" {
53 | return CGImage(jpegDataProviderSource: dataProvider, decode: nil,
54 | shouldInterpolate: true, intent: .defaultIntent)!
55 | }
56 | return CGImage(pngDataProviderSource: dataProvider, decode: nil,
57 | shouldInterpolate: true, intent: .defaultIntent)!
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/WebPKitTests/Encoding/WebPEncodingUIImageTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebPEncodingUIImageTests.swift
3 | // WebPKitTests
4 | //
5 | // Created by Tim Oliver on 31/10/20.
6 | //
7 |
8 | import XCTest
9 | import UIKit
10 |
11 | class WebPEncodingUIImageTests: WebPEncodingTests {
12 |
13 | func testEmptyImage() throws {
14 | if (try? UIImage().webpData()) != nil {
15 | XCTFail("Image should have been nil")
16 | }
17 | }
18 |
19 | func testLossyUIImageEncoding() throws {
20 | // Convert image to webp data
21 | let image = UIImage(cgImage: opaqueJPEGImage)
22 | let webPData = try image.webpData(preset: .default, quality: 100)
23 |
24 | // Convert back to UIImage
25 | let decodedImage = UIImage(data: webPData)
26 | XCTAssertNotNil(decodedImage)
27 |
28 | // Check sizes match
29 | XCTAssertEqual(image.size, decodedImage!.size)
30 | }
31 |
32 | func testLosslessUIImageEncoding() throws {
33 | // Convert image to webp data
34 | let image = UIImage(cgImage: grayscaleTransparentPNGImage)
35 | let webPData = try image.webpLosslessData(level: 6)
36 |
37 | // Convert back to UIImage
38 | let decodedImage = UIImage(data: webPData)
39 | XCTAssertNotNil(decodedImage)
40 |
41 | // Check sizes match
42 | XCTAssertEqual(image.size, decodedImage!.size)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/WebPKitTests/Resources/circle-grayscale-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/circle-grayscale-transparent.png
--------------------------------------------------------------------------------
/WebPKitTests/Resources/circle-index-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/circle-index-transparent.png
--------------------------------------------------------------------------------
/WebPKitTests/Resources/circle-opaque.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/circle-opaque.jpg
--------------------------------------------------------------------------------
/WebPKitTests/Resources/circle-opaque.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/circle-opaque.png
--------------------------------------------------------------------------------
/WebPKitTests/Resources/circle-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/circle-transparent.png
--------------------------------------------------------------------------------
/WebPKitTests/Resources/logo-lossless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/logo-lossless.webp
--------------------------------------------------------------------------------
/WebPKitTests/Resources/logo-lossy.WEBP:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/logo-lossy.WEBP
--------------------------------------------------------------------------------
/WebPKitTests/Resources/rect-horizontal.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/rect-horizontal.webp
--------------------------------------------------------------------------------
/WebPKitTests/Resources/rect-vertical.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/WebPKitTests/Resources/rect-vertical.webp
--------------------------------------------------------------------------------
/WebPKitTests/Supporting/HostApp/iOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WebPKitTestHost-iOS
4 | //
5 | // Created by Tim Oliver on 30/10/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 | func application(_ application: UIApplication,
13 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
14 | return true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WebPKitTests/Supporting/HostApp/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSupportsIndirectInputEvents
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/WebPKitTests/Supporting/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/download.sh:
--------------------------------------------------------------------------------
1 |
2 | # download.sh
3 |
4 | # Copyright 2020 Timothy Oliver. All rights reserved.
5 |
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to
8 | # deal in the Software without restriction, including without limitation the
9 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 | # sell copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 |
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 |
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21 | # IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | # Downloads and integrates precompiled versions of WebP.framework
24 | # from TimOliver/WebP-Cocoa before building any of the project targets.
25 |
26 | VERSION="v1.2.2"
27 | FOLDER="WebPKitExample-${1}"
28 | FRAMEWORK="WebP.xcframework"
29 | URL="https://github.com/TimOliver/WebP-Cocoa/releases/download/${VERSION}/libwebp-${VERSION}-framework-${2}-webp.zip"
30 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
31 |
32 | # Move to project folder
33 | cd ${SCRIPT_DIR}/${FOLDER}
34 |
35 | # If the folder is empty, download a copy of the framework and install
36 | if [ ! "$(ls $FRAMEWORK)" ]; then
37 | curl -L -sS ${URL} > framework.zip
38 | unzip framework.zip
39 | cp -a libwebp*/${FRAMEWORK}/. ${FRAMEWORK}/
40 | rm -r libwebp*/
41 | rm framework.zip
42 | fi
43 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TimOliver/WebPKit/1040b37a3d58bdccc853136cd5e9eb9b1117d03c/screenshot.png
--------------------------------------------------------------------------------