Skip to content

Commit

Permalink
Use ByteArray for images
Browse files Browse the repository at this point in the history
Add PlatformImage
Add ios support - but not working yet as images are no found in resources.
  • Loading branch information
HenrikJannsen committed Nov 30, 2024
1 parent 2ceb8d3 commit 1c315bd
Show file tree
Hide file tree
Showing 18 changed files with 260 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
package network.bisq.mobile.service

import android.content.Context
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.asImageBitmap
import network.bisq.mobile.client.cathash.ClientCatHashService
import network.bisq.mobile.utils.ImageUtil
import network.bisq.mobile.utils.ImageUtil.PATH_TO_DRAWABLE
Expand All @@ -28,22 +25,23 @@ import java.io.File
const val CAT_HASH_PATH = PATH_TO_DRAWABLE + "cathash/"

class AndroidClientCatHashService(private val context: Context, filesDir: String) :
ClientCatHashService<ImageBitmap>("$filesDir/Bisq2_mobile") {
override fun composeImage(paths: Array<String>, size: Int): ImageBitmap {
return ImageUtil.composeImage(
ClientCatHashService<ByteArray>("$filesDir/Bisq2_mobile") {
override fun composeImage(paths: Array<String>, size: Int): ByteArray {
val profileIcon = ImageUtil.composeImage(
context,
CAT_HASH_PATH,
paths,
size,
size
).asImageBitmap()
)
return ImageUtil.bitmapToPngByteArray(profileIcon)
}

override fun writeRawImage(image: ImageBitmap, iconFilePath: String) {
ImageUtil.writeRawImage(image.asAndroidBitmap(), File(iconFilePath))
override fun writeRawImage(image: ByteArray, iconFilePath: String) {
ImageUtil.saveByteArrayAsPng(image,File(iconFilePath))
}

override fun readRawImage(iconFilePath: String): ImageBitmap? {
return ImageUtil.readRawImage(File((iconFilePath)))?.asImageBitmap()
override fun readRawImage(iconFilePath: String): ByteArray? {
return ImageUtil.readPngByteArray(File((iconFilePath)))
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package network.bisq.mobile.android.node.domain.user_profile

import android.graphics.Bitmap
import androidx.compose.ui.graphics.asImageBitmap
import bisq.common.encoding.Hex
import bisq.security.DigestUtil
import bisq.security.SecurityService
Expand All @@ -12,6 +11,7 @@ import bisq.user.profile.UserProfile
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.service.AndroidNodeCatHashService
import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
import network.bisq.mobile.utils.ImageUtil
import network.bisq.mobile.utils.Logging
import java.security.KeyPair
import java.util.Random
Expand Down Expand Up @@ -53,7 +53,7 @@ class NodeUserProfileServiceFacade(private val applicationService: AndroidApplic
return userService.userIdentityService.userIdentities.isNotEmpty()
}

override suspend fun generateKeyPair(result: (String, String, Any?) -> Unit) {
override suspend fun generateKeyPair(result: (String, String, ByteArray?) -> Unit) {
keyPair = securityService.keyBundleService.generateKeyPair()
pubKeyHash = DigestUtil.hash(keyPair!!.public.encoded)

Expand All @@ -72,8 +72,9 @@ class NodeUserProfileServiceFacade(private val applicationService: AndroidApplic
0,
120.0
)
val imageBitmap = profileIcon.asImageBitmap()
result(id!!, nym!!, imageBitmap)

val pngByteArray = ImageUtil.bitmapToPngByteArray(profileIcon)
result(id!!, nym!!, pngByteArray)
}

override suspend fun createAndPublishNewUserProfile(nickName: String) {
Expand Down
3 changes: 3 additions & 0 deletions shared/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ kotlin {
}

sourceSets {
androidMain.dependencies {
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
//put your multiplatform dependencies here
implementation(libs.koin.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")

package network.bisq.mobile

import android.graphics.Bitmap
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import network.bisq.mobile.utils.ImageUtil

// FIXME if ImageBitmap is used it gives a compile error
actual typealias PlatformImage = Any

actual fun ByteArray.toImageBitmap(): PlatformImage {
val bitmap:Bitmap = ImageUtil.decodePngToImageBitmap(this)
val asImageBitmap: ImageBitmap = bitmap.asImageBitmap()
return asImageBitmap
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import java.io.IOException
object ImageUtil : Logging {
const val PATH_TO_DRAWABLE =
"composeResources/bisqapps.shared.presentation.generated.resources/drawable/"

fun composeImage(
context: Context,
basePath: String,
Expand Down Expand Up @@ -75,8 +76,41 @@ object ImageUtil : Logging {
return outputStream.toByteArray()
}

internal fun byteArrayToBitmap(data: ByteArray): Bitmap? {
fun byteArrayToBitmap(data: ByteArray): Bitmap? {
val inputStream = ByteArrayInputStream(data)
return BitmapFactory.decodeStream(inputStream)
}
}

fun bitmapToPngByteArray(bitmap: Bitmap): ByteArray {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
}

fun decodePngToImageBitmap(pngByteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(pngByteArray, 0, pngByteArray.size)
}

fun saveByteArrayAsPng(data: ByteArray, file: File) {
val bitmap = byteArrayToBitmap(data)
if (bitmap != null) {
saveBitmapAsPng(bitmap, file)
}
}

fun saveBitmapAsPng(bitmap: Bitmap, file: File) {
FileOutputStream(file).use { fos ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
}
}

fun readPngByteArray(file: File): ByteArray? {
return try {
file.readBytes()
} catch (e: IOException) {
log.e("Reading $file failed", e)
null
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package network.bisq.mobile.service

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package network.bisq.mobile

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")

expect class PlatformImage

expect fun ByteArray.toImageBitmap(): PlatformImage
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ object BucketEncoder {
val reminder: Long = divisorReminder.second.longValue()
val res = abs(reminder % bucketSize).toInt()
result[currentBucket] = res
println("divisorReminder1 " + divisorReminder.first.toString())
println("divisorReminder2 " + divisorReminder.second.toString())
println("currentBucket " + currentBucket)
println("reminder " + reminder)
println("res " + res)
currentBucket++
}
return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ abstract class ClientCatHashService<T>(private val baseDirPath: String) : Loggin
val userProfileId = pubKeyHash.toHex()
val subPath = "db/cache/cat_hash_icons/v$avatarVersion"
val iconsDir = baseDirPath.toPath().resolve(subPath.toPath())
val iconFilePath = iconsDir.resolve("$userProfileId.raw")
val iconFilePath = iconsDir.resolve("$userProfileId.png")

val useCache = size <= SIZE_OF_CACHED_ICONS
if (useCache) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlin.random.Random

class ClientUserProfileServiceFacade(
private val apiGateway: UserProfileApiGateway,
private val clientCatHashService: ClientCatHashService<Any>
private val clientCatHashService: ClientCatHashService<ByteArray>
) :
UserProfileServiceFacade, Logging {

Expand All @@ -26,7 +26,7 @@ class ClientUserProfileServiceFacade(
return getUserIdentityIds().isNotEmpty()
}

override suspend fun generateKeyPair(result: (String, String, Any?) -> Unit) {
override suspend fun generateKeyPair(result: (String, String, ByteArray?) -> Unit) {
try {
val ts = Clock.System.now().toEpochMilliseconds()
val preparedData = apiGateway.requestPreparedData()
Expand All @@ -42,7 +42,7 @@ class ClientUserProfileServiceFacade(
result(preparedData.id, preparedData.nym, image)
this.preparedData = preparedData
} catch (e: Exception) {
log.e { e.toString() }
log.e("generateKeyPair failed", e)
}
}

Expand All @@ -57,7 +57,7 @@ class ClientUserProfileServiceFacade(
this.preparedData = null
log.i { "Call to createAndPublishNewUserProfile successful. userProfileId = ${response.userProfileId}" }
} catch (e: Exception) {
log.e { e.toString() }
log.e("createAndPublishNewUserProfile failed", e)
}
}
}
Expand All @@ -66,7 +66,7 @@ class ClientUserProfileServiceFacade(
return try {
apiGateway.getUserIdentityIds()
} catch (e: Exception) {
log.e { e.toString() }
log.e("getUserIdentityIds failed", e)
emptyList()
}
}
Expand All @@ -76,7 +76,7 @@ class ClientUserProfileServiceFacade(
val userProfile = getSelectedUserProfile()
result(userProfile.nickName, userProfile.nym, userProfile.id)
} catch (e: Exception) {
log.e { e.toString() }
log.e("applySelectedUserProfile failed", e)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface UserProfileServiceFacade {
* the proof of work solution.
* The CatHash image is also created based on that hash and the proof of work solution.
*/
suspend fun generateKeyPair(result: (String, String, Any?) -> Unit)
suspend fun generateKeyPair(result: (String, String, ByteArray?) -> Unit)

/**
* Once the user clicks the `create` button we create a user identity and publish the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
@file:OptIn(BetaInteropApi::class)

package network.bisq.mobile

import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.NSData
import platform.Foundation.create
import platform.UIKit.UIImage


actual typealias PlatformImage = UIImage

@OptIn(ExperimentalForeignApi::class)
actual fun ByteArray.toImageBitmap(): PlatformImage {
// Pin the ByteArray to get a stable memory address
val nsData = this.usePinned { pinnedByteArray ->
NSData.create(
bytes = pinnedByteArray.addressOf(0),
length = this.size.toULong()
)
}
val uiImage = UIImage(data = nsData) ?: throw IllegalArgumentException("Failed to decode image")
return uiImage
}

Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
package network.bisq.mobile.service

import network.bisq.mobile.client.cathash.ClientCatHashService
import network.bisq.mobile.utils.getFilesDir
import platform.UIKit.UIImage

// TODO Implement
class IosClientCatHashService() :
ClientCatHashService<Any?>("Bisq2_mobile") {
const val PATH_TO_DRAWABLE =
"composeResources/bisqapps/shared/presentation/generated/resources/drawable/"

override fun composeImage(paths: Array<String>, size: Int): Any? {
return null
const val CAT_HASH_PATH = /*PATH_TO_DRAWABLE +*/ "drawable/cathash/"

class IosClientCatHashService : ClientCatHashService<ByteArray>("${getFilesDir()}/Bisq2_mobile") {

override fun composeImage(paths: Array<String>, size: Int): ByteArray {
val profileIcon: UIImage? = IosImageUtil.composeImage(
CAT_HASH_PATH,
paths,
size,
size
)
val uiImageToPngByteArray =
profileIcon?.let { IosImageUtil.uiImageToPngByteArray(profileIcon) }
return uiImageToPngByteArray ?: ByteArray(0)
}

override fun writeRawImage(image: Any?, iconFilePath: String) {
override fun writeRawImage(image: ByteArray, iconFilePath: String) {
//todo
}

override fun readRawImage(iconFilePath: String): Any? {
override fun readRawImage(iconFilePath: String): ByteArray? {
//todo
return null
}
}


Loading

0 comments on commit 1c315bd

Please sign in to comment.