├── .gitignore
├── .swiftpm
├── configuration
│ └── Package.resolved
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Demo
├── SwiftWebPDemo.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── swiftpm
│ │ └── Package.resolved
└── SwiftWebPDemo
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── jiro.imageset
│ │ ├── Contents.json
│ │ └── jiro.jpg
│ ├── ContentView.swift
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ ├── SwiftWebPDemo.entitlements
│ └── SwiftWebPDemoApp.swift
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── WebP
│ ├── CGImage+Util.swift
│ ├── Info.plist
│ ├── InternalRawRepresentable.swift
│ ├── WebPDecoder+Platform.swift
│ ├── WebPDecoder.swift
│ ├── WebPDecoderConfig.swift
│ ├── WebPEncoder+CGImage.swift
│ ├── WebPEncoder+Platform.swift
│ ├── WebPEncoder.swift
│ ├── WebPEncoderConfig.swift
│ ├── WebPError.swift
│ └── WebPImageInspector.swift
└── Tests
├── Info.plist
└── WebPTests
├── ResourceAccessHelper.swift
├── Resources
└── jiro.jpg
├── WebPEncoderCGImageTests.swift
├── WebPEncoderIOSTests.swift
├── WebPEncoderMacOSTests.swift
└── WebPImageInspectorTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
67 | .DS_Store
68 |
69 | Packages/
--------------------------------------------------------------------------------
/.swiftpm/configuration/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "libwebp",
6 | "repositoryURL": "https://github.com/SDWebImage/libwebp-Xcode.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "4f52fc9b29600a03de6e05af16df0d694cb44301",
10 | "version": "1.2.4"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | `WebP` adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## v0.6.0 (incomming)
7 |
8 |
9 | ## v0.5.0
10 |
11 | ### Enhanced
12 |
13 | * Bumped libwebp version to v1.2.0 or newer depending on libwebp-Xcode via SPM
14 |
15 | ### Changed
16 |
17 | * Switched the source of libwebp from git submodule to libwebp-Xcode
18 | * Demo app is updated in SwiftUI
19 |
20 | ## v0.4.0
21 |
22 | ### Enhanced
23 |
24 | * Bump embeded libwebp version to v1.1.0 (was v1.0.3)
25 |
26 | ## v0.3.0
27 |
28 | * Added `WebPDecoder.encode(RGBA cgImage: CGImage, ...)` and so on for ainame/Swift-WebP#40
29 |
30 | ## v0.2.0
31 |
32 | ### Enhanced
33 |
34 | * Make WebPImageInspector publicly exposed
35 | * Added `WebPDecoder.decode(toUIImage:, options:)` and `WebPDecoder.decode(toNSImage:, options:)`
36 | * Bump embeded libwebp version to v1.0.3 (was v1.0.0)
37 | * Add -fembed-bitcode flag to CFLAGS when compiling libwebp for iOS
38 |
39 | ## v0.1.0
40 |
41 | ### Changed
42 |
43 | * Add WebPImageInspector internally
44 |
45 | ### Bug fix
46 |
47 | * Fixed a memory issue in WebPDecoder+Platform.swift
48 |
49 |
50 | ## v0.0.10
51 |
52 | ### Changed
53 |
54 | * Support swift-tools-version 5.0 to build with swift package manager
55 |
56 | ## v0.0.9
57 |
58 | ### Changed
59 |
60 | * Support Xcode 10.2's build and Swift 5
61 |
62 | ## v0.0.8
63 |
64 | ### Bug fix
65 |
66 | Fixed wrong file paths of WebPDecoder
67 |
68 | ## v0.0.7
69 |
70 | ### Changed
71 |
72 | * Added WebPDecoder
73 |
74 | ### Removed
75 |
76 | * WebPSimple.decode
77 |
78 | ## v0.0.7
79 |
80 | ### Changed
81 |
82 | Support Xcode10 and Swift4.2 (nothing changed at all)
83 |
84 | ## v0.0.5
85 |
86 | ### Changed
87 |
88 | * Update libwebp v0.60 -> v1.0.0
89 | * Now WebPEncoder supports iOS platform
90 |
91 | ### Bug fix
92 |
93 | * Handle use_argb flag properly
94 |
95 | ### Removed
96 |
97 | * WebPSimple.encode
98 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | All contributors are welcome. Please use issues and pull requests to contribute to the project. And update [CHANGELOG.md](CHANGELOG.md) when committing.
4 |
5 | ## Making a Change
6 |
7 | When you commit a change, please add a note to [CHANGELOG.md](CHANGELOG.md).
8 |
9 | ## Release Process
10 |
11 | 1. Confirm the build is [passing in travis](https://travis-ci.org/awesome_octocat/WebP)
12 | 1. This automatically checks the Podfile is building
13 | 2. Push a release commit
14 | 1. Create a new Master section at the top
15 | 2. Rename the old Master section like:
16 | ## [1.0.5](https://github.com/awesome_octocat/WebP/releases/tag/1.0.5)
17 | Released on 2016-02-14.
18 | 3. Update the Podspec version number
19 | 3. Create a GitHub release
20 | 1. Tag the release (like `1.0.5`)
21 | 2. Paste notes from [CHANGELOG.md](CHANGELOG.md)
22 | 3. Push the Podspec to CocoaPods
23 | 1. `pod trunk push`
24 | 4. Create Carthage binaries
25 | 1. `carthage build --no-skip-current`
26 | 2. `carthage archive WebP`
27 | 3. Add to the GitHub release
28 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 70;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FDB383D92C444EE3001F3852 /* WebP in Frameworks */ = {isa = PBXBuildFile; productRef = FDB383D82C444EE3001F3852 /* WebP */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXFileReference section */
14 | FDB383C52C444E9B001F3852 /* SwiftWebPDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftWebPDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
15 | /* End PBXFileReference section */
16 |
17 | /* Begin PBXFileSystemSynchronizedRootGroup section */
18 | FDB383C72C444E9B001F3852 /* SwiftWebPDemo */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = SwiftWebPDemo; sourceTree = ""; };
19 | /* End PBXFileSystemSynchronizedRootGroup section */
20 |
21 | /* Begin PBXFrameworksBuildPhase section */
22 | FDB383C22C444E9B001F3852 /* Frameworks */ = {
23 | isa = PBXFrameworksBuildPhase;
24 | buildActionMask = 2147483647;
25 | files = (
26 | FDB383D92C444EE3001F3852 /* WebP in Frameworks */,
27 | );
28 | runOnlyForDeploymentPostprocessing = 0;
29 | };
30 | /* End PBXFrameworksBuildPhase section */
31 |
32 | /* Begin PBXGroup section */
33 | FDB383BC2C444E9B001F3852 = {
34 | isa = PBXGroup;
35 | children = (
36 | FDB383C72C444E9B001F3852 /* SwiftWebPDemo */,
37 | FDB383C62C444E9B001F3852 /* Products */,
38 | );
39 | sourceTree = "";
40 | };
41 | FDB383C62C444E9B001F3852 /* Products */ = {
42 | isa = PBXGroup;
43 | children = (
44 | FDB383C52C444E9B001F3852 /* SwiftWebPDemo.app */,
45 | );
46 | name = Products;
47 | sourceTree = "";
48 | };
49 | /* End PBXGroup section */
50 |
51 | /* Begin PBXNativeTarget section */
52 | FDB383C42C444E9B001F3852 /* SwiftWebPDemo */ = {
53 | isa = PBXNativeTarget;
54 | buildConfigurationList = FDB383D42C444E9C001F3852 /* Build configuration list for PBXNativeTarget "SwiftWebPDemo" */;
55 | buildPhases = (
56 | FDB383C12C444E9B001F3852 /* Sources */,
57 | FDB383C22C444E9B001F3852 /* Frameworks */,
58 | FDB383C32C444E9B001F3852 /* Resources */,
59 | );
60 | buildRules = (
61 | );
62 | dependencies = (
63 | );
64 | fileSystemSynchronizedGroups = (
65 | FDB383C72C444E9B001F3852 /* SwiftWebPDemo */,
66 | );
67 | name = SwiftWebPDemo;
68 | packageProductDependencies = (
69 | FDB383D82C444EE3001F3852 /* WebP */,
70 | );
71 | productName = SwiftWebPDemo;
72 | productReference = FDB383C52C444E9B001F3852 /* SwiftWebPDemo.app */;
73 | productType = "com.apple.product-type.application";
74 | };
75 | /* End PBXNativeTarget section */
76 |
77 | /* Begin PBXProject section */
78 | FDB383BD2C444E9B001F3852 /* Project object */ = {
79 | isa = PBXProject;
80 | attributes = {
81 | BuildIndependentTargetsInParallel = 1;
82 | LastSwiftUpdateCheck = 1600;
83 | LastUpgradeCheck = 1600;
84 | TargetAttributes = {
85 | FDB383C42C444E9B001F3852 = {
86 | CreatedOnToolsVersion = 16.0;
87 | };
88 | };
89 | };
90 | buildConfigurationList = FDB383C02C444E9B001F3852 /* Build configuration list for PBXProject "SwiftWebPDemo" */;
91 | compatibilityVersion = "Xcode 15.0";
92 | developmentRegion = en;
93 | hasScannedForEncodings = 0;
94 | knownRegions = (
95 | en,
96 | Base,
97 | );
98 | mainGroup = FDB383BC2C444E9B001F3852;
99 | packageReferences = (
100 | FDB383D72C444EE3001F3852 /* XCLocalSwiftPackageReference "../../Swift-WebP" */,
101 | );
102 | productRefGroup = FDB383C62C444E9B001F3852 /* Products */;
103 | projectDirPath = "";
104 | projectRoot = "";
105 | targets = (
106 | FDB383C42C444E9B001F3852 /* SwiftWebPDemo */,
107 | );
108 | };
109 | /* End PBXProject section */
110 |
111 | /* Begin PBXResourcesBuildPhase section */
112 | FDB383C32C444E9B001F3852 /* Resources */ = {
113 | isa = PBXResourcesBuildPhase;
114 | buildActionMask = 2147483647;
115 | files = (
116 | );
117 | runOnlyForDeploymentPostprocessing = 0;
118 | };
119 | /* End PBXResourcesBuildPhase section */
120 |
121 | /* Begin PBXSourcesBuildPhase section */
122 | FDB383C12C444E9B001F3852 /* Sources */ = {
123 | isa = PBXSourcesBuildPhase;
124 | buildActionMask = 2147483647;
125 | files = (
126 | );
127 | runOnlyForDeploymentPostprocessing = 0;
128 | };
129 | /* End PBXSourcesBuildPhase section */
130 |
131 | /* Begin XCBuildConfiguration section */
132 | FDB383D22C444E9C001F3852 /* Debug */ = {
133 | isa = XCBuildConfiguration;
134 | buildSettings = {
135 | ALWAYS_SEARCH_USER_PATHS = NO;
136 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
137 | CLANG_ANALYZER_NONNULL = YES;
138 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
139 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
140 | CLANG_ENABLE_MODULES = YES;
141 | CLANG_ENABLE_OBJC_ARC = YES;
142 | CLANG_ENABLE_OBJC_WEAK = YES;
143 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
144 | CLANG_WARN_BOOL_CONVERSION = YES;
145 | CLANG_WARN_COMMA = YES;
146 | CLANG_WARN_CONSTANT_CONVERSION = YES;
147 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
148 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
149 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
150 | CLANG_WARN_EMPTY_BODY = YES;
151 | CLANG_WARN_ENUM_CONVERSION = YES;
152 | CLANG_WARN_INFINITE_RECURSION = YES;
153 | CLANG_WARN_INT_CONVERSION = YES;
154 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
155 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
156 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
157 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
158 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
159 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
160 | CLANG_WARN_STRICT_PROTOTYPES = YES;
161 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
162 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
163 | CLANG_WARN_UNREACHABLE_CODE = YES;
164 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
165 | COPY_PHASE_STRIP = NO;
166 | DEBUG_INFORMATION_FORMAT = dwarf;
167 | ENABLE_STRICT_OBJC_MSGSEND = YES;
168 | ENABLE_TESTABILITY = YES;
169 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
170 | GCC_C_LANGUAGE_STANDARD = gnu17;
171 | GCC_DYNAMIC_NO_PIC = NO;
172 | GCC_NO_COMMON_BLOCKS = YES;
173 | GCC_OPTIMIZATION_LEVEL = 0;
174 | GCC_PREPROCESSOR_DEFINITIONS = (
175 | "DEBUG=1",
176 | "$(inherited)",
177 | );
178 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
179 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
180 | GCC_WARN_UNDECLARED_SELECTOR = YES;
181 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
182 | GCC_WARN_UNUSED_FUNCTION = YES;
183 | GCC_WARN_UNUSED_VARIABLE = YES;
184 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
185 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
186 | MTL_FAST_MATH = YES;
187 | ONLY_ACTIVE_ARCH = YES;
188 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
189 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
190 | };
191 | name = Debug;
192 | };
193 | FDB383D32C444E9C001F3852 /* Release */ = {
194 | isa = XCBuildConfiguration;
195 | buildSettings = {
196 | ALWAYS_SEARCH_USER_PATHS = NO;
197 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
198 | CLANG_ANALYZER_NONNULL = YES;
199 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
201 | CLANG_ENABLE_MODULES = YES;
202 | CLANG_ENABLE_OBJC_ARC = YES;
203 | CLANG_ENABLE_OBJC_WEAK = YES;
204 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
205 | CLANG_WARN_BOOL_CONVERSION = YES;
206 | CLANG_WARN_COMMA = YES;
207 | CLANG_WARN_CONSTANT_CONVERSION = YES;
208 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
209 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
210 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
211 | CLANG_WARN_EMPTY_BODY = YES;
212 | CLANG_WARN_ENUM_CONVERSION = YES;
213 | CLANG_WARN_INFINITE_RECURSION = YES;
214 | CLANG_WARN_INT_CONVERSION = YES;
215 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
216 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
217 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
219 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
221 | CLANG_WARN_STRICT_PROTOTYPES = YES;
222 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
224 | CLANG_WARN_UNREACHABLE_CODE = YES;
225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
226 | COPY_PHASE_STRIP = NO;
227 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
228 | ENABLE_NS_ASSERTIONS = NO;
229 | ENABLE_STRICT_OBJC_MSGSEND = YES;
230 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
231 | GCC_C_LANGUAGE_STANDARD = gnu17;
232 | GCC_NO_COMMON_BLOCKS = YES;
233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
235 | GCC_WARN_UNDECLARED_SELECTOR = YES;
236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
237 | GCC_WARN_UNUSED_FUNCTION = YES;
238 | GCC_WARN_UNUSED_VARIABLE = YES;
239 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
240 | MTL_ENABLE_DEBUG_INFO = NO;
241 | MTL_FAST_MATH = YES;
242 | SWIFT_COMPILATION_MODE = wholemodule;
243 | };
244 | name = Release;
245 | };
246 | FDB383D52C444E9C001F3852 /* Debug */ = {
247 | isa = XCBuildConfiguration;
248 | buildSettings = {
249 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
250 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
251 | CODE_SIGN_ENTITLEMENTS = SwiftWebPDemo/SwiftWebPDemo.entitlements;
252 | CODE_SIGN_STYLE = Automatic;
253 | CURRENT_PROJECT_VERSION = 1;
254 | DEVELOPMENT_ASSET_PATHS = "\"SwiftWebPDemo/Preview Content\"";
255 | DEVELOPMENT_TEAM = 2Q7H62SFG3;
256 | ENABLE_HARDENED_RUNTIME = YES;
257 | ENABLE_PREVIEWS = YES;
258 | GENERATE_INFOPLIST_FILE = YES;
259 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
260 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
261 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
262 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
263 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
264 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
265 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
266 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
267 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
268 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
269 | IPHONEOS_DEPLOYMENT_TARGET = 18.0;
270 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
271 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
272 | MACOSX_DEPLOYMENT_TARGET = 15.0;
273 | MARKETING_VERSION = 1.0;
274 | PRODUCT_BUNDLE_IDENTIFIER = ainame.tokyo.SwiftWebPDemo;
275 | PRODUCT_NAME = "$(TARGET_NAME)";
276 | SDKROOT = auto;
277 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
278 | SUPPORTS_MACCATALYST = NO;
279 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
280 | SWIFT_EMIT_LOC_STRINGS = YES;
281 | SWIFT_VERSION = 5.0;
282 | TARGETED_DEVICE_FAMILY = "1,2,7";
283 | XROS_DEPLOYMENT_TARGET = 2.0;
284 | };
285 | name = Debug;
286 | };
287 | FDB383D62C444E9C001F3852 /* Release */ = {
288 | isa = XCBuildConfiguration;
289 | buildSettings = {
290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
291 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
292 | CODE_SIGN_ENTITLEMENTS = SwiftWebPDemo/SwiftWebPDemo.entitlements;
293 | CODE_SIGN_STYLE = Automatic;
294 | CURRENT_PROJECT_VERSION = 1;
295 | DEVELOPMENT_ASSET_PATHS = "\"SwiftWebPDemo/Preview Content\"";
296 | DEVELOPMENT_TEAM = 2Q7H62SFG3;
297 | ENABLE_HARDENED_RUNTIME = YES;
298 | ENABLE_PREVIEWS = YES;
299 | GENERATE_INFOPLIST_FILE = YES;
300 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
301 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
302 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
303 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
304 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
305 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
306 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
307 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
308 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
309 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
310 | IPHONEOS_DEPLOYMENT_TARGET = 18.0;
311 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
312 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
313 | MACOSX_DEPLOYMENT_TARGET = 15.0;
314 | MARKETING_VERSION = 1.0;
315 | PRODUCT_BUNDLE_IDENTIFIER = ainame.tokyo.SwiftWebPDemo;
316 | PRODUCT_NAME = "$(TARGET_NAME)";
317 | SDKROOT = auto;
318 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
319 | SUPPORTS_MACCATALYST = NO;
320 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
321 | SWIFT_EMIT_LOC_STRINGS = YES;
322 | SWIFT_VERSION = 5.0;
323 | TARGETED_DEVICE_FAMILY = "1,2,7";
324 | XROS_DEPLOYMENT_TARGET = 2.0;
325 | };
326 | name = Release;
327 | };
328 | /* End XCBuildConfiguration section */
329 |
330 | /* Begin XCConfigurationList section */
331 | FDB383C02C444E9B001F3852 /* Build configuration list for PBXProject "SwiftWebPDemo" */ = {
332 | isa = XCConfigurationList;
333 | buildConfigurations = (
334 | FDB383D22C444E9C001F3852 /* Debug */,
335 | FDB383D32C444E9C001F3852 /* Release */,
336 | );
337 | defaultConfigurationIsVisible = 0;
338 | defaultConfigurationName = Release;
339 | };
340 | FDB383D42C444E9C001F3852 /* Build configuration list for PBXNativeTarget "SwiftWebPDemo" */ = {
341 | isa = XCConfigurationList;
342 | buildConfigurations = (
343 | FDB383D52C444E9C001F3852 /* Debug */,
344 | FDB383D62C444E9C001F3852 /* Release */,
345 | );
346 | defaultConfigurationIsVisible = 0;
347 | defaultConfigurationName = Release;
348 | };
349 | /* End XCConfigurationList section */
350 |
351 | /* Begin XCLocalSwiftPackageReference section */
352 | FDB383D72C444EE3001F3852 /* XCLocalSwiftPackageReference "../../Swift-WebP" */ = {
353 | isa = XCLocalSwiftPackageReference;
354 | relativePath = "../../Swift-WebP";
355 | };
356 | /* End XCLocalSwiftPackageReference section */
357 |
358 | /* Begin XCSwiftPackageProductDependency section */
359 | FDB383D82C444EE3001F3852 /* WebP */ = {
360 | isa = XCSwiftPackageProductDependency;
361 | productName = WebP;
362 | };
363 | /* End XCSwiftPackageProductDependency section */
364 | };
365 | rootObject = FDB383BD2C444E9B001F3852 /* Project object */;
366 | }
367 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "f2bcc02bd306b65d02fa3c4b979176e919d032d12c12c6fce4eb6a26b57cd203",
3 | "pins" : [
4 | {
5 | "identity" : "libwebp-xcode",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/SDWebImage/libwebp-Xcode.git",
8 | "state" : {
9 | "revision" : "b2b1d20a90b14d11f6ef4241da6b81c1d3f171e4",
10 | "version" : "1.3.2"
11 | }
12 | }
13 | ],
14 | "version" : 3
15 | }
16 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/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 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "idiom" : "universal",
16 | "platform" : "ios",
17 | "size" : "1024x1024"
18 | },
19 | {
20 | "appearances" : [
21 | {
22 | "appearance" : "luminosity",
23 | "value" : "tinted"
24 | }
25 | ],
26 | "idiom" : "universal",
27 | "platform" : "ios",
28 | "size" : "1024x1024"
29 | },
30 | {
31 | "idiom" : "mac",
32 | "scale" : "1x",
33 | "size" : "16x16"
34 | },
35 | {
36 | "idiom" : "mac",
37 | "scale" : "2x",
38 | "size" : "16x16"
39 | },
40 | {
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "32x32"
44 | },
45 | {
46 | "idiom" : "mac",
47 | "scale" : "2x",
48 | "size" : "32x32"
49 | },
50 | {
51 | "idiom" : "mac",
52 | "scale" : "1x",
53 | "size" : "128x128"
54 | },
55 | {
56 | "idiom" : "mac",
57 | "scale" : "2x",
58 | "size" : "128x128"
59 | },
60 | {
61 | "idiom" : "mac",
62 | "scale" : "1x",
63 | "size" : "256x256"
64 | },
65 | {
66 | "idiom" : "mac",
67 | "scale" : "2x",
68 | "size" : "256x256"
69 | },
70 | {
71 | "idiom" : "mac",
72 | "scale" : "1x",
73 | "size" : "512x512"
74 | },
75 | {
76 | "idiom" : "mac",
77 | "scale" : "2x",
78 | "size" : "512x512"
79 | }
80 | ],
81 | "info" : {
82 | "author" : "xcode",
83 | "version" : 1
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/Assets.xcassets/jiro.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "jiro.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/Assets.xcassets/jiro.imageset/jiro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ainame/Swift-WebP/c7c0adebb374493a665136d98198b11437045fb4/Demo/SwiftWebPDemo/Assets.xcassets/jiro.imageset/jiro.jpg
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import WebP
3 | import UIKit
4 |
5 | struct ContentView: View {
6 | @State var converted: UIImage?
7 |
8 | var body: some View {
9 | VStack {
10 | Button {
11 | convertImage()
12 | } label: {
13 | Text("Convert")
14 | }
15 | .buttonStyle(.borderedProminent)
16 |
17 | VStack {
18 | VStack {
19 | Text("Original Image")
20 |
21 | Image(.jiro)
22 | .resizable()
23 | .aspectRatio(contentMode: .fit)
24 | .frame(width: 350)
25 | }
26 | .padding()
27 |
28 | VStack {
29 | Text("Converted Image quality=10%")
30 |
31 | if let converted {
32 | Image(uiImage: converted)
33 | .resizable()
34 | .aspectRatio(contentMode: .fit)
35 | .frame(width: 350)
36 | } else {
37 | Color
38 | .black
39 | .opacity(0.2)
40 | .frame(width: 350, height: 200)
41 | }
42 | }
43 | .padding()
44 | }
45 |
46 | Spacer()
47 | }
48 | .containerRelativeFrame([.horizontal, .vertical])
49 | }
50 |
51 | func convertImage() {
52 | let encoder = WebPEncoder()
53 | let decoder = WebPDecoder()
54 | let queue = DispatchQueue(label: "me.ainam.webp")
55 | let image = UIImage(named: "jiro")!
56 |
57 | queue.async {
58 | do {
59 | let data = try! encoder.encode(image, config: .preset(.picture, quality: 10))
60 | var options = WebPDecoderOptions()
61 | options.scaledWidth = Int(image.size.width)
62 | options.scaledHeight = Int(image.size.height)
63 | let webpImage = try decoder.decode(toUImage: data, options: options)
64 |
65 | DispatchQueue.main.async {
66 | self.converted = webpImage
67 | }
68 | } catch let error {
69 | print(error)
70 | }
71 | }
72 | }
73 | }
74 |
75 | #Preview {
76 | ContentView()
77 | }
78 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/SwiftWebPDemo.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 |
--------------------------------------------------------------------------------
/Demo/SwiftWebPDemo/SwiftWebPDemoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftWebPDemoApp.swift
3 | // SwiftWebPDemo
4 | //
5 | // Created by Satoshi Namai on 14/07/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct SwiftWebPDemoApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Satoshi Namai
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 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "libwebp",
6 | "repositoryURL": "https://github.com/SDWebImage/libwebp-Xcode.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "4f52fc9b29600a03de6e05af16df0d694cb44301",
10 | "version": "1.2.4"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.10
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "WebP",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v11)
11 | ],
12 | products: [
13 | .library(name: "WebP", targets: ["WebP"]),
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/SDWebImage/libwebp-Xcode.git", from: "1.2.0"),
17 | ],
18 | targets: [
19 | .target(
20 | name: "WebP",
21 | dependencies: [
22 | .product(name: "libwebp", package: "libwebp-Xcode")
23 | ]
24 | ),
25 | .testTarget(
26 | name: "WebPTests",
27 | dependencies: ["WebP"],
28 | resources: [
29 | .copy("Resources/jiro.jpg")
30 | ]
31 | )
32 | ]
33 | )
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Swift-WebP
2 |
3 | Swift-WebP provides libwebp APIs in Swift manner for both encoding and decoding.
4 |
5 | ### Support Versions:
6 |
7 | * libwebp: v1.2.0
8 | * iOS Deployment Target: 13.0
9 | * macOS Deployment Target: 11.0
10 |
11 | #### Features
12 |
13 | * [x] Support mutiplatform; iOS, macOS, and Linux (swift-docker)
14 | * [x] Support SPM
15 | * [x] [Advanced Encoder API](https://developers.google.com/speed/webp/docs/api#advanced_encoding_api) - WebPEncoder, WebPEncoderConfig
16 | * [x] [Advanced Decoding API](https://developers.google.com/speed/webp/docs/api#advanced_decoding_api) - WebPDecoder, WebPDecoderOptions
17 | * [x] Image inspection for WebP files - WebPImageInspector
18 |
19 | #### TODO
20 |
21 | * [ ] Progressively encoding/decoding option
22 | * [ ] Animated WebP
23 |
24 |
25 | ## Usage
26 |
27 | #### Encoding
28 |
29 | ```swift
30 | let image = UIImage(named: "demo")
31 | let encoder = WebPEncoder()
32 | let queue = DispatchQueue(label: "me.ainam.webp")
33 |
34 | // should encode in background
35 | queue.async {
36 | let data = try! encoder.encode(image, config: .preset(.picture, quality: 95))
37 | // using webp binary data...
38 | }
39 | ```
40 |
41 | #### Decoding
42 |
43 | ```swift
44 | let data: Data = loadWebPData()
45 | let encoder = WebPDecoder()
46 | let queue = DispatchQueue(label: "me.ainam.webp")
47 |
48 | // should decode in background
49 | queue.async {
50 | var options = WebPDecoderOptions()
51 | options.scaledWidth = Int(originalWidth / 2)
52 | options.scaledHeight = Int(originalHeight / 2)
53 | let cgImage = try! decoder.decode(data, options: options)
54 | let webpImage = UIImage(cgImage: cgImage)
55 |
56 | DispatchQueue.main.async {
57 | self.imageView.image = webpImage
58 | }
59 | }
60 | ```
61 |
62 |
63 | ## Example
64 |
65 | Please check example project
66 |
67 | ## Installation
68 |
69 | Swift-WebP supports Swift Package Manager installation.
70 |
71 | ```
72 | .package(url: "https://github.com/ainame/Swift-WebP.git", from: "0.5.0"),
73 | ```
74 |
75 |
76 | ## Author
77 |
78 | ainame
79 |
80 | ## License
81 |
82 | Swift-WebP is available under the MIT license. See the LICENSE file for more info.
83 |
--------------------------------------------------------------------------------
/Sources/WebP/CGImage+Util.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(CoreGraphics)
4 | import CoreGraphics
5 |
6 | extension CGImage {
7 | func getBaseAddress() throws -> UnsafeMutablePointer {
8 | guard let dataProvider = dataProvider,
9 | let data = dataProvider.data else {
10 | throw WebPError.unexpectedPointerError
11 | }
12 | // This downcast always succeeds
13 | let mutableData = data as! CFMutableData
14 | return CFDataGetMutableBytePtr(mutableData)
15 | }
16 | }
17 | #endif
18 |
--------------------------------------------------------------------------------
/Sources/WebP/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.3.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/WebP/InternalRawRepresentable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol InternalRawRepresentable {
4 | associatedtype RawValue
5 |
6 | init?(rawValue: Self.RawValue)
7 |
8 | var rawValue: Self.RawValue { get }
9 | }
10 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPDecoder+Platform.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import libwebp
3 |
4 | #if os(macOS) || os(iOS)
5 | import CoreGraphics
6 |
7 | extension WebPDecoder {
8 | public func decode(_ webPData: Data, options: WebPDecoderOptions) throws -> CGImage {
9 | let feature = try WebPImageInspector.inspect(webPData)
10 | let height: Int = options.useScaling ? options.scaledHeight : feature.height
11 | let width: Int = options.useScaling ? options.scaledWidth : feature.width
12 |
13 | let decodedData: CFData = try decode(byRGBA: webPData, options: options) as CFData
14 | guard let provider = CGDataProvider(data: decodedData) else {
15 | throw WebPError.unexpectedError(withMessage: "Couldn't initialize CGDataProvider")
16 | }
17 |
18 | let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue)
19 | let colorSpace = CGColorSpaceCreateDeviceRGB()
20 | let renderingIntent = CGColorRenderingIntent.defaultIntent
21 | let bytesPerPixel = 4
22 |
23 | if let cgImage = CGImage(width: width,
24 | height: height,
25 | bitsPerComponent: 8,
26 | bitsPerPixel: 8 * bytesPerPixel,
27 | bytesPerRow: bytesPerPixel * width,
28 | space: colorSpace,
29 | bitmapInfo: bitmapInfo,
30 | provider: provider,
31 | decode: nil,
32 | shouldInterpolate: false,
33 | intent: renderingIntent) {
34 | return cgImage
35 | }
36 |
37 | throw WebPError.unexpectedError(withMessage: "Couldn't initialize CGImage")
38 | }
39 | }
40 | #endif
41 |
42 | #if os(iOS)
43 | import UIKit
44 |
45 | extension WebPDecoder {
46 | public func decode(toUImage webPData: Data, options: WebPDecoderOptions) throws -> UIImage {
47 | let cgImage: CGImage = try decode(webPData, options: options)
48 | return UIImage(cgImage: cgImage)
49 | }
50 | }
51 | #endif
52 |
53 | #if os(macOS)
54 | import AppKit
55 |
56 | extension WebPDecoder {
57 | public func decode(toNSImage webPData: Data, options: WebPDecoderOptions) throws -> NSImage {
58 | let cgImage: CGImage = try decode(webPData, options: options)
59 | return NSImage(cgImage: cgImage, size: NSSize(width: cgImage.width, height: cgImage.height))
60 | }
61 | }
62 | #endif
63 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPDecoder.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import libwebp
3 |
4 | /// There's no definition of WebPDecodingError in libwebp.
5 | /// We map VP8StatusCode enum as WebPDecodingError instead.
6 | public enum WebPDecodingError: UInt32, Error {
7 | case ok = 0 // shouldn't be used as this is the succseed case
8 | case outOfMemory
9 | case invalidParam
10 | case bitstreamError
11 | case unsupportedFeature
12 | case suspended
13 | case userAbort
14 | case notEnoughData
15 | case unknownError = 9999 // This is an own error to deal with internal problems
16 | }
17 |
18 | public struct WebPDecoder {
19 | public init() {
20 | }
21 |
22 | public func decode(byRGB webPData: Data, options: WebPDecoderOptions) throws -> Data {
23 | var config = makeConfig(options, .RGB)
24 | try decode(webPData, config: &config)
25 |
26 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
27 | count: config.output.u.RGBA.size,
28 | deallocator: .free)
29 | }
30 |
31 | public func decode(byRGBA webPData: Data, options: WebPDecoderOptions) throws -> Data {
32 | var config = makeConfig(options, .RGBA)
33 | try decode(webPData, config: &config)
34 |
35 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
36 | count: config.output.u.RGBA.size,
37 | deallocator: .free)
38 | }
39 |
40 | public func decode(byBGR webPData: Data, options: WebPDecoderOptions,
41 | width: Int, height: Int) throws -> Data {
42 | var config = makeConfig(options, .BGR)
43 | try decode(webPData, config: &config)
44 |
45 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
46 | count: config.output.u.RGBA.size,
47 | deallocator: .free)
48 | }
49 |
50 | public func decode(byBGRA webPData: Data, options: WebPDecoderOptions) throws -> Data {
51 | var config = makeConfig(options, .BGRA)
52 | try decode(webPData, config: &config)
53 |
54 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
55 | count: config.output.u.RGBA.size,
56 | deallocator: .free)
57 | }
58 |
59 | public func decode(byARGB webPData: Data, options: WebPDecoderOptions) throws -> Data {
60 | var config = makeConfig(options, .ARGB)
61 | try decode(webPData, config: &config)
62 |
63 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
64 | count: config.output.u.RGBA.size,
65 | deallocator: .free)
66 | }
67 |
68 | public func decode(byRGBA4444 webPData: Data, options: WebPDecoderOptions) throws -> Data {
69 |
70 | var config = makeConfig(options, .RGBA4444)
71 | try decode(webPData, config: &config)
72 |
73 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
74 | count: config.output.u.RGBA.size,
75 | deallocator: .free)
76 | }
77 |
78 | public func decode(byRGB565 webPData: Data, options: WebPDecoderOptions) throws -> Data {
79 | var config = makeConfig(options, .RGB565)
80 | try decode(webPData, config: &config)
81 |
82 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
83 | count: config.output.u.RGBA.size,
84 | deallocator: .free)
85 | }
86 |
87 | public func decode(byrgbA webPData: Data, options: WebPDecoderOptions) throws -> Data {
88 | var config = makeConfig(options, .rgbA)
89 | try decode(webPData, config: &config)
90 |
91 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
92 | count: config.output.u.RGBA.size,
93 | deallocator: .free)
94 | }
95 |
96 | public func decode(bybgrA webPData: Data, options: WebPDecoderOptions) throws -> Data {
97 | var config = makeConfig(options, .bgrA)
98 | try decode(webPData, config: &config)
99 |
100 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
101 | count: config.output.u.RGBA.size,
102 | deallocator: .free)
103 | }
104 |
105 | public func decode(byArgb webPData: Data, options: WebPDecoderOptions) throws -> Data {
106 | var config = makeConfig(options, .Argb)
107 | try decode(webPData, config: &config)
108 |
109 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
110 | count: config.output.u.RGBA.size,
111 | deallocator: .free)
112 | }
113 |
114 | public func decode(byrgbA4444 webPData: Data, options: WebPDecoderOptions) throws -> Data {
115 | var config = makeConfig(options, .rgbA4444)
116 | try decode(webPData, config: &config)
117 |
118 | return Data(bytesNoCopy: config.output.u.RGBA.rgba,
119 | count: config.output.u.RGBA.size,
120 | deallocator: .free)
121 | }
122 |
123 | public func decode(byYUV webPData: Data, options: WebPDecoderOptions) throws -> Data {
124 | fatalError("didn't implement this yet")
125 | }
126 |
127 | public func decode(byYUVA webPData: Data, options: WebPDecoderOptions) throws -> Data {
128 | fatalError("didn't implement this yet")
129 | }
130 |
131 | private func decode(_ webPData: Data, config: inout WebPDecoderConfig) throws {
132 | var mutableWebPData = webPData
133 | var rawConfig: libwebp.WebPDecoderConfig = config.rawValue
134 |
135 | try mutableWebPData.withUnsafeMutableBytes { rawPtr in
136 |
137 | guard let bindedBasePtr = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
138 | throw WebPDecodingError.unknownError
139 | }
140 |
141 | let status = WebPDecode(bindedBasePtr, webPData.count, &rawConfig)
142 | if status != VP8_STATUS_OK {
143 | throw WebPDecodingError(rawValue: status.rawValue)!
144 | }
145 | }
146 |
147 | switch config.output.u {
148 | case .RGBA:
149 | config.output.u = WebPDecBuffer.Colorspace.RGBA(rawConfig.output.u.RGBA)
150 | case .YUVA:
151 | config.output.u = WebPDecBuffer.Colorspace.YUVA(rawConfig.output.u.YUVA)
152 | }
153 | }
154 |
155 | private func makeConfig(_ options: WebPDecoderOptions,
156 | _ colorspace: ColorspaceMode) -> WebPDecoderConfig {
157 | var config = WebPDecoderConfig()
158 | config.options = options
159 | config.output.colorspace = colorspace
160 | return config
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPDecoderConfig.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import libwebp
3 |
4 | public struct WebPDecoderConfig: InternalRawRepresentable {
5 | public var input: WebPBitstreamFeatures? // Immutable bitstream features (optional)
6 | public var output: WebPDecBuffer // Output buffer (can point to external mem)
7 | public var options: WebPDecoderOptions // Decoding options
8 |
9 | public init() {
10 | var originConfig = libwebp.WebPDecoderConfig()
11 | if (libwebp.WebPInitDecoderConfig(&originConfig) == 0) {
12 | fatalError("can't init decoder config")
13 | }
14 | self.init(rawValue: originConfig)!
15 | }
16 |
17 | internal init?(rawValue: libwebp.WebPDecoderConfig) {
18 | self.input = WebP.WebPBitstreamFeatures(rawValue: rawValue.input)
19 | self.output = WebP.WebPDecBuffer(rawValue: rawValue.output)!
20 | self.options = WebP.WebPDecoderOptions(rawValue: rawValue.options)!
21 | }
22 |
23 | internal var rawValue: libwebp.WebPDecoderConfig {
24 | return libwebp.WebPDecoderConfig(input: (input?.rawValue)!, output: output.rawValue, options: options.rawValue)
25 | }
26 | }
27 |
28 | public struct WebPBitstreamFeatures: InternalRawRepresentable {
29 | public enum Format: Int {
30 | case undefined = 0
31 | case lossy
32 | case lossless
33 | }
34 |
35 | public var width: Int // Width in pixels, as read from the bitstream.
36 |
37 | public var height: Int // Height in pixels, as read from the bitstream.
38 |
39 | public var hasAlpha: Bool // True if the bitstream contains an alpha channel.
40 |
41 | public var hasAnimation: Bool // True if the bitstream is an animation.
42 |
43 | public var format: Format // 0 = undefined (/mixed), 1 = lossy, 2 = lossless
44 |
45 | public var pad: (Int, Int, Int, Int, Int) // padding for later use
46 |
47 | internal var rawValue: libwebp.WebPBitstreamFeatures {
48 | let has_alpha = hasAlpha ? 1 : 0
49 | let has_animation = hasAnimation ? 1 : 0
50 |
51 | return libwebp.WebPBitstreamFeatures(
52 | width: Int32(width),
53 | height: Int32(height),
54 | has_alpha: Int32(has_alpha),
55 | has_animation: Int32(has_animation),
56 | format: Int32(format.rawValue),
57 | pad: (UInt32(pad.0), UInt32(pad.1), UInt32(pad.2), UInt32(pad.3), UInt32(pad.4))
58 | )
59 | }
60 |
61 | internal init?(rawValue: libwebp.WebPBitstreamFeatures) {
62 | width = Int(rawValue.width)
63 | height = Int(rawValue.height)
64 | hasAlpha = rawValue.has_alpha != 0
65 | hasAnimation = rawValue.has_animation != 0
66 | format = Format(rawValue: Int(rawValue.format))!
67 | pad = (Int(rawValue.pad.0), Int(rawValue.pad.1), Int(rawValue.pad.2), Int(rawValue.pad.3), Int(rawValue.pad.4))
68 | }
69 | }
70 |
71 | // Colorspaces
72 | // Note: the naming describes the byte-ordering of packed samples in memory.
73 | // For instance, MODE_BGRA relates to samples ordered as B,G,R,A,B,G,R,A,...
74 | // Non-capital names (e.g.:MODE_Argb) relates to pre-multiplied RGB channels.
75 | // RGBA-4444 and RGB-565 colorspaces are represented by following byte-order:
76 | // RGBA-4444: [r3 r2 r1 r0 g3 g2 g1 g0], [b3 b2 b1 b0 a3 a2 a1 a0], ...
77 | // RGB-565: [r4 r3 r2 r1 r0 g5 g4 g3], [g2 g1 g0 b4 b3 b2 b1 b0], ...
78 | // In the case WEBP_SWAP_16BITS_CSP is defined, the bytes are swapped for
79 | // these two modes:
80 | // RGBA-4444: [b3 b2 b1 b0 a3 a2 a1 a0], [r3 r2 r1 r0 g3 g2 g1 g0], ...
81 | // RGB-565: [g2 g1 g0 b4 b3 b2 b1 b0], [r4 r3 r2 r1 r0 g5 g4 g3], ...
82 | public enum ColorspaceMode: Int {
83 | case RGB = 0
84 | case RGBA = 1
85 | case BGR = 2
86 | case BGRA = 3
87 | case ARGB = 4
88 | case RGBA4444 = 5
89 | case RGB565 = 6
90 |
91 | // RGB-premultiplied transparent modes (alpha value is preserved)
92 | case rgbA = 7
93 | case bgrA = 8
94 | case Argb = 9
95 | case rgbA4444 = 10
96 |
97 | // YUV modes must come after RGB ones.
98 | case YUV = 11
99 | case YUVA = 12
100 |
101 | public var isPremultipliedMode: Bool {
102 | if self == .rgbA || self == .bgrA || self == .Argb || self == .rgbA4444 {
103 | return true
104 | }
105 | return false
106 | }
107 |
108 | public var isAlphaMode: Bool {
109 | if self == .RGBA || self == .BGRA || self == .ARGB ||
110 | self == .RGBA4444 || self == .YUVA || isPremultipliedMode {
111 | return true
112 | }
113 | return false
114 | }
115 |
116 | public var isRGBMode: Bool {
117 | return rawValue < ColorspaceMode.YUV.rawValue;
118 | }
119 | }
120 |
121 | public struct WebPDecBuffer: InternalRawRepresentable {
122 | public enum Colorspace {
123 | case RGBA(WebPRGBABuffer)
124 | case YUVA(WebPYUVABuffer)
125 |
126 | var RGBA: WebPRGBABuffer {
127 | if case .RGBA(let buffer) = self {
128 | return buffer
129 | }
130 | fatalError("please use yuva")
131 | }
132 |
133 | var YUVA: WebPYUVABuffer {
134 | if case .YUVA(let buffer) = self {
135 | return buffer
136 | }
137 | fatalError("please use rgba")
138 | }
139 | }
140 |
141 | // Colorspace.
142 | public var colorspace: ColorspaceMode
143 |
144 | // Dimensions.
145 | public var width: Int
146 | public var height: Int
147 |
148 | // If non-zero, 'internal_memory' pointer is not
149 | // used. If value is '2' or more, the external
150 | // memory is considered 'slow' and multiple
151 | // read/write will be avoided.
152 | public var isExternalMemory: Bool
153 |
154 | public var u: Colorspace
155 |
156 | // Nameless union of buffer parameters.
157 | public var pad: (Int, Int, Int, Int) // padding for later use
158 |
159 |
160 | public var privateMemory: UnsafeMutablePointer? // Internally allocated memory (only when
161 |
162 |
163 | internal var rawValue: libwebp.WebPDecBuffer {
164 | let originU: libwebp.WebPDecBuffer.__Unnamed_union_u
165 | switch u {
166 | case .RGBA(let buffer):
167 | originU = libwebp.WebPDecBuffer.__Unnamed_union_u(RGBA: buffer)
168 | case .YUVA(let buffer):
169 | originU = libwebp.WebPDecBuffer.__Unnamed_union_u(YUVA: buffer)
170 | }
171 | // let u = colorspace.isRGBMode ? libwebp.WebPDecBuffer.__Unnamed_union_u(RGBA: u.RGBA) : libwebp.WebPDecBuffer.__Unnamed_union_u(YUVA: u.YUVA)
172 | return libwebp.WebPDecBuffer(
173 | colorspace: WEBP_CSP_MODE(rawValue: UInt32(colorspace.rawValue)),
174 | width: Int32(width),
175 | height: Int32(height),
176 | is_external_memory: Int32(isExternalMemory ? 1 : 0),
177 | u: originU,
178 | pad: (UInt32(pad.0), UInt32(pad.1), UInt32(pad.2), UInt32(pad.3)),
179 | private_memory: privateMemory
180 | )
181 | }
182 |
183 | internal init?(rawValue: libwebp.WebPDecBuffer) {
184 | colorspace = ColorspaceMode(rawValue: Int(rawValue.colorspace.rawValue))!
185 | width = Int(rawValue.width)
186 | height = Int(rawValue.height)
187 | isExternalMemory = rawValue.is_external_memory != 0
188 | u = colorspace.isRGBMode ? Colorspace.RGBA(rawValue.u.RGBA) : Colorspace.YUVA(rawValue.u.YUVA)
189 | pad = (Int(rawValue.pad.0), Int(rawValue.pad.1), Int(rawValue.pad.2), Int(rawValue.pad.3))
190 | privateMemory = rawValue.private_memory
191 | }
192 | }
193 |
194 | public struct WebPDecoderOptions: InternalRawRepresentable {
195 | public var bypassFiltering: Int // if true, skip the in-loop filtering
196 |
197 | public var noFancyUpsampling: Int // if true, use faster pointwise upsampler
198 |
199 | public var useCropping: Bool // if true, cropping is applied _first_
200 |
201 | public var cropLeft: Int // top-left position for cropping.
202 |
203 | public var cropTop: Int
204 |
205 | // Will be snapped to even values.
206 | public var cropWidth: Int // dimension of the cropping area
207 |
208 | public var cropHeight: Int
209 |
210 | public var useScaling: Bool // if true, scaling is applied _afterward_
211 |
212 | public var scaledWidth: Int // final resolution
213 |
214 | public var scaledHeight: Int
215 |
216 | public var useThreads: Bool // if true, use multi-threaded decoding
217 |
218 | public var ditheringStrength: Int // dithering strength (0=Off, 100=full)
219 |
220 | public var flip: Int // flip output vertically
221 |
222 | public var alphaDitheringStrength: Int // alpha dithering strength in [0..100]
223 |
224 | public var pad: (Int, Int, Int, Int, Int) // padding for later use
225 |
226 | internal var rawValue: libwebp.WebPDecoderOptions {
227 | let useCropping = self.useCropping ? 1 : 0
228 | let useScaling = self.useScaling ? 1 : 0
229 | let useThreads = self.useThreads ? 1 : 0
230 |
231 | return libwebp.WebPDecoderOptions(
232 | bypass_filtering: Int32(bypassFiltering),
233 | no_fancy_upsampling: Int32(noFancyUpsampling),
234 | use_cropping: Int32(useCropping),
235 | crop_left: Int32(cropLeft),
236 | crop_top: Int32(cropTop),
237 | crop_width: Int32(cropWidth),
238 | crop_height: Int32(cropHeight),
239 | use_scaling: Int32(useScaling),
240 | scaled_width: Int32(scaledWidth),
241 | scaled_height: Int32(scaledHeight),
242 | use_threads: Int32(useThreads),
243 | dithering_strength: Int32(ditheringStrength),
244 | flip: Int32(flip),
245 | alpha_dithering_strength: Int32(alphaDitheringStrength),
246 | pad: (UInt32(pad.0), UInt32(pad.1), UInt32(pad.2), UInt32(pad.3), UInt32(pad.4))
247 | )
248 | }
249 |
250 | internal init?(rawValue: libwebp.WebPDecoderOptions) {
251 | self.bypassFiltering = Int(rawValue.bypass_filtering)
252 | self.noFancyUpsampling = Int(rawValue.no_fancy_upsampling)
253 | self.useCropping = rawValue.use_cropping != 0
254 | self.cropLeft = Int(rawValue.crop_left)
255 | self.cropTop = Int(rawValue.crop_top)
256 | self.cropWidth = Int(rawValue.crop_width)
257 | self.cropHeight = Int(rawValue.crop_height)
258 | self.useScaling = rawValue.use_scaling != 0
259 | self.scaledWidth = Int(rawValue.scaled_width)
260 | self.scaledHeight = Int(rawValue.scaled_height)
261 | self.useThreads = rawValue.use_threads != 0
262 | self.ditheringStrength = Int(rawValue.dithering_strength)
263 | self.flip = Int(rawValue.flip)
264 | self.alphaDitheringStrength = Int(rawValue.alpha_dithering_strength)
265 | self.pad = (Int(rawValue.pad.0), Int(rawValue.pad.1), Int(rawValue.pad.2), Int(rawValue.pad.3), Int(rawValue.pad.4))
266 | }
267 |
268 | public init() {
269 | self = WebPDecoderConfig().options
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPEncoder+CGImage.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(CoreGraphics)
4 | import CoreGraphics
5 |
6 | extension WebPEncoder {
7 | public func encode(RGB cgImage: CGImage, config: WebPEncoderConfig, resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
8 | return try encode(RGB: cgImage.getBaseAddress(), config: config,
9 | originWidth: cgImage.width, originHeight: cgImage.height, stride: cgImage.bytesPerRow)
10 | }
11 |
12 | public func encode(RGBA cgImage: CGImage, config: WebPEncoderConfig, resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
13 | return try encode(RGBA: cgImage.getBaseAddress(), config: config,
14 | originWidth: cgImage.width, originHeight: cgImage.height, stride: cgImage.bytesPerRow)
15 | }
16 |
17 | public func encode(RGBX cgImage: CGImage, config: WebPEncoderConfig, resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
18 | return try encode(RGBX: cgImage.getBaseAddress(), config: config,
19 | originWidth: cgImage.width, originHeight: cgImage.height, stride: cgImage.bytesPerRow)
20 | }
21 |
22 | public func encode(BGR cgImage: CGImage, config: WebPEncoderConfig, resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
23 | return try encode(BGR: cgImage.getBaseAddress(), config: config,
24 | originWidth: cgImage.width, originHeight: cgImage.height, stride: cgImage.bytesPerRow)
25 | }
26 |
27 | public func encode(BGRA cgImage: CGImage, config: WebPEncoderConfig, resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
28 | return try encode(BGRA: cgImage.getBaseAddress(), config: config,
29 | originWidth: cgImage.width, originHeight: cgImage.height, stride: cgImage.bytesPerRow)
30 | }
31 |
32 | public func encode(BGRX cgImage: CGImage, config: WebPEncoderConfig, resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
33 | return try encode(BGRX: cgImage.getBaseAddress(), config: config,
34 | originWidth: cgImage.width, originHeight: cgImage.height, stride: cgImage.bytesPerRow)
35 | }
36 | }
37 |
38 | #endif
39 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPEncoder+Platform.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if os(macOS)
4 | import AppKit
5 | import CoreGraphics
6 |
7 | extension WebPEncoder {
8 | public func encode(_ image: NSImage, config: WebPEncoderConfig, width: Int = 0, height: Int = 0) throws -> Data {
9 | guard let data = image.tiffRepresentation else {
10 | throw WebPError.unexpectedError(withMessage: "Given image doesn't support TIFF representation.")
11 | }
12 | guard let bitmapData = NSBitmapImageRep(data: data)?.bitmapData else {
13 | throw WebPError.unexpectedError(withMessage: "NSBitmapImageRep couldn't interpret given image.")
14 | }
15 |
16 | let stride = Int(image.size.width) * MemoryLayout.size * 3 // RGB = 3byte
17 | let webPData = try encode(RGB: bitmapData, config: config,
18 | originWidth: Int(image.size.width), originHeight: Int(image.size.height), stride: stride,
19 | resizeWidth: width, resizeHeight: height)
20 | return webPData
21 | }
22 | }
23 | #endif
24 |
25 | #if os(iOS)
26 | import UIKit
27 | import CoreGraphics
28 |
29 | extension WebPEncoder {
30 | public func encode(_ image: UIImage, config: WebPEncoderConfig, width: Int = 0, height: Int = 0) throws -> Data {
31 | let cgImage = try convertUIImageToCGImageWithRGBA(image)
32 | let stride = cgImage.bytesPerRow
33 | let webPData = try encode(RGBA: cgImage.getBaseAddress(), config: config,
34 | originWidth: Int(image.size.width), originHeight: Int(image.size.height), stride: stride,
35 | resizeWidth: width, resizeHeight: height)
36 | return webPData
37 | }
38 |
39 | private func convertUIImageToCGImageWithRGBA(_ image: UIImage) throws -> CGImage {
40 | guard let inputCGImage = image.cgImage else {
41 | throw WebPError.unexpectedError(withMessage: "")
42 | }
43 |
44 | let colorSpace = CGColorSpaceCreateDeviceRGB()
45 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
46 |
47 | guard let context = CGContext(data: nil, width: Int(image.size.width), height: Int(image.size.height),
48 | bitsPerComponent: 8, bytesPerRow: Int(image.size.width) * 4,
49 | space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
50 | throw WebPError.unexpectedError(withMessage: "Couldn't initialize CGContext")
51 | }
52 |
53 | context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
54 | guard let cgImage = context.makeImage() else {
55 | throw WebPError.unexpectedError(withMessage: "Couldn't ")
56 | }
57 |
58 | return cgImage
59 | }
60 | }
61 |
62 | #endif
63 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPEncoder.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import libwebp
3 |
4 | // This is customised error that describes the pattern of error causes.
5 | // However, the error is unlikely to happen normally but it's still better to handle with throw-catch than fatal error.
6 | public enum WebPEncoderError: Error {
7 | case invalidParameter
8 | case versionMismatched
9 | }
10 |
11 | // This is the mapped error codes that CWebP.WebPEncode returns
12 | public enum WebPEncodeStatusCode: Int, Error {
13 | case ok = 0
14 | case outOfMemory // memory error allocating objects
15 | case bitstreamOutOfMemory // memory error while flushing bits
16 | case nullParameter // a pointer parameter is NULL
17 | case invalidConfiguration // configuration is invalid
18 | case badDimension // picture has invalid width/height
19 | case partition0Overflow // partition is bigger than 512k
20 | case partitionOverflow // partition is bigger than 16M
21 | case badWrite // error while flushing bytes
22 | case fileTooBig // file is bigger than 4G
23 | case userAbort // abort request by user
24 | case last // list terminator. always last.
25 | }
26 |
27 | public struct WebPEncoder {
28 | typealias WebPPictureImporter = (UnsafeMutablePointer, UnsafeMutablePointer, Int32) -> Int32
29 |
30 | public init() {
31 | }
32 |
33 | public func encode(RGB: UnsafeMutablePointer, config: WebPEncoderConfig,
34 | originWidth: Int, originHeight: Int, stride: Int,
35 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
36 | let importer: WebPPictureImporter = { picturePtr, data, stride in
37 | return WebPPictureImportRGB(picturePtr, data, stride)
38 | }
39 | return try encode(RGB, importer: importer, config: config, originWidth: originWidth, originHeight: originHeight, stride: stride)
40 | }
41 |
42 | public func encode(RGBA: UnsafeMutablePointer, config: WebPEncoderConfig,
43 | originWidth: Int, originHeight: Int, stride: Int,
44 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
45 | let importer: WebPPictureImporter = { picturePtr, data, stride in
46 | return WebPPictureImportRGBA(picturePtr, data, stride)
47 | }
48 | return try encode(RGBA, importer: importer, config: config, originWidth: originWidth, originHeight: originHeight, stride: stride)
49 | }
50 |
51 | public func encode(RGBX: UnsafeMutablePointer, config: WebPEncoderConfig,
52 | originWidth: Int, originHeight: Int, stride: Int,
53 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
54 | let importer: WebPPictureImporter = { picturePtr, data, stride in
55 | return WebPPictureImportRGBX(picturePtr, data, stride)
56 | }
57 | return try encode(RGBX, importer: importer, config: config, originWidth: originWidth, originHeight: originHeight, stride: stride)
58 | }
59 |
60 | public func encode(BGR: UnsafeMutablePointer, config: WebPEncoderConfig,
61 | originWidth: Int, originHeight: Int, stride: Int,
62 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
63 | let importer: WebPPictureImporter = { picturePtr, data, stride in
64 | return WebPPictureImportBGR(picturePtr, data, stride)
65 | }
66 | return try encode(BGR, importer: importer, config: config, originWidth: originWidth, originHeight: originHeight, stride: stride)
67 | }
68 |
69 | public func encode(BGRA: UnsafeMutablePointer, config: WebPEncoderConfig,
70 | originWidth: Int, originHeight: Int, stride: Int,
71 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
72 | let importer: WebPPictureImporter = { picturePtr, data, stride in
73 | return WebPPictureImportBGRA(picturePtr, data, stride)
74 | }
75 | return try encode(BGRA, importer: importer, config: config, originWidth: originWidth, originHeight: originHeight, stride: stride)
76 | }
77 |
78 | public func encode(BGRX: UnsafeMutablePointer, config: WebPEncoderConfig,
79 | originWidth: Int, originHeight: Int, stride: Int,
80 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
81 | let importer: WebPPictureImporter = { picturePtr, data, stride in
82 | return WebPPictureImportBGRX(picturePtr, data, stride)
83 | }
84 | return try encode(BGRX, importer: importer, config: config, originWidth: originWidth, originHeight: originHeight, stride: stride)
85 | }
86 |
87 | private func encode(_ dataPtr: UnsafeMutablePointer, importer: WebPPictureImporter,
88 | config: WebPEncoderConfig, originWidth: Int, originHeight: Int, stride: Int,
89 | resizeWidth: Int = 0, resizeHeight: Int = 0) throws -> Data {
90 | var config = config.rawValue
91 | if WebPValidateConfig(&config) == 0 {
92 | throw WebPEncoderError.invalidParameter
93 | }
94 |
95 | var picture = WebPPicture()
96 | if WebPPictureInit(&picture) == 0 {
97 | throw WebPEncoderError.invalidParameter
98 | }
99 |
100 | picture.use_argb = config.lossless == 0 ? 0 : 1
101 | picture.width = Int32(originWidth)
102 | picture.height = Int32(originHeight)
103 |
104 | let ok = importer(&picture, dataPtr, Int32(stride))
105 | if ok == 0 {
106 | WebPPictureFree(&picture)
107 | throw WebPEncoderError.versionMismatched
108 | }
109 |
110 | if resizeHeight > 0 && resizeWidth > 0 {
111 | if (WebPPictureRescale(&picture, Int32(resizeWidth), Int32(resizeHeight)) == 0) {
112 | throw WebPEncodeStatusCode.outOfMemory
113 | }
114 | }
115 |
116 | var buffer = WebPMemoryWriter()
117 | WebPMemoryWriterInit(&buffer)
118 | let writeWebP: @convention(c) (UnsafePointer?, Int, UnsafePointer?) -> Int32 = { (data, size, picture) -> Int32 in
119 | return WebPMemoryWrite(data, size, picture)
120 | }
121 | picture.writer = writeWebP
122 |
123 | try withUnsafeMutableBytes(of: &buffer) { ptr in
124 | picture.custom_ptr = ptr.baseAddress
125 |
126 | if WebPEncode(&config, &picture) == 0 {
127 | WebPPictureFree(&picture)
128 |
129 | let error = WebPEncodeStatusCode(rawValue: Int(picture.error_code.rawValue))!
130 | throw error
131 | }
132 | }
133 |
134 | WebPPictureFree(&picture)
135 |
136 | return Data(bytesNoCopy: buffer.mem, count: buffer.size, deallocator: .free)
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPEncoderConfig.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import libwebp
3 |
4 | extension libwebp.WebPImageHint: ExpressibleByIntegerLiteral {
5 | /// Create an instance initialized to `value`.
6 | public init(integerLiteral value: Int) {
7 | switch UInt32(value) {
8 | case libwebp.WEBP_HINT_DEFAULT.rawValue:
9 | self = libwebp.WEBP_HINT_DEFAULT
10 | case libwebp.WEBP_HINT_PICTURE.rawValue:
11 | self = libwebp.WEBP_HINT_PICTURE
12 | case libwebp.WEBP_HINT_PHOTO.rawValue:
13 | self = libwebp.WEBP_HINT_PHOTO
14 | case libwebp.WEBP_HINT_GRAPH.rawValue:
15 | self = libwebp.WEBP_HINT_GRAPH
16 | default:
17 | fatalError()
18 | }
19 | }
20 | }
21 |
22 |
23 | // mapping from libwebp.WebPConfig
24 | public struct WebPEncoderConfig: InternalRawRepresentable {
25 | public enum WebPImageHint: libwebp.WebPImageHint {
26 | case `default` = 0
27 | case picture = 1
28 | case photo = 2
29 | case graph = 3
30 | }
31 |
32 | // Lossless encoding (0=lossy(default), 1=lossless).
33 | public var lossless: Int = 0
34 |
35 | // between 0 (smallest file) and 100 (biggest)
36 | public var quality: Float
37 |
38 | // quality/speed trade-off (0=fast, 6=slower-better)
39 | public var method: Int
40 |
41 | // Hint for image type (lossless only for now).
42 | public var imageHint: WebPImageHint = .default
43 |
44 | // Parameters related to lossy compression only:
45 |
46 | // if non-zero, set the desired target size in bytes.
47 | // Takes precedence over the 'compression' parameter.
48 | public var targetSize: Int = 0
49 |
50 | // if non-zero, specifies the minimal distortion to
51 | // try to achieve. Takes precedence over target_size.
52 | public var targetPSNR: Float = 0
53 |
54 | // maximum number of segments to use, in [1..4]
55 | public var segments: Int
56 |
57 | // Spatial Noise Shaping. 0=off, 100=maximum.
58 | public var snsStrength: Int
59 |
60 | // range: [0 = off .. 100 = strongest]
61 | public var filterStrength: Int
62 |
63 | // range: [0 = off .. 7 = least sharp]
64 | public var filterSharpness: Int
65 |
66 | // filtering type: 0 = simple, 1 = strong (only used
67 | // if filter_strength > 0 or autofilter > 0)
68 | public var filterType: Int
69 |
70 | // Auto adjust filter's strength [0 = off, 1 = on]
71 | public var autofilter: Int
72 |
73 | // Algorithm for encoding the alpha plane (0 = none,
74 | // 1 = compressed with WebP lossless). Default is 1.
75 | public var alphaCompression: Int = 1
76 |
77 | // Predictive filtering method for alpha plane.
78 | // 0: none, 1: fast, 2: best. Default if 1.
79 | public var alphaFiltering: Int
80 |
81 | // Between 0 (smallest size) and 100 (lossless).
82 | // Default is 100.
83 | public var alphaQuality: Int = 100
84 |
85 | // number of entropy-analysis passes (in [1..10]).
86 | public var pass: Int
87 |
88 | // if true, export the compressed picture back.
89 | // In-loop filtering is not applied.
90 | public var showCompressed: Bool
91 |
92 | // preprocessing filter:
93 | // 0=none, 1=segment-smooth, 2=pseudo-random dithering
94 | public var preprocessing: Int
95 |
96 | // log2(number of token partitions) in [0..3]. Default
97 | // is set to 0 for easier progressive decoding.
98 | public var partitions: Int = 0
99 |
100 | // quality degradation allowed to fit the 512k limit
101 | // on prediction modes coding (0: no degradation,
102 | // 100: maximum possible degradation).
103 | public var partitionLimit: Int
104 |
105 | // If true, compression parameters will be remapped
106 | // to better match the expected output size from
107 | // JPEG compression. Generally, the output size will
108 | // be similar but the degradation will be lower.
109 | public var emulateJpegSize: Bool
110 |
111 | // If non-zero, try and use multi-threaded encoding.
112 | public var threadLevel: Int
113 |
114 | // If set, reduce memory usage (but increase CPU use).
115 | public var lowMemory: Bool
116 |
117 | // Near lossless encoding [0 = max loss .. 100 = off
118 | // Int(default)].
119 | public var nearLossless: Int = 100
120 |
121 | // if non-zero, preserve the exact RGB values under
122 | // transparent area. Otherwise, discard this invisible
123 | // RGB information for better compression. The default
124 | // value is 0.
125 | public var exact: Int
126 |
127 | public var qmin: Int = 0
128 | public var qmax: Int = 100
129 |
130 | // reserved for future lossless feature
131 | var useDeltaPalette: Bool
132 | // if needed, use sharp (and slow) RGB->YUV conversion
133 | var useSharpYUV: Bool
134 |
135 | static public func preset(_ preset: Preset, quality: Float) -> WebPEncoderConfig {
136 | let webPConfig = preset.webPConfig(quality: quality)
137 | return WebPEncoderConfig(rawValue: webPConfig)!
138 | }
139 |
140 | internal init?(rawValue: libwebp.WebPConfig) {
141 | lossless = Int(rawValue.lossless)
142 | quality = rawValue.quality
143 | method = Int(rawValue.method)
144 | imageHint = WebPImageHint(rawValue: rawValue.image_hint)!
145 | targetSize = Int(rawValue.target_size)
146 | targetPSNR = Float(rawValue.target_PSNR)
147 | segments = Int(rawValue.segments)
148 | snsStrength = Int(rawValue.sns_strength)
149 | filterStrength = Int(rawValue.filter_strength)
150 | filterSharpness = Int(rawValue.filter_sharpness)
151 | filterType = Int(rawValue.filter_type)
152 | autofilter = Int(rawValue.autofilter)
153 | alphaCompression = Int(rawValue.alpha_compression)
154 | alphaFiltering = Int(rawValue.alpha_filtering)
155 | alphaQuality = Int(rawValue.alpha_quality)
156 | pass = Int(rawValue.pass)
157 | showCompressed = rawValue.show_compressed != 0 ? true : false
158 | preprocessing = Int(rawValue.preprocessing)
159 | partitions = Int(rawValue.partitions)
160 | partitionLimit = Int(rawValue.partition_limit)
161 | emulateJpegSize = rawValue.emulate_jpeg_size != 0 ? true : false
162 | threadLevel = Int(rawValue.thread_level)
163 | lowMemory = rawValue.low_memory != 0 ? true : false
164 | nearLossless = Int(rawValue.near_lossless)
165 | exact = Int(rawValue.exact)
166 | useDeltaPalette = rawValue.use_delta_palette != 0 ? true : false
167 | useSharpYUV = rawValue.use_sharp_yuv != 0 ? true : false
168 | qmin = Int(rawValue.qmin)
169 | qmax = Int(rawValue.qmax)
170 | }
171 |
172 | internal var rawValue: libwebp.WebPConfig {
173 | let show_compressed = showCompressed ? Int32(1) : Int32(0)
174 | let emulate_jpeg_size = emulateJpegSize ? Int32(1) : Int32(0)
175 | let low_memory = lowMemory ? Int32(1) : Int32(0)
176 | let use_delta_palette = useDeltaPalette ? Int32(1) : Int32(0)
177 | let use_sharp_yuv = useSharpYUV ? Int32(1) : Int32(0)
178 |
179 | return libwebp.WebPConfig(
180 | lossless: Int32(lossless),
181 | quality: Float(quality),
182 | method: Int32(method),
183 | image_hint: imageHint.rawValue,
184 | target_size: Int32(targetSize),
185 | target_PSNR: Float(targetPSNR),
186 | segments: Int32(segments),
187 | sns_strength: Int32(snsStrength),
188 | filter_strength: Int32(filterStrength),
189 | filter_sharpness: Int32(filterSharpness),
190 | filter_type: Int32(filterType),
191 | autofilter: Int32(autofilter),
192 | alpha_compression: Int32(alphaCompression),
193 | alpha_filtering: Int32(alphaFiltering),
194 | alpha_quality: Int32(alphaQuality),
195 | pass: Int32(pass),
196 | show_compressed: show_compressed,
197 | preprocessing: Int32(preprocessing),
198 | partitions: Int32(partitions),
199 | partition_limit: Int32(partitionLimit),
200 | emulate_jpeg_size: emulate_jpeg_size,
201 | thread_level: Int32(threadLevel),
202 | low_memory: low_memory,
203 | near_lossless: Int32(nearLossless),
204 | exact: Int32(exact),
205 | use_delta_palette: Int32(use_delta_palette),
206 | use_sharp_yuv: Int32(use_sharp_yuv),
207 | qmin: Int32(qmin),
208 | qmax: Int32(qmax)
209 | )
210 | }
211 |
212 | public enum Preset {
213 | case `default`, picture, photo, drawing, icon, text
214 |
215 | func webPConfig(quality: Float) -> libwebp.WebPConfig {
216 | var config = libwebp.WebPConfig()
217 |
218 | switch self {
219 | case .default:
220 | WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality)
221 | case .picture:
222 | WebPConfigPreset(&config, WEBP_PRESET_PICTURE, quality)
223 | case .photo:
224 | WebPConfigPreset(&config, WEBP_PRESET_PHOTO, quality)
225 | case .drawing:
226 | WebPConfigPreset(&config, WEBP_PRESET_DRAWING, quality)
227 | case .icon:
228 | WebPConfigPreset(&config, WEBP_PRESET_ICON, quality)
229 | case .text:
230 | WebPConfigPreset(&config, WEBP_PRESET_TEXT, quality)
231 | }
232 |
233 | return config
234 | }
235 | }
236 |
237 | }
238 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum WebPError: Error {
4 | case unexpectedPointerError // Something related pointer operation's error
5 | case unexpectedError(withMessage: String) // Something happened
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/WebP/WebPImageInspector.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import libwebp
3 |
4 | public struct WebPImageInspector {
5 |
6 | public static func inspect(_ webPData: Data) throws -> WebPBitstreamFeatures {
7 | let cFeature = UnsafeMutablePointer.allocate(capacity: 1)
8 | defer { cFeature.deallocate() }
9 |
10 | let status = try webPData.withUnsafeBytes { rawPtr -> VP8StatusCode in
11 | guard let bindedBasePtr = rawPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
12 | throw WebPError.unexpectedPointerError
13 | }
14 | return WebPGetFeatures(bindedBasePtr, webPData.count, cFeature)
15 | }
16 |
17 | guard status == VP8_STATUS_OK else {
18 | throw WebPError.unexpectedError(withMessage: "Error VP8StatusCode=\(status.rawValue)")
19 | }
20 |
21 | guard let feature = WebPBitstreamFeatures(rawValue: cFeature.pointee) else {
22 | throw WebPError.unexpectedPointerError
23 | }
24 |
25 | return feature
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Tests/WebPTests/ResourceAccessHelper.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct ResourceAccessHelper {
4 | static func getExamplImagePath(of filename: String = "jiro.jpg") -> String {
5 | let currentFileURL = URL(fileURLWithPath: String(#file))
6 | return currentFileURL.deletingLastPathComponent()
7 | .deletingLastPathComponent()
8 | .deletingLastPathComponent()
9 | .appendingPathComponent("iOS Example")
10 | .appendingPathComponent("Resources")
11 | .appendingPathComponent(filename, isDirectory: false)
12 | .path
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Tests/WebPTests/Resources/jiro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ainame/Swift-WebP/c7c0adebb374493a665136d98198b11437045fb4/Tests/WebPTests/Resources/jiro.jpg
--------------------------------------------------------------------------------
/Tests/WebPTests/WebPEncoderCGImageTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | #if canImport(CoreGraphics) && canImport(CoreImage)
5 | import CoreGraphics
6 | import CoreImage
7 |
8 | @testable import WebP
9 |
10 | class WebPEncoderCGImageTests: XCTestCase {
11 | func testRGBAImageFromCGImage() throws {
12 | let imagePath = ResourceAccessHelper.getExamplImagePath()
13 |
14 | guard FileManager.default.fileExists(atPath: imagePath) else {
15 | XCTFail("Image couldn't be found at \(imagePath)")
16 | return
17 | }
18 |
19 | let inputURL = URL(fileURLWithPath: imagePath)
20 | let cgSource = CGImageSourceCreateWithURL(inputURL as CFURL, nil)
21 | let inputCGImage = CGImageSourceCreateImageAtIndex(cgSource!, 0, nil)
22 | let ciImage = CIImage(cgImage: inputCGImage!)
23 | let context = CIContext()
24 | let cgImage = context.createCGImage(
25 | ciImage,
26 | from: ciImage.extent,
27 | format: CIFormat.RGBA8,
28 | colorSpace: CGColorSpace(name: CGColorSpace.extendedSRGB)!
29 | )!
30 |
31 | let encoder = WebPEncoder()
32 | let data = try encoder.encode(RGBA: cgImage, config: .preset(.photo, quality: 90))
33 | XCTAssertTrue(data.count > 0)
34 |
35 | let decoder = WebPDecoder()
36 | var options = WebPDecoderOptions()
37 | options.scaledWidth = Int(cgImage.width)
38 | options.scaledHeight = Int(cgImage.height)
39 | options.useScaling = true
40 | let decodedImage = try decoder.decode(data, options: options)
41 | XCTAssertEqual(decodedImage.width, options.scaledWidth)
42 | XCTAssertEqual(decodedImage.height, options.scaledHeight)
43 | }
44 | }
45 |
46 | #endif
47 |
--------------------------------------------------------------------------------
/Tests/WebPTests/WebPEncoderIOSTests.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS)
2 | import XCTest
3 | import Foundation
4 | import UIKit
5 | @testable import WebP
6 |
7 | class WebPEncoderIOSTests: XCTestCase {
8 |
9 | override func setUp() {
10 | super.setUp()
11 | }
12 |
13 | override func tearDown() {
14 | super.tearDown()
15 | }
16 |
17 | func testExample() throws {
18 | let encoder = WebPEncoder()
19 |
20 | let path = Bundle.module.url(forResource: "jiro", withExtension: "jpg")!
21 | let uiimage = UIImage(contentsOfFile: path.path)!
22 | let data = try encoder.encode(uiimage, config: .preset(.photo, quality: 100))
23 | XCTAssertTrue(data.count > 0)
24 |
25 | let decoder = WebPDecoder()
26 | var options = WebPDecoderOptions()
27 | options.useScaling = true
28 | options.scaledWidth = Int(uiimage.size.width)
29 | options.scaledHeight = Int(uiimage.size.height)
30 | let decodedImage = try decoder.decode(data, options: options)
31 | XCTAssertEqual(decodedImage.width, options.scaledWidth)
32 | XCTAssertEqual(decodedImage.height, options.scaledHeight)
33 | }
34 |
35 | }
36 | #endif
37 |
--------------------------------------------------------------------------------
/Tests/WebPTests/WebPEncoderMacOSTests.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import XCTest
3 | import Foundation
4 | import AppKit
5 | @testable import WebP
6 |
7 | class WebPEncoderMacOSTests: XCTestCase {
8 | override func setUp() {
9 | super.setUp()
10 | // Put setup code here. This method is called before the invocation of each test method in the class.
11 | }
12 |
13 | override func tearDown() {
14 | // Put teardown code here. This method is called after the invocation of each test method in the class.
15 | super.tearDown()
16 | }
17 |
18 | func testExample() throws {
19 | let imagePath = ResourceAccessHelper.getExamplImagePath()
20 |
21 | guard FileManager.default.fileExists(atPath: imagePath) else {
22 | XCTFail("Image couldn't be found at \(imagePath)")
23 | return
24 | }
25 |
26 | let nsImage = NSImage(contentsOfFile: imagePath)!
27 | let encoder = WebPEncoder()
28 | let data = try encoder.encode(nsImage, config: .preset(.photo, quality: 10))
29 | XCTAssertTrue(data.count > 0)
30 |
31 | let decoder = WebPDecoder()
32 | var options = WebPDecoderOptions()
33 | options.scaledWidth = Int(nsImage.size.width)
34 | options.scaledHeight = Int(nsImage.size.height)
35 | options.useScaling = true
36 | let decodedImage = try decoder.decode(data, options: options)
37 | XCTAssertEqual(decodedImage.width, options.scaledWidth)
38 | XCTAssertEqual(decodedImage.height, options.scaledHeight)
39 | }
40 | }
41 | #endif
42 |
--------------------------------------------------------------------------------
/Tests/WebPTests/WebPImageInspectorTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import WebP
3 | import XCTest
4 |
5 | class WebPImageInspectorTests: XCTestCase {
6 | enum WebPImageInspectorTestError: Error {
7 | case cantReadTestData(String)
8 | }
9 |
10 | func testInspectWebPImageHeightAndWidth() throws {
11 | let path = ResourceAccessHelper.getExamplImagePath(of: "jiro.webp")
12 | guard let data = FileManager.default.contents(atPath: path) else {
13 | throw WebPImageInspectorTestError.cantReadTestData(path)
14 | }
15 | let feature = try WebPImageInspector.inspect(data)
16 | XCTAssert(feature.width > 0)
17 | XCTAssert(feature.height > 0)
18 | XCTAssertFalse(feature.hasAlpha)
19 | XCTAssertFalse(feature.hasAnimation)
20 | }
21 |
22 | func testInspectingJpegImageThrowsError() throws {
23 | let path = ResourceAccessHelper.getExamplImagePath(of: "jiro.jpg")
24 | guard let data = FileManager.default.contents(atPath: path) else {
25 | throw WebPImageInspectorTestError.cantReadTestData(path)
26 | }
27 | XCTAssertThrowsError(try WebPImageInspector.inspect(data)) { _error in
28 | if let error = _error as? WebPError,
29 | case .unexpectedError(let _) = error {
30 | XCTAssert(true)
31 | } else {
32 | XCTFail()
33 | }
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------