expo-background-remover 1.0.2 → 1.2.2
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/android/build.gradle
CHANGED
|
@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
|
|
|
2
2
|
apply plugin: 'kotlin-android'
|
|
3
3
|
|
|
4
4
|
group = 'expo.modules.backgroundremover'
|
|
5
|
-
version = '1.
|
|
5
|
+
version = '1.2.2'
|
|
6
6
|
|
|
7
7
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
8
8
|
apply from: expoModulesCorePlugin
|
|
@@ -37,7 +37,7 @@ android {
|
|
|
37
37
|
namespace "expo.modules.backgroundremover"
|
|
38
38
|
defaultConfig {
|
|
39
39
|
versionCode 1
|
|
40
|
-
versionName "1.
|
|
40
|
+
versionName "1.2.2"
|
|
41
41
|
}
|
|
42
42
|
lintOptions {
|
|
43
43
|
abortOnError false
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
-
xmlns:tools="http://schemas.android.com/tools">
|
|
2
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
3
|
+
<application>
|
|
3
4
|
<meta-data
|
|
4
5
|
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
|
5
|
-
/* 2. Combine both values: barcode_ui (from expo-camera) and subject_segment (from this) */
|
|
6
6
|
android:value="barcode_ui,subject_segment"
|
|
7
|
-
|
|
8
|
-
tools:replace="android:value" />
|
|
7
|
+
tools:replace="android:value" />
|
|
9
8
|
</application>
|
|
9
|
+
|
|
10
10
|
</manifest>
|
|
@@ -12,6 +12,8 @@ import kotlinx.coroutines.tasks.await
|
|
|
12
12
|
import kotlinx.coroutines.withContext
|
|
13
13
|
import java.io.File
|
|
14
14
|
import java.io.FileOutputStream
|
|
15
|
+
import java.io.InputStream
|
|
16
|
+
import java.io.FileInputStream
|
|
15
17
|
import java.util.UUID
|
|
16
18
|
import android.graphics.PorterDuff
|
|
17
19
|
import android.graphics.PorterDuffXfermode
|
|
@@ -25,7 +27,9 @@ class BackgroundRemoverProcessor(private val context: Context) {
|
|
|
25
27
|
)
|
|
26
28
|
|
|
27
29
|
suspend fun processImage(uriString: String): String = withContext(Dispatchers.IO) {
|
|
28
|
-
|
|
30
|
+
var bitmap: Bitmap? = null
|
|
31
|
+
try{
|
|
32
|
+
bitmap = loadAndResizeBitmap(uriString, 2048)
|
|
29
33
|
|
|
30
34
|
// FIX 1: Defined getRotation helper
|
|
31
35
|
val inputImage = InputImage.fromBitmap(bitmap, getRotation(uriString))
|
|
@@ -42,32 +46,47 @@ class BackgroundRemoverProcessor(private val context: Context) {
|
|
|
42
46
|
val maskBitmap = createMaskFromBuffer(maskBuffer, maskWidth, maskHeight)
|
|
43
47
|
val outputBitmap = applyMaskToBitmap(bitmap, maskBitmap)
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
// mask no longer needed
|
|
50
|
+
maskBitmap.recycle()
|
|
51
|
+
|
|
52
|
+
val resultPath = saveResult(outputBitmap)
|
|
53
|
+
|
|
54
|
+
// cleanup
|
|
55
|
+
outputBitmap.recycle()
|
|
56
|
+
return@withContext resultPath
|
|
57
|
+
}finally {
|
|
58
|
+
// ALWAYS recycle original bitmap
|
|
59
|
+
bitmap?.recycle()
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
// Helper to handle image rotation logic
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} catch (e: Exception) {
|
|
60
|
-
0
|
|
65
|
+
private fun getRotation(uriString: String): Int {
|
|
66
|
+
return try {
|
|
67
|
+
// Use the helper and the .use block for automatic closing
|
|
68
|
+
val exifInterface = openStream(uriString)?.use { ExifInterface(it) }
|
|
69
|
+
|
|
70
|
+
when (exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
|
|
71
|
+
ExifInterface.ORIENTATION_ROTATE_90 -> 90
|
|
72
|
+
ExifInterface.ORIENTATION_ROTATE_180 -> 180
|
|
73
|
+
ExifInterface.ORIENTATION_ROTATE_270 -> 270
|
|
74
|
+
else -> 0
|
|
61
75
|
}
|
|
76
|
+
} catch (e: Exception) {
|
|
77
|
+
e.printStackTrace() // Logs the specific exception (e.g., FileNotFound, SecurityException)
|
|
78
|
+
0
|
|
62
79
|
}
|
|
80
|
+
}
|
|
63
81
|
|
|
64
82
|
private fun loadAndResizeBitmap(uriString: String, maxDimension: Int): Bitmap {
|
|
65
83
|
val uri = Uri.parse(uriString)
|
|
66
|
-
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
85
|
+
|
|
86
|
+
// Using the helper with the .use extension
|
|
87
|
+
openStream(uriString)?.use { stream ->
|
|
88
|
+
BitmapFactory.decodeStream(stream, null, options)
|
|
89
|
+
} ?: throw Exception("Could not open stream for image")
|
|
71
90
|
|
|
72
91
|
var sampleSize = 1
|
|
73
92
|
while ((options.outWidth / (sampleSize * 2)) >= maxDimension &&
|
|
@@ -76,9 +95,9 @@ class BackgroundRemoverProcessor(private val context: Context) {
|
|
|
76
95
|
}
|
|
77
96
|
|
|
78
97
|
val loadOptions = BitmapFactory.Options().apply { inSampleSize = sampleSize }
|
|
79
|
-
val subsampledBitmap =
|
|
80
|
-
|
|
81
|
-
} ?: throw Exception("Failed to decode image")
|
|
98
|
+
val subsampledBitmap = openStream(uriString)?.use { stream ->
|
|
99
|
+
BitmapFactory.decodeStream(stream, null, loadOptions)
|
|
100
|
+
} ?: throw Exception("Failed to decode subsampled image")
|
|
82
101
|
|
|
83
102
|
val scale = minOf(maxDimension.toFloat() / subsampledBitmap.width, maxDimension.toFloat() / subsampledBitmap.height)
|
|
84
103
|
if (scale >= 1f) return subsampledBitmap
|
|
@@ -125,6 +144,24 @@ class BackgroundRemoverProcessor(private val context: Context) {
|
|
|
125
144
|
FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
|
|
126
145
|
return Uri.fromFile(file).toString()
|
|
127
146
|
}
|
|
147
|
+
|
|
148
|
+
private fun openStream(uriString: String): java.io.InputStream? {
|
|
149
|
+
val uri = Uri.parse(uriString)
|
|
150
|
+
return when (uri.scheme) {
|
|
151
|
+
"content" -> {
|
|
152
|
+
// Asks the Android System to resolve the virtual URI
|
|
153
|
+
context.contentResolver.openInputStream(uri)
|
|
154
|
+
}
|
|
155
|
+
"file" -> {
|
|
156
|
+
// Opens a direct pipe to the file on the storage
|
|
157
|
+
uri.path?.let { java.io.FileInputStream(it) }
|
|
158
|
+
}
|
|
159
|
+
else -> {
|
|
160
|
+
// Handles raw paths that might not have a scheme prefix
|
|
161
|
+
java.io.FileInputStream(uriString)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
128
165
|
|
|
129
166
|
fun close() {
|
|
130
167
|
segmenter.close()
|
|
@@ -3,6 +3,7 @@ import Vision
|
|
|
3
3
|
import CoreImage
|
|
4
4
|
import CoreImage.CIFilterBuiltins
|
|
5
5
|
import UIKit
|
|
6
|
+
import Foundation
|
|
6
7
|
|
|
7
8
|
public class ExpoBackgroundRemoverModule: Module {
|
|
8
9
|
private let context = CIContext()
|
|
@@ -94,7 +95,9 @@ public class ExpoBackgroundRemoverModule: Module {
|
|
|
94
95
|
guard let result = request.results?.first else { throw NoSubjectDetectedException() }
|
|
95
96
|
|
|
96
97
|
// Generate combined mask for all subjects
|
|
97
|
-
let maskBuffer = try result.generateMask(forInstances: result.allInstances)
|
|
98
|
+
guard let maskBuffer = try result.generateMask(forInstances: result.allInstances) else {
|
|
99
|
+
throw ImageProcessingException()
|
|
100
|
+
}
|
|
98
101
|
return try applyMaskAndSave(image: image, maskBuffer: maskBuffer)
|
|
99
102
|
}
|
|
100
103
|
|
|
@@ -129,10 +132,10 @@ public class ExpoBackgroundRemoverModule: Module {
|
|
|
129
132
|
guard let filter = CIFilter(name: "CIBlendWithMask") else {
|
|
130
133
|
throw ImageProcessingException()
|
|
131
134
|
}
|
|
132
|
-
filter.
|
|
135
|
+
filter.setValue(ciImage, forKey: kCIInputImageKey)
|
|
133
136
|
let transparentBG = CIImage(color: .clear).cropped(to: ciImage.extent)
|
|
134
|
-
filter.
|
|
135
|
-
filter.
|
|
137
|
+
filter.setValue(transparentBG, forKey: kCIInputBackgroundImageKey)
|
|
138
|
+
filter.setValue(scaledMask, forKey: kCIInputMaskImageKey)
|
|
136
139
|
|
|
137
140
|
guard let outputImage = filter.outputImage,
|
|
138
141
|
let cgImage = context.createCGImage(outputImage, from: ciImage.extent) else {
|