build: compile release APK and update dependencies (expo-image-manipulator/loader)
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'host.exp.exponent'
|
||||
version = '14.0.8'
|
||||
|
||||
android {
|
||||
namespace "expo.modules.imagemanipulator"
|
||||
defaultConfig {
|
||||
versionCode 23
|
||||
versionName "14.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api "androidx.annotation:annotation:1.0.0"
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<manifest>
|
||||
</manifest>
|
||||
node_modules/expo-image-manipulator/android/src/main/java/expo/modules/imagemanipulator/FileUtils.kt
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
package expo.modules.imagemanipulator
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
internal object FileUtils {
|
||||
@Throws(IOException::class)
|
||||
fun generateRandomOutputPath(context: Context, imageFormat: ImageFormat): String {
|
||||
val directory = File("${context.cacheDir}${File.separator}ImageManipulator")
|
||||
ensureDirExists(directory)
|
||||
return "${directory}${File.separator}${UUID.randomUUID()}${imageFormat.fileExtension}"
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun ensureDirExists(dir: File): File {
|
||||
if (!(dir.isDirectory || dir.mkdirs())) {
|
||||
throw ImageWriteFailedException(dir.path)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
package expo.modules.imagemanipulator
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import expo.modules.kotlin.records.Field
|
||||
import expo.modules.kotlin.records.Record
|
||||
import expo.modules.kotlin.types.Enumerable
|
||||
|
||||
/**
|
||||
* Options provided for resize action.
|
||||
*/
|
||||
class ResizeOptions : Record {
|
||||
@Field
|
||||
val width: Int? = null
|
||||
|
||||
@Field
|
||||
val height: Int? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Cropping rect for crop action.
|
||||
*/
|
||||
class CropRect : Record {
|
||||
@Field
|
||||
val originX: Double = 0.0
|
||||
|
||||
@Field
|
||||
val originY: Double = 0.0
|
||||
|
||||
@Field
|
||||
val width: Double = 0.0
|
||||
|
||||
@Field
|
||||
val height: Double = 0.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to use when saving the resulted image.
|
||||
*/
|
||||
class ManipulateOptions : Record {
|
||||
@Field
|
||||
val base64: Boolean = false
|
||||
|
||||
@Field
|
||||
val compress: Double = 1.0
|
||||
|
||||
@Field
|
||||
val format: ImageFormat = ImageFormat.JPEG
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible options for flip action.
|
||||
*/
|
||||
enum class FlipType(val value: String) : Enumerable {
|
||||
VERTICAL("vertical"),
|
||||
HORIZONTAL("horizontal")
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum with supported image formats.
|
||||
*/
|
||||
enum class ImageFormat(val value: String) : Enumerable {
|
||||
JPEG("jpeg"),
|
||||
JPG("jpg"),
|
||||
PNG("png"),
|
||||
WEBP("webp");
|
||||
|
||||
val fileExtension: String
|
||||
get() = when (this) {
|
||||
JPEG, JPG -> ".jpg"
|
||||
PNG -> ".png"
|
||||
WEBP -> ".webp"
|
||||
}
|
||||
|
||||
val compressFormat: Bitmap.CompressFormat
|
||||
get() = when (this) {
|
||||
JPEG, JPG -> Bitmap.CompressFormat.JPEG
|
||||
PNG -> Bitmap.CompressFormat.PNG
|
||||
WEBP -> Bitmap.CompressFormat.WEBP
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
package expo.modules.imagemanipulator
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import expo.modules.imagemanipulator.transformers.ImageTransformer
|
||||
import expo.modules.kotlin.RuntimeContext
|
||||
import expo.modules.kotlin.exception.CodedException
|
||||
import expo.modules.kotlin.exception.toCodedException
|
||||
import expo.modules.kotlin.sharedobjects.SharedObject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
data class ManipulatorResult(
|
||||
private val value: Bitmap?,
|
||||
private val error: CodedException?
|
||||
) {
|
||||
fun map(transformer: ImageTransformer): ManipulatorResult {
|
||||
if (error != null) {
|
||||
return ManipulatorResult(null, error)
|
||||
}
|
||||
|
||||
return try {
|
||||
ManipulatorResult(
|
||||
transformer.transform(
|
||||
requireNotNull(value) { "The result doesn't have a value or error" }
|
||||
),
|
||||
null
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
ManipulatorResult(null, e.toCodedException())
|
||||
}
|
||||
}
|
||||
|
||||
fun get(): Bitmap {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return requireNotNull(value) { "The result doesn't have a value or error" }
|
||||
}
|
||||
}
|
||||
|
||||
class ManipulatorTask(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val loader: suspend () -> Bitmap
|
||||
) {
|
||||
private var task: Deferred<ManipulatorResult> = launchLoader()
|
||||
|
||||
private fun launchLoader(): Deferred<ManipulatorResult> = coroutineScope.async {
|
||||
try {
|
||||
ManipulatorResult(loader(), null)
|
||||
} catch (e: Throwable) {
|
||||
ManipulatorResult(null, e.toCodedException())
|
||||
}
|
||||
}
|
||||
|
||||
fun addTransformer(transformer: ImageTransformer) {
|
||||
val oldTask = task
|
||||
task = coroutineScope.async {
|
||||
val currentValue = oldTask.await()
|
||||
return@async currentValue.map(transformer)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun render(): Bitmap {
|
||||
return task.await().get()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
task.cancel()
|
||||
task = launchLoader()
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
class ImageManipulatorContext(
|
||||
runtimeContext: RuntimeContext,
|
||||
private val task: ManipulatorTask
|
||||
) : SharedObject(runtimeContext) {
|
||||
fun addTransformer(transformer: ImageTransformer) = apply { task.addTransformer(transformer) }
|
||||
|
||||
fun reset() = apply { task.reset() }
|
||||
|
||||
suspend fun render() = task.render()
|
||||
|
||||
override fun sharedObjectDidRelease() {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
package expo.modules.imagemanipulator
|
||||
|
||||
import expo.modules.kotlin.exception.CodedException
|
||||
import expo.modules.kotlin.exception.DecoratedException
|
||||
|
||||
internal class ImageInvalidCropException :
|
||||
CodedException("Invalid crop options has been passed. Please make sure the requested crop rectangle is inside source image")
|
||||
|
||||
internal class ImageLoaderNotFoundException :
|
||||
CodedException(message = "ImageLoader module not found, make sure 'expo-image-loader' is linked correctly")
|
||||
|
||||
internal class ImageLoadingFailedException(image: String, cause: CodedException) :
|
||||
DecoratedException(message = "Could not load the image: $image", cause)
|
||||
|
||||
internal class ImageWriteFailedException(file: String) :
|
||||
CodedException(message = "Writing image data to the file has failed: $file")
|
||||
Generated
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
@file:OptIn(EitherType::class)
|
||||
|
||||
package expo.modules.imagemanipulator
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import expo.modules.imagemanipulator.transformers.CropTransformer
|
||||
import expo.modules.imagemanipulator.transformers.FlipTransformer
|
||||
import expo.modules.imagemanipulator.transformers.ResizeTransformer
|
||||
import expo.modules.imagemanipulator.transformers.RotateTransformer
|
||||
import expo.modules.interfaces.imageloader.ImageLoaderInterface.ResultListener
|
||||
import expo.modules.kotlin.apifeatures.EitherType
|
||||
import expo.modules.kotlin.exception.Exceptions
|
||||
import expo.modules.kotlin.exception.toCodedException
|
||||
import expo.modules.kotlin.functions.Coroutine
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
import expo.modules.kotlin.sharedobjects.SharedRef
|
||||
import expo.modules.kotlin.types.EitherOfThree
|
||||
import expo.modules.kotlin.types.toKClass
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
class ImageManipulatorModule : Module() {
|
||||
private val context: Context
|
||||
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
||||
|
||||
private fun createManipulatorContext(url: Uri): ImageManipulatorContext {
|
||||
val loader = suspend {
|
||||
val imageLoader = appContext.imageLoader
|
||||
?: throw ImageLoaderNotFoundException()
|
||||
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
imageLoader.loadImageForManipulationFromURL(
|
||||
url.toString(),
|
||||
object : ResultListener {
|
||||
override fun onSuccess(bitmap: Bitmap) {
|
||||
continuation.resume(bitmap)
|
||||
}
|
||||
|
||||
override fun onFailure(cause: Throwable?) {
|
||||
continuation.resumeWithException(ImageLoadingFailedException(url.toString(), cause.toCodedException()))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val task = ManipulatorTask(appContext.backgroundCoroutineScope, loader)
|
||||
return ImageManipulatorContext(runtimeContext, task)
|
||||
}
|
||||
|
||||
private fun createManipulatorContext(bitmap: Bitmap): ImageManipulatorContext {
|
||||
val task = ManipulatorTask(appContext.backgroundCoroutineScope) { bitmap }
|
||||
return ImageManipulatorContext(runtimeContext, task)
|
||||
}
|
||||
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("ExpoImageManipulator")
|
||||
|
||||
Function("manipulate") { url: EitherOfThree<Uri, SharedRef<Bitmap>, SharedRef<Drawable>> ->
|
||||
return@Function if (url.`is`(Uri::class)) {
|
||||
createManipulatorContext(url.get(Uri::class))
|
||||
} else if (url.`is`(toKClass<SharedRef<Bitmap>>())) {
|
||||
val bitmap = url.get(toKClass<SharedRef<Bitmap>>()).ref
|
||||
createManipulatorContext(bitmap)
|
||||
} else {
|
||||
val drawable = url.get(toKClass<SharedRef<Drawable>>()).ref
|
||||
val bitmap = (drawable as? BitmapDrawable)?.bitmap
|
||||
?: throw Exceptions.IllegalArgument("The drawable cannot be converted to a bitmap")
|
||||
createManipulatorContext(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
Class<ImageManipulatorContext>("Context") {
|
||||
Constructor { url: Uri ->
|
||||
createManipulatorContext(url)
|
||||
}
|
||||
|
||||
Function("resize") { context: ImageManipulatorContext, options: ResizeOptions ->
|
||||
context.addTransformer(ResizeTransformer(options))
|
||||
}
|
||||
|
||||
Function("rotate") { context: ImageManipulatorContext, rotation: Float ->
|
||||
context.addTransformer(RotateTransformer(rotation))
|
||||
}
|
||||
|
||||
Function("flip") { context: ImageManipulatorContext, flipType: FlipType ->
|
||||
context.addTransformer(FlipTransformer(flipType))
|
||||
}
|
||||
|
||||
Function("crop") { context: ImageManipulatorContext, rect: CropRect ->
|
||||
context.addTransformer(CropTransformer(rect))
|
||||
}
|
||||
|
||||
Function("reset") { context: ImageManipulatorContext ->
|
||||
context.reset()
|
||||
}
|
||||
|
||||
AsyncFunction("renderAsync") Coroutine { context: ImageManipulatorContext ->
|
||||
val image = context.render()
|
||||
ImageRef(image, runtimeContext)
|
||||
}
|
||||
}
|
||||
|
||||
Class<ImageRef>("Image") {
|
||||
Property("width") { image: ImageRef -> image.ref.width }
|
||||
Property("height") { image: ImageRef -> image.ref.height }
|
||||
|
||||
AsyncFunction("saveAsync") Coroutine { image: ImageRef, options: ManipulateOptions? ->
|
||||
val options = options ?: ManipulateOptions()
|
||||
val path = FileUtils.generateRandomOutputPath(context, options.format)
|
||||
val compression = (options.compress * 100).toInt()
|
||||
val resultBitmap = image.ref
|
||||
|
||||
var base64String: String? = null
|
||||
appContext.backgroundCoroutineScope.async {
|
||||
FileOutputStream(path).use { fileOut ->
|
||||
val compressFormat = options.format.compressFormat
|
||||
resultBitmap.compress(compressFormat, compression, fileOut)
|
||||
if (options.base64) {
|
||||
ByteArrayOutputStream().use { byteOut ->
|
||||
resultBitmap.compress(compressFormat, compression, byteOut)
|
||||
base64String = Base64.encodeToString(byteOut.toByteArray(), Base64.NO_WRAP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.await()
|
||||
|
||||
mapOf(
|
||||
"uri" to Uri.fromFile(File(path)).toString(),
|
||||
"width" to resultBitmap.width,
|
||||
"height" to resultBitmap.height,
|
||||
"base64" to base64String
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
package expo.modules.imagemanipulator
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import expo.modules.kotlin.RuntimeContext
|
||||
import expo.modules.kotlin.sharedobjects.SharedRef
|
||||
|
||||
class ImageRef(bitmap: Bitmap, runtimeContext: RuntimeContext) : SharedRef<Bitmap>(bitmap, runtimeContext) {
|
||||
override val nativeRefType: String = "image"
|
||||
|
||||
override fun getAdditionalMemoryPressure(): Int {
|
||||
return ref.allocationByteCount
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
package expo.modules.imagemanipulator.transformers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import expo.modules.imagemanipulator.CropRect
|
||||
import expo.modules.imagemanipulator.ImageInvalidCropException
|
||||
|
||||
class CropTransformer(
|
||||
private val rect: CropRect
|
||||
) : ImageTransformer {
|
||||
override fun transform(bitmap: Bitmap): Bitmap {
|
||||
val isInBounds = rect.originX <= bitmap.width &&
|
||||
rect.originY <= bitmap.height &&
|
||||
rect.width <= bitmap.width &&
|
||||
rect.height <= bitmap.height
|
||||
if (!isInBounds) {
|
||||
throw ImageInvalidCropException()
|
||||
}
|
||||
|
||||
return Bitmap.createBitmap(bitmap, rect.originX.toInt(), rect.originY.toInt(), rect.width.toInt(), rect.height.toInt())
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
package expo.modules.imagemanipulator.transformers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import expo.modules.imagemanipulator.FlipType
|
||||
|
||||
class FlipTransformer(
|
||||
private val flipType: FlipType
|
||||
) : ImageTransformer {
|
||||
override fun transform(bitmap: Bitmap): Bitmap {
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, rotationMatrix, true)
|
||||
}
|
||||
|
||||
private val rotationMatrix: Matrix
|
||||
get() = Matrix().apply {
|
||||
when (flipType) {
|
||||
FlipType.VERTICAL -> postScale(1f, -1f)
|
||||
FlipType.HORIZONTAL -> postScale(-1f, 1f)
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
package expo.modules.imagemanipulator.transformers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
@FunctionalInterface
|
||||
interface ImageTransformer {
|
||||
fun transform(bitmap: Bitmap): Bitmap
|
||||
}
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
package expo.modules.imagemanipulator.transformers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import expo.modules.imagemanipulator.ResizeOptions
|
||||
|
||||
class ResizeTransformer(
|
||||
private val resizeOptions: ResizeOptions
|
||||
) : ImageTransformer {
|
||||
override fun transform(bitmap: Bitmap): Bitmap {
|
||||
var targetWidth = 0
|
||||
var targetHeight = 0
|
||||
|
||||
val imageRatio = bitmap.width.toDouble() / bitmap.height.toDouble()
|
||||
|
||||
if (resizeOptions.width != null) {
|
||||
targetWidth = resizeOptions.width
|
||||
targetHeight = (resizeOptions.width / imageRatio).toInt()
|
||||
}
|
||||
|
||||
if (resizeOptions.height != null) {
|
||||
targetHeight = resizeOptions.height
|
||||
targetWidth = if (targetWidth == 0) (resizeOptions.height * imageRatio).toInt() else targetWidth
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
package expo.modules.imagemanipulator.transformers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
|
||||
class RotateTransformer(private val rotation: Float) : ImageTransformer {
|
||||
override fun transform(bitmap: Bitmap): Bitmap {
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, rotationMatrix, true)
|
||||
}
|
||||
|
||||
private val rotationMatrix: Matrix
|
||||
get() = Matrix().apply { postRotate(rotation) }
|
||||
}
|
||||
Reference in New Issue
Block a user