Skip to content

Commit 032aac0

Browse files
committed
feat: use Coil instead of Fresco to load images on the Android platform
1 parent a178178 commit 032aac0

File tree

36 files changed

+1516
-708
lines changed

36 files changed

+1516
-708
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
if: steps.verify-dev-changed-files.outputs.any_changed == 'true'
4444
uses: actions/setup-node@v3
4545
with:
46-
node-version: '16'
46+
node-version: '18'
4747

4848
- name: Install npm dependencies
4949
if: steps.cache-node-modules.outputs.cache-hit != 'true' && steps.verify-dev-changed-files.outputs.any_changed == 'true'
@@ -108,14 +108,14 @@ jobs:
108108
uses: actions/setup-node@v3
109109
if: steps.verify-android-changed-files.outputs.any_changed == 'true'
110110
with:
111-
node-version: '16'
111+
node-version: '18'
112112

113113
- name: Set up JDK
114114
uses: actions/setup-java@v3
115115
if: steps.verify-android-changed-files.outputs.any_changed == 'true'
116116
with:
117117
distribution: 'zulu'
118-
java-version: 11
118+
java-version: 17
119119

120120
- name: Install Gradle dependencies
121121
if: steps.cache-gradle.outputs.cache-hit != 'true' && steps.verify-android-changed-files.outputs.any_changed == 'true'
@@ -201,14 +201,14 @@ jobs:
201201
uses: actions/setup-node@v3
202202
if: steps.verify-android-changed-files.outputs.any_changed == 'true'
203203
with:
204-
node-version: '16'
204+
node-version: '18'
205205

206206
- name: Set up JDK
207207
uses: actions/setup-java@v3
208208
if: steps.verify-android-changed-files.outputs.any_changed == 'true'
209209
with:
210210
distribution: 'zulu'
211-
java-version: 11
211+
java-version: 17
212212

213213
- name: Instrumentation Tests
214214
uses: reactivecircus/android-emulator-runner@v2
@@ -285,7 +285,7 @@ jobs:
285285
if: steps.verify-iOS-changed-files.outputs.any_changed == 'true'
286286
uses: actions/setup-node@v3
287287
with:
288-
node-version: '16'
288+
node-version: '18'
289289

290290
- name: Install Pods
291291
if: steps.cache-pods.outputs.cache-hit != 'true' && steps.verify-iOS-changed-files.outputs.any_changed == 'true'

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ DerivedData
2828
*.ipa
2929
*.xcuserstate
3030
project.xcworkspace
31+
.xcode.env.local
3132

3233
# Android/IJ
3334
#
@@ -72,3 +73,6 @@ android/keystores/debug.keystore
7273
lib/
7374

7475
docs/**/*.html
76+
77+
# testing
78+
/coverage

README.MD

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ const options = {
101101
quality: 100,
102102
filename: 'test',
103103
saveFormat: ImageFormat.png,
104-
maxSize: 1000,
105104
};
106105
Marker.markText(options);
107106

