├── .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 | WebPKit running on various devices 4 | 5 | [![CI](https://github.com/TimOliver/WebPKit/workflows/CI/badge.svg)](https://github.com/TimOliver/WebPKit/actions?query=workflow%3ACI) 6 | [![Version](https://img.shields.io/cocoapods/v/WebPKit.svg?style=flat)](http://cocoadocs.org/docsets/TOCropViewController) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/TimOliver/WebPKit/main/LICENSE) 9 | [![Platform](https://img.shields.io/cocoapods/p/WebPKit.svg?style=flat)](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 --------------------------------------------------------------------------------