expo-image 2.4.0-canary-20250713-8f814f8 → 2.5.0-canary-20250722-599a28f
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/CHANGELOG.md +13 -2
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/image/ExpoImageModule.kt +23 -4
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt +1 -146
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashEncoder.kt +184 -0
- package/build/Image.d.ts +8 -0
- package/build/Image.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/ImageModule.swift +27 -15
- package/ios/ImageSource.swift +1 -2
- package/ios/ImageUtils.swift +4 -8
- package/ios/ImageView.swift +4 -6
- package/ios/Loaders/PhotoLibraryAssetLoader.swift +1 -1
- package/local-maven-repo/BareExpo/expo.modules.image/{2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8-sources.jar → 2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar} +0 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar.md5 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar.sha1 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar.sha256 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar.sha512 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.aar +0 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.aar.md5 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.aar.sha1 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.aar.sha256 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.aar.sha512 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/{2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.module → 2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.module} +22 -22
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.module.md5 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.module.sha1 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.module.sha256 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.module.sha512 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/{2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.pom → 2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.pom} +1 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.pom.md5 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.pom.sha1 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.pom.sha256 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.5.0-canary-20250722-599a28f/expo.modules.image-2.5.0-canary-20250722-599a28f.pom.sha512 +1 -0
- package/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml +4 -4
- package/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha512 +1 -1
- package/package.json +3 -3
- package/src/Image.tsx +11 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8-sources.jar.md5 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8-sources.jar.sha1 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8-sources.jar.sha256 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8-sources.jar.sha512 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.aar +0 -0
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.aar.md5 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.aar.sha1 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.aar.sha256 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.aar.sha512 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.module.md5 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.module.sha1 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.module.sha256 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.module.sha512 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.pom.md5 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.pom.sha1 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.pom.sha256 +0 -1
- package/local-maven-repo/BareExpo/expo.modules.image/2.4.0-canary-20250713-8f814f8/expo.modules.image-2.4.0-canary-20250713-8f814f8.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,20 +6,31 @@
|
|
|
6
6
|
|
|
7
7
|
### 🎉 New features
|
|
8
8
|
|
|
9
|
+
- Add `generateThumbhashAsync` ([#38090](https://github.com/expo/expo/pull/38090) by [@Wenszel](https://github.com/Wenszel))
|
|
9
10
|
- Add support for `ImageRef` source in `generateBlurhashAsync` ([#37901](https://github.com/expo/expo/pull/37901) by [@Wenszel](https://github.com/Wenszel))
|
|
10
11
|
- [Android] Add generateBlurhashAsync ([#37817](https://github.com/expo/expo/pull/37817) by [@Wenszel](https://github.com/Wenszel))
|
|
11
|
-
- [iOS] Add a new prop - `enforceEarlyResizing` to reduce the memory usage of the image view. ([#37909](https://github.com/expo/expo/pull/37909) by [@lukmccall](https://github.com/lukmccall))
|
|
12
12
|
|
|
13
13
|
### 🐛 Bug fixes
|
|
14
14
|
|
|
15
|
-
- [iOS] Speed up displaying local assets. ([#37795](https://github.com/expo/expo/pull/37795) by [@aleqsio](https://github.com/aleqsio))
|
|
16
15
|
- [Android] Fix animation resuming by casting image to GifDrawable. ([#37363](https://github.com/expo/expo/pull/37363) by [@Wenszel](https://github.com/Wenszel))
|
|
17
16
|
- [Web] Fix `alt` as an alias for `accessibilityLabel` ([#37682](https://github.com/expo/expo/pull/37682) by [@huextrat](https://github.com/huextrat))
|
|
17
|
+
- [iOS] Fix caching resized images from Photo Library. ([#38105](https://github.com/expo/expo/pull/38105) by [@jakex7](https://github.com/jakex7))
|
|
18
18
|
|
|
19
19
|
### 💡 Others
|
|
20
20
|
|
|
21
21
|
### 📚 3rd party library updates
|
|
22
22
|
|
|
23
|
+
## 2.4.0 - 2025-07-17
|
|
24
|
+
|
|
25
|
+
### 🎉 New features
|
|
26
|
+
|
|
27
|
+
- [iOS] Add a new prop - `enforceEarlyResizing` to reduce the memory usage of the image view. ([#37909](https://github.com/expo/expo/pull/37909) by [@lukmccall](https://github.com/lukmccall))
|
|
28
|
+
|
|
29
|
+
### 🐛 Bug fixes
|
|
30
|
+
|
|
31
|
+
- [iOS] Speed up displaying local assets. ([#37795](https://github.com/expo/expo/pull/37795) by [@aleqsio](https://github.com/aleqsio))
|
|
32
|
+
- [iOS] Fix some operation were incorrectly cancelled. ([#37987](https://github.com/expo/expo/pull/37987) by [@lukmccall](https://github.com/lukmccall))
|
|
33
|
+
|
|
23
34
|
## 2.3.2 - 2025-07-01
|
|
24
35
|
|
|
25
36
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ android {
|
|
|
8
8
|
namespace "expo.modules.image"
|
|
9
9
|
defaultConfig {
|
|
10
10
|
versionCode 1
|
|
11
|
-
versionName "2.
|
|
11
|
+
versionName "2.5.0-canary-20250722-599a28f"
|
|
12
12
|
consumerProguardFiles("proguard-rules.pro")
|
|
13
13
|
|
|
14
14
|
buildConfigField("boolean", "ALLOW_GLIDE_LOGS", project.properties.get("EXPO_ALLOW_GLIDE_LOGS", "false"))
|
|
@@ -5,6 +5,7 @@ package expo.modules.image
|
|
|
5
5
|
import android.graphics.Bitmap
|
|
6
6
|
import android.graphics.drawable.BitmapDrawable
|
|
7
7
|
import android.graphics.drawable.Drawable
|
|
8
|
+
import android.util.Base64
|
|
8
9
|
import androidx.core.graphics.drawable.toBitmap
|
|
9
10
|
import androidx.core.graphics.drawable.toBitmapOrNull
|
|
10
11
|
import androidx.core.view.doOnDetach
|
|
@@ -30,6 +31,7 @@ import expo.modules.image.records.DecodedSource
|
|
|
30
31
|
import expo.modules.image.records.ImageLoadOptions
|
|
31
32
|
import expo.modules.image.records.ImageTransition
|
|
32
33
|
import expo.modules.image.records.SourceMap
|
|
34
|
+
import expo.modules.image.thumbhash.ThumbhashEncoder
|
|
33
35
|
import expo.modules.kotlin.Promise
|
|
34
36
|
import expo.modules.kotlin.apifeatures.EitherType
|
|
35
37
|
import expo.modules.kotlin.exception.Exceptions
|
|
@@ -118,7 +120,10 @@ class ExpoImageModule : Module() {
|
|
|
118
120
|
ImageLoadTask(appContext, source, options ?: ImageLoadOptions()).load()
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
suspend fun generatePlaceholder(
|
|
124
|
+
source: Either<URL, Image>,
|
|
125
|
+
encoder: (Bitmap) -> String
|
|
126
|
+
): String {
|
|
122
127
|
val image = source.let {
|
|
123
128
|
if (it.`is`(Image::class)) {
|
|
124
129
|
it.get(Image::class)
|
|
@@ -126,10 +131,24 @@ class ExpoImageModule : Module() {
|
|
|
126
131
|
ImageLoadTask(appContext, SourceMap(uri = it.get(URL::class).toString()), ImageLoadOptions()).load()
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
return withContext(Dispatchers.Default) {
|
|
135
|
+
encoder(image.ref.toBitmap())
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
AsyncFunction("generateBlurhashAsync") Coroutine { source: Either<URL, Image>, numberOfComponents: Pair<Int, Int> ->
|
|
140
|
+
generatePlaceholder(source) { bitmap ->
|
|
141
|
+
BlurhashEncoder.encode(bitmap, numberOfComponents)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
AsyncFunction("generateThumbhashAsync") Coroutine { source: Either<URL, Image> ->
|
|
146
|
+
generatePlaceholder(source) { bitmap ->
|
|
147
|
+
Base64.encodeToString(
|
|
148
|
+
ThumbhashEncoder.encode(bitmap),
|
|
149
|
+
Base64.NO_WRAP
|
|
150
|
+
)
|
|
131
151
|
}
|
|
132
|
-
blurHash
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
Class(Image::class) {
|
|
@@ -5,113 +5,6 @@ import android.graphics.Color
|
|
|
5
5
|
|
|
6
6
|
// ThumbHash Java implementation (converted to kotlin) thanks to @evanw https://github.com/evanw/thumbhash
|
|
7
7
|
object ThumbhashDecoder {
|
|
8
|
-
/**
|
|
9
|
-
* Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A.
|
|
10
|
-
*
|
|
11
|
-
* @param w The width of the input image. Must be ≤100px.
|
|
12
|
-
* @param h The height of the input image. Must be ≤100px.
|
|
13
|
-
* @param rgba The pixels in the input image, row-by-row. Must have w*h*4 elements.
|
|
14
|
-
* @return The ThumbHash as a byte array.
|
|
15
|
-
*/
|
|
16
|
-
fun rgbaToThumbHash(w: Int, h: Int, rgba: ByteArray): ByteArray {
|
|
17
|
-
// Encoding an image larger than 100x100 is slow with no benefit
|
|
18
|
-
require(!(w > 100 || h > 100)) { w.toString() + "x" + h + " doesn't fit in 100x100" }
|
|
19
|
-
|
|
20
|
-
// Determine the average color
|
|
21
|
-
var avg_r = 0f
|
|
22
|
-
var avg_g = 0f
|
|
23
|
-
var avg_b = 0f
|
|
24
|
-
var avg_a = 0f
|
|
25
|
-
var i = 0
|
|
26
|
-
var j = 0
|
|
27
|
-
while (i < w * h) {
|
|
28
|
-
val alpha = (rgba[j + 3].toInt() and 255) / 255.0f
|
|
29
|
-
avg_r += alpha / 255.0f * (rgba[j].toInt() and 255)
|
|
30
|
-
avg_g += alpha / 255.0f * (rgba[j + 1].toInt() and 255)
|
|
31
|
-
avg_b += alpha / 255.0f * (rgba[j + 2].toInt() and 255)
|
|
32
|
-
avg_a += alpha
|
|
33
|
-
i++
|
|
34
|
-
j += 4
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (avg_a > 0) {
|
|
38
|
-
avg_r /= avg_a
|
|
39
|
-
avg_g /= avg_a
|
|
40
|
-
avg_b /= avg_a
|
|
41
|
-
}
|
|
42
|
-
val hasAlpha = avg_a < w * h
|
|
43
|
-
val l_limit = if (hasAlpha) 5 else 7 // Use fewer luminance bits if there's alpha
|
|
44
|
-
val lx = Math.max(1, Math.round((l_limit * w).toFloat() / Math.max(w, h).toFloat()))
|
|
45
|
-
val ly = Math.max(1, Math.round((l_limit * h).toFloat() / Math.max(w, h).toFloat()))
|
|
46
|
-
val l = FloatArray(w * h) // luminance
|
|
47
|
-
val p = FloatArray(w * h) // yellow - blue
|
|
48
|
-
val q = FloatArray(w * h) // red - green
|
|
49
|
-
val a = FloatArray(w * h) // alpha
|
|
50
|
-
|
|
51
|
-
// Convert the image from RGBA to LPQA (composite atop the average color)
|
|
52
|
-
i = 0
|
|
53
|
-
j = 0
|
|
54
|
-
while (i < w * h) {
|
|
55
|
-
val alpha = (rgba[j + 3].toInt() and 255) / 255.0f
|
|
56
|
-
val r = avg_r * (1.0f - alpha) + alpha / 255.0f * (rgba[j].toInt() and 255)
|
|
57
|
-
val g = avg_g * (1.0f - alpha) + alpha / 255.0f * (rgba[j + 1].toInt() and 255)
|
|
58
|
-
val b = avg_b * (1.0f - alpha) + alpha / 255.0f * (rgba[j + 2].toInt() and 255)
|
|
59
|
-
l[i] = (r + g + b) / 3.0f
|
|
60
|
-
p[i] = (r + g) / 2.0f - b
|
|
61
|
-
q[i] = r - g
|
|
62
|
-
a[i] = alpha
|
|
63
|
-
i++
|
|
64
|
-
j += 4
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Encode using the DCT into DC (constant) and normalized AC (varying) terms
|
|
68
|
-
val l_channel = Channel(Math.max(3, lx), Math.max(3, ly)).encode(w, h, l)
|
|
69
|
-
val p_channel = Channel(3, 3).encode(w, h, p)
|
|
70
|
-
val q_channel = Channel(3, 3).encode(w, h, q)
|
|
71
|
-
val a_channel = if (hasAlpha) Channel(5, 5).encode(w, h, a) else null
|
|
72
|
-
|
|
73
|
-
// Write the constants
|
|
74
|
-
val isLandscape = w > h
|
|
75
|
-
val header24 = (
|
|
76
|
-
Math.round(63.0f * l_channel.dc)
|
|
77
|
-
or (Math.round(31.5f + 31.5f * p_channel.dc) shl 6)
|
|
78
|
-
or (Math.round(31.5f + 31.5f * q_channel.dc) shl 12)
|
|
79
|
-
or (Math.round(31.0f * l_channel.scale) shl 18)
|
|
80
|
-
or if (hasAlpha) 1 shl 23 else 0
|
|
81
|
-
)
|
|
82
|
-
val header16 = (
|
|
83
|
-
(if (isLandscape) ly else lx)
|
|
84
|
-
or (Math.round(63.0f * p_channel.scale) shl 3)
|
|
85
|
-
or (Math.round(63.0f * q_channel.scale) shl 9)
|
|
86
|
-
or if (isLandscape) 1 shl 15 else 0
|
|
87
|
-
)
|
|
88
|
-
val ac_start = if (hasAlpha) 6 else 5
|
|
89
|
-
val ac_count = (
|
|
90
|
-
l_channel.ac.size + p_channel.ac.size + q_channel.ac.size +
|
|
91
|
-
if (hasAlpha) a_channel!!.ac.size else 0
|
|
92
|
-
)
|
|
93
|
-
val hash = ByteArray(ac_start + (ac_count + 1) / 2)
|
|
94
|
-
hash[0] = header24.toByte()
|
|
95
|
-
hash[1] = (header24 shr 8).toByte()
|
|
96
|
-
hash[2] = (header24 shr 16).toByte()
|
|
97
|
-
hash[3] = header16.toByte()
|
|
98
|
-
hash[4] = (header16 shr 8).toByte()
|
|
99
|
-
if (hasAlpha) {
|
|
100
|
-
hash[5] = (
|
|
101
|
-
Math.round(15.0f * a_channel!!.dc)
|
|
102
|
-
or (Math.round(15.0f * a_channel.scale) shl 4)
|
|
103
|
-
).toByte()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Write the varying factors
|
|
107
|
-
var ac_index = 0
|
|
108
|
-
ac_index = l_channel.writeTo(hash, ac_start, ac_index)
|
|
109
|
-
ac_index = p_channel.writeTo(hash, ac_start, ac_index)
|
|
110
|
-
ac_index = q_channel.writeTo(hash, ac_start, ac_index)
|
|
111
|
-
if (hasAlpha) a_channel!!.writeTo(hash, ac_start, ac_index)
|
|
112
|
-
return hash
|
|
113
|
-
}
|
|
114
|
-
|
|
115
8
|
/**
|
|
116
9
|
* Decodes a ThumbHash to an RGBA image. RGB is not be premultiplied by A.
|
|
117
10
|
*
|
|
@@ -302,11 +195,8 @@ object ThumbhashDecoder {
|
|
|
302
195
|
|
|
303
196
|
class Image(var width: Int, var height: Int, var rgba: ByteArray)
|
|
304
197
|
class RGBA(var r: Float, var g: Float, var b: Float, var a: Float)
|
|
305
|
-
private class Channel
|
|
306
|
-
var dc = 0f
|
|
198
|
+
private class Channel(nx: Int, ny: Int) {
|
|
307
199
|
var ac: FloatArray
|
|
308
|
-
var scale = 0f
|
|
309
|
-
|
|
310
200
|
init {
|
|
311
201
|
var n = 0
|
|
312
202
|
for (cy in 0 until ny) {
|
|
@@ -319,32 +209,6 @@ object ThumbhashDecoder {
|
|
|
319
209
|
ac = FloatArray(n)
|
|
320
210
|
}
|
|
321
211
|
|
|
322
|
-
fun encode(w: Int, h: Int, channel: FloatArray): Channel {
|
|
323
|
-
var n = 0
|
|
324
|
-
val fx = FloatArray(w)
|
|
325
|
-
for (cy in 0 until ny) {
|
|
326
|
-
var cx = 0
|
|
327
|
-
while (cx * ny < nx * (ny - cy)) {
|
|
328
|
-
var f = 0f
|
|
329
|
-
for (x in 0 until w) fx[x] = Math.cos(Math.PI / w * cx * (x + 0.5f)).toFloat()
|
|
330
|
-
for (y in 0 until h) {
|
|
331
|
-
val fy = Math.cos(Math.PI / h * cy * (y + 0.5f)).toFloat()
|
|
332
|
-
for (x in 0 until w) f += channel[x + y * w] * fx[x] * fy
|
|
333
|
-
}
|
|
334
|
-
f /= (w * h).toFloat()
|
|
335
|
-
if (cx > 0 || cy > 0) {
|
|
336
|
-
ac[n++] = f
|
|
337
|
-
scale = Math.max(scale, Math.abs(f))
|
|
338
|
-
} else {
|
|
339
|
-
dc = f
|
|
340
|
-
}
|
|
341
|
-
cx++
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (scale > 0) for (i in ac.indices) ac[i] = 0.5f + 0.5f / scale * ac[i]
|
|
345
|
-
return this
|
|
346
|
-
}
|
|
347
|
-
|
|
348
212
|
fun decode(hash: ByteArray, start: Int, index: Int, scale: Float): Int {
|
|
349
213
|
var currentIndex = index
|
|
350
214
|
for (i in ac.indices) {
|
|
@@ -354,14 +218,5 @@ object ThumbhashDecoder {
|
|
|
354
218
|
}
|
|
355
219
|
return currentIndex
|
|
356
220
|
}
|
|
357
|
-
|
|
358
|
-
fun writeTo(hash: ByteArray, start: Int, index: Int): Int {
|
|
359
|
-
var currentIndex = index
|
|
360
|
-
for (v in ac) {
|
|
361
|
-
hash[start + (currentIndex shr 1)] = (hash[start + (currentIndex shr 1)].toInt() or (Math.round(15.0f * v) shl (currentIndex and 1 shl 2))).toByte()
|
|
362
|
-
currentIndex++
|
|
363
|
-
}
|
|
364
|
-
return currentIndex
|
|
365
|
-
}
|
|
366
221
|
}
|
|
367
222
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
package expo.modules.image.thumbhash
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
|
|
6
|
+
// ThumbHash Java implementation (converted to kotlin) thanks to @evanw https://github.com/evanw/thumbhash
|
|
7
|
+
object ThumbhashEncoder {
|
|
8
|
+
/**
|
|
9
|
+
* Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A.
|
|
10
|
+
* @param bitmap The bitmap to generate the ThumbHash from.
|
|
11
|
+
* @return The ThumbHash as a byte array.
|
|
12
|
+
*/
|
|
13
|
+
fun encode(bitmap: Bitmap): ByteArray {
|
|
14
|
+
// Encoding an image larger than 100x100 is slow with no benefit
|
|
15
|
+
val resizedBitmap = resizeKeepingAspectRatio(bitmap, 100)
|
|
16
|
+
val w = resizedBitmap.width
|
|
17
|
+
val h = resizedBitmap.height
|
|
18
|
+
|
|
19
|
+
val pixels = IntArray(w * h)
|
|
20
|
+
resizedBitmap.getPixels(pixels, 0, w, 0, 0, w, h)
|
|
21
|
+
|
|
22
|
+
var avg_r = 0f
|
|
23
|
+
var avg_g = 0f
|
|
24
|
+
var avg_b = 0f
|
|
25
|
+
var avg_a = 0f
|
|
26
|
+
var i = 0
|
|
27
|
+
while (i < w * h) {
|
|
28
|
+
val alpha = Color.alpha(pixels[i]) / 255.0f
|
|
29
|
+
avg_r += alpha / 255.0f * Color.red(pixels[i])
|
|
30
|
+
avg_g += alpha / 255.0f * Color.green(pixels[i])
|
|
31
|
+
avg_b += alpha / 255.0f * Color.blue(pixels[i])
|
|
32
|
+
avg_a += alpha
|
|
33
|
+
i++
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (avg_a > 0) {
|
|
37
|
+
avg_r /= avg_a
|
|
38
|
+
avg_g /= avg_a
|
|
39
|
+
avg_b /= avg_a
|
|
40
|
+
}
|
|
41
|
+
val hasAlpha = avg_a < w * h
|
|
42
|
+
val l_limit = if (hasAlpha) 5 else 7 // Use fewer luminance bits if there's alpha
|
|
43
|
+
val lx = Math.max(1, Math.round((l_limit * w).toFloat() / Math.max(w, h).toFloat()))
|
|
44
|
+
val ly = Math.max(1, Math.round((l_limit * h).toFloat() / Math.max(w, h).toFloat()))
|
|
45
|
+
val l = FloatArray(w * h) // luminance
|
|
46
|
+
val p = FloatArray(w * h) // yellow - blue
|
|
47
|
+
val q = FloatArray(w * h) // red - green
|
|
48
|
+
val a = FloatArray(w * h) // alpha
|
|
49
|
+
|
|
50
|
+
// Convert the image from RGBA to LPQA (composite atop the average color)
|
|
51
|
+
i = 0
|
|
52
|
+
while (i < w * h) {
|
|
53
|
+
val alpha = (Color.alpha(pixels[i]) and 255) / 255.0f
|
|
54
|
+
val r = avg_r * (1.0f - alpha) + alpha / 255.0f * Color.red(pixels[i])
|
|
55
|
+
val g = avg_g * (1.0f - alpha) + alpha / 255.0f * Color.green(pixels[i])
|
|
56
|
+
val b = avg_b * (1.0f - alpha) + alpha / 255.0f * Color.blue(pixels[i])
|
|
57
|
+
l[i] = (r + g + b) / 3.0f
|
|
58
|
+
p[i] = (r + g) / 2.0f - b
|
|
59
|
+
q[i] = r - g
|
|
60
|
+
a[i] = alpha
|
|
61
|
+
i++
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Encode using the DCT into DC (constant) and normalized AC (varying) terms
|
|
65
|
+
val l_channel = Channel(Math.max(3, lx), Math.max(3, ly)).encode(w, h, l)
|
|
66
|
+
val p_channel = Channel(3, 3).encode(w, h, p)
|
|
67
|
+
val q_channel = Channel(3, 3).encode(w, h, q)
|
|
68
|
+
val a_channel = if (hasAlpha) Channel(5, 5).encode(w, h, a) else null
|
|
69
|
+
|
|
70
|
+
// Write the constants
|
|
71
|
+
val isLandscape = w > h
|
|
72
|
+
val header24 = (
|
|
73
|
+
Math.round(63.0f * l_channel.dc)
|
|
74
|
+
or (Math.round(31.5f + 31.5f * p_channel.dc) shl 6)
|
|
75
|
+
or (Math.round(31.5f + 31.5f * q_channel.dc) shl 12)
|
|
76
|
+
or (Math.round(31.0f * l_channel.scale) shl 18)
|
|
77
|
+
or if (hasAlpha) 1 shl 23 else 0
|
|
78
|
+
)
|
|
79
|
+
val header16 = (
|
|
80
|
+
(if (isLandscape) ly else lx)
|
|
81
|
+
or (Math.round(63.0f * p_channel.scale) shl 3)
|
|
82
|
+
or (Math.round(63.0f * q_channel.scale) shl 9)
|
|
83
|
+
or if (isLandscape) 1 shl 15 else 0
|
|
84
|
+
)
|
|
85
|
+
val ac_start = if (hasAlpha) 6 else 5
|
|
86
|
+
val ac_count = (
|
|
87
|
+
l_channel.ac.size + p_channel.ac.size + q_channel.ac.size +
|
|
88
|
+
if (hasAlpha) a_channel!!.ac.size else 0
|
|
89
|
+
)
|
|
90
|
+
val hash = ByteArray(ac_start + (ac_count + 1) / 2)
|
|
91
|
+
hash[0] = header24.toByte()
|
|
92
|
+
hash[1] = (header24 shr 8).toByte()
|
|
93
|
+
hash[2] = (header24 shr 16).toByte()
|
|
94
|
+
hash[3] = header16.toByte()
|
|
95
|
+
hash[4] = (header16 shr 8).toByte()
|
|
96
|
+
if (hasAlpha) {
|
|
97
|
+
hash[5] = (
|
|
98
|
+
Math.round(15.0f * a_channel!!.dc)
|
|
99
|
+
or (Math.round(15.0f * a_channel.scale) shl 4)
|
|
100
|
+
).toByte()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Write the varying factors
|
|
104
|
+
var ac_index = 0
|
|
105
|
+
ac_index = l_channel.writeTo(hash, ac_start, ac_index)
|
|
106
|
+
ac_index = p_channel.writeTo(hash, ac_start, ac_index)
|
|
107
|
+
ac_index = q_channel.writeTo(hash, ac_start, ac_index)
|
|
108
|
+
if (hasAlpha) a_channel!!.writeTo(hash, ac_start, ac_index)
|
|
109
|
+
|
|
110
|
+
return hash
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private fun resizeKeepingAspectRatio(bitmap: Bitmap, maxSize: Int): Bitmap {
|
|
114
|
+
val width = bitmap.width
|
|
115
|
+
val height = bitmap.height
|
|
116
|
+
val ratio = width.toFloat() / height.toFloat()
|
|
117
|
+
|
|
118
|
+
val newWidth: Int
|
|
119
|
+
val newHeight: Int
|
|
120
|
+
|
|
121
|
+
if (ratio > 1) {
|
|
122
|
+
newWidth = maxSize
|
|
123
|
+
newHeight = (maxSize / ratio).toInt()
|
|
124
|
+
} else {
|
|
125
|
+
newHeight = maxSize
|
|
126
|
+
newWidth = (maxSize * ratio).toInt()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private class Channel(var nx: Int, var ny: Int) {
|
|
133
|
+
var dc = 0f
|
|
134
|
+
var ac: FloatArray
|
|
135
|
+
var scale = 0f
|
|
136
|
+
|
|
137
|
+
init {
|
|
138
|
+
var n = 0
|
|
139
|
+
for (cy in 0 until ny) {
|
|
140
|
+
var cx = if (cy > 0) 0 else 1
|
|
141
|
+
while (cx * ny < nx * (ny - cy)) {
|
|
142
|
+
n++
|
|
143
|
+
cx++
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
ac = FloatArray(n)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fun encode(w: Int, h: Int, channel: FloatArray): Channel {
|
|
150
|
+
var n = 0
|
|
151
|
+
val fx = FloatArray(w)
|
|
152
|
+
for (cy in 0 until ny) {
|
|
153
|
+
var cx = 0
|
|
154
|
+
while (cx * ny < nx * (ny - cy)) {
|
|
155
|
+
var f = 0f
|
|
156
|
+
for (x in 0 until w) fx[x] = Math.cos(Math.PI / w * cx * (x + 0.5f)).toFloat()
|
|
157
|
+
for (y in 0 until h) {
|
|
158
|
+
val fy = Math.cos(Math.PI / h * cy * (y + 0.5f)).toFloat()
|
|
159
|
+
for (x in 0 until w) f += channel[x + y * w] * fx[x] * fy
|
|
160
|
+
}
|
|
161
|
+
f /= (w * h).toFloat()
|
|
162
|
+
if (cx > 0 || cy > 0) {
|
|
163
|
+
ac[n++] = f
|
|
164
|
+
scale = Math.max(scale, Math.abs(f))
|
|
165
|
+
} else {
|
|
166
|
+
dc = f
|
|
167
|
+
}
|
|
168
|
+
cx++
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (scale > 0) for (i in ac.indices) ac[i] = 0.5f + 0.5f / scale * ac[i]
|
|
172
|
+
return this
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fun writeTo(hash: ByteArray, start: Int, index: Int): Int {
|
|
176
|
+
var currentIndex = index
|
|
177
|
+
for (v in ac) {
|
|
178
|
+
hash[start + (currentIndex shr 1)] = (hash[start + (currentIndex shr 1)].toInt() or (Math.round(15.0f * v) shl (currentIndex and 1 shl 2))).toByte()
|
|
179
|
+
currentIndex++
|
|
180
|
+
}
|
|
181
|
+
return currentIndex
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
package/build/Image.d.ts
CHANGED
|
@@ -77,6 +77,14 @@ export declare class Image extends React.PureComponent<ImageProps> {
|
|
|
77
77
|
width: number;
|
|
78
78
|
height: number;
|
|
79
79
|
}): Promise<string | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Asynchronously generates a [Thumbhash](https://evanw.github.io/thumbhash/) from an image.
|
|
82
|
+
* @param source - The image source, either a URL (string) or an ImageRef
|
|
83
|
+
* @platform android
|
|
84
|
+
* @platform ios
|
|
85
|
+
* @return A promise resolving to the thumbhash string.
|
|
86
|
+
*/
|
|
87
|
+
static generateThumbhashAsync(source: string | ImageRef): Promise<string>;
|
|
80
88
|
/**
|
|
81
89
|
* Asynchronously starts playback of the view's image if it is animated.
|
|
82
90
|
* @platform android
|
package/build/Image.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../src/Image.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,SAAS,MAAM,aAAa,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,EACV,QAAQ,EACR,WAAW,EACZ,MAAM,eAAe,CAAC;AAQvB,qBAAa,KAAM,SAAQ,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC;IACxD,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACjD,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;gBAEnC,KAAK,EAAE,UAAU;IAO7B,gBAAgB,2BAMd;IAEF;;OAEG;IACH,MAAM,CAAC,KAAK,kBAAqB;IAEjC;;;;;;;;;;OAUG;WACU,QAAQ,CACnB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,WAAW,CAAC,EAAE,oBAAoB,CAAC,aAAa,CAAC,GAChD,OAAO,CAAC,OAAO,CAAC;IACnB;;;;;;;;;;OAUG;WACU,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBhG;;;;;;;OAOG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjD;;;;;;;OAOG;WACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/C;;;;;;;;;OASG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIxE;;;;;;;;OAQG;WACU,qBAAqB,CAChC,MAAM,EAAE,MAAM,GAAG,QAAQ,EACzB,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACvE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIzB;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC;;;;OAIG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1C;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlC;;;;;;OAMG;WACU,SAAS,CACpB,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,EACrC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,QAAQ,CAAC;IAKpB,MAAM;CA6CP"}
|
|
1
|
+
{"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../src/Image.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,SAAS,MAAM,aAAa,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,EACV,QAAQ,EACR,WAAW,EACZ,MAAM,eAAe,CAAC;AAQvB,qBAAa,KAAM,SAAQ,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC;IACxD,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACjD,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;gBAEnC,KAAK,EAAE,UAAU;IAO7B,gBAAgB,2BAMd;IAEF;;OAEG;IACH,MAAM,CAAC,KAAK,kBAAqB;IAEjC;;;;;;;;;;OAUG;WACU,QAAQ,CACnB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,WAAW,CAAC,EAAE,oBAAoB,CAAC,aAAa,CAAC,GAChD,OAAO,CAAC,OAAO,CAAC;IACnB;;;;;;;;;;OAUG;WACU,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBhG;;;;;;;OAOG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjD;;;;;;;OAOG;WACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/C;;;;;;;;;OASG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIxE;;;;;;;;OAQG;WACU,qBAAqB,CAChC,MAAM,EAAE,MAAM,GAAG,QAAQ,EACzB,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACvE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIzB;;;;;;OAMG;WACU,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/E;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC;;;;OAIG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1C;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlC;;;;;;OAMG;WACU,SAAS,CACpB,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,EACrC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,QAAQ,CAAC;IAKpB,MAAM;CA6CP"}
|
package/expo-module.config.json
CHANGED
package/ios/ImageModule.swift
CHANGED
|
@@ -171,26 +171,20 @@ public final class ImageModule: Module {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
AsyncFunction("generateBlurhashAsync") { (source: Either<Image, URL>, numberOfComponents: CGSize, promise: Promise) in
|
|
174
|
-
let downloader = SDWebImageDownloader()
|
|
175
174
|
let parsedNumberOfComponents = (width: Int(numberOfComponents.width), height: Int(numberOfComponents.height))
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if let blurhashString = blurhash(fromImage: image.ref, numberOfComponents: parsedNumberOfComponents) {
|
|
175
|
+
generatePlaceholder(source: source) { (image: UIImage) in
|
|
176
|
+
if let blurhashString = blurhash(fromImage: image, numberOfComponents: parsedNumberOfComponents) {
|
|
179
177
|
promise.resolve(blurhashString)
|
|
180
178
|
} else {
|
|
181
179
|
promise.reject(BlurhashGenerationException())
|
|
182
180
|
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
promise.reject(BlurhashGenerationException())
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
})
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
AsyncFunction("generateThumbhashAsync") { (source: Either<Image, URL>, promise: Promise) in
|
|
185
|
+
generatePlaceholder(source: source) { (image: UIImage) in
|
|
186
|
+
let blurhashString = thumbHash(fromImage: image)
|
|
187
|
+
promise.resolve(blurhashString.base64EncodedString())
|
|
194
188
|
}
|
|
195
189
|
}
|
|
196
190
|
|
|
@@ -237,6 +231,24 @@ public final class ImageModule: Module {
|
|
|
237
231
|
}
|
|
238
232
|
}
|
|
239
233
|
|
|
234
|
+
func generatePlaceholder(
|
|
235
|
+
source: Either<Image, URL>,
|
|
236
|
+
generator: @escaping (UIImage) -> Void,
|
|
237
|
+
) {
|
|
238
|
+
if let image: Image = source.get() {
|
|
239
|
+
generator(image.ref)
|
|
240
|
+
} else if let url: URL = source.get() {
|
|
241
|
+
let downloader = SDWebImageDownloader()
|
|
242
|
+
downloader.downloadImage(with: url, progress: nil, completed: { image, _, _, _ in
|
|
243
|
+
DispatchQueue.global().async {
|
|
244
|
+
if let downloadedImage = image {
|
|
245
|
+
generator(downloadedImage)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
240
252
|
static func registerCoders() {
|
|
241
253
|
SDImageCodersManager.shared.addCoder(WebPCoder.shared)
|
|
242
254
|
SDImageCodersManager.shared.addCoder(SDImageAVIFCoder.shared)
|
package/ios/ImageSource.swift
CHANGED
|
@@ -37,8 +37,7 @@ struct ImageSource: Record {
|
|
|
37
37
|
return isPhotoLibraryAssetUrl(uri)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
var
|
|
41
|
-
// TODO: Don't cache other non-network requests (e.g. data URIs, local files)
|
|
40
|
+
var cacheOriginalImage: Bool {
|
|
42
41
|
return !isPhotoLibraryAsset
|
|
43
42
|
}
|
|
44
43
|
}
|
package/ios/ImageUtils.swift
CHANGED
|
@@ -183,17 +183,13 @@ func createSDWebImageContext(forSource source: ImageSource, cachePolicy: ImageCa
|
|
|
183
183
|
// incorrectly rendered images for resize modes that don't scale (`center` and `repeat`).
|
|
184
184
|
context[.imageScaleFactor] = source.scale
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
context[.
|
|
189
|
-
context[.storeCacheType] = SDImageCacheType.none.rawValue
|
|
186
|
+
let sdCacheType = cachePolicy.toSdCacheType().rawValue
|
|
187
|
+
context[.queryCacheType] = sdCacheType
|
|
188
|
+
context[.storeCacheType] = sdCacheType
|
|
190
189
|
|
|
191
|
-
if source.
|
|
192
|
-
let sdCacheType = cachePolicy.toSdCacheType().rawValue
|
|
190
|
+
if source.cacheOriginalImage {
|
|
193
191
|
context[.originalQueryCacheType] = sdCacheType
|
|
194
192
|
context[.originalStoreCacheType] = sdCacheType
|
|
195
|
-
context[.queryCacheType] = sdCacheType
|
|
196
|
-
context[.storeCacheType] = sdCacheType
|
|
197
193
|
} else {
|
|
198
194
|
context[.originalQueryCacheType] = SDImageCacheType.none.rawValue
|
|
199
195
|
context[.originalStoreCacheType] = SDImageCacheType.none.rawValue
|
package/ios/ImageView.swift
CHANGED
|
@@ -121,12 +121,10 @@ public final class ImageView: ExpoView {
|
|
|
121
121
|
if window == nil {
|
|
122
122
|
// Cancel pending requests when the view is unmounted.
|
|
123
123
|
cancelPendingOperation()
|
|
124
|
-
|
|
125
|
-
// Reload the image after mounting the view with non-empty bounds.
|
|
126
|
-
reload()
|
|
127
|
-
} else {
|
|
128
|
-
loadPlaceholderIfNecessary()
|
|
124
|
+
return
|
|
129
125
|
}
|
|
126
|
+
|
|
127
|
+
loadPlaceholderIfNecessary()
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
@@ -165,7 +163,7 @@ public final class ImageView: ExpoView {
|
|
|
165
163
|
|
|
166
164
|
// It seems that `UIImageView` can't tint some vector graphics. If the `tintColor` prop is specified,
|
|
167
165
|
// we tell the SVG coder to decode to a bitmap instead. This will become useless when we switch to SVGNative coder.
|
|
168
|
-
let shouldEarlyResize = imageTintColor != nil || enforceEarlyResizing
|
|
166
|
+
let shouldEarlyResize = imageTintColor != nil || enforceEarlyResizing || source.isPhotoLibraryAsset
|
|
169
167
|
if shouldEarlyResize {
|
|
170
168
|
context[.imagePreserveAspectRatio] = true
|
|
171
169
|
context[.imageThumbnailPixelSize] = CGSize(
|
|
@@ -113,7 +113,7 @@ private func requestAsset(
|
|
|
113
113
|
if let scale = context?[ImageView.screenScaleKey] as? Double,
|
|
114
114
|
let containerSize = context?[ImageView.frameSizeKey] as? CGSize,
|
|
115
115
|
let contentFit = context?[ImageView.contentFitKey] as? ContentFit {
|
|
116
|
-
|
|
116
|
+
targetSize = idealSize(
|
|
117
117
|
contentPixelSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
|
|
118
118
|
containerSize: containerSize,
|
|
119
119
|
scale: scale,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
549abe213eac6144464aca6be80419a2
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
af6e947ec09e1d0bf54ef2276c9aefcf03c30e80
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a71bd4c1dc06465f11ca7b28e36ffc84d8fc28f94212400a9eda74dae84f68bc
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0e053061d59fe36acf72811a3302c44446d4eccb71922bac5f4939a045ada73c5cb25856618ba46fb9fabeb89769e7e236f266af08858f37c70e90c4e1ce4d9f
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1dfd91d5d7c26b8b455f45c08e0bc657
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aec6c7ff43a00c6b2fec2ad77c8e0371d947cd58
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
391955de6c74abb540684536936a36dd3164d7fd3c29f8207a63b60fc3b7e715
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fd70527a9a22d6aad8ce719ad75bc6fdc108db86d4cd8c4c8fd73d489fd96c3337ba7642f0088bf00d5e8d57a1da6c73cb6f384ca9b20c0cb27c2750457168d8
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"component": {
|
|
4
4
|
"group": "BareExpo",
|
|
5
5
|
"module": "expo.modules.image",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.5.0-canary-20250722-599a28f",
|
|
7
7
|
"attributes": {
|
|
8
8
|
"org.gradle.status": "release"
|
|
9
9
|
}
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
],
|
|
55
55
|
"files": [
|
|
56
56
|
{
|
|
57
|
-
"name": "expo.modules.image-2.
|
|
58
|
-
"url": "expo.modules.image-2.
|
|
59
|
-
"size":
|
|
60
|
-
"sha512": "
|
|
61
|
-
"sha256": "
|
|
62
|
-
"sha1": "
|
|
63
|
-
"md5": "
|
|
57
|
+
"name": "expo.modules.image-2.5.0-canary-20250722-599a28f.aar",
|
|
58
|
+
"url": "expo.modules.image-2.5.0-canary-20250722-599a28f.aar",
|
|
59
|
+
"size": 302365,
|
|
60
|
+
"sha512": "fd70527a9a22d6aad8ce719ad75bc6fdc108db86d4cd8c4c8fd73d489fd96c3337ba7642f0088bf00d5e8d57a1da6c73cb6f384ca9b20c0cb27c2750457168d8",
|
|
61
|
+
"sha256": "391955de6c74abb540684536936a36dd3164d7fd3c29f8207a63b60fc3b7e715",
|
|
62
|
+
"sha1": "aec6c7ff43a00c6b2fec2ad77c8e0371d947cd58",
|
|
63
|
+
"md5": "1dfd91d5d7c26b8b455f45c08e0bc657"
|
|
64
64
|
}
|
|
65
65
|
]
|
|
66
66
|
},
|
|
@@ -143,13 +143,13 @@
|
|
|
143
143
|
],
|
|
144
144
|
"files": [
|
|
145
145
|
{
|
|
146
|
-
"name": "expo.modules.image-2.
|
|
147
|
-
"url": "expo.modules.image-2.
|
|
148
|
-
"size":
|
|
149
|
-
"sha512": "
|
|
150
|
-
"sha256": "
|
|
151
|
-
"sha1": "
|
|
152
|
-
"md5": "
|
|
146
|
+
"name": "expo.modules.image-2.5.0-canary-20250722-599a28f.aar",
|
|
147
|
+
"url": "expo.modules.image-2.5.0-canary-20250722-599a28f.aar",
|
|
148
|
+
"size": 302365,
|
|
149
|
+
"sha512": "fd70527a9a22d6aad8ce719ad75bc6fdc108db86d4cd8c4c8fd73d489fd96c3337ba7642f0088bf00d5e8d57a1da6c73cb6f384ca9b20c0cb27c2750457168d8",
|
|
150
|
+
"sha256": "391955de6c74abb540684536936a36dd3164d7fd3c29f8207a63b60fc3b7e715",
|
|
151
|
+
"sha1": "aec6c7ff43a00c6b2fec2ad77c8e0371d947cd58",
|
|
152
|
+
"md5": "1dfd91d5d7c26b8b455f45c08e0bc657"
|
|
153
153
|
}
|
|
154
154
|
]
|
|
155
155
|
},
|
|
@@ -163,13 +163,13 @@
|
|
|
163
163
|
},
|
|
164
164
|
"files": [
|
|
165
165
|
{
|
|
166
|
-
"name": "expo.modules.image-2.
|
|
167
|
-
"url": "expo.modules.image-2.
|
|
168
|
-
"size":
|
|
169
|
-
"sha512": "
|
|
170
|
-
"sha256": "
|
|
171
|
-
"sha1": "
|
|
172
|
-
"md5": "
|
|
166
|
+
"name": "expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar",
|
|
167
|
+
"url": "expo.modules.image-2.5.0-canary-20250722-599a28f-sources.jar",
|
|
168
|
+
"size": 67711,
|
|
169
|
+
"sha512": "0e053061d59fe36acf72811a3302c44446d4eccb71922bac5f4939a045ada73c5cb25856618ba46fb9fabeb89769e7e236f266af08858f37c70e90c4e1ce4d9f",
|
|
170
|
+
"sha256": "a71bd4c1dc06465f11ca7b28e36ffc84d8fc28f94212400a9eda74dae84f68bc",
|
|
171
|
+
"sha1": "af6e947ec09e1d0bf54ef2276c9aefcf03c30e80",
|
|
172
|
+
"md5": "549abe213eac6144464aca6be80419a2"
|
|
173
173
|
}
|
|
174
174
|
]
|
|
175
175
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2124fe9528631e462e2e61c47265a0e4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
81b3af9e016b5019af5de87f78b92a9561c27299
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
b09e6165027768b68ff02a6fa2c6ea7735861a98d3a390da8c98deff11020619
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3337b860b96648aee56787aab20b670936754360bc1ae9aabd31da34f3e5f75dc9cab32556d9388d90e09dacf1f154c899fd6f05224edf9b9003b7eae3da72e6
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<modelVersion>4.0.0</modelVersion>
|
|
10
10
|
<groupId>BareExpo</groupId>
|
|
11
11
|
<artifactId>expo.modules.image</artifactId>
|
|
12
|
-
<version>2.
|
|
12
|
+
<version>2.5.0-canary-20250722-599a28f</version>
|
|
13
13
|
<packaging>aar</packaging>
|
|
14
14
|
<name>expo.modules.image</name>
|
|
15
15
|
<url>https://github.com/expo/expo</url>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d694cb955d9fda794764b4e6f5c72ed4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
5d9c835ef1e1a5fa0bf3f890edde9f0706f22823
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e54a11f1ce976e3775676e2e03aaf1f1c4821228aa6b1b2bba03d76d94fbbb10
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
c1cc974bc3a6d0f1bcb7162959aac7ade03053efdac5feac4f8bff55c247964e41b405bfc2ddd3b82ecabd7ecddcb8183ce02a2eaacdb56ba67f98a46a59d121
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
<groupId>BareExpo</groupId>
|
|
4
4
|
<artifactId>expo.modules.image</artifactId>
|
|
5
5
|
<versioning>
|
|
6
|
-
<latest>2.
|
|
7
|
-
<release>2.
|
|
6
|
+
<latest>2.5.0-canary-20250722-599a28f</latest>
|
|
7
|
+
<release>2.5.0-canary-20250722-599a28f</release>
|
|
8
8
|
<versions>
|
|
9
|
-
<version>2.
|
|
9
|
+
<version>2.5.0-canary-20250722-599a28f</version>
|
|
10
10
|
</versions>
|
|
11
|
-
<lastUpdated>
|
|
11
|
+
<lastUpdated>20250722134543</lastUpdated>
|
|
12
12
|
</versioning>
|
|
13
13
|
</metadata>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
245a829128982550118c3f85cef9732a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d2d5de7b7516ae918e2bb68e3760ff7a9d055292
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
50120f99cd14e99757d38e4980efedce96bccf56b12f3a3f61d4d70a44a92d3b
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1a7423133d5254c2858ef6e97964c64bfa02a225ec014c45d7cb5e46a270688386012677643ef60f34e110e5a7146e875536a1ba610262bdd0c59b9abdf4cb90
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-image",
|
|
3
3
|
"title": "Expo Image",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.0-canary-20250722-599a28f",
|
|
5
5
|
"description": "A cross-platform, performant image component for React Native and Expo with Web support",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "build/index.d.ts",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"expo-module-scripts": "4.1.10-canary-
|
|
32
|
+
"expo-module-scripts": "4.1.10-canary-20250722-599a28f"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"expo": "54.0.0-canary-
|
|
35
|
+
"expo": "54.0.0-canary-20250722-599a28f",
|
|
36
36
|
"react": "*",
|
|
37
37
|
"react-native": "*",
|
|
38
38
|
"react-native-web": "*"
|
package/src/Image.tsx
CHANGED
|
@@ -143,6 +143,17 @@ export class Image extends React.PureComponent<ImageProps> {
|
|
|
143
143
|
return ImageModule.generateBlurhashAsync(source, numberOfComponents);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Asynchronously generates a [Thumbhash](https://evanw.github.io/thumbhash/) from an image.
|
|
148
|
+
* @param source - The image source, either a URL (string) or an ImageRef
|
|
149
|
+
* @platform android
|
|
150
|
+
* @platform ios
|
|
151
|
+
* @return A promise resolving to the thumbhash string.
|
|
152
|
+
*/
|
|
153
|
+
static async generateThumbhashAsync(source: string | ImageRef): Promise<string> {
|
|
154
|
+
return ImageModule.generateThumbhashAsync(source);
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
/**
|
|
147
158
|
* Asynchronously starts playback of the view's image if it is animated.
|
|
148
159
|
* @platform android
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
fb1a021a3c959caf9c844732221afe77
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
f59c1a61e799d72cf2da93e51bdb6bfc76a1fecb
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
047cf4256d9f1f9d37b874fe403d46abd838a59bbdf3d5b923d25b546242d99f
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
5088775ebe8dbdd3636ae44627d45f466d3b26af289ccd64cc7f59a1db50045ec4c246dfacdfa80fdedd21b09f837215dd2f0843c8915c10afe74400ec3e626c
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
987b16fc46ba4dfb8d53af2daf99fc7a
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
d892986ab6d024ecfd622bf341abc5e0852b67e6
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
28459572fbb29787a5b5eaab8cbec8d65ebcb8b1bff80d4b41020b76188714dc
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
5928b97432f974641d14f99976ddd03c1933731a6dc215222f3635e8fd3a93fc807eb2068ac61b5618ade31e881c73cba403dec7737c0ad5ea15b23e146c9b87
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ab8018d2c5d5ffa5bd5955ea673d4c35
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
a2795a35433c43379b7d7ce4215c798d3b09cc42
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
15e74935002f0cfa1b14a35221699ae70a98f4e2a04dfc6308170c86fd2fb432
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0c24d72091a2260318e8f0634b73a786a68edcc3efa686ed3a9756740e2cf897073c54071530268629ab1f28eb461941a36303ed0ba4db5f6df5fba0cbf82fec
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ebe2292114020700c6ef19086cf988c4
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1e208717d87a1ba95f2a0f47e129733b80893de8
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
19e4200b968e5ea875ddfc5e6052016998164c079fe546219ac4c1694c32dad5
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
fbbb81d82cacda91b017192e2a95b7d6aae669ec1e6609d211d7ac571a74556e58e81ae5e626ea2ced1ce982dc42d9e76c797135572bcb06d282ef3e3e628a0b
|