@@ -154,7 +153,6 @@ const options = {
154153
quality: 100,
155154
filename: 'test',
156155
saveFormat: ImageFormat.png,
157-
maxSize: 1000,
158156
};
159157
Marker.markText(options);
160158
```
@@ -206,7 +204,6 @@ const options = {
206204
quality: 100,
207205
filename: 'test',
208206
saveFormat: ImageFormat.png,
209-
maxSize: 1000,
210207
};
211208
ImageMarker.markText(options);
212209

@@ -268,7 +265,6 @@ const options = {
268265
quality: 100,
269266
filename: 'test',
270267
saveFormat: ImageFormat.png,
271-
maxSize: 1000,
272268
};
273269
ImageMarker.markText(options);
274270

@@ -316,7 +312,6 @@ const options = {
316312
quality: 100,
317313
filename: 'test',
318314
saveFormat: ImageFormat.png,
319-
maxSize: 1000,
320315
};
321316
Marker.markText(options);
322317

android/build.gradle

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ buildscript {
55
mavenCentral()
66
}
77

8-
dependencies {
9-
classpath "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0"
10-
classpath "com.android.tools.build:gradle:7.2.1"
11-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
8+
if (project == rootProject) {
9+
repositories {
10+
google()
11+
mavenCentral()
12+
}
13+
} else {
14+
dependencies {
15+
classpath 'com.android.tools.build:gradle:7.2.1'
16+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
17+
}
1218
}
1319
}
1420

@@ -76,6 +82,9 @@ dependencies {
7682
testImplementation 'junit:junit:4.13.2'
7783
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
7884
testImplementation "org.mockito:mockito-core:3.+"
85+
implementation "io.coil-kt:coil:2.5.0"
86+
implementation "io.coil-kt:coil-svg:2.5.0"
87+
implementation "io.coil-kt:coil-gif:2.5.0"
7988
}
8089

8190
if (isNewArchitectureEnabled()) {

android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ImageMarker_kotlinVersion=1.7.0
1+
ImageMarker_kotlinVersion=1.8.0
22
ImageMarker_minSdkVersion=24
33
ImageMarker_targetSdkVersion=31
44
ImageMarker_compileSdkVersion=31

android/src/main/java/com/jimmydaddy/imagemarker/ImageMarkerManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ class ImageMarkerManager(private val context: ReactApplicationContext) : ReactCo
226226
Log.d(IMAGE_MARKER_TAG, "src: " + markOpts.backgroundImage.src.toString())
227227
GlobalScope.launch(Dispatchers.Main) {
228228
try {
229-
val bitmaps = ImageLoader(context, markOpts.maxSize).loadImages(
229+
val bitmaps = MarkerImageLoader(context, markOpts.maxSize).loadImages(
230230
listOf(
231231
markOpts.backgroundImage,
232232
)
@@ -256,7 +256,7 @@ class ImageMarkerManager(private val context: ReactApplicationContext) : ReactCo
256256
val concatenatedArray = listOf(
257257
markOpts.backgroundImage,
258258
).plus(markers)
259-
val bitmaps = ImageLoader(context, markOpts.maxSize).loadImages(
259+
val bitmaps = MarkerImageLoader(context, markOpts.maxSize).loadImages(
260260
concatenatedArray
261261
)
262262
val bg = bitmaps[0]

android/src/main/java/com/jimmydaddy/imagemarker/ImageLoader.kt renamed to android/src/main/java/com/jimmydaddy/imagemarker/MarkerImageLoader.kt

Lines changed: 40 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,78 @@ import android.content.res.Resources
55
import android.graphics.Bitmap
66
import android.graphics.BitmapFactory
77
import android.os.Build
8+
import android.os.Build.VERSION.SDK_INT
89
import android.util.Log
910
import androidx.annotation.RequiresApi
10-
import com.facebook.common.internal.Supplier
11-
import com.facebook.common.references.CloseableReference
12-
import com.facebook.datasource.DataSource
13-
import com.facebook.drawee.backends.pipeline.Fresco
14-
import com.facebook.imagepipeline.cache.MemoryCacheParams
15-
import com.facebook.imagepipeline.common.ResizeOptions
16-
import com.facebook.imagepipeline.core.ImagePipelineConfig
17-
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
18-
import com.facebook.imagepipeline.image.CloseableImage
19-
import com.facebook.imagepipeline.request.ImageRequest
20-
import com.facebook.imagepipeline.request.ImageRequestBuilder
11+
import androidx.core.graphics.drawable.toBitmap
12+
import coil.ImageLoader
13+
import coil.decode.GifDecoder
14+
import coil.decode.ImageDecoderDecoder
15+
import coil.decode.SvgDecoder
16+
import coil.request.ImageRequest
2117
import com.facebook.react.bridge.ReactApplicationContext
22-
import com.facebook.react.modules.systeminfo.ReactNativeVersion
2318
import com.jimmydaddy.imagemarker.base.Constants.IMAGE_MARKER_TAG
2419
import com.jimmydaddy.imagemarker.base.ErrorCode
2520
import com.jimmydaddy.imagemarker.base.ImageOptions
2621
import com.jimmydaddy.imagemarker.base.MarkerError
27-
import com.jimmydaddy.imagemarker.base.Utils
2822
import kotlinx.coroutines.Dispatchers
2923
import kotlinx.coroutines.async
3024
import kotlinx.coroutines.awaitAll
3125
import kotlinx.coroutines.withContext
3226
import java.util.concurrent.CompletableFuture
33-
import java.util.concurrent.Executor
34-
import java.util.concurrent.Executors
3527

36-
class ImageLoader(private val context: ReactApplicationContext, private val maxSize: Int) {
28+
class MarkerImageLoader(private val context: ReactApplicationContext, private val maxSize: Int) {
3729

38-
init {
39-
if (maxSize > 0) {
40-
setMaxBitmapSize(maxSize)
30+
private var imageLoader: ImageLoader = ImageLoader.Builder(context)
31+
.components {
32+
if (SDK_INT >= 28) {
33+
add(ImageDecoderDecoder.Factory())
34+
} else {
35+
add(GifDecoder.Factory())
36+
}
37+
add(SvgDecoder.Factory())
4138
}
42-
}
39+
.allowHardware(false)
40+
.build()
4341
private val resources: Resources
4442
get() = context.resources
4543

4644
@RequiresApi(Build.VERSION_CODES.N)
4745
suspend fun loadImages(images: List<ImageOptions>): List<Bitmap?> = withContext(Dispatchers.IO) {
46+
4847
val deferredList = images.map { img ->
4948
async {
5049
try {
51-
val isFrescoImg = isFrescoImg(img.uri)
52-
Log.d(IMAGE_MARKER_TAG, "isFrescoImg: " + isFrescoImg(img.uri))
53-
if (isFrescoImg) {
50+
val isCoilImg = isCoilImg(img.uri)
51+
Log.d(IMAGE_MARKER_TAG, "isCoilImg: $isCoilImg")
52+
if (isCoilImg) {
5453
val future = CompletableFuture<Bitmap?>()
55-
var imageRequest = ImageRequest.fromUri(img.uri)
54+
var request = ImageRequest.Builder(context)
55+
.data(img.uri)
5656
if (img.src != null && img.src.width > 0 && img.src.height > 0) {
57-
val options: ResizeOptions? = ResizeOptions(img.src.width, img.src.height)
58-
imageRequest = ImageRequestBuilder.fromRequest(imageRequest).setResizeOptions(options).build()
57+
request = request.size(img.src.width, img.src.height)
5958
Log.d(IMAGE_MARKER_TAG, "src.width: " + img.src.width + " src.height: " + img.src.height)
6059
}
61-
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null)
62-
val executor: Executor = Executors.newSingleThreadExecutor()
63-
dataSource.subscribe(object : BaseBitmapDataSubscriber() {
64-
public override fun onNewResultImpl(bitmap: Bitmap?) {
65-
if (bitmap != null) {
66-
val bg = ImageProcess.scaleBitmap(bitmap, img.scale)
67-
future.complete(bg)
68-
} else {
60+
imageLoader.enqueue(request.target (
61+
onStart = { _ ->
62+
// Handle the placeholder drawable.
63+
Log.d(IMAGE_MARKER_TAG, "start to load image: " + img.uri)
64+
},
65+
onSuccess = { result ->
66+
val bitmap = result.toBitmap()
67+
val bg = ImageProcess.scaleBitmap(bitmap, img.scale)
68+
if (bg == null) {
6969
future.completeExceptionally(MarkerError(ErrorCode.LOAD_IMAGE_FAILED,
70-
"Can't retrieve the file from the src: " + img.uri))
70+
"Can't retrieve the file from the src: " + img.uri))
7171
}
72-
}
73-
74-
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
72+
future.complete(bg)
73+
},
74+
onError = { _ ->
7575
future.completeExceptionally(MarkerError(ErrorCode.LOAD_IMAGE_FAILED,
7676
"Can't retrieve the file from the src: " + img.uri))
7777
}
78-
}, executor)
78+
).build())
7979
return@async future.get()
80-
8180
} else {
8281
val resId = getDrawableResourceByName(img.uri)
8382
Log.d(IMAGE_MARKER_TAG, "resId: $resId")
@@ -111,7 +110,7 @@ class ImageLoader(private val context: ReactApplicationContext, private val maxS
111110
deferredList.awaitAll()
112111
}
113112

114-
private fun isFrescoImg(uri: String?): Boolean {
113+
private fun isCoilImg(uri: String?): Boolean {
115114
// val base64Pattern =
116115
// "^data:(image|img)/(bmp|jpg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp);base64,(([[A-Za-z0-9+/])*\\s\\S*)*"
117116
return uri!!.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("file://") || uri.startsWith(
@@ -128,29 +127,4 @@ class ImageLoader(private val context: ReactApplicationContext, private val maxS
128127
)
129128
}
130129

131-
private fun setMaxBitmapSize(maxSize: Int) {
132-
val major = Utils.getStringSafe("major", ReactNativeVersion.VERSION)
133-
val minor = Utils.getStringSafe("minor", ReactNativeVersion.VERSION)
134-
val patch = Utils.getStringSafe("patch", ReactNativeVersion.VERSION)
135-
if (Integer.valueOf(major.toString()) >= 0 && Integer.valueOf(minor.toString()) >= 60 && Integer.valueOf(
136-
patch.toString()
137-
) >= 0
138-
) {
139-
val bitmapMemoryCacheParamsSupplier = Supplier<MemoryCacheParams> {
140-
MemoryCacheParams(
141-
maxSize, // max cache entry size
142-
Integer.MAX_VALUE, // max cache entries
143-
maxSize, // max cache size
144-
Integer.MAX_VALUE, // max cache eviction size
145-
Integer.MAX_VALUE // max cache eviction count
146-
)
147-
}
148-
149-
val config = ImagePipelineConfig.newBuilder(context)
150-
.setBitmapMemoryCacheParamsSupplier(bitmapMemoryCacheParamsSupplier)
151-
.build()
152-
Fresco.initialize(context, config)
153-
}
154-
}
155-
156130
}

android/src/main/java/com/jimmydaddy/imagemarker/base/WatermarkImageOptions.kt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@ package com.jimmydaddy.imagemarker.base
33
import com.facebook.react.bridge.ReadableMap
44

55
data class WatermarkImageOptions(val options: ReadableMap?) {
6-
var imageOption: ImageOptions
7-
var x: String?
8-
var y: String?
9-
var positionEnum: PositionEnum?
6+
lateinit var imageOption: ImageOptions
7+
var x: String? = null
8+
var y: String? = null
9+
var positionEnum: PositionEnum? = null
1010

1111
init {
12-
imageOption = options?.let { ImageOptions(it) }!!
13-
val positionOptions =
14-
if (null != options.getMap("position")) options.getMap("position") else null
15-
x = if (positionOptions!!.hasKey("X")) Utils.handleDynamicToString(positionOptions.getDynamic("X")) else null
16-
y = if (positionOptions.hasKey("Y")) Utils.handleDynamicToString(positionOptions.getDynamic("Y")) else null
17-
positionEnum =
18-
if (null != positionOptions.getString("position")) PositionEnum.getPosition(
19-
positionOptions.getString("position")
20-
) else null
12+
if (options != null) {
13+
imageOption = ImageOptions(options)
14+
val positionOptions =
15+
if (null != options.getMap("position")) options.getMap("position") else null
16+
x =
17+
if (positionOptions!!.hasKey("X")) Utils.handleDynamicToString(positionOptions.getDynamic("X")) else null
18+
y =
19+
if (positionOptions.hasKey("Y")) Utils.handleDynamicToString(positionOptions.getDynamic("Y")) else null
20+
positionEnum =
21+
if (null != positionOptions.getString("position")) PositionEnum.getPosition(
22+
positionOptions.getString("position")
23+
) else null
24+
}
2125
}
2226

2327
constructor(watermarkImage: ImageOptions, x: String?, y: String?, position: PositionEnum?) : this(null) {

example/.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
root: true,
3+
extends: '@react-native',
4+
};

example/Gemfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
source 'https://rubygems.org'
22

33
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4-
ruby '>= 2.6.10'
5-
6-
gem 'cocoapods', '>= 1.11.3'
4+
ruby ">= 2.6.10"
5+
gem 'cocoapods', '~> 1.13'
6+
gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'

0 commit comments

Comments
 (0)