From 58521b27de82e5e1981e7333e1a9418580eba0f1 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 1 Mar 2023 15:48:18 -0500 Subject: [PATCH] Fix for a String.format crashing the original library by using the Locale's default number format. --- .../vitorpamplona/amethyst/RoboHashCache.kt | 152 +++++++++++++++++- 1 file changed, 147 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/RoboHashCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/RoboHashCache.kt index 1430a7c65..2f23c5fbc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/RoboHashCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/RoboHashCache.kt @@ -1,22 +1,29 @@ package com.vitorpamplona.amethyst import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.util.LruCache import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import java.util.UUID -import name.neuhalfen.projects.android.robohash.RoboHash +import name.neuhalfen.projects.android.robohash.buckets.VariableSizeHashing +import name.neuhalfen.projects.android.robohash.handle.Handle +import name.neuhalfen.projects.android.robohash.handle.HandleFactory +import name.neuhalfen.projects.android.robohash.paths.Configuration +import name.neuhalfen.projects.android.robohash.repository.ImageRepository object RoboHashCache { - lateinit var robots: RoboHash + lateinit var robots: MyRoboHash lateinit var defaultAvatar: ImageBitmap @Synchronized fun get(context: Context, hash: String): ImageBitmap { if (!this::robots.isInitialized) { - robots = RoboHash(context) - //robots.useCache(LruCache(100)); + robots = MyRoboHash(context) defaultAvatar = robots.imageForHandle(robots.calculateHandleFromUUID(UUID.nameUUIDFromBytes("aaaa".toByteArray()))).asImageBitmap() } @@ -24,4 +31,139 @@ object RoboHashCache { return defaultAvatar } -} \ No newline at end of file +} + + +/** + * Recreates RoboHash to use a custom configuration + */ +class MyRoboHash(context: Context) { + private val configuration: Configuration = ModifiedSet1Configuration() + private val repository: ImageRepository + private val hashing = VariableSizeHashing(configuration.bucketSizes) + + // Optional + private var memoryCache: LruCache? = null + + init { + repository = ImageRepository(context.assets) + } + + fun useCache(memoryCache: LruCache?) { + this.memoryCache = memoryCache + } + + fun calculateHandleFromUUID(uuid: UUID?): Handle { + val data = hashing.createBuckets(uuid) + return handleFactory.calculateHandle(data) + } + + fun imageForHandle(handle: Handle): Bitmap { + if (null != memoryCache) { + val cached = memoryCache!![handle.toString()] + if (null != cached) return cached + } + val bucketValues = handle.bucketValues() + val paths = configuration.convertToFacetParts(bucketValues) + val sampleSize = 1 + val buffer = repository.createBuffer(configuration.width(), configuration.height()) + val target = buffer.copy(Bitmap.Config.ARGB_8888, true) + val merged = Canvas(target) + val paint = Paint(0) + + // The first image is not added as copy form the buffer + for (i in paths.indices) { + merged.drawBitmap(repository.getInto(buffer, paths[i], sampleSize), 0f, 0f, paint) + } + repository.returnBuffer(buffer) + if (null != memoryCache) { + memoryCache!!.put(handle.toString(), target) + } + return target + } + + companion object { + private val handleFactory = HandleFactory() + } +} + +/** + * Custom configuration to avoid the use of String.format in the GeneratePath + * This uses the default location and ends up encoding number in the local language + */ +class ModifiedSet1Configuration : Configuration { + override fun convertToFacetParts(bucketValues: ByteArray): Array { + require(bucketValues.size == BUCKET_COUNT) + val color = INT_TO_COLOR[bucketValues[BUCKET_COLOR].toInt()] + val paths = mutableListOf() + + // e.g. + // blue face #2 + // blue nose #7 + // blue + val firstFacetBucket = BUCKET_COLOR + 1 + for (facet in 0 until FACET_COUNT) { + val bucketValue = bucketValues[firstFacetBucket + facet].toInt() + paths.add(generatePath(FACET_PATH_TEMPLATES[facet], color, bucketValue)) + } + return paths.toTypedArray() + } + + private fun generatePath(facetPathTemplate: String, color: String, bucketValue: Int): String { + // TODO: Make more efficient + return facetPathTemplate.replace("#ROOT#", ROOT).replace("#COLOR#".toRegex(), color) + .replace("#ITEM#".toRegex(), (bucketValue + 1).toString().padStart(2, '0')) + } + + override fun getBucketSizes(): ByteArray { + return BUCKET_SIZES + } + + override fun width(): Int { + return 300 + } + + override fun height(): Int { + return 300 + } + + companion object { + private const val ROOT = "sets/set1" + private const val BUCKET_COLOR = 0 + private const val COLOR_COUNT = 10 + private const val BODY_COUNT = 10 + private const val FACE_COUNT = 10 + private const val MOUTH_COUNT = 10 + private const val EYES_COUNT = 10 + private const val ACCESSORY_COUNT = 10 + private const val BUCKET_COUNT = 6 + private const val FACET_COUNT = 5 + private val BUCKET_SIZES = byteArrayOf( + COLOR_COUNT.toByte(), + BODY_COUNT.toByte(), + FACE_COUNT.toByte(), + MOUTH_COUNT.toByte(), + EYES_COUNT.toByte(), + ACCESSORY_COUNT.toByte() + ) + private val INT_TO_COLOR = arrayOf( + "blue", + "brown", + "green", + "grey", + "orange", + "pink", + "purple", + "red", + "white", + "yellow" + ) + private val FACET_PATH_TEMPLATES = arrayOf( + "#ROOT#/#COLOR#/01Body/#COLOR#_body-#ITEM#.png", + "#ROOT#/#COLOR#/02Face/#COLOR#_face-#ITEM#.png", + "#ROOT#/#COLOR#/Mouth/#COLOR#_mouth-#ITEM#.png", + "#ROOT#/#COLOR#/Eyes/#COLOR#_eyes-#ITEM#.png", + "#ROOT#/#COLOR#/Accessory/#COLOR#_accessory-#ITEM#.png" + ) + } +}