@amazon-devices/expo-image 2.0.1758683737
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +2 -0
- package/CHANGELOG.md +285 -0
- package/CMakeLists.txt +91 -0
- package/LICENSE +7 -0
- package/README.kepler.md +123 -0
- package/README.md +54 -0
- package/THIRD_PARTY_NOTICES.txt +24 -0
- package/android/build.gradle +124 -0
- package/android/proguard-rules.pro +28 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/expo/modules/image/Exceptions.kt +9 -0
- package/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt +19 -0
- package/android/src/main/java/expo/modules/image/ExpoImageModule.kt +192 -0
- package/android/src/main/java/expo/modules/image/ExpoImageView.kt +220 -0
- package/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt +652 -0
- package/android/src/main/java/expo/modules/image/GlideModel.kt +73 -0
- package/android/src/main/java/expo/modules/image/ImageUtils.kt +50 -0
- package/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt +313 -0
- package/android/src/main/java/expo/modules/image/ResourceIdHelper.kt +47 -0
- package/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt +21 -0
- package/android/src/main/java/expo/modules/image/YogaUtils.kt +20 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt +32 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt +189 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt +30 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt +14 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt +17 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt +27 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt +24 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt +12 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt +16 -0
- package/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt +201 -0
- package/android/src/main/java/expo/modules/image/enums/ContentFit.kt +89 -0
- package/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt +14 -0
- package/android/src/main/java/expo/modules/image/enums/Priority.kt +15 -0
- package/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt +64 -0
- package/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt +26 -0
- package/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt +80 -0
- package/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt +55 -0
- package/android/src/main/java/expo/modules/image/records/CachePolicy.kt +10 -0
- package/android/src/main/java/expo/modules/image/records/ContentPosition.kt +89 -0
- package/android/src/main/java/expo/modules/image/records/ImageTransition.kt +8 -0
- package/android/src/main/java/expo/modules/image/records/SourceMap.kt +160 -0
- package/android/src/main/java/expo/modules/image/records/events.kt +25 -0
- package/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt +33 -0
- package/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt +37 -0
- package/android/src/main/java/expo/modules/image/svg/SVGModule.kt +26 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt +365 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt +31 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt +26 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt +14 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt +17 -0
- package/build/ExpoImage.d.ts +15 -0
- package/build/ExpoImage.d.ts.map +1 -0
- package/build/ExpoImage.js +75 -0
- package/build/ExpoImage.js.map +1 -0
- package/build/ExpoImage.kepler.d.ts +23 -0
- package/build/ExpoImage.kepler.d.ts.map +1 -0
- package/build/ExpoImage.kepler.js +71 -0
- package/build/ExpoImage.kepler.js.map +1 -0
- package/build/ExpoImage.web.d.ts +9 -0
- package/build/ExpoImage.web.d.ts.map +1 -0
- package/build/ExpoImage.web.js +90 -0
- package/build/ExpoImage.web.js.map +1 -0
- package/build/ExpoImageModules.d.ts +3 -0
- package/build/ExpoImageModules.d.ts.map +1 -0
- package/build/ExpoImageModules.js +4 -0
- package/build/ExpoImageModules.js.map +1 -0
- package/build/ExpoImageModules.kepler.d.ts +6 -0
- package/build/ExpoImageModules.kepler.d.ts.map +1 -0
- package/build/ExpoImageModules.kepler.js +5 -0
- package/build/ExpoImageModules.kepler.js.map +1 -0
- package/build/Image.d.ts +30 -0
- package/build/Image.d.ts.map +1 -0
- package/build/Image.js +49 -0
- package/build/Image.js.map +1 -0
- package/build/Image.types.d.ts +374 -0
- package/build/Image.types.d.ts.map +1 -0
- package/build/Image.types.js +2 -0
- package/build/Image.types.js.map +1 -0
- package/build/ImageBackground.d.ts +6 -0
- package/build/ImageBackground.d.ts.map +1 -0
- package/build/ImageBackground.js +13 -0
- package/build/ImageBackground.js.map +1 -0
- package/build/ImageViewNativeComponent.d.ts +20 -0
- package/build/ImageViewNativeComponent.d.ts.map +1 -0
- package/build/ImageViewNativeComponent.js +42 -0
- package/build/ImageViewNativeComponent.js.map +1 -0
- package/build/NativeImageLoaderKepler.d.ts +18 -0
- package/build/NativeImageLoaderKepler.d.ts.map +1 -0
- package/build/NativeImageLoaderKepler.js +3 -0
- package/build/NativeImageLoaderKepler.js.map +1 -0
- package/build/fabric/registerNativeComponent.d.ts +15 -0
- package/build/fabric/registerNativeComponent.d.ts.map +1 -0
- package/build/fabric/registerNativeComponent.js +47 -0
- package/build/fabric/registerNativeComponent.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/utils/AssetSourceResolver.web.d.ts +24 -0
- package/build/utils/AssetSourceResolver.web.d.ts.map +1 -0
- package/build/utils/AssetSourceResolver.web.js +67 -0
- package/build/utils/AssetSourceResolver.web.js.map +1 -0
- package/build/utils/blurhash/base83.d.ts +3 -0
- package/build/utils/blurhash/base83.d.ts.map +1 -0
- package/build/utils/blurhash/base83.js +103 -0
- package/build/utils/blurhash/base83.js.map +1 -0
- package/build/utils/blurhash/decode.d.ts +7 -0
- package/build/utils/blurhash/decode.d.ts.map +1 -0
- package/build/utils/blurhash/decode.js +92 -0
- package/build/utils/blurhash/decode.js.map +1 -0
- package/build/utils/blurhash/error.d.ts +4 -0
- package/build/utils/blurhash/error.d.ts.map +1 -0
- package/build/utils/blurhash/error.js +8 -0
- package/build/utils/blurhash/error.js.map +1 -0
- package/build/utils/blurhash/useBlurhash.d.ts +8 -0
- package/build/utils/blurhash/useBlurhash.d.ts.map +1 -0
- package/build/utils/blurhash/useBlurhash.js +64 -0
- package/build/utils/blurhash/useBlurhash.js.map +1 -0
- package/build/utils/blurhash/utils.d.ts +5 -0
- package/build/utils/blurhash/utils.d.ts.map +1 -0
- package/build/utils/blurhash/utils.js +21 -0
- package/build/utils/blurhash/utils.js.map +1 -0
- package/build/utils/resolveAssetSource.d.ts +3 -0
- package/build/utils/resolveAssetSource.d.ts.map +1 -0
- package/build/utils/resolveAssetSource.js +3 -0
- package/build/utils/resolveAssetSource.js.map +1 -0
- package/build/utils/resolveAssetSource.web.d.ts +9 -0
- package/build/utils/resolveAssetSource.web.d.ts.map +1 -0
- package/build/utils/resolveAssetSource.web.js +31 -0
- package/build/utils/resolveAssetSource.web.js.map +1 -0
- package/build/utils/resolveHashString.d.ts +14 -0
- package/build/utils/resolveHashString.d.ts.map +1 -0
- package/build/utils/resolveHashString.js +31 -0
- package/build/utils/resolveHashString.js.map +1 -0
- package/build/utils/resolveHashString.web.d.ts +20 -0
- package/build/utils/resolveHashString.web.d.ts.map +1 -0
- package/build/utils/resolveHashString.web.js +31 -0
- package/build/utils/resolveHashString.web.js.map +1 -0
- package/build/utils/resolveSources.d.ts +8 -0
- package/build/utils/resolveSources.d.ts.map +1 -0
- package/build/utils/resolveSources.js +45 -0
- package/build/utils/resolveSources.js.map +1 -0
- package/build/utils/thumbhash/thumbhash.d.ts +66 -0
- package/build/utils/thumbhash/thumbhash.d.ts.map +1 -0
- package/build/utils/thumbhash/thumbhash.js +338 -0
- package/build/utils/thumbhash/thumbhash.js.map +1 -0
- package/build/utils.d.ts +17 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +85 -0
- package/build/utils.js.map +1 -0
- package/build/web/AnimationManager.d.ts +37 -0
- package/build/web/AnimationManager.d.ts.map +1 -0
- package/build/web/AnimationManager.js +155 -0
- package/build/web/AnimationManager.js.map +1 -0
- package/build/web/ColorTintFilter.d.ts +12 -0
- package/build/web/ColorTintFilter.d.ts.map +1 -0
- package/build/web/ColorTintFilter.js +30 -0
- package/build/web/ColorTintFilter.js.map +1 -0
- package/build/web/ImageWrapper.d.ts +5 -0
- package/build/web/ImageWrapper.d.ts.map +1 -0
- package/build/web/ImageWrapper.js +44 -0
- package/build/web/ImageWrapper.js.map +1 -0
- package/build/web/ImageWrapper.types.d.ts +29 -0
- package/build/web/ImageWrapper.types.d.ts.map +1 -0
- package/build/web/ImageWrapper.types.js +2 -0
- package/build/web/ImageWrapper.types.js.map +1 -0
- package/build/web/getImageWrapperEventHandler.d.ts +8 -0
- package/build/web/getImageWrapperEventHandler.d.ts.map +1 -0
- package/build/web/getImageWrapperEventHandler.js +25 -0
- package/build/web/getImageWrapperEventHandler.js.map +1 -0
- package/build/web/hooks.d.ts +13 -0
- package/build/web/hooks.d.ts.map +1 -0
- package/build/web/hooks.js +58 -0
- package/build/web/hooks.js.map +1 -0
- package/build/web/imageStyles.d.ts +2 -0
- package/build/web/imageStyles.d.ts.map +1 -0
- package/build/web/imageStyles.js +137 -0
- package/build/web/imageStyles.js.map +1 -0
- package/build/web/positioning.d.ts +11 -0
- package/build/web/positioning.d.ts.map +1 -0
- package/build/web/positioning.js +35 -0
- package/build/web/positioning.js.map +1 -0
- package/build/web/useSourceSelection.d.ts +14 -0
- package/build/web/useSourceSelection.d.ts.map +1 -0
- package/build/web/useSourceSelection.js +98 -0
- package/build/web/useSourceSelection.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Blurhash.swift +326 -0
- package/ios/BlurhashLoader.swift +52 -0
- package/ios/ContentFit.swift +55 -0
- package/ios/ContentPosition.swift +108 -0
- package/ios/ExpoImage.podspec +30 -0
- package/ios/ImageCachePolicy.swift +24 -0
- package/ios/ImageCacheType.swift +21 -0
- package/ios/ImageModule.swift +127 -0
- package/ios/ImagePriority.swift +22 -0
- package/ios/ImageSource.swift +44 -0
- package/ios/ImageTransition.swift +67 -0
- package/ios/ImageUtils.swift +227 -0
- package/ios/ImageView.swift +469 -0
- package/ios/Loaders/PhotoLibraryAssetLoader.swift +146 -0
- package/ios/Thumbhash.swift +550 -0
- package/ios/ThumbhashLoader.swift +53 -0
- package/jest.config.js +3 -0
- package/kepler/ExpoImage.cpp +63 -0
- package/kepler/ExpoImage.h +29 -0
- package/kepler/ExpoImageCacheHandler.cpp +15 -0
- package/kepler/ExpoImageCacheHandler.h +28 -0
- package/kepler/ExpoImageComponent.cpp +579 -0
- package/kepler/ExpoImageComponent.h +199 -0
- package/kepler/ImageWrapper/BlurhashImageWrapper.cpp +37 -0
- package/kepler/ImageWrapper/BlurhashImageWrapper.h +23 -0
- package/kepler/ImageWrapper/CacheImageWrapper.cpp +80 -0
- package/kepler/ImageWrapper/CacheImageWrapper.h +41 -0
- package/kepler/ImageWrapper/HashImageWrapper.cpp +21 -0
- package/kepler/ImageWrapper/HashImageWrapper.h +22 -0
- package/kepler/ImageWrapper/ImageWrapper.cpp +24 -0
- package/kepler/ImageWrapper/ImageWrapper.h +33 -0
- package/kepler/ImageWrapper/LocalImageWrapper.cpp +61 -0
- package/kepler/ImageWrapper/LocalImageWrapper.h +30 -0
- package/kepler/ImageWrapper/ThumbhashImageWrapper.cpp +33 -0
- package/kepler/ImageWrapper/ThumbhashImageWrapper.h +20 -0
- package/kepler/KeplerImageViewComponent.h +64 -0
- package/kepler/KeplerInit.cpp +35 -0
- package/kepler/blurhash/common.h +32 -0
- package/kepler/blurhash/decode.cpp +187 -0
- package/kepler/blurhash/decode.h +58 -0
- package/kepler/contentposition/ImageContentPosition.cpp +86 -0
- package/kepler/contentposition/ImageContentPosition.h +48 -0
- package/kepler/enums/ImageContentFit.h +17 -0
- package/kepler/enums/ImagePriority.h +17 -0
- package/kepler/managers/ExpoImageManager.cpp +38 -0
- package/kepler/managers/ExpoImageManager.h +23 -0
- package/kepler/test/ExpoImage.cpp +73 -0
- package/kepler/test/ExpoImageComponent.cpp +290 -0
- package/kepler/test/blurhash/decode.cpp +39 -0
- package/kepler/test/contentposition/ImageContentPosition.cpp +100 -0
- package/kepler/test/managers/ExpoImageManager.cpp +39 -0
- package/kepler/test/thumbhash/decode.cpp +18 -0
- package/kepler/test/utils/LoaderDelegate.cpp +130 -0
- package/kepler/thumbhash/decode.cpp +166 -0
- package/kepler/thumbhash/decode.h +22 -0
- package/kepler/utils/PlaceholderImageLoaderDelegate.cpp +26 -0
- package/kepler/utils/PlaceholderImageLoaderDelegate.h +26 -0
- package/kepler/utils/PrefetchImageLoaderDelegate.cpp +55 -0
- package/kepler/utils/PrefetchImageLoaderDelegate.h +35 -0
- package/kepler/utils/SourceImageLoaderDelegate.cpp +47 -0
- package/kepler/utils/SourceImageLoaderDelegate.h +42 -0
- package/kepler/utils/SourceImageLoaderDelegateEmitter.cpp +15 -0
- package/kepler/utils/SourceImageLoaderDelegateEmitter.h +28 -0
- package/kepler-compatibility.json +8 -0
- package/package.json +1 -0
- package/react-native.config.js +22 -0
- package/src/ExpoImage.kepler.tsx +119 -0
- package/src/ExpoImage.tsx +119 -0
- package/src/ExpoImage.web.tsx +155 -0
- package/src/ExpoImageModules.kepler.ts +7 -0
- package/src/ExpoImageModules.ts +4 -0
- package/src/Image.tsx +82 -0
- package/src/Image.types.ts +450 -0
- package/src/ImageBackground.tsx +18 -0
- package/src/ImageViewNativeComponent.ts +59 -0
- package/src/NativeImageLoaderKepler.ts +22 -0
- package/src/fabric/registerNativeComponent.ts +70 -0
- package/src/index.ts +3 -0
- package/src/utils/AssetSourceResolver.web.ts +88 -0
- package/src/utils/blurhash/base83.ts +104 -0
- package/src/utils/blurhash/decode.ts +114 -0
- package/src/utils/blurhash/error.ts +7 -0
- package/src/utils/blurhash/useBlurhash.tsx +81 -0
- package/src/utils/blurhash/utils.ts +21 -0
- package/src/utils/resolveAssetSource.tsx +3 -0
- package/src/utils/resolveAssetSource.web.ts +40 -0
- package/src/utils/resolveHashString.tsx +36 -0
- package/src/utils/resolveHashString.web.tsx +33 -0
- package/src/utils/resolveSources.tsx +48 -0
- package/src/utils/thumbhash/thumbhash.ts +387 -0
- package/src/utils.ts +109 -0
- package/src/web/AnimationManager.tsx +224 -0
- package/src/web/ColorTintFilter.tsx +36 -0
- package/src/web/ImageWrapper.tsx +85 -0
- package/src/web/ImageWrapper.types.ts +36 -0
- package/src/web/getImageWrapperEventHandler.ts +29 -0
- package/src/web/hooks.ts +72 -0
- package/src/web/imageStyles.tsx +136 -0
- package/src/web/positioning.ts +48 -0
- package/src/web/useSourceSelection.ts +151 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// Blurhash implementation thanks to @evanw work
|
|
4
|
+
// https://github.com/evanw/thumbhash
|
|
5
|
+
|
|
6
|
+
// NOTE: Swift has an exponential-time type checker and compiling very simple
|
|
7
|
+
// expressions can easily take many seconds, especially when expressions involve
|
|
8
|
+
// numeric type constructors.
|
|
9
|
+
//
|
|
10
|
+
// This file deliberately breaks compound expressions up into separate variables
|
|
11
|
+
// to improve compile time even though this comes at the expense of readability.
|
|
12
|
+
// This is a known workaround for this deficiency in the Swift compiler.
|
|
13
|
+
//
|
|
14
|
+
// The following command is helpful when debugging Swift compile time issues:
|
|
15
|
+
//
|
|
16
|
+
// swiftc ThumbHash.swift -Xfrontend -debug-time-function-bodies
|
|
17
|
+
//
|
|
18
|
+
// These optimizations brought the compile time for this file from around 2.5
|
|
19
|
+
// seconds to around 250ms (10x faster).
|
|
20
|
+
// NOTE: Swift's debug-build performance of for-in loops over numeric ranges is
|
|
21
|
+
// really awful. Debug builds compile a very generic indexing iterator thing
|
|
22
|
+
// that makes many nested calls for every iteration, which makes debug-build
|
|
23
|
+
// performance crawl.
|
|
24
|
+
//
|
|
25
|
+
// This file deliberately avoids for-in loops that loop for more than a few
|
|
26
|
+
// times to improve debug-build run time even though this comes at the expense
|
|
27
|
+
// of readability. Similarly unsafe pointers are used instead of array getters
|
|
28
|
+
// to avoid unnecessary bounds checks, which have extra overhead in debug builds.
|
|
29
|
+
//
|
|
30
|
+
// These optimizations brought the run time to encode and decode 10 ThumbHashes
|
|
31
|
+
// in debug mode from 700ms to 70ms (10x faster).
|
|
32
|
+
|
|
33
|
+
// swiftlint:disable all
|
|
34
|
+
func rgbaToThumbHash(w: Int, h: Int, rgba: Data) -> Data {
|
|
35
|
+
// Encoding an image larger than 100x100 is slow with no benefit
|
|
36
|
+
assert(w <= 100 && h <= 100)
|
|
37
|
+
assert(rgba.count == w * h * 4)
|
|
38
|
+
|
|
39
|
+
// Determine the average color
|
|
40
|
+
var avg_r: Float32 = 0
|
|
41
|
+
var avg_g: Float32 = 0
|
|
42
|
+
var avg_b: Float32 = 0
|
|
43
|
+
var avg_a: Float32 = 0
|
|
44
|
+
rgba.withUnsafeBytes { rgba in
|
|
45
|
+
var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count)
|
|
46
|
+
let n = w * h
|
|
47
|
+
var i = 0
|
|
48
|
+
while i < n {
|
|
49
|
+
let alpha = Float32(rgba[3]) / 255
|
|
50
|
+
avg_r += alpha / 255 * Float32(rgba[0])
|
|
51
|
+
avg_g += alpha / 255 * Float32(rgba[1])
|
|
52
|
+
avg_b += alpha / 255 * Float32(rgba[2])
|
|
53
|
+
avg_a += alpha
|
|
54
|
+
rgba = rgba.advanced(by: 4)
|
|
55
|
+
i += 1
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if avg_a > 0 {
|
|
59
|
+
avg_r /= avg_a
|
|
60
|
+
avg_g /= avg_a
|
|
61
|
+
avg_b /= avg_a
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let hasAlpha = avg_a < Float32(w * h)
|
|
65
|
+
let l_limit = hasAlpha ? 5 : 7 // Use fewer luminance bits if there's alpha
|
|
66
|
+
let imax_wh = max(w, h)
|
|
67
|
+
let iwl_limit = l_limit * w
|
|
68
|
+
let ihl_limit = l_limit * h
|
|
69
|
+
let fmax_wh = Float32(imax_wh)
|
|
70
|
+
let fwl_limit = Float32(iwl_limit)
|
|
71
|
+
let fhl_limit = Float32(ihl_limit)
|
|
72
|
+
let flx = round(fwl_limit / fmax_wh)
|
|
73
|
+
let fly = round(fhl_limit / fmax_wh)
|
|
74
|
+
var lx = Int(flx)
|
|
75
|
+
var ly = Int(fly)
|
|
76
|
+
lx = max(1, lx)
|
|
77
|
+
ly = max(1, ly)
|
|
78
|
+
var lpqa = [Float32](repeating: 0, count: w * h * 4)
|
|
79
|
+
|
|
80
|
+
// Convert the image from RGBA to LPQA (composite atop the average color)
|
|
81
|
+
rgba.withUnsafeBytes { rgba in
|
|
82
|
+
lpqa.withUnsafeMutableBytes { lpqa in
|
|
83
|
+
var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count)
|
|
84
|
+
var lpqa = lpqa.baseAddress!.bindMemory(to: Float32.self, capacity: lpqa.count)
|
|
85
|
+
let n = w * h
|
|
86
|
+
var i = 0
|
|
87
|
+
while i < n {
|
|
88
|
+
let alpha = Float32(rgba[3]) / 255
|
|
89
|
+
let r = avg_r * (1 - alpha) + alpha / 255 * Float32(rgba[0])
|
|
90
|
+
let g = avg_g * (1 - alpha) + alpha / 255 * Float32(rgba[1])
|
|
91
|
+
let b = avg_b * (1 - alpha) + alpha / 255 * Float32(rgba[2])
|
|
92
|
+
lpqa[0] = (r + g + b) / 3
|
|
93
|
+
lpqa[1] = (r + g) / 2 - b
|
|
94
|
+
lpqa[2] = r - g
|
|
95
|
+
lpqa[3] = alpha
|
|
96
|
+
rgba = rgba.advanced(by: 4)
|
|
97
|
+
lpqa = lpqa.advanced(by: 4)
|
|
98
|
+
i += 1
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Encode using the DCT into DC (constant) and normalized AC (varying) terms
|
|
104
|
+
let encodeChannel = { (channel: UnsafePointer<Float32>, nx: Int, ny: Int) -> (Float32, [Float32], Float32) in
|
|
105
|
+
var dc: Float32 = 0
|
|
106
|
+
var ac: [Float32] = []
|
|
107
|
+
var scale: Float32 = 0
|
|
108
|
+
var fx = [Float32](repeating: 0, count: w)
|
|
109
|
+
fx.withUnsafeMutableBytes { fx in
|
|
110
|
+
let fx = fx.baseAddress!.bindMemory(to: Float32.self, capacity: fx.count)
|
|
111
|
+
var cy = 0
|
|
112
|
+
while cy < ny {
|
|
113
|
+
var cx = 0
|
|
114
|
+
while cx * ny < nx * (ny - cy) {
|
|
115
|
+
var ptr = channel
|
|
116
|
+
var f: Float32 = 0
|
|
117
|
+
var x = 0
|
|
118
|
+
while x < w {
|
|
119
|
+
let fw = Float32(w)
|
|
120
|
+
let fxx = Float32(x)
|
|
121
|
+
let fcx = Float32(cx)
|
|
122
|
+
fx[x] = cos(Float32.pi / fw * fcx * (fxx + 0.5))
|
|
123
|
+
x += 1
|
|
124
|
+
}
|
|
125
|
+
var y = 0
|
|
126
|
+
while y < h {
|
|
127
|
+
let fh = Float32(h)
|
|
128
|
+
let fyy = Float32(y)
|
|
129
|
+
let fcy = Float32(cy)
|
|
130
|
+
let fy = cos(Float32.pi / fh * fcy * (fyy + 0.5))
|
|
131
|
+
var x = 0
|
|
132
|
+
while x < w {
|
|
133
|
+
f += ptr.pointee * fx[x] * fy
|
|
134
|
+
x += 1
|
|
135
|
+
ptr = ptr.advanced(by: 4)
|
|
136
|
+
}
|
|
137
|
+
y += 1
|
|
138
|
+
}
|
|
139
|
+
f /= Float32(w * h)
|
|
140
|
+
if cx > 0 || cy > 0 {
|
|
141
|
+
ac.append(f)
|
|
142
|
+
scale = max(scale, abs(f))
|
|
143
|
+
} else {
|
|
144
|
+
dc = f
|
|
145
|
+
}
|
|
146
|
+
cx += 1
|
|
147
|
+
}
|
|
148
|
+
cy += 1
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if scale > 0 {
|
|
152
|
+
let n = ac.count
|
|
153
|
+
var i = 0
|
|
154
|
+
while i < n {
|
|
155
|
+
ac[i] = 0.5 + 0.5 / scale * ac[i]
|
|
156
|
+
i += 1
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return (dc, ac, scale)
|
|
160
|
+
}
|
|
161
|
+
let (
|
|
162
|
+
(l_dc, l_ac, l_scale),
|
|
163
|
+
(p_dc, p_ac, p_scale),
|
|
164
|
+
(q_dc, q_ac, q_scale),
|
|
165
|
+
(a_dc, a_ac, a_scale)
|
|
166
|
+
) = lpqa.withUnsafeBytes { lpqa in
|
|
167
|
+
let lpqa = lpqa.baseAddress!.bindMemory(to: Float32.self, capacity: lpqa.count)
|
|
168
|
+
return (
|
|
169
|
+
encodeChannel(lpqa, max(3, lx), max(3, ly)),
|
|
170
|
+
encodeChannel(lpqa.advanced(by: 1), 3, 3),
|
|
171
|
+
encodeChannel(lpqa.advanced(by: 2), 3, 3),
|
|
172
|
+
hasAlpha ? encodeChannel(lpqa.advanced(by: 3), 5, 5) : (1, [], 1)
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Write the constants
|
|
177
|
+
let isLandscape = w > h
|
|
178
|
+
let fl_dc = round(63.0 * l_dc)
|
|
179
|
+
let fp_dc = round(31.5 + 31.5 * p_dc)
|
|
180
|
+
let fq_dc = round(31.5 + 31.5 * q_dc)
|
|
181
|
+
let fl_scale = round(31.0 * l_scale)
|
|
182
|
+
let il_dc = UInt32(fl_dc)
|
|
183
|
+
let ip_dc = UInt32(fp_dc)
|
|
184
|
+
let iq_dc = UInt32(fq_dc)
|
|
185
|
+
let il_scale = UInt32(fl_scale)
|
|
186
|
+
let ihasAlpha = UInt32(hasAlpha ? 1 : 0)
|
|
187
|
+
let header24 = il_dc | (ip_dc << 6) | (iq_dc << 12) | (il_scale << 18) | (ihasAlpha << 23)
|
|
188
|
+
let fp_scale = round(63.0 * p_scale)
|
|
189
|
+
let fq_scale = round(63.0 * q_scale)
|
|
190
|
+
let ilxy = UInt16(isLandscape ? ly : lx)
|
|
191
|
+
let ip_scale = UInt16(fp_scale)
|
|
192
|
+
let iq_scale = UInt16(fq_scale)
|
|
193
|
+
let iisLandscape = UInt16(isLandscape ? 1 : 0)
|
|
194
|
+
let header16 = ilxy | (ip_scale << 3) | (iq_scale << 9) | (iisLandscape << 15)
|
|
195
|
+
var hash = Data(capacity: 25)
|
|
196
|
+
hash.append(UInt8(header24 & 255))
|
|
197
|
+
hash.append(UInt8((header24 >> 8) & 255))
|
|
198
|
+
hash.append(UInt8(header24 >> 16))
|
|
199
|
+
hash.append(UInt8(header16 & 255))
|
|
200
|
+
hash.append(UInt8(header16 >> 8))
|
|
201
|
+
var isOdd = false
|
|
202
|
+
if hasAlpha {
|
|
203
|
+
let fa_dc = round(15.0 * a_dc)
|
|
204
|
+
let fa_scale = round(15.0 * a_scale)
|
|
205
|
+
let ia_dc = UInt8(fa_dc)
|
|
206
|
+
let ia_scale = UInt8(fa_scale)
|
|
207
|
+
hash.append(ia_dc | (ia_scale << 4))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Write the varying factors
|
|
211
|
+
for ac in [l_ac, p_ac, q_ac] {
|
|
212
|
+
for f in ac {
|
|
213
|
+
let f15 = round(15.0 * f)
|
|
214
|
+
let i15 = UInt8(f15)
|
|
215
|
+
if isOdd {
|
|
216
|
+
hash[hash.count - 1] |= i15 << 4
|
|
217
|
+
} else {
|
|
218
|
+
hash.append(i15)
|
|
219
|
+
}
|
|
220
|
+
isOdd = !isOdd
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if hasAlpha {
|
|
224
|
+
for f in a_ac {
|
|
225
|
+
let f15 = round(15.0 * f)
|
|
226
|
+
let i15 = UInt8(f15)
|
|
227
|
+
if isOdd {
|
|
228
|
+
hash[hash.count - 1] |= i15 << 4
|
|
229
|
+
} else {
|
|
230
|
+
hash.append(i15)
|
|
231
|
+
}
|
|
232
|
+
isOdd = !isOdd
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return hash
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
func thumbHashToRGBA(hash: Data) -> (Int, Int, Data) {
|
|
239
|
+
// Read the constants
|
|
240
|
+
let h0 = UInt32(hash[0])
|
|
241
|
+
let h1 = UInt32(hash[1])
|
|
242
|
+
let h2 = UInt32(hash[2])
|
|
243
|
+
let h3 = UInt16(hash[3])
|
|
244
|
+
let h4 = UInt16(hash[4])
|
|
245
|
+
let header24 = h0 | (h1 << 8) | (h2 << 16)
|
|
246
|
+
let header16 = h3 | (h4 << 8)
|
|
247
|
+
let il_dc = header24 & 63
|
|
248
|
+
let ip_dc = (header24 >> 6) & 63
|
|
249
|
+
let iq_dc = (header24 >> 12) & 63
|
|
250
|
+
var l_dc = Float32(il_dc)
|
|
251
|
+
var p_dc = Float32(ip_dc)
|
|
252
|
+
var q_dc = Float32(iq_dc)
|
|
253
|
+
l_dc = l_dc / 63
|
|
254
|
+
p_dc = p_dc / 31.5 - 1
|
|
255
|
+
q_dc = q_dc / 31.5 - 1
|
|
256
|
+
let il_scale = (header24 >> 18) & 31
|
|
257
|
+
var l_scale = Float32(il_scale)
|
|
258
|
+
l_scale = l_scale / 31
|
|
259
|
+
let hasAlpha = (header24 >> 23) != 0
|
|
260
|
+
let ip_scale = (header16 >> 3) & 63
|
|
261
|
+
let iq_scale = (header16 >> 9) & 63
|
|
262
|
+
var p_scale = Float32(ip_scale)
|
|
263
|
+
var q_scale = Float32(iq_scale)
|
|
264
|
+
p_scale = p_scale / 63
|
|
265
|
+
q_scale = q_scale / 63
|
|
266
|
+
let isLandscape = (header16 >> 15) != 0
|
|
267
|
+
let lx16 = max(3, isLandscape ? hasAlpha ? 5 : 7 : header16 & 7)
|
|
268
|
+
let ly16 = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7)
|
|
269
|
+
let lx = Int(lx16)
|
|
270
|
+
let ly = Int(ly16)
|
|
271
|
+
var a_dc = Float32(1)
|
|
272
|
+
var a_scale = Float32(1)
|
|
273
|
+
if hasAlpha {
|
|
274
|
+
let ia_dc = hash[5] & 15
|
|
275
|
+
let ia_scale = hash[5] >> 4
|
|
276
|
+
a_dc = Float32(ia_dc)
|
|
277
|
+
a_scale = Float32(ia_scale)
|
|
278
|
+
a_dc /= 15
|
|
279
|
+
a_scale /= 15
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Read the varying factors (boost saturation by 1.25x to compensate for quantization)
|
|
283
|
+
let ac_start = hasAlpha ? 6 : 5
|
|
284
|
+
var ac_index = 0
|
|
285
|
+
let decodeChannel = { (nx: Int, ny: Int, scale: Float32) -> [Float32] in
|
|
286
|
+
var ac: [Float32] = []
|
|
287
|
+
for cy in 0 ..< ny {
|
|
288
|
+
var cx = cy > 0 ? 0 : 1
|
|
289
|
+
while cx * ny < nx * (ny - cy) {
|
|
290
|
+
let iac = (hash[ac_start + (ac_index >> 1)] >> ((ac_index & 1) << 2)) & 15
|
|
291
|
+
var fac = Float32(iac)
|
|
292
|
+
fac = (fac / 7.5 - 1) * scale
|
|
293
|
+
ac.append(fac)
|
|
294
|
+
ac_index += 1
|
|
295
|
+
cx += 1
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return ac
|
|
299
|
+
}
|
|
300
|
+
let l_ac = decodeChannel(lx, ly, l_scale)
|
|
301
|
+
let p_ac = decodeChannel(3, 3, p_scale * 1.25)
|
|
302
|
+
let q_ac = decodeChannel(3, 3, q_scale * 1.25)
|
|
303
|
+
let a_ac = hasAlpha ? decodeChannel(5, 5, a_scale) : []
|
|
304
|
+
|
|
305
|
+
// Decode using the DCT into RGB
|
|
306
|
+
let ratio = thumbHashToApproximateAspectRatio(hash: hash)
|
|
307
|
+
let fw = round(ratio > 1 ? 32 : 32 * ratio)
|
|
308
|
+
let fh = round(ratio > 1 ? 32 / ratio : 32)
|
|
309
|
+
let w = Int(fw)
|
|
310
|
+
let h = Int(fh)
|
|
311
|
+
var rgba = Data(count: w * h * 4)
|
|
312
|
+
let cx_stop = max(lx, hasAlpha ? 5 : 3)
|
|
313
|
+
let cy_stop = max(ly, hasAlpha ? 5 : 3)
|
|
314
|
+
var fx = [Float32](repeating: 0, count: cx_stop)
|
|
315
|
+
var fy = [Float32](repeating: 0, count: cy_stop)
|
|
316
|
+
fx.withUnsafeMutableBytes { fx in
|
|
317
|
+
let fx = fx.baseAddress!.bindMemory(to: Float32.self, capacity: fx.count)
|
|
318
|
+
fy.withUnsafeMutableBytes { fy in
|
|
319
|
+
let fy = fy.baseAddress!.bindMemory(to: Float32.self, capacity: fy.count)
|
|
320
|
+
rgba.withUnsafeMutableBytes { rgba in
|
|
321
|
+
var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count)
|
|
322
|
+
var y = 0
|
|
323
|
+
while y < h {
|
|
324
|
+
var x = 0
|
|
325
|
+
while x < w {
|
|
326
|
+
var l = l_dc
|
|
327
|
+
var p = p_dc
|
|
328
|
+
var q = q_dc
|
|
329
|
+
var a = a_dc
|
|
330
|
+
|
|
331
|
+
// Precompute the coefficients
|
|
332
|
+
var cx = 0
|
|
333
|
+
while cx < cx_stop {
|
|
334
|
+
let fw = Float32(w)
|
|
335
|
+
let fxx = Float32(x)
|
|
336
|
+
let fcx = Float32(cx)
|
|
337
|
+
fx[cx] = cos(Float32.pi / fw * (fxx + 0.5) * fcx)
|
|
338
|
+
cx += 1
|
|
339
|
+
}
|
|
340
|
+
var cy = 0
|
|
341
|
+
while cy < cy_stop {
|
|
342
|
+
let fh = Float32(h)
|
|
343
|
+
let fyy = Float32(y)
|
|
344
|
+
let fcy = Float32(cy)
|
|
345
|
+
fy[cy] = cos(Float32.pi / fh * (fyy + 0.5) * fcy)
|
|
346
|
+
cy += 1
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Decode L
|
|
350
|
+
var j = 0
|
|
351
|
+
cy = 0
|
|
352
|
+
while cy < ly {
|
|
353
|
+
var cx = cy > 0 ? 0 : 1
|
|
354
|
+
let fy2 = fy[cy] * 2
|
|
355
|
+
while cx * ly < lx * (ly - cy) {
|
|
356
|
+
l += l_ac[j] * fx[cx] * fy2
|
|
357
|
+
j += 1
|
|
358
|
+
cx += 1
|
|
359
|
+
}
|
|
360
|
+
cy += 1
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Decode P and Q
|
|
364
|
+
j = 0
|
|
365
|
+
cy = 0
|
|
366
|
+
while cy < 3 {
|
|
367
|
+
var cx = cy > 0 ? 0 : 1
|
|
368
|
+
let fy2 = fy[cy] * 2
|
|
369
|
+
while cx < 3 - cy {
|
|
370
|
+
let f = fx[cx] * fy2
|
|
371
|
+
p += p_ac[j] * f
|
|
372
|
+
q += q_ac[j] * f
|
|
373
|
+
j += 1
|
|
374
|
+
cx += 1
|
|
375
|
+
}
|
|
376
|
+
cy += 1
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Decode A
|
|
380
|
+
if hasAlpha {
|
|
381
|
+
j = 0
|
|
382
|
+
cy = 0
|
|
383
|
+
while cy < 5 {
|
|
384
|
+
var cx = cy > 0 ? 0 : 1
|
|
385
|
+
let fy2 = fy[cy] * 2
|
|
386
|
+
while cx < 5 - cy {
|
|
387
|
+
a += a_ac[j] * fx[cx] * fy2
|
|
388
|
+
j += 1
|
|
389
|
+
cx += 1
|
|
390
|
+
}
|
|
391
|
+
cy += 1
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Convert to RGB
|
|
396
|
+
var b = l - 2 / 3 * p
|
|
397
|
+
var r = (3 * l - b + q) / 2
|
|
398
|
+
var g = r - q
|
|
399
|
+
r = max(0, 255 * min(1, r))
|
|
400
|
+
g = max(0, 255 * min(1, g))
|
|
401
|
+
b = max(0, 255 * min(1, b))
|
|
402
|
+
a = max(0, 255 * min(1, a))
|
|
403
|
+
rgba[0] = UInt8(r)
|
|
404
|
+
rgba[1] = UInt8(g)
|
|
405
|
+
rgba[2] = UInt8(b)
|
|
406
|
+
rgba[3] = UInt8(a)
|
|
407
|
+
rgba = rgba.advanced(by: 4)
|
|
408
|
+
x += 1
|
|
409
|
+
}
|
|
410
|
+
y += 1
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return (w, h, rgba)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
func thumbHashToAverageRGBA(hash: Data) -> (Float32, Float32, Float32, Float32) {
|
|
419
|
+
let h0 = UInt32(hash[0])
|
|
420
|
+
let h1 = UInt32(hash[1])
|
|
421
|
+
let h2 = UInt32(hash[2])
|
|
422
|
+
let header = h0 | (h1 << 8) | (h2 << 16)
|
|
423
|
+
let il = header & 63
|
|
424
|
+
let ip = (header >> 6) & 63
|
|
425
|
+
let iq = (header >> 12) & 63
|
|
426
|
+
var l = Float32(il)
|
|
427
|
+
var p = Float32(ip)
|
|
428
|
+
var q = Float32(iq)
|
|
429
|
+
l = l / 63
|
|
430
|
+
p = p / 31.5 - 1
|
|
431
|
+
q = q / 31.5 - 1
|
|
432
|
+
let hasAlpha = (header >> 23) != 0
|
|
433
|
+
var a = Float32(1)
|
|
434
|
+
if hasAlpha {
|
|
435
|
+
let ia = hash[5] & 15
|
|
436
|
+
a = Float32(ia)
|
|
437
|
+
a = a / 15
|
|
438
|
+
}
|
|
439
|
+
let b = l - 2 / 3 * p
|
|
440
|
+
let r = (3 * l - b + q) / 2
|
|
441
|
+
let g = r - q
|
|
442
|
+
return (
|
|
443
|
+
max(0, min(1, r)),
|
|
444
|
+
max(0, min(1, g)),
|
|
445
|
+
max(0, min(1, b)),
|
|
446
|
+
a
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
func thumbHashToApproximateAspectRatio(hash: Data) -> Float32 {
|
|
451
|
+
let header = hash[3]
|
|
452
|
+
let hasAlpha = (hash[2] & 0x80) != 0
|
|
453
|
+
let isLandscape = (hash[4] & 0x80) != 0
|
|
454
|
+
let lx = isLandscape ? hasAlpha ? 5 : 7 : header & 7
|
|
455
|
+
let ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7
|
|
456
|
+
return Float32(lx) / Float32(ly)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
#if os(iOS)
|
|
460
|
+
import UIKit
|
|
461
|
+
|
|
462
|
+
func thumbHash(fromImage: UIImage) -> Data {
|
|
463
|
+
let size = fromImage.size
|
|
464
|
+
let w = Int(round(100 * size.width / max(size.width, size.height)))
|
|
465
|
+
let h = Int(round(100 * size.height / max(size.width, size.height)))
|
|
466
|
+
var rgba = Data(count: w * h * 4)
|
|
467
|
+
rgba.withUnsafeMutableBytes { rgba in
|
|
468
|
+
if
|
|
469
|
+
let space = fromImage.cgImage?.colorSpace,
|
|
470
|
+
let context = CGContext(
|
|
471
|
+
data: rgba.baseAddress,
|
|
472
|
+
width: w,
|
|
473
|
+
height: h,
|
|
474
|
+
bitsPerComponent: 8,
|
|
475
|
+
bytesPerRow: w * 4,
|
|
476
|
+
space: space,
|
|
477
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
|
478
|
+
)
|
|
479
|
+
{
|
|
480
|
+
// EXIF orientation only works if you draw the UIImage, not the CGImage
|
|
481
|
+
context.concatenate(CGAffineTransform(1, 0, 0, -1, 0, CGFloat(h)))
|
|
482
|
+
UIGraphicsPushContext(context)
|
|
483
|
+
fromImage.draw(in: CGRect(x: 0, y: 0, width: w, height: h))
|
|
484
|
+
UIGraphicsPopContext()
|
|
485
|
+
|
|
486
|
+
// Convert from premultiplied alpha to unpremultiplied alpha
|
|
487
|
+
var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count)
|
|
488
|
+
let n = w * h
|
|
489
|
+
var i = 0
|
|
490
|
+
while i < n {
|
|
491
|
+
let a = UInt16(rgba[3])
|
|
492
|
+
if a > 0 && a < 255 {
|
|
493
|
+
var r = UInt16(rgba[0])
|
|
494
|
+
var g = UInt16(rgba[1])
|
|
495
|
+
var b = UInt16(rgba[2])
|
|
496
|
+
r = min(255, r * 255 / a)
|
|
497
|
+
g = min(255, g * 255 / a)
|
|
498
|
+
b = min(255, b * 255 / a)
|
|
499
|
+
rgba[0] = UInt8(r)
|
|
500
|
+
rgba[1] = UInt8(g)
|
|
501
|
+
rgba[2] = UInt8(b)
|
|
502
|
+
}
|
|
503
|
+
rgba = rgba.advanced(by: 4)
|
|
504
|
+
i += 1
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return rgbaToThumbHash(w: w, h: h, rgba: rgba)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
func image(fromThumbhash: Data) -> UIImage {
|
|
512
|
+
var (w, h, rgba) = thumbHashToRGBA(hash: fromThumbhash)
|
|
513
|
+
rgba.withUnsafeMutableBytes { rgba in
|
|
514
|
+
// Convert from unpremultiplied alpha to premultiplied alpha
|
|
515
|
+
var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count)
|
|
516
|
+
let n = w * h
|
|
517
|
+
var i = 0
|
|
518
|
+
while i < n {
|
|
519
|
+
let a = UInt16(rgba[3])
|
|
520
|
+
if a < 255 {
|
|
521
|
+
var r = UInt16(rgba[0])
|
|
522
|
+
var g = UInt16(rgba[1])
|
|
523
|
+
var b = UInt16(rgba[2])
|
|
524
|
+
r = min(255, r * a / 255)
|
|
525
|
+
g = min(255, g * a / 255)
|
|
526
|
+
b = min(255, b * a / 255)
|
|
527
|
+
rgba[0] = UInt8(r)
|
|
528
|
+
rgba[1] = UInt8(g)
|
|
529
|
+
rgba[2] = UInt8(b)
|
|
530
|
+
}
|
|
531
|
+
rgba = rgba.advanced(by: 4)
|
|
532
|
+
i += 1
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
let image = CGImage(
|
|
536
|
+
width: w,
|
|
537
|
+
height: h,
|
|
538
|
+
bitsPerComponent: 8,
|
|
539
|
+
bitsPerPixel: 32,
|
|
540
|
+
bytesPerRow: w * 4,
|
|
541
|
+
space: CGColorSpaceCreateDeviceRGB(),
|
|
542
|
+
bitmapInfo: CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue),
|
|
543
|
+
provider: CGDataProvider(data: rgba as CFData)!,
|
|
544
|
+
decode: nil,
|
|
545
|
+
shouldInterpolate: true,
|
|
546
|
+
intent: .perceptual
|
|
547
|
+
)
|
|
548
|
+
return UIImage(cgImage: image!)
|
|
549
|
+
}
|
|
550
|
+
#endif
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import SDWebImage
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
|
|
4
|
+
class ThumbhashLoader: NSObject, SDImageLoader {
|
|
5
|
+
// MARK: - SDImageLoader
|
|
6
|
+
|
|
7
|
+
func canRequestImage(for url: URL?) -> Bool {
|
|
8
|
+
return url?.scheme == "thumbhash"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
func requestImage(
|
|
12
|
+
with url: URL?,
|
|
13
|
+
options: SDWebImageOptions = [],
|
|
14
|
+
context: [SDWebImageContextOption: Any]?,
|
|
15
|
+
progress progressBlock: SDImageLoaderProgressBlock?,
|
|
16
|
+
completed completedBlock: SDImageLoaderCompletedBlock? = nil
|
|
17
|
+
) -> SDWebImageOperation? {
|
|
18
|
+
guard let url = url else {
|
|
19
|
+
let error = makeNSError(description: "URL provided to ThumbhashLoader is missing")
|
|
20
|
+
completedBlock?(nil, nil, error, false)
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The URI looks like this: thumbhash:/3OcRJYB4d3h\iIeHeEh3eIhw+j2w
|
|
25
|
+
// ThumbHash may include slashes which could break the structure of the URL, so we replace them
|
|
26
|
+
// with backslashes on the JS side and revert them back to slashes here, before generating the image.
|
|
27
|
+
var thumbhash = (url.pathComponents[1] ?? "").replacingOccurrences(of: "\\", with: "/")
|
|
28
|
+
|
|
29
|
+
// Thumbhashes with transparency cause the conversion to data to fail, padding the thumbhash string to correct length fixes that
|
|
30
|
+
let remainder = thumbhash.count % 4
|
|
31
|
+
if remainder > 0 {
|
|
32
|
+
thumbhash = thumbhash.padding(toLength: thumbhash.count + 4 - remainder, withPad: "=", startingAt: 0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
guard !thumbhash.isEmpty, let thumbhashData = Data(base64Encoded: thumbhash, options: .ignoreUnknownCharacters) else {
|
|
36
|
+
let error = makeNSError(description: "URL provided to ThumbhashLoader is invalid")
|
|
37
|
+
completedBlock?(nil, nil, error, false)
|
|
38
|
+
return nil
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
42
|
+
let image = image(fromThumbhash: thumbhashData)
|
|
43
|
+
DispatchQueue.main.async {
|
|
44
|
+
completedBlock?(image, nil, nil, true)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func shouldBlockFailedURL(with url: URL, error: Error) -> Bool {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* PROPRIETARY/CONFIDENTIAL
|
|
5
|
+
*
|
|
6
|
+
* Use is subject to license terms.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include "ExpoImage.h"
|
|
10
|
+
|
|
11
|
+
#include <KeplerScript/appinit/KeplerScriptApplication.h>
|
|
12
|
+
#include <KeplerScript/components/ImageSourceBuilder.h>
|
|
13
|
+
#include <KeplerScript/logger/KPLog.h>
|
|
14
|
+
#include <KeplerScript/utils/image/KeplerScriptImageLoaderDelegate.h>
|
|
15
|
+
|
|
16
|
+
#include "utils/PrefetchImageLoaderDelegate.h"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ExpoImage::ExpoImage(std::shared_ptr<IExpoImageCacheHandler> handler) : volta::react::KeplerTurboModule("ExpoImage") {
|
|
20
|
+
volta::react::KPLog.debug("ExpoImage constructor {}", name());
|
|
21
|
+
|
|
22
|
+
if (handler) {
|
|
23
|
+
cacheHandler = std::move(handler);
|
|
24
|
+
} else {
|
|
25
|
+
cacheHandler = std::make_shared<ExpoImageCacheHandler>();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void ExpoImage::aggregateMethods(
|
|
30
|
+
volta::react::MethodAggregator<volta::react::KeplerTurboModule>& methodAggregator) const noexcept {
|
|
31
|
+
methodAggregator.addMethod("prefetch", 1, &ExpoImage::prefetch);
|
|
32
|
+
methodAggregator.addMethod("clearMemoryCache", 0, &ExpoImage::clearMemoryCache);
|
|
33
|
+
methodAggregator.addMethod("clearDiskCache", 0, &ExpoImage::clearDiskCache);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
volta::react::Promise ExpoImage::prefetch(const std::vector<std::string> urls) {
|
|
37
|
+
volta::react::KPLog.debug("ExpoImage :: prefetch ");
|
|
38
|
+
return volta::react::Promise([handler = cacheHandler, urls = urls](std::shared_ptr<volta::react::Promise> promise) {
|
|
39
|
+
auto delegate = std::make_shared<PrefetchImageLoaderDelegate>(urls.size(), [promise](bool success) {
|
|
40
|
+
promise->resolve(success);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
for (const auto& url : urls) {
|
|
44
|
+
// KeplerScriptImageCacheHandler::prefetch does not emit any events to `delegate`
|
|
45
|
+
// As a workaround `load` is used, but sometimes does not keep image in cache
|
|
46
|
+
const volta::ImageSource imageSourceUrl = volta::react::buildImageSource(url);
|
|
47
|
+
volta::ImageDetails details(imageSourceUrl, volta::ImageOptions{}, static_cast<uint8_t>(0));
|
|
48
|
+
handler->load(details, delegate);
|
|
49
|
+
// cacheHandler.prefetch(url, 1, delegate);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
void ExpoImage::clearMemoryCache() {
|
|
56
|
+
volta::react::KPLog.debug("ExpoImage :: clearMemoryCache");
|
|
57
|
+
cacheHandler->clearMemoryCache();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void ExpoImage::clearDiskCache() {
|
|
61
|
+
volta::react::KPLog.debug("ExpoImage :: clearDiskCache");
|
|
62
|
+
cacheHandler->clearDiskCache();
|
|
63
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* PROPRIETARY/CONFIDENTIAL
|
|
5
|
+
*
|
|
6
|
+
* Use is subject to license terms.
|
|
7
|
+
*/
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <KeplerScript/modules/KeplerTurboModule.h>
|
|
11
|
+
#include "ExpoImageCacheHandler.h"
|
|
12
|
+
#include <string>
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExpoImage : public volta::react::KeplerTurboModule {
|
|
16
|
+
public:
|
|
17
|
+
explicit ExpoImage(std::shared_ptr<IExpoImageCacheHandler> cacheHandler = nullptr);
|
|
18
|
+
|
|
19
|
+
volta::react::Promise prefetch(const std::vector<std::string> urls);
|
|
20
|
+
void clearMemoryCache();
|
|
21
|
+
void clearDiskCache();
|
|
22
|
+
|
|
23
|
+
protected:
|
|
24
|
+
void aggregateMethods(
|
|
25
|
+
volta::react::MethodAggregator<volta::react::KeplerTurboModule>& methodAggregator) const noexcept override;
|
|
26
|
+
|
|
27
|
+
private:
|
|
28
|
+
std::shared_ptr<IExpoImageCacheHandler> cacheHandler = nullptr;
|
|
29
|
+
};
|