expo-image 1.1.0 → 1.2.0
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 +14 -0
- package/README.md +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt +1 -1
- package/android/src/main/java/expo/modules/image/GlideModel.kt +14 -0
- package/android/src/main/java/expo/modules/image/records/SourceMap.kt +9 -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 +23 -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.map +1 -1
- package/build/ExpoImage.js +2 -2
- package/build/ExpoImage.js.map +1 -1
- package/build/ExpoImage.web.d.ts.map +1 -1
- package/build/ExpoImage.web.js +2 -2
- package/build/ExpoImage.web.js.map +1 -1
- package/build/Image.types.d.ts +17 -1
- package/build/Image.types.d.ts.map +1 -1
- package/build/Image.types.js.map +1 -1
- 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/resolveAssetSource.web.d.ts +8 -1
- package/build/utils/resolveAssetSource.web.d.ts.map +1 -1
- package/build/utils/resolveAssetSource.web.js +28 -2
- package/build/utils/resolveAssetSource.web.js.map +1 -1
- package/build/utils/resolveHashString.d.ts +14 -0
- package/build/utils/resolveHashString.d.ts.map +1 -0
- package/build/utils/resolveHashString.js +29 -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 +1 -0
- package/build/utils/resolveSources.d.ts.map +1 -1
- package/build/utils/resolveSources.js +15 -4
- package/build/utils/resolveSources.js.map +1 -1
- 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/web/ImageWrapper.d.ts +3 -2
- package/build/web/ImageWrapper.d.ts.map +1 -1
- package/build/web/ImageWrapper.js +13 -7
- package/build/web/ImageWrapper.js.map +1 -1
- package/build/web/style.d.ts.map +1 -1
- package/build/web/style.js +5 -3
- package/build/web/style.js.map +1 -1
- package/ios/ImageModule.swift +1 -0
- package/ios/ImageSource.swift +4 -0
- package/ios/ImageView.swift +2 -2
- package/ios/Thumbhash.swift +550 -0
- package/ios/ThumbhashLoader.swift +52 -0
- package/package.json +2 -2
- package/src/ExpoImage.tsx +2 -1
- package/src/ExpoImage.web.tsx +5 -4
- package/src/Image.types.ts +19 -1
- package/src/utils/AssetSourceResolver.web.ts +88 -0
- package/src/utils/resolveAssetSource.web.ts +40 -0
- package/src/utils/resolveHashString.tsx +34 -0
- package/src/utils/resolveHashString.web.tsx +33 -0
- package/src/utils/resolveSources.tsx +15 -4
- package/src/utils/thumbhash/thumbhash.ts +387 -0
- package/src/web/ImageWrapper.tsx +24 -9
- package/src/web/style.tsx +5 -3
- package/android/src/main/java/expo/modules/image/enums/ImageResizeMode.kt +0 -20
- package/build/utils/resolveBlurhashString.d.ts +0 -3
- package/build/utils/resolveBlurhashString.d.ts.map +0 -1
- package/build/utils/resolveBlurhashString.js +0 -13
- package/build/utils/resolveBlurhashString.js.map +0 -1
- package/build/utils/resolveBlurhashString.web.d.ts +0 -3
- package/build/utils/resolveBlurhashString.web.d.ts.map +0 -1
- package/build/utils/resolveBlurhashString.web.js +0 -9
- package/build/utils/resolveBlurhashString.web.js.map +0 -1
- package/src/utils/resolveAssetSource.web.tsx +0 -4
- package/src/utils/resolveBlurhashString.tsx +0 -15
- package/src/utils/resolveBlurhashString.web.tsx +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 1.2.0 — 2023-04-14
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- [Web] Add support for `require()` assets. ([#21798](https://github.com/expo/expo/pull/21798) by [@aleqsio](https://github.com/aleqsio))
|
|
18
|
+
- Add `alt` prop as an alias to `accessibilityLabel`. ([#21884](https://github.com/expo/expo/pull/21884) by [@EvanBacon](https://github.com/EvanBacon))
|
|
19
|
+
- [Web] Add `accessibilityLabel` support on web. ([#21884](https://github.com/expo/expo/pull/21884) by [@EvanBacon](https://github.com/EvanBacon))
|
|
20
|
+
- Added `ThumbHash` support for Android, iOS and Web. ([#21952](https://github.com/expo/expo/pull/21952) by [@behenate](https://github.com/behenate))
|
|
21
|
+
|
|
22
|
+
### 🐛 Bug fixes
|
|
23
|
+
|
|
24
|
+
- [Web] Prevent breaking in static rendering environments. ([#21883](https://github.com/expo/expo/pull/21883) by [@EvanBacon](https://github.com/EvanBacon))
|
|
25
|
+
- [Web] Fixed monorepo asset resolution in production for Metro web. ([#22094](https://github.com/expo/expo/pull/22094) by [@EvanBacon](https://github.com/EvanBacon))
|
|
26
|
+
|
|
13
27
|
## 1.1.0 — 2023-03-25
|
|
14
28
|
|
|
15
29
|
### 🎉 New features
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A cross-platform, performant image component for React Native and Expo.
|
|
|
14
14
|
- Designed for speed
|
|
15
15
|
- Support for many image formats (including animated ones)
|
|
16
16
|
- Disk and memory caching
|
|
17
|
-
- Supports [
|
|
17
|
+
- Supports [BlurHash](https://blurha.sh) and [ThumbHash](https://evanw.github.io/thumbhash/) - compact representations of a placeholder for an image
|
|
18
18
|
- Transitioning between images when the source changes (no more flickering!)
|
|
19
19
|
- Implements the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) and [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) properties (see [`contentFit`](#contentfit) and [`contentPosition`](#contentposition) props)
|
|
20
20
|
- Uses performant [`SDWebImage`](https://github.com/SDWebImage/SDWebImage) and [`Glide`](https://github.com/bumptech/glide) under the hood
|
package/android/build.gradle
CHANGED
|
@@ -560,7 +560,7 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
|
|
|
560
560
|
.apply {
|
|
561
561
|
if (placeholder != null) {
|
|
562
562
|
thumbnail(requestManager.load(placeholder.glideData))
|
|
563
|
-
val newPlaceholderContentFit = if (bestPlaceholder.isBlurhash()) {
|
|
563
|
+
val newPlaceholderContentFit = if (bestPlaceholder.isBlurhash() || bestPlaceholder.isThumbhash()) {
|
|
564
564
|
contentFit
|
|
565
565
|
} else {
|
|
566
566
|
placeholderContentFit
|
|
@@ -57,3 +57,17 @@ class GlideBlurhashModel(
|
|
|
57
57
|
return result
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
class GlideThumbhashModel(
|
|
62
|
+
var uri: Uri
|
|
63
|
+
) : GlideModel() {
|
|
64
|
+
override val glideData: GlideThumbhashModel = this
|
|
65
|
+
|
|
66
|
+
override fun equals(other: Any?): Boolean {
|
|
67
|
+
return (this === other) || other is GlideThumbhashModel && uri == other.uri
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun hashCode(): Int {
|
|
71
|
+
return uri.hashCode()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -10,6 +10,7 @@ import com.bumptech.glide.signature.ApplicationVersionSignature
|
|
|
10
10
|
import expo.modules.image.GlideBlurhashModel
|
|
11
11
|
import expo.modules.image.GlideModel
|
|
12
12
|
import expo.modules.image.GlideRawModel
|
|
13
|
+
import expo.modules.image.GlideThumbhashModel
|
|
13
14
|
import expo.modules.image.GlideUriModel
|
|
14
15
|
import expo.modules.image.GlideUrlModel
|
|
15
16
|
import expo.modules.image.ResourceIdHelper
|
|
@@ -39,6 +40,8 @@ data class SourceMap(
|
|
|
39
40
|
|
|
40
41
|
fun isBlurhash() = parsedUri?.scheme?.startsWith("blurhash") ?: false
|
|
41
42
|
|
|
43
|
+
fun isThumbhash() = parsedUri?.scheme?.startsWith("thumbhash") ?: false
|
|
44
|
+
|
|
42
45
|
internal fun createGlideModel(context: Context): GlideModel? {
|
|
43
46
|
if (uri.isNullOrBlank()) {
|
|
44
47
|
return null
|
|
@@ -60,6 +63,12 @@ data class SourceMap(
|
|
|
60
63
|
)
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
if (isThumbhash()) {
|
|
67
|
+
return GlideThumbhashModel(
|
|
68
|
+
parsedUri!!
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
if (isResourceUri()) {
|
|
64
73
|
return GlideUriModel(parsedUri!!)
|
|
65
74
|
}
|
|
@@ -0,0 +1,365 @@
|
|
|
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 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) hash[5] = (
|
|
100
|
+
Math.round(15.0f * a_channel!!.dc)
|
|
101
|
+
or (Math.round(15.0f * a_channel.scale) shl 4)
|
|
102
|
+
).toByte()
|
|
103
|
+
|
|
104
|
+
// Write the varying factors
|
|
105
|
+
var ac_index = 0
|
|
106
|
+
ac_index = l_channel.writeTo(hash, ac_start, ac_index)
|
|
107
|
+
ac_index = p_channel.writeTo(hash, ac_start, ac_index)
|
|
108
|
+
ac_index = q_channel.writeTo(hash, ac_start, ac_index)
|
|
109
|
+
if (hasAlpha) a_channel!!.writeTo(hash, ac_start, ac_index)
|
|
110
|
+
return hash
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Decodes a ThumbHash to an RGBA image. RGB is not be premultiplied by A.
|
|
115
|
+
*
|
|
116
|
+
* @param hash The bytes of the ThumbHash.
|
|
117
|
+
* @return The width, height, and pixels of the rendered placeholder image.
|
|
118
|
+
*/
|
|
119
|
+
fun thumbHashToRGBA(hash: ByteArray): Image {
|
|
120
|
+
// Read the constants
|
|
121
|
+
val header24 = hash[0].toInt() and 255 or (hash[1].toInt() and 255 shl 8) or (hash[2].toInt() and 255 shl 16)
|
|
122
|
+
val header16 = hash[3].toInt() and 255 or (hash[4].toInt() and 255 shl 8)
|
|
123
|
+
val l_dc = (header24 and 63).toFloat() / 63.0f
|
|
124
|
+
val p_dc = (header24 shr 6 and 63).toFloat() / 31.5f - 1.0f
|
|
125
|
+
val q_dc = (header24 shr 12 and 63).toFloat() / 31.5f - 1.0f
|
|
126
|
+
val l_scale = (header24 shr 18 and 31).toFloat() / 31.0f
|
|
127
|
+
val hasAlpha = header24 shr 23 != 0
|
|
128
|
+
val p_scale = (header16 shr 3 and 63).toFloat() / 63.0f
|
|
129
|
+
val q_scale = (header16 shr 9 and 63).toFloat() / 63.0f
|
|
130
|
+
val isLandscape = header16 shr 15 != 0
|
|
131
|
+
val lx = Math.max(3, if (isLandscape) if (hasAlpha) 5 else 7 else header16 and 7)
|
|
132
|
+
val ly = Math.max(3, if (isLandscape) header16 and 7 else if (hasAlpha) 5 else 7)
|
|
133
|
+
val a_dc = if (hasAlpha) (hash[5].toInt() and 15).toFloat() / 15.0f else 1.0f
|
|
134
|
+
val a_scale = (hash[5].toInt() shr 4 and 15).toFloat() / 15.0f
|
|
135
|
+
|
|
136
|
+
// Read the varying factors (boost saturation by 1.25x to compensate for quantization)
|
|
137
|
+
val ac_start = if (hasAlpha) 6 else 5
|
|
138
|
+
var ac_index = 0
|
|
139
|
+
val l_channel = Channel(lx, ly)
|
|
140
|
+
val p_channel = Channel(3, 3)
|
|
141
|
+
val q_channel = Channel(3, 3)
|
|
142
|
+
var a_channel: Channel? = null
|
|
143
|
+
ac_index = l_channel.decode(hash, ac_start, ac_index, l_scale)
|
|
144
|
+
ac_index = p_channel.decode(hash, ac_start, ac_index, p_scale * 1.25f)
|
|
145
|
+
ac_index = q_channel.decode(hash, ac_start, ac_index, q_scale * 1.25f)
|
|
146
|
+
if (hasAlpha) {
|
|
147
|
+
a_channel = Channel(5, 5)
|
|
148
|
+
a_channel.decode(hash, ac_start, ac_index, a_scale)
|
|
149
|
+
}
|
|
150
|
+
val l_ac = l_channel.ac
|
|
151
|
+
val p_ac = p_channel.ac
|
|
152
|
+
val q_ac = q_channel.ac
|
|
153
|
+
val a_ac = if (hasAlpha) a_channel!!.ac else null
|
|
154
|
+
|
|
155
|
+
// Decode using the DCT into RGB
|
|
156
|
+
val ratio = thumbHashToApproximateAspectRatio(hash)
|
|
157
|
+
val w = Math.round(if (ratio > 1.0f) 32.0f else 32.0f * ratio)
|
|
158
|
+
val h = Math.round(if (ratio > 1.0f) 32.0f / ratio else 32.0f)
|
|
159
|
+
val rgba = ByteArray(w * h * 4)
|
|
160
|
+
val cx_stop = Math.max(lx, if (hasAlpha) 5 else 3)
|
|
161
|
+
val cy_stop = Math.max(ly, if (hasAlpha) 5 else 3)
|
|
162
|
+
val fx = FloatArray(cx_stop)
|
|
163
|
+
val fy = FloatArray(cy_stop)
|
|
164
|
+
var y = 0
|
|
165
|
+
var i = 0
|
|
166
|
+
while (y < h) {
|
|
167
|
+
var x = 0
|
|
168
|
+
while (x < w) {
|
|
169
|
+
var l = l_dc
|
|
170
|
+
var p = p_dc
|
|
171
|
+
var q = q_dc
|
|
172
|
+
var a = a_dc
|
|
173
|
+
|
|
174
|
+
// Precompute the coefficients
|
|
175
|
+
for (cx in 0 until cx_stop) fx[cx] = Math.cos(Math.PI / w * (x + 0.5f) * cx).toFloat()
|
|
176
|
+
for (cy in 0 until cy_stop) fy[cy] = Math.cos(Math.PI / h * (y + 0.5f) * cy).toFloat()
|
|
177
|
+
|
|
178
|
+
// Decode L
|
|
179
|
+
run {
|
|
180
|
+
var cy = 0
|
|
181
|
+
var j = 0
|
|
182
|
+
while (cy < ly) {
|
|
183
|
+
val fy2 = fy[cy] * 2.0f
|
|
184
|
+
var cx = if (cy > 0) 0 else 1
|
|
185
|
+
while (cx * ly < lx * (ly - cy)) {
|
|
186
|
+
l += l_ac[j] * fx[cx] * fy2
|
|
187
|
+
cx++
|
|
188
|
+
j++
|
|
189
|
+
}
|
|
190
|
+
cy++
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Decode P and Q
|
|
195
|
+
var cy = 0
|
|
196
|
+
var j = 0
|
|
197
|
+
while (cy < 3) {
|
|
198
|
+
val fy2 = fy[cy] * 2.0f
|
|
199
|
+
var cx = if (cy > 0) 0 else 1
|
|
200
|
+
while (cx < 3 - cy) {
|
|
201
|
+
val f = fx[cx] * fy2
|
|
202
|
+
p += p_ac[j] * f
|
|
203
|
+
q += q_ac[j] * f
|
|
204
|
+
cx++
|
|
205
|
+
j++
|
|
206
|
+
}
|
|
207
|
+
cy++
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Decode A
|
|
211
|
+
if (hasAlpha) {
|
|
212
|
+
var cy = 0
|
|
213
|
+
var j = 0
|
|
214
|
+
while (cy < 5) {
|
|
215
|
+
val fy2 = fy[cy] * 2.0f
|
|
216
|
+
var cx = if (cy > 0) 0 else 1
|
|
217
|
+
while (cx < 5 - cy) {
|
|
218
|
+
a += a_ac!![j] * fx[cx] * fy2
|
|
219
|
+
cx++
|
|
220
|
+
j++
|
|
221
|
+
}
|
|
222
|
+
cy++
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Convert to RGB
|
|
227
|
+
val b = l - 2.0f / 3.0f * p
|
|
228
|
+
val r = (3.0f * l - b + q) / 2.0f
|
|
229
|
+
val g = r - q
|
|
230
|
+
rgba[i] = Math.max(0, Math.round(255.0f * Math.min(1f, r))).toByte()
|
|
231
|
+
rgba[i + 1] = Math.max(0, Math.round(255.0f * Math.min(1f, g))).toByte()
|
|
232
|
+
rgba[i + 2] = Math.max(0, Math.round(255.0f * Math.min(1f, b))).toByte()
|
|
233
|
+
rgba[i + 3] = Math.max(0, Math.round(255.0f * Math.min(1f, a))).toByte()
|
|
234
|
+
x++
|
|
235
|
+
i += 4
|
|
236
|
+
}
|
|
237
|
+
y++
|
|
238
|
+
}
|
|
239
|
+
return Image(w, h, rgba)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Converts a ThumbHash into a Bitmap image
|
|
244
|
+
*/
|
|
245
|
+
fun thumbHashToBitmap(hash: ByteArray): Bitmap {
|
|
246
|
+
val thumbhashImage = thumbHashToRGBA(hash)
|
|
247
|
+
// TODO: @behenate it should be possible to replace all of the code below with
|
|
248
|
+
// with BitmapFactory.decodeByteArray but it always returns null when using thumbhashImage.rgba
|
|
249
|
+
val imageArray = IntArray(thumbhashImage.width * thumbhashImage.height)
|
|
250
|
+
val thumbhashImageInt = thumbhashImage.rgba.map { it.toUByte().toInt() }
|
|
251
|
+
for (i in thumbhashImageInt.indices step 4) {
|
|
252
|
+
imageArray[i / 4] = Color.argb(
|
|
253
|
+
thumbhashImageInt[i + 3],
|
|
254
|
+
thumbhashImageInt[i],
|
|
255
|
+
thumbhashImageInt[i + 1],
|
|
256
|
+
thumbhashImageInt[i + 2]
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
return Bitmap.createBitmap(imageArray, thumbhashImage.width, thumbhashImage.height, Bitmap.Config.ARGB_8888)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Extracts the average color from a ThumbHash. RGB is not be premultiplied by A.
|
|
264
|
+
*
|
|
265
|
+
* @param hash The bytes of the ThumbHash.
|
|
266
|
+
* @return The RGBA values for the average color. Each value ranges from 0 to 1.
|
|
267
|
+
*/
|
|
268
|
+
fun thumbHashToAverageRGBA(hash: ByteArray): RGBA {
|
|
269
|
+
val header = hash[0].toInt() and 255 or (hash[1].toInt() and 255 shl 8) or (hash[2].toInt() and 255 shl 16)
|
|
270
|
+
val l = (header and 63).toFloat() / 63.0f
|
|
271
|
+
val p = (header shr 6 and 63).toFloat() / 31.5f - 1.0f
|
|
272
|
+
val q = (header shr 12 and 63).toFloat() / 31.5f - 1.0f
|
|
273
|
+
val hasAlpha = header shr 23 != 0
|
|
274
|
+
val a = if (hasAlpha) (hash[5].toInt() and 15).toFloat() / 15.0f else 1.0f
|
|
275
|
+
val b = l - 2.0f / 3.0f * p
|
|
276
|
+
val r = (3.0f * l - b + q) / 2.0f
|
|
277
|
+
val g = r - q
|
|
278
|
+
return RGBA(
|
|
279
|
+
Math.max(0f, Math.min(1f, r)),
|
|
280
|
+
Math.max(0f, Math.min(1f, g)),
|
|
281
|
+
Math.max(0f, Math.min(1f, b)),
|
|
282
|
+
a
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Extracts the approximate aspect ratio of the original image.
|
|
288
|
+
*
|
|
289
|
+
* @param hash The bytes of the ThumbHash.
|
|
290
|
+
* @return The approximate aspect ratio (i.e. width / height).
|
|
291
|
+
*/
|
|
292
|
+
fun thumbHashToApproximateAspectRatio(hash: ByteArray): Float {
|
|
293
|
+
val header = hash[3]
|
|
294
|
+
val hasAlpha = hash[2].toInt() and 0x80 != 0
|
|
295
|
+
val isLandscape = hash[4].toInt() and 0x80 != 0
|
|
296
|
+
val lx = if (isLandscape) if (hasAlpha) 5 else 7 else header.toInt() and 7
|
|
297
|
+
val ly = if (isLandscape) header.toInt() and 7 else if (hasAlpha) 5 else 7
|
|
298
|
+
return lx.toFloat() / ly.toFloat()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
class Image(var width: Int, var height: Int, var rgba: ByteArray)
|
|
302
|
+
class RGBA(var r: Float, var g: Float, var b: Float, var a: Float)
|
|
303
|
+
private class Channel internal constructor(var nx: Int, var ny: Int) {
|
|
304
|
+
var dc = 0f
|
|
305
|
+
var ac: FloatArray
|
|
306
|
+
var scale = 0f
|
|
307
|
+
|
|
308
|
+
init {
|
|
309
|
+
var n = 0
|
|
310
|
+
for (cy in 0 until ny) {
|
|
311
|
+
var cx = if (cy > 0) 0 else 1
|
|
312
|
+
while (cx * ny < nx * (ny - cy)) {
|
|
313
|
+
n++
|
|
314
|
+
cx++
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
ac = FloatArray(n)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fun encode(w: Int, h: Int, channel: FloatArray): Channel {
|
|
321
|
+
var n = 0
|
|
322
|
+
val fx = FloatArray(w)
|
|
323
|
+
for (cy in 0 until ny) {
|
|
324
|
+
var cx = 0
|
|
325
|
+
while (cx * ny < nx * (ny - cy)) {
|
|
326
|
+
var f = 0f
|
|
327
|
+
for (x in 0 until w) fx[x] = Math.cos(Math.PI / w * cx * (x + 0.5f)).toFloat()
|
|
328
|
+
for (y in 0 until h) {
|
|
329
|
+
val fy = Math.cos(Math.PI / h * cy * (y + 0.5f)).toFloat()
|
|
330
|
+
for (x in 0 until w) f += channel[x + y * w] * fx[x] * fy
|
|
331
|
+
}
|
|
332
|
+
f /= (w * h).toFloat()
|
|
333
|
+
if (cx > 0 || cy > 0) {
|
|
334
|
+
ac[n++] = f
|
|
335
|
+
scale = Math.max(scale, Math.abs(f))
|
|
336
|
+
} else {
|
|
337
|
+
dc = f
|
|
338
|
+
}
|
|
339
|
+
cx++
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (scale > 0) for (i in ac.indices) ac[i] = 0.5f + 0.5f / scale * ac[i]
|
|
343
|
+
return this
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fun decode(hash: ByteArray, start: Int, index: Int, scale: Float): Int {
|
|
347
|
+
var index = index
|
|
348
|
+
for (i in ac.indices) {
|
|
349
|
+
val data = hash[start + (index shr 1)].toInt() shr (index and 1 shl 2)
|
|
350
|
+
ac[i] = ((data and 15).toFloat() / 7.5f - 1.0f) * scale
|
|
351
|
+
index++
|
|
352
|
+
}
|
|
353
|
+
return index
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
fun writeTo(hash: ByteArray, start: Int, index: Int): Int {
|
|
357
|
+
var index = index
|
|
358
|
+
for (v in ac) {
|
|
359
|
+
hash[start + (index shr 1)] = (hash[start + (index shr 1)].toInt() or (Math.round(15.0f * v) shl (index and 1 shl 2))).toByte()
|
|
360
|
+
index++
|
|
361
|
+
}
|
|
362
|
+
return index
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package expo.modules.image.thumbhash
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.util.Base64
|
|
5
|
+
import com.bumptech.glide.Priority
|
|
6
|
+
import com.bumptech.glide.load.DataSource
|
|
7
|
+
import com.bumptech.glide.load.data.DataFetcher
|
|
8
|
+
import expo.modules.kotlin.exception.CodedException
|
|
9
|
+
|
|
10
|
+
class ThumbhashDecodingFailure(thumbhash: String?, cause: Exception?) : CodedException(
|
|
11
|
+
message = "Cannot decode provided thumbhash '$thumbhash' $cause"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
class ThumbhashFetcher(
|
|
15
|
+
private val thumbhash: String?
|
|
16
|
+
) : DataFetcher<Bitmap> {
|
|
17
|
+
override fun cleanup() = Unit
|
|
18
|
+
override fun cancel() = Unit
|
|
19
|
+
override fun getDataClass(): Class<Bitmap> = Bitmap::class.java
|
|
20
|
+
override fun getDataSource(): DataSource = DataSource.LOCAL
|
|
21
|
+
|
|
22
|
+
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
|
|
23
|
+
try {
|
|
24
|
+
val decodedThumbhash = Base64.decode(thumbhash, Base64.DEFAULT)
|
|
25
|
+
val bitmap = ThumbhashDecoder.thumbHashToBitmap(decodedThumbhash)
|
|
26
|
+
callback.onDataReady(bitmap)
|
|
27
|
+
} catch (e: Exception) {
|
|
28
|
+
callback.onLoadFailed(ThumbhashDecodingFailure(thumbhash, e))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package expo.modules.image.thumbhash
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
|
|
5
|
+
import com.bumptech.glide.load.Options
|
|
6
|
+
import com.bumptech.glide.load.model.ModelLoader
|
|
7
|
+
import com.bumptech.glide.signature.ObjectKey
|
|
8
|
+
|
|
9
|
+
import expo.modules.image.GlideThumbhashModel
|
|
10
|
+
|
|
11
|
+
class ThumbhashModelLoader : ModelLoader<GlideThumbhashModel, Bitmap> {
|
|
12
|
+
|
|
13
|
+
override fun handles(model: GlideThumbhashModel): Boolean = true
|
|
14
|
+
|
|
15
|
+
override fun buildLoadData(model: GlideThumbhashModel, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
|
|
16
|
+
// Thumbhash might contain '/' characters
|
|
17
|
+
val thumbhash = model.uri.pathSegments.joinToString(separator = "/")
|
|
18
|
+
return ModelLoader.LoadData(
|
|
19
|
+
ObjectKey(model),
|
|
20
|
+
ThumbhashFetcher(thumbhash)
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package expo.modules.image.thumbhash
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import com.bumptech.glide.load.model.ModelLoader
|
|
5
|
+
import com.bumptech.glide.load.model.ModelLoaderFactory
|
|
6
|
+
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
|
7
|
+
import expo.modules.image.GlideThumbhashModel
|
|
8
|
+
|
|
9
|
+
class ThumbhashModelLoaderFactory : ModelLoaderFactory<GlideThumbhashModel, Bitmap> {
|
|
10
|
+
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<GlideThumbhashModel, Bitmap> =
|
|
11
|
+
ThumbhashModelLoader()
|
|
12
|
+
|
|
13
|
+
override fun teardown() = Unit
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package expo.modules.image.thumbhash
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import com.bumptech.glide.Glide
|
|
6
|
+
import com.bumptech.glide.Registry
|
|
7
|
+
import com.bumptech.glide.annotation.GlideModule
|
|
8
|
+
import com.bumptech.glide.module.LibraryGlideModule
|
|
9
|
+
import expo.modules.image.GlideThumbhashModel
|
|
10
|
+
|
|
11
|
+
@GlideModule
|
|
12
|
+
class ThumbhashModule : LibraryGlideModule() {
|
|
13
|
+
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
|
14
|
+
super.registerComponents(context, glide, registry)
|
|
15
|
+
registry.prepend(GlideThumbhashModel::class.java, Bitmap::class.java, ThumbhashModelLoaderFactory())
|
|
16
|
+
}
|
|
17
|
+
}
|
package/build/ExpoImage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoImage.d.ts","sourceRoot":"","sources":["../src/ExpoImage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAsC,MAAM,cAAc,CAAC;AAExF,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAIvB,QAAA,MAAM,eAAe,KAAmC,CAAC;AAgBzD,cAAM,SAAU,SAAQ,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC;IAC3D,WAAW,aAET;IAEF,MAAM,UAAW,qBAAqB,kBAAkB,CAAC,UAGvD;IAEF,UAAU,UAAW,qBAAqB,sBAAsB,CAAC,UAE/D;IAEF,OAAO,UAAW,qBAAqB,mBAAmB,CAAC,UAGzD;IAEF,SAAS,aAEP;IAEF,MAAM;
|
|
1
|
+
{"version":3,"file":"ExpoImage.d.ts","sourceRoot":"","sources":["../src/ExpoImage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAsC,MAAM,cAAc,CAAC;AAExF,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAIvB,QAAA,MAAM,eAAe,KAAmC,CAAC;AAgBzD,cAAM,SAAU,SAAQ,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC;IAC3D,WAAW,aAET;IAEF,MAAM,UAAW,qBAAqB,kBAAkB,CAAC,UAGvD;IAEF,UAAU,UAAW,qBAAqB,sBAAsB,CAAC,UAE/D;IAEF,OAAO,UAAW,qBAAqB,mBAAmB,CAAC,UAGzD;IAEF,SAAS,aAEP;IAEF,MAAM;CA+DP;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,eAAe,SAAS,CAAC"}
|
package/build/ExpoImage.js
CHANGED
|
@@ -31,7 +31,7 @@ class ExpoImage extends React.PureComponent {
|
|
|
31
31
|
this.props.onLoadEnd?.();
|
|
32
32
|
};
|
|
33
33
|
render() {
|
|
34
|
-
const { style, ...props } = this.props;
|
|
34
|
+
const { style, accessibilityLabel, alt, ...props } = this.props;
|
|
35
35
|
const resolvedStyle = StyleSheet.flatten(style);
|
|
36
36
|
// Shadows behave different on iOS, Android & Web.
|
|
37
37
|
// Android uses the `elevation` prop, whereas iOS
|
|
@@ -67,7 +67,7 @@ class ExpoImage extends React.PureComponent {
|
|
|
67
67
|
const borderTopColor = processColor(resolvedStyle.borderTopColor);
|
|
68
68
|
// @ts-ignore
|
|
69
69
|
const borderBottomColor = processColor(resolvedStyle.borderBottomColor);
|
|
70
|
-
return (React.createElement(NativeExpoImage, { ...props, ...resolvedStyle, style: resolvedStyle, onLoadStart: this.onLoadStart, onLoad: this.onLoad, onProgress: this.onProgress, onError: this.onError, tintColor: tintColor, borderColor: borderColor, borderLeftColor: borderLeftColor, borderRightColor: borderRightColor, borderTopColor: borderTopColor, borderBottomColor: borderBottomColor, borderStartColor: borderStartColor, borderEndColor: borderEndColor, backgroundColor: backgroundColor }));
|
|
70
|
+
return (React.createElement(NativeExpoImage, { ...props, ...resolvedStyle, accessibilityLabel: accessibilityLabel ?? alt, style: resolvedStyle, onLoadStart: this.onLoadStart, onLoad: this.onLoad, onProgress: this.onProgress, onError: this.onError, tintColor: tintColor, borderColor: borderColor, borderLeftColor: borderLeftColor, borderRightColor: borderRightColor, borderTopColor: borderTopColor, borderBottomColor: borderBottomColor, borderStartColor: borderStartColor, borderEndColor: borderEndColor, backgroundColor: backgroundColor }));
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
export { ExpoImageModule };
|
package/build/ExpoImage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoImage.js","sourceRoot":"","sources":["../src/ExpoImage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAwB,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AASxF,MAAM,eAAe,GAAG,wBAAwB,CAAC,WAAW,CAAC,CAAC;AAE9D,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAEzD,SAAS,yBAAyB,CAChC,KAAwC;IAExC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE;QACtD,GAAG;YACD,OAAO,CAAC,IAAI,CACV,sHAAsH,CACvH,CAAC;YACF,OAAO,KAAK,CAAC,WAAW,CAAC;QAC3B,CAAC;KACF,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,WAAW,CAAC;AAC3B,CAAC;AAED,MAAM,SAAU,SAAQ,KAAK,CAAC,aAA+B;IAC3D,WAAW,GAAG,GAAG,EAAE;QACjB,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;IAC7B,CAAC,CAAC;IAEF,MAAM,GAAG,CAAC,KAA+C,EAAE,EAAE;QAC3D,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC,CAAC;IAEF,UAAU,GAAG,CAAC,KAAmD,EAAE,EAAE;QACnE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC;IAEF,OAAO,GAAG,CAAC,KAAgD,EAAE,EAAE;QAC7D,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC,CAAC;IAEF,SAAS,GAAG,GAAG,EAAE;QACf,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;IAC3B,CAAC,CAAC;IAEF,MAAM;QACJ,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoImage.js","sourceRoot":"","sources":["../src/ExpoImage.tsx"],"names":[],"mappings":"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","sourcesContent":["import { requireNativeViewManager, requireNativeModule } from 'expo-modules-core';\nimport React from 'react';\nimport { NativeSyntheticEvent, StyleSheet, Platform, processColor } from 'react-native';\n\nimport {\n ImageErrorEventData,\n ImageLoadEventData,\n ImageNativeProps,\n ImageProgressEventData,\n} from './Image.types';\n\nconst NativeExpoImage = requireNativeViewManager('ExpoImage');\n\nconst ExpoImageModule = requireNativeModule('ExpoImage');\n\nfunction withDeprecatedNativeEvent<NativeEvent>(\n event: NativeSyntheticEvent<NativeEvent>\n): NativeEvent {\n Object.defineProperty(event.nativeEvent, 'nativeEvent', {\n get() {\n console.warn(\n '[expo-image]: Accessing event payload through \"nativeEvent\" is deprecated, it is now part of the event object itself'\n );\n return event.nativeEvent;\n },\n });\n return event.nativeEvent;\n}\n\nclass ExpoImage extends React.PureComponent<ImageNativeProps> {\n onLoadStart = () => {\n this.props.onLoadStart?.();\n };\n\n onLoad = (event: NativeSyntheticEvent<ImageLoadEventData>) => {\n this.props.onLoad?.(withDeprecatedNativeEvent(event));\n this.onLoadEnd();\n };\n\n onProgress = (event: NativeSyntheticEvent<ImageProgressEventData>) => {\n this.props.onProgress?.(withDeprecatedNativeEvent(event));\n };\n\n onError = (event: NativeSyntheticEvent<ImageErrorEventData>) => {\n this.props.onError?.(withDeprecatedNativeEvent(event));\n this.onLoadEnd();\n };\n\n onLoadEnd = () => {\n this.props.onLoadEnd?.();\n };\n\n render() {\n const { style, accessibilityLabel, alt, ...props } = this.props;\n const resolvedStyle = StyleSheet.flatten(style);\n\n // Shadows behave different on iOS, Android & Web.\n // Android uses the `elevation` prop, whereas iOS\n // and web use the regular `shadow...` props.\n if (Platform.OS === 'android') {\n delete resolvedStyle.shadowColor;\n delete resolvedStyle.shadowOffset;\n delete resolvedStyle.shadowOpacity;\n delete resolvedStyle.shadowRadius;\n } else {\n // @ts-expect-error\n delete resolvedStyle.elevation;\n }\n\n // @ts-ignore\n const backgroundColor = processColor(resolvedStyle.backgroundColor);\n // On Android, we have to set the `backgroundColor` directly on the correct component.\n // So we have to remove it from styles. Otherwise, the background color won't take into consideration the border-radius.\n if (Platform.OS === 'android') {\n delete resolvedStyle.backgroundColor;\n }\n\n const tintColor = processColor(props.tintColor || resolvedStyle.tintColor);\n\n const borderColor = processColor(resolvedStyle.borderColor);\n // @ts-ignore\n const borderStartColor = processColor(resolvedStyle.borderStartColor);\n // @ts-ignore\n const borderEndColor = processColor(resolvedStyle.borderEndColor);\n // @ts-ignore\n const borderLeftColor = processColor(resolvedStyle.borderLeftColor);\n // @ts-ignore\n const borderRightColor = processColor(resolvedStyle.borderRightColor);\n // @ts-ignore\n const borderTopColor = processColor(resolvedStyle.borderTopColor);\n // @ts-ignore\n const borderBottomColor = processColor(resolvedStyle.borderBottomColor);\n\n return (\n <NativeExpoImage\n {...props}\n {...resolvedStyle}\n accessibilityLabel={accessibilityLabel ?? alt}\n style={resolvedStyle}\n onLoadStart={this.onLoadStart}\n onLoad={this.onLoad}\n onProgress={this.onProgress}\n onError={this.onError}\n tintColor={tintColor}\n borderColor={borderColor}\n borderLeftColor={borderLeftColor}\n borderRightColor={borderRightColor}\n borderTopColor={borderTopColor}\n borderBottomColor={borderBottomColor}\n borderStartColor={borderStartColor}\n borderEndColor={borderEndColor}\n backgroundColor={backgroundColor}\n />\n );\n }\n}\n\nexport { ExpoImageModule };\nexport default ExpoImage;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoImage.web.d.ts","sourceRoot":"","sources":["../src/ExpoImage.web.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,eAAe,CAAC;AAoClF,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,MAAM,EACN,UAAU,EACV,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"ExpoImage.web.d.ts","sourceRoot":"","sources":["../src/ExpoImage.web.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,eAAe,CAAC;AAoClF,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,MAAM,EACN,UAAU,EACV,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,gBAAgB,eA2FlB"}
|
package/build/ExpoImage.web.js
CHANGED
|
@@ -46,7 +46,7 @@ export default function ExpoImage({ source, placeholder, contentFit, contentPosi
|
|
|
46
46
|
...style,
|
|
47
47
|
}, className: className, events: {
|
|
48
48
|
onTransitionEnd: [onAnimationFinished],
|
|
49
|
-
}, contentPosition: { left: '50%', top: '50%' },
|
|
49
|
+
}, contentPosition: { left: '50%', top: '50%' }, hashPlaceholderContentPosition: contentPosition, hashPlaceholderStyle: blurhashStyle })),
|
|
50
50
|
]
|
|
51
51
|
: null;
|
|
52
52
|
const currentNodeAnimationKey = (recyclingKey
|
|
@@ -63,7 +63,7 @@ export default function ExpoImage({ source, placeholder, contentFit, contentPosi
|
|
|
63
63
|
objectFit: selectedSource ? contentFit : imagePlaceholderContentFit,
|
|
64
64
|
...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}),
|
|
65
65
|
...style,
|
|
66
|
-
}, className: className, priority: priority, contentPosition: selectedSource ? contentPosition : { top: '50%', left: '50%' },
|
|
66
|
+
}, className: className, priority: priority, contentPosition: selectedSource ? contentPosition : { top: '50%', left: '50%' }, hashPlaceholderContentPosition: contentPosition, hashPlaceholderStyle: blurhashStyle, accessibilityLabel: props.accessibilityLabel })),
|
|
67
67
|
];
|
|
68
68
|
return (React.createElement("div", { ref: containerRef, className: "expo-image-container", style: {
|
|
69
69
|
aspectRatio: String(aspectRatio),
|