@onekeyfe/react-native-image 3.0.98

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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/OneKeyImage.podspec +32 -0
  3. package/README.md +88 -0
  4. package/android/CMakeLists.txt +12 -0
  5. package/android/build.gradle +85 -0
  6. package/android/consumer-rules.pro +10 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +1 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  10. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImage.kt +616 -0
  11. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageCache.kt +131 -0
  12. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensions.kt +169 -0
  13. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +285 -0
  14. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageModel.kt +68 -0
  15. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +28 -0
  16. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageSafety.kt +1120 -0
  17. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyTosUrl.kt +93 -0
  18. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyAnimationTimelineInspectorTest.kt +210 -0
  19. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageCacheTypeTest.kt +16 -0
  20. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensionsTest.kt +70 -0
  21. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageModelTest.kt +65 -0
  22. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageRequestSignatureTest.kt +33 -0
  23. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageSafetyTest.kt +346 -0
  24. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyResourceCleanupTest.kt +43 -0
  25. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTerminalEventPairTest.kt +38 -0
  26. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTosUrlTest.kt +82 -0
  27. package/ios/OneKeyImage.swift +636 -0
  28. package/ios/OneKeyImageCache.swift +215 -0
  29. package/ios/OneKeyImageRequestContext.swift +781 -0
  30. package/ios/OneKeyTosURL.swift +69 -0
  31. package/ios/tests/OneKeyImageInfrastructureTests.swift +555 -0
  32. package/ios/tests/OneKeyTosURLTests.swift +142 -0
  33. package/lib/module/OneKeyImage.nitro.js +30 -0
  34. package/lib/module/index.js +223 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  37. package/lib/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
  38. package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  39. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
  40. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
  41. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
  42. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
  43. package/lib/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
  44. package/lib/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
  45. package/lib/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
  46. package/lib/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
  47. package/lib/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
  48. package/lib/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
  49. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
  50. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
  51. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
  52. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
  53. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
  54. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
  55. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
  56. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
  57. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
  58. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
  59. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
  60. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
  61. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
  62. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
  63. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
  64. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
  65. package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
  66. package/lib/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
  67. package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
  68. package/lib/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
  69. package/lib/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
  70. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
  71. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
  72. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
  73. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
  74. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
  75. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
  76. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
  77. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
  78. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
  79. package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
  80. package/lib/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  81. package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  82. package/lib/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
  83. package/lib/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  84. package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  85. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
  86. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
  87. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
  88. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
  89. package/lib/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
  90. package/lib/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
  91. package/lib/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
  92. package/lib/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
  93. package/lib/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
  94. package/lib/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
  95. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
  96. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
  97. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
  98. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
  99. package/lib/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
  100. package/lib/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
  101. package/lib/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
  102. package/lib/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
  103. package/lib/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
  104. package/lib/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
  105. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
  106. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
  107. package/lib/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
  108. package/lib/typescript/package.json +1 -0
  109. package/lib/typescript/src/OneKeyImage.nitro.d.ts +67 -0
  110. package/lib/typescript/src/index.d.ts +55 -0
  111. package/nitro.json +31 -0
  112. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  113. package/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
  114. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  115. package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
  116. package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
  117. package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
  118. package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
  119. package/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
  120. package/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
  121. package/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
  122. package/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
  123. package/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
  124. package/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
  125. package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
  126. package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
  128. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
  129. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
  130. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
  131. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
  132. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
  133. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
  134. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
  141. package/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
  142. package/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
  143. package/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
  144. package/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
  145. package/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
  146. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
  147. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
  148. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
  149. package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
  150. package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
  151. package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
  152. package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
  153. package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
  154. package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
  155. package/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
  156. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  157. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  158. package/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
  159. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  160. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  161. package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
  162. package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
  163. package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
  164. package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
  165. package/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
  166. package/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
  167. package/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
  168. package/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
  169. package/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
  170. package/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
  171. package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
  172. package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
  173. package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
  174. package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
  175. package/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
  176. package/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
  177. package/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
  178. package/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
  179. package/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
  180. package/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
  181. package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
  182. package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
  183. package/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
  184. package/package.json +128 -0
  185. package/src/OneKeyImage.nitro.ts +86 -0
  186. package/src/index.tsx +376 -0
@@ -0,0 +1,93 @@
1
+ package com.margelo.nitro.onekeyimage
2
+
3
+ import java.net.URI
4
+ import java.net.URLDecoder
5
+ import java.nio.charset.StandardCharsets
6
+ import kotlin.math.ceil
7
+
8
+ internal object OneKeyTosUrl {
9
+ internal val widthBuckets = intArrayOf(
10
+ 32, 40, 48, 64, 96, 128, 160, 200, 256, 320, 480, 640, 960, 1280,
11
+ )
12
+
13
+ fun optimized(
14
+ rawUrl: String,
15
+ displaySize: Int,
16
+ density: Float,
17
+ overscan: Double,
18
+ hasCustomIdentity: Boolean,
19
+ ): String {
20
+ if (hasCustomIdentity || displaySize <= 0) return rawUrl
21
+ val uri = try {
22
+ URI(rawUrl)
23
+ } catch (_: Exception) {
24
+ return rawUrl
25
+ }
26
+ val host = uri.host?.lowercase() ?: return rawUrl
27
+ if (host !in ALLOWED_HOSTS) return rawUrl
28
+ if (unsupportedExtension(uri.path.orEmpty())) return rawUrl
29
+ if (hasProtectedQuery(uri.rawQuery)) return rawUrl
30
+
31
+ val bucket = selectWidthBucket(displaySize, density, overscan)
32
+ val fragmentIndex = rawUrl.indexOf('#')
33
+ val base = if (fragmentIndex >= 0) rawUrl.substring(0, fragmentIndex) else rawUrl
34
+ val fragment = if (fragmentIndex >= 0) rawUrl.substring(fragmentIndex) else ""
35
+ val separator = when {
36
+ uri.rawQuery == null -> "?"
37
+ base.endsWith("?") || base.endsWith("&") -> ""
38
+ else -> "&"
39
+ }
40
+ return "$base${separator}x-tos-process=image%2Fresize%2Cw_$bucket$fragment"
41
+ }
42
+
43
+ internal fun selectWidthBucket(displaySize: Int, density: Float, overscan: Double): Int {
44
+ val normalizedDensity = if (density.isFinite()) density.coerceIn(1f, MAX_DPR) else 1f
45
+ val normalizedOverscan = if (overscan.isFinite()) overscan.coerceAtLeast(1.0) else 1.0
46
+ val requestedPixels = ceil(displaySize * normalizedDensity * normalizedOverscan)
47
+ if (!requestedPixels.isFinite() || requestedPixels >= widthBuckets.last()) {
48
+ return widthBuckets.last()
49
+ }
50
+ val requested = requestedPixels.toInt()
51
+ return widthBuckets.firstOrNull { it >= requested } ?: widthBuckets.last()
52
+ }
53
+
54
+ private fun unsupportedExtension(path: String): Boolean {
55
+ val extension = path.substringAfterLast('.', "").lowercase()
56
+ return extension in UNSUPPORTED_EXTENSIONS
57
+ }
58
+
59
+ internal fun hasProtectedQuery(rawQuery: String?): Boolean {
60
+ if (rawQuery.isNullOrEmpty()) return false
61
+ return rawQuery.split('&').any { pair ->
62
+ val rawName = pair.substringBefore('=')
63
+ val name = try {
64
+ URLDecoder.decode(rawName, StandardCharsets.UTF_8.name())
65
+ } catch (_: Exception) {
66
+ rawName
67
+ }.lowercase()
68
+ name in PROTECTED_QUERY_KEYS || PROTECTED_QUERY_PREFIXES.any(name::startsWith)
69
+ }
70
+ }
71
+
72
+ private val ALLOWED_HOSTS = setOf(
73
+ "app-assets.onekey.so",
74
+ "common.onekey-asset.com",
75
+ "asset.onekey-asset.com",
76
+ "uni.onekey-asset.com",
77
+ "uni-test.onekey-asset.com",
78
+ )
79
+
80
+ private val PROTECTED_QUERY_KEYS = setOf(
81
+ "expires",
82
+ "policy",
83
+ "signature",
84
+ "token",
85
+ "auth_key",
86
+ "accesskeyid",
87
+ "ossaccesskeyid",
88
+ "security-token",
89
+ )
90
+ private val PROTECTED_QUERY_PREFIXES = arrayOf("x-amz-", "x-oss-", "x-tos-")
91
+ private val UNSUPPORTED_EXTENSIONS = setOf("svg", "mp4", "webm", "m4v", "mov", "avi")
92
+ private const val MAX_DPR = 3f
93
+ }
@@ -0,0 +1,210 @@
1
+ package com.margelo.nitro.onekeyimage
2
+
3
+ import org.junit.Assert.assertThrows
4
+ import org.junit.Test
5
+ import java.io.ByteArrayOutputStream
6
+
7
+ class OneKeyAnimationTimelineInspectorTest {
8
+ @Test
9
+ fun acceptsFrameAndDurationBoundariesForEveryAnimatedFormat() {
10
+ validate(OneKeyAnimatedFormat.GIF, gif(1000, delayCentiseconds = 6))
11
+ validate(OneKeyAnimatedFormat.WEBP, webp(1000, durationMs = 60))
12
+ validate(OneKeyAnimatedFormat.APNG, apng(1000, delayNumerator = 6))
13
+ }
14
+
15
+ @Test
16
+ fun rejectsFrameCountsAboveLimitForEveryAnimatedFormat() {
17
+ assertUnsafe(OneKeyAnimatedFormat.GIF, gif(1001, delayCentiseconds = 1))
18
+ assertUnsafe(OneKeyAnimatedFormat.WEBP, webp(1001, durationMs = 1))
19
+ assertUnsafe(OneKeyAnimatedFormat.APNG, apng(1001, delayNumerator = 1))
20
+ }
21
+
22
+ @Test
23
+ fun zeroDelayUsesDecoderMinimumForDurationLimit() {
24
+ assertUnsafe(OneKeyAnimatedFormat.GIF, gif(601, delayCentiseconds = 0))
25
+ assertUnsafe(
26
+ OneKeyAnimatedFormat.GIF,
27
+ gif(601, delayCentiseconds = 0, writeControlEveryFrame = false),
28
+ )
29
+ assertUnsafe(OneKeyAnimatedFormat.WEBP, webp(601, durationMs = 0))
30
+ assertUnsafe(OneKeyAnimatedFormat.APNG, apng(601, delayNumerator = 0))
31
+ }
32
+
33
+ @Test
34
+ fun rejectsDurationsAboveLimitForEveryAnimatedFormat() {
35
+ assertUnsafe(OneKeyAnimatedFormat.GIF, gif(1000, delayCentiseconds = 7))
36
+ assertUnsafe(OneKeyAnimatedFormat.WEBP, webp(1000, durationMs = 61))
37
+ assertUnsafe(OneKeyAnimatedFormat.APNG, apng(1000, delayNumerator = 7))
38
+ }
39
+
40
+ @Test
41
+ fun rejectsTruncatedDataForEveryAnimatedFormat() {
42
+ val gif = gif(1, delayCentiseconds = 1)
43
+ val webp = webp(1, durationMs = 1)
44
+ val apng = apng(1, delayNumerator = 1)
45
+
46
+ assertUnsafe(OneKeyAnimatedFormat.GIF, gif.copyOf(gif.size - 1))
47
+ assertUnsafe(OneKeyAnimatedFormat.WEBP, webp.copyOf(webp.size - 1))
48
+ assertUnsafe(OneKeyAnimatedFormat.APNG, apng.copyOf(apng.size - 1))
49
+ }
50
+
51
+ @Test
52
+ fun rejectsInvalidCanvasesAndAcceptsMinimumAnimatedWebpCanvas() {
53
+ assertUnsafe(OneKeyAnimatedFormat.GIF, gif(1, 1, width = 0, height = 1))
54
+ assertUnsafe(OneKeyAnimatedFormat.APNG, apng(1, 1, width = 1, height = 0))
55
+ validate(OneKeyAnimatedFormat.WEBP, webp(1, 1, width = 1, height = 1))
56
+ assertUnsafe(OneKeyAnimatedFormat.WEBP, webp(1, 1, width = 8193, height = 1))
57
+ }
58
+
59
+ private fun validate(format: OneKeyAnimatedFormat, bytes: ByteArray) {
60
+ OneKeyAnimationTimelineInspector.validate(format, bytes)
61
+ }
62
+
63
+ private fun assertUnsafe(format: OneKeyAnimatedFormat, bytes: ByteArray) {
64
+ assertThrows(OneKeyImageSafetyException::class.java) {
65
+ OneKeyAnimationTimelineInspector.validate(format, bytes)
66
+ }
67
+ }
68
+
69
+ private fun gif(
70
+ frameCount: Int,
71
+ delayCentiseconds: Int,
72
+ width: Int = 2,
73
+ height: Int = 2,
74
+ writeControlEveryFrame: Boolean = true,
75
+ ): ByteArray = ByteArrayOutputStream().apply {
76
+ write("GIF89a".toByteArray())
77
+ writeU16Le(this, width)
78
+ writeU16Le(this, height)
79
+ write(byteArrayOf(0, 0, 0))
80
+ repeat(frameCount) { index ->
81
+ if (writeControlEveryFrame || index == 0) {
82
+ write(byteArrayOf(0x21, 0xf9.toByte(), 4, 0))
83
+ writeU16Le(this, delayCentiseconds)
84
+ write(byteArrayOf(0, 0))
85
+ }
86
+ write(0x2c)
87
+ writeU16Le(this, 0)
88
+ writeU16Le(this, 0)
89
+ writeU16Le(this, width)
90
+ writeU16Le(this, height)
91
+ write(0)
92
+ write(byteArrayOf(2, 1, 0, 0))
93
+ }
94
+ write(0x3b)
95
+ }.toByteArray()
96
+
97
+ private fun webp(
98
+ frameCount: Int,
99
+ durationMs: Int,
100
+ width: Int = 2,
101
+ height: Int = 2,
102
+ ): ByteArray {
103
+ val chunks = ByteArrayOutputStream().apply {
104
+ val vp8x = ByteArray(10).also { payload ->
105
+ payload[0] = 0x02
106
+ writeU24Le(payload, 4, width - 1)
107
+ writeU24Le(payload, 7, height - 1)
108
+ }
109
+ writeWebpChunk(this, "VP8X", vp8x)
110
+ writeWebpChunk(this, "ANIM", ByteArray(6))
111
+ repeat(frameCount) {
112
+ val frame = ByteArray(16).also { payload ->
113
+ writeU24Le(payload, 6, width - 1)
114
+ writeU24Le(payload, 9, height - 1)
115
+ writeU24Le(payload, 12, durationMs)
116
+ }
117
+ writeWebpChunk(this, "ANMF", frame)
118
+ }
119
+ }.toByteArray()
120
+ val body = ByteArrayOutputStream().apply {
121
+ write("WEBP".toByteArray())
122
+ write(chunks)
123
+ }.toByteArray()
124
+ return ByteArrayOutputStream().apply {
125
+ write("RIFF".toByteArray())
126
+ writeU32Le(this, body.size.toLong())
127
+ write(body)
128
+ }.toByteArray()
129
+ }
130
+
131
+ private fun apng(
132
+ frameCount: Int,
133
+ delayNumerator: Int,
134
+ width: Int = 2,
135
+ height: Int = 2,
136
+ ): ByteArray = ByteArrayOutputStream().apply {
137
+ write(byteArrayOf(0x89.toByte(), 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a))
138
+ val ihdr = ByteArrayOutputStream().apply {
139
+ writeU32Be(this, width.toLong())
140
+ writeU32Be(this, height.toLong())
141
+ write(byteArrayOf(8, 6, 0, 0, 0))
142
+ }.toByteArray()
143
+ writePngChunk(this, "IHDR", ihdr)
144
+ val actl = ByteArrayOutputStream().apply {
145
+ writeU32Be(this, frameCount.toLong())
146
+ writeU32Be(this, 0)
147
+ }.toByteArray()
148
+ writePngChunk(this, "acTL", actl)
149
+ repeat(frameCount) { index ->
150
+ val fctl = ByteArrayOutputStream().apply {
151
+ writeU32Be(this, (index * 2).toLong())
152
+ writeU32Be(this, width.toLong())
153
+ writeU32Be(this, height.toLong())
154
+ writeU32Be(this, 0)
155
+ writeU32Be(this, 0)
156
+ writeU16Be(this, delayNumerator)
157
+ writeU16Be(this, 100)
158
+ write(byteArrayOf(0, 0))
159
+ }.toByteArray()
160
+ writePngChunk(this, "fcTL", fctl)
161
+ if (index == 0) {
162
+ writePngChunk(this, "IDAT", byteArrayOf())
163
+ } else {
164
+ val fdat = ByteArrayOutputStream().apply {
165
+ writeU32Be(this, (index * 2 + 1).toLong())
166
+ }.toByteArray()
167
+ writePngChunk(this, "fdAT", fdat)
168
+ }
169
+ }
170
+ writePngChunk(this, "IEND", byteArrayOf())
171
+ }.toByteArray()
172
+
173
+ private fun writeWebpChunk(output: ByteArrayOutputStream, type: String, payload: ByteArray) {
174
+ output.write(type.toByteArray())
175
+ writeU32Le(output, payload.size.toLong())
176
+ output.write(payload)
177
+ if (payload.size and 1 != 0) output.write(0)
178
+ }
179
+
180
+ private fun writePngChunk(output: ByteArrayOutputStream, type: String, payload: ByteArray) {
181
+ writeU32Be(output, payload.size.toLong())
182
+ output.write(type.toByteArray())
183
+ output.write(payload)
184
+ output.write(byteArrayOf(0, 0, 0, 0))
185
+ }
186
+
187
+ private fun writeU16Le(output: ByteArrayOutputStream, value: Int) {
188
+ output.write(value and 0xff)
189
+ output.write((value ushr 8) and 0xff)
190
+ }
191
+
192
+ private fun writeU16Be(output: ByteArrayOutputStream, value: Int) {
193
+ output.write((value ushr 8) and 0xff)
194
+ output.write(value and 0xff)
195
+ }
196
+
197
+ private fun writeU24Le(bytes: ByteArray, offset: Int, value: Int) {
198
+ bytes[offset] = value.toByte()
199
+ bytes[offset + 1] = (value ushr 8).toByte()
200
+ bytes[offset + 2] = (value ushr 16).toByte()
201
+ }
202
+
203
+ private fun writeU32Le(output: ByteArrayOutputStream, value: Long) {
204
+ repeat(4) { shift -> output.write(((value ushr (shift * 8)) and 0xffL).toInt()) }
205
+ }
206
+
207
+ private fun writeU32Be(output: ByteArrayOutputStream, value: Long) {
208
+ for (shift in 3 downTo 0) output.write(((value ushr (shift * 8)) and 0xffL).toInt())
209
+ }
210
+ }
@@ -0,0 +1,16 @@
1
+ package com.margelo.nitro.onekeyimage
2
+
3
+ import com.bumptech.glide.load.DataSource
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Test
6
+
7
+ class OneKeyImageCacheTypeTest {
8
+ @Test
9
+ fun exposesOnlyThePublicCacheTypeSemantics() {
10
+ assertEquals(OneKeyImageCacheType.MEMORY, DataSource.MEMORY_CACHE.toOneKeyImageCacheType())
11
+ assertEquals(OneKeyImageCacheType.DISK, DataSource.DATA_DISK_CACHE.toOneKeyImageCacheType())
12
+ assertEquals(OneKeyImageCacheType.DISK, DataSource.RESOURCE_DISK_CACHE.toOneKeyImageCacheType())
13
+ assertEquals(OneKeyImageCacheType.NONE, DataSource.LOCAL.toOneKeyImageCacheType())
14
+ assertEquals(OneKeyImageCacheType.NONE, DataSource.REMOTE.toOneKeyImageCacheType())
15
+ }
16
+ }
@@ -0,0 +1,70 @@
1
+ package com.margelo.nitro.onekeyimage
2
+
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertTrue
5
+ import org.junit.Test
6
+
7
+ class OneKeyImageDecodeDimensionsTest {
8
+ @Test
9
+ fun keepsRenderDimensionsWithinSixteenMiB() {
10
+ val dimensions = OneKeyImageDecodeDimensions.forRender(1200, 800)
11
+
12
+ assertEquals(1200, dimensions.width)
13
+ assertEquals(800, dimensions.height)
14
+ assertTrue(dimensions.estimatedBytes <= OneKeyImageDecodeDimensions.MAX_DECODE_BYTES)
15
+ }
16
+
17
+ @Test
18
+ fun constrainsLargeRenderDimensionsAndPreservesAspectRatio() {
19
+ val dimensions = OneKeyImageDecodeDimensions.forRender(4000, 2000)
20
+
21
+ assertTrue(dimensions.estimatedBytes <= OneKeyImageDecodeDimensions.MAX_DECODE_BYTES)
22
+ assertEquals(2.0, dimensions.width.toDouble() / dimensions.height, 0.01)
23
+ }
24
+
25
+ @Test
26
+ fun constrainsExtremeAspectRatiosWithoutOverflow() {
27
+ val dimensions = OneKeyImageDecodeDimensions.forRender(Int.MAX_VALUE, 1)
28
+
29
+ assertTrue(dimensions.estimatedBytes <= OneKeyImageDecodeDimensions.MAX_DECODE_BYTES)
30
+ assertEquals(1, dimensions.height)
31
+ }
32
+
33
+ @Test
34
+ fun preloadUsesBothLogicalDimensionsAndPhysicalPixelRatio() {
35
+ val dimensions = OneKeyImageDecodeDimensions.forPreload(40.0, 64.0, 2.5)
36
+
37
+ assertEquals(100, dimensions.width)
38
+ assertEquals(160, dimensions.height)
39
+ }
40
+
41
+ @Test
42
+ fun unsizedPreloadStillHasAHardDecodeCap() {
43
+ val dimensions = OneKeyImageDecodeDimensions.forPreload(null, null, 3.0)
44
+
45
+ assertEquals(2048, dimensions.width)
46
+ assertEquals(2048, dimensions.height)
47
+ assertEquals(
48
+ OneKeyImageDecodeDimensions.MAX_DECODE_BYTES,
49
+ dimensions.estimatedBytes,
50
+ )
51
+ }
52
+
53
+ @Test
54
+ fun animatedDecoderUsesAPowerOfTwoSampleWithinTheCap() {
55
+ val desired = OneKeyImageDecodeDimensions.forAnimatedDecoder(8000, 4000, 2048, 2048)
56
+
57
+ requireNotNull(desired)
58
+ assertEquals(2000, desired.width)
59
+ assertEquals(1000, desired.height)
60
+ assertTrue(desired.estimatedBytes <= OneKeyImageDecodeDimensions.MAX_DECODE_BYTES)
61
+ }
62
+
63
+ @Test
64
+ fun staticDownsampleStrategyCapsExtremeAspectRatioArea() {
65
+ val scale = OneKeyImageDecodeDimensions.safeStaticScaleFactor(8000, 1000, 2048, 2048)
66
+ val decodedBytes = 8000.0 * scale * 1000.0 * scale * 4.0
67
+
68
+ assertTrue(decodedBytes <= OneKeyImageDecodeDimensions.MAX_DECODE_BYTES)
69
+ }
70
+ }
@@ -0,0 +1,65 @@
1
+ package com.margelo.nitro.onekeyimage
2
+
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertFalse
5
+ import org.junit.Assert.assertNotEquals
6
+ import org.junit.Assert.assertNull
7
+ import org.junit.Assert.assertTrue
8
+ import org.junit.Test
9
+ import com.bumptech.glide.load.Key
10
+ import java.security.MessageDigest
11
+
12
+ class OneKeyImageModelTest {
13
+ @Test
14
+ fun dataUriUsesDedicatedModelWithoutBuiltInStringFallback() {
15
+ val uri = "data:image/png;base64,AA=="
16
+ val model = OneKeyImageModel.build(uri, null)
17
+
18
+ assertTrue(model is OneKeyImageDataUriModel)
19
+ assertFalse(model is String)
20
+ assertEquals(uri, (model as OneKeyImageDataUriModel).dataUri)
21
+ }
22
+
23
+ @Test
24
+ fun headerDigestIsStableAndDoesNotContainPlaintext() {
25
+ val first = OneKeyImageModel.remoteHeadersDigest(
26
+ linkedMapOf("X-Mode" to "private-value", "Authorization" to "token"),
27
+ )
28
+ val same = OneKeyImageModel.remoteHeadersDigest(
29
+ linkedMapOf("authorization" to "token", "x-mode" to "private-value"),
30
+ )
31
+
32
+ assertEquals(first, same)
33
+ assertFalse(first!!.contains("token"))
34
+ assertFalse(first.contains("private-value"))
35
+ assertEquals(64, first.length)
36
+ assertNull(OneKeyImageModel.remoteHeadersDigest(emptyMap()))
37
+ }
38
+
39
+ @Test
40
+ fun remoteSourceKeyIncludesUrlAndHashedHeadersInMemoryAndDiskIdentity() {
41
+ val url = "https://example.com/image.png"
42
+ val otherUrl = "https://example.com/other.png"
43
+ val firstHeaders = OneKeyImageModel.remoteHeadersDigest(mapOf("Authorization" to "first"))
44
+ val secondHeaders = OneKeyImageModel.remoteHeadersDigest(mapOf("Authorization" to "second"))
45
+ val first = OneKeyImageRemoteSourceKey(url, firstHeaders)
46
+ val same = OneKeyImageRemoteSourceKey("https://example.com/image.png", firstHeaders)
47
+ val differentHeaders = OneKeyImageRemoteSourceKey(url, secondHeaders)
48
+ val differentUrl = OneKeyImageRemoteSourceKey(otherUrl, firstHeaders)
49
+ val withoutHeaders = OneKeyImageRemoteSourceKey(url, null)
50
+
51
+ assertEquals(first, same)
52
+ assertEquals(first.hashCode(), same.hashCode())
53
+ assertTrue(diskDigest(first).contentEquals(diskDigest(same)))
54
+ assertNotEquals(first, differentHeaders)
55
+ assertFalse(diskDigest(first).contentEquals(diskDigest(differentHeaders)))
56
+ assertNotEquals(first, differentUrl)
57
+ assertFalse(diskDigest(first).contentEquals(diskDigest(differentUrl)))
58
+ assertNotEquals(first, withoutHeaders)
59
+ assertFalse(diskDigest(first).contentEquals(diskDigest(withoutHeaders)))
60
+ }
61
+
62
+ private fun diskDigest(key: Key): ByteArray = MessageDigest.getInstance("SHA-256").apply {
63
+ key.updateDiskCacheKey(this)
64
+ }.digest()
65
+ }
@@ -0,0 +1,33 @@
1
+ package com.margelo.nitro.onekeyimage
2
+
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertNotEquals
5
+ import org.junit.Test
6
+
7
+ class OneKeyImageRequestSignatureTest {
8
+ @Test
9
+ fun contentFitParticipatesInRequestIdentity() {
10
+ val cover = signature(OneKeyImageContentFit.COVER)
11
+ val contain = signature(OneKeyImageContentFit.CONTAIN)
12
+
13
+ assertNotEquals(cover, contain)
14
+ }
15
+
16
+ @Test
17
+ fun nullContentFitUsesCoverIdentity() {
18
+ assertEquals(signature(OneKeyImageContentFit.COVER), signature(null))
19
+ }
20
+
21
+ private fun signature(contentFit: OneKeyImageContentFit?): String = oneKeyImageRequestSignature(
22
+ rawUrl = "https://example.com/image.png",
23
+ sourceHeadersJson = null,
24
+ recyclingKey = null,
25
+ cachePolicy = OneKeyImageCachePolicy.MEMORY_DISK,
26
+ contentFit = contentFit,
27
+ optimizeTos = true,
28
+ overscan = 1.1,
29
+ width = 200,
30
+ height = 100,
31
+ density = 2f,
32
+ )
33
+ }