From e5a064d0c353a2065297281f0ac068f100698068 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Sat, 27 Dec 2025 12:48:32 +0200 Subject: [PATCH] feat: Add expect/actual for blurhash and base64Image in commons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create PlatformImage expect/actual abstracting Bitmap/BufferedImage - Move pure blurhash algorithms (Base83, SRGB, CosineCache, BlurHashEncoder) to commonMain - Rewrite BlurHashDecoder to return PlatformImage instead of Bitmap - Add PlatformImage.toBlurhash() extension in commonMain - Add platform-specific Bitmap.toBlurhash() and BufferedImage.toBlurhash() - Add base64Image toPlatformImage() for both Android and JVM 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../commons/base64Image/Base64ImageBitmap.kt | 8 ++ .../amethyst/commons/blurhash/BitmapUtils.kt | 42 ++--------- .../commons/blurhash/PlatformImage.android.kt | 65 ++++++++++++++++ .../amethyst/commons/blurhash/Base83.kt | 0 .../commons/blurhash/BlurHashDecoder.kt | 7 +- .../commons/blurhash/BlurHashEncoder.kt | 0 .../commons/blurhash/BlurHashEncoderExt.kt | 64 ++++++++++++++++ .../amethyst/commons/blurhash/CosineCache.kt | 5 +- .../commons/blurhash/PlatformImage.kt | 65 ++++++++++++++++ .../amethyst/commons/blurhash/SRGB.kt | 0 .../base64Image/Base64ImagePlatform.jvm.kt | 45 +++++++++++ .../commons/blurhash/BitmapUtils.jvm.kt | 29 +++++++ .../commons/blurhash/PlatformImage.jvm.kt | 75 +++++++++++++++++++ 13 files changed, 362 insertions(+), 43 deletions(-) create mode 100644 commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.android.kt rename commons/src/{androidMain => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/blurhash/Base83.kt (100%) rename commons/src/{androidMain => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt (97%) rename commons/src/{androidMain => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoder.kt (100%) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoderExt.kt rename commons/src/{androidMain => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt (96%) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.kt rename commons/src/{androidMain => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/blurhash/SRGB.kt (100%) create mode 100644 commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImagePlatform.jvm.kt create mode 100644 commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.jvm.kt create mode 100644 commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.jvm.kt diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImageBitmap.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImageBitmap.kt index 975331bc3..9340c5d08 100644 --- a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImageBitmap.kt +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImageBitmap.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.commons.base64Image import android.graphics.Bitmap import android.graphics.BitmapFactory +import com.vitorpamplona.amethyst.commons.blurhash.PlatformImage +import com.vitorpamplona.amethyst.commons.blurhash.toPlatformImage import java.util.Base64 fun Base64Image.toBitmap(content: String): Bitmap { @@ -35,3 +37,9 @@ fun Base64Image.toBitmap(content: String): Bitmap { throw Exception("Unable to convert base64 to image $content") } + +/** + * Converts a base64 image data URI to a PlatformImage. + * Delegates to toBitmap and wraps the result. + */ +fun Base64Image.toPlatformImage(content: String): PlatformImage = toBitmap(content).toPlatformImage() diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.kt index 7a3bd652c..bf40e816b 100644 --- a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.kt +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.kt @@ -21,41 +21,9 @@ package com.vitorpamplona.amethyst.commons.blurhash import android.graphics.Bitmap -import kotlin.math.roundToInt -fun Bitmap.toBlurhash(): String { - val aspectRatio = this.width.toFloat() / this.height.toFloat() - - if (this.width > 100 && this.height > 100) { - return Bitmap.createScaledBitmap(this, 100, (100 / aspectRatio).toInt(), false).toBlurhash() - } - - val intArray = IntArray(width * height) - this.getPixels(intArray, 0, width, 0, 0, width, height) - - val numX = - if (aspectRatio > 1) { - 9 - } else if (aspectRatio < 1) { - (9 * aspectRatio).roundToInt() - } else { - 4 - } - - val numY = - if (aspectRatio > 1) { - (9 * (1 / aspectRatio)).roundToInt() - } else if (aspectRatio < 1) { - 9 - } else { - 4 - } - - return BlurHashEncoder().encode( - intArray, - width, - height, - numX, - numY, - ) -} +/** + * Encodes this Android Bitmap to a blurhash string. + * Delegates to PlatformImage.toBlurhash() for the actual encoding. + */ +fun Bitmap.toBlurhash(): String = this.toPlatformImage().toBlurhash() diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.android.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.android.kt new file mode 100644 index 000000000..b864da631 --- /dev/null +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.android.kt @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.blurhash + +import android.graphics.Bitmap + +actual class PlatformImage( + val bitmap: Bitmap, +) { + actual val width: Int get() = bitmap.width + actual val height: Int get() = bitmap.height + + actual fun getPixels( + pixels: IntArray, + offset: Int, + stride: Int, + x: Int, + y: Int, + width: Int, + height: Int, + ) { + bitmap.getPixels(pixels, offset, stride, x, y, width, height) + } + + actual fun scale( + width: Int, + height: Int, + ): PlatformImage = PlatformImage(Bitmap.createScaledBitmap(bitmap, width, height, false)) + + actual companion object { + actual fun create( + pixels: IntArray, + width: Int, + height: Int, + ): PlatformImage = PlatformImage(Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)) + } +} + +/** + * Extension to convert Android Bitmap to PlatformImage. + */ +fun Bitmap.toPlatformImage(): PlatformImage = PlatformImage(this) + +/** + * Extension to get the underlying Android Bitmap. + */ +fun PlatformImage.toAndroidBitmap(): Bitmap = this.bitmap diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/Base83.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/Base83.kt similarity index 100% rename from commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/Base83.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/Base83.kt diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt similarity index 97% rename from commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt index baf540fd3..140f3c86c 100644 --- a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashDecoder.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.commons.blurhash -import android.graphics.Bitmap import com.vitorpamplona.amethyst.commons.blurhash.SRGB.Companion.linearToSrgb import com.vitorpamplona.amethyst.commons.blurhash.SRGB.Companion.srgbToLinear import kotlin.math.pow @@ -66,7 +65,7 @@ object BlurHashDecoder { } /** - * Decode a blur hash into a new bitmap. + * Decode a blur hash into a new PlatformImage. * * @param useCache use in memory cache for the calculated math, reused by images with same size. * if the cache does not exist yet it will be created and populated with new calculations. By @@ -76,7 +75,7 @@ object BlurHashDecoder { blurHash: String?, width: Int, useCache: Boolean = true, - ): Bitmap? { + ): PlatformImage? { if (blurHash == null || blurHash.length < 6) { return null } @@ -87,7 +86,7 @@ object BlurHashDecoder { val height = (width * (1 / (numCompX.toFloat() / numCompY.toFloat()))).roundToInt() val colors = computeColors(numCompX, numCompY, blurHash) val imageArray = composeImageArray(width, height, numCompX, numCompY, colors, useCache) - return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888) + return PlatformImage.create(imageArray, width, height) } private fun decodeDc(colorEnc: Int): FloatArray { diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoder.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoder.kt similarity index 100% rename from commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoder.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoder.kt diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoderExt.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoderExt.kt new file mode 100644 index 000000000..f476afb6a --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BlurHashEncoderExt.kt @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.blurhash + +import kotlin.math.roundToInt + +/** + * Encodes this PlatformImage to a blurhash string. + * The image will be scaled down if larger than 100x100 for performance. + */ +fun PlatformImage.toBlurhash(): String { + val aspectRatio = this.width.toFloat() / this.height.toFloat() + + if (this.width > 100 && this.height > 100) { + return this.scale(100, (100 / aspectRatio).toInt()).toBlurhash() + } + + val intArray = IntArray(width * height) + this.getPixels(intArray, 0, width, 0, 0, width, height) + + val numX = + if (aspectRatio > 1) { + 9 + } else if (aspectRatio < 1) { + (9 * aspectRatio).roundToInt() + } else { + 4 + } + + val numY = + if (aspectRatio > 1) { + (9 * (1 / aspectRatio)).roundToInt() + } else if (aspectRatio < 1) { + 9 + } else { + 4 + } + + return BlurHashEncoder().encode( + intArray, + width, + height, + numX, + numY, + ) +} diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt similarity index 96% rename from commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt index 7248cf5a1..a3d609915 100644 --- a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/CosineCache.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.commons.blurhash import androidx.collection.LruCache +import kotlin.math.PI import kotlin.math.cos object CosineCache { @@ -54,7 +55,7 @@ object CosineCache { DoubleArray(height * numCompY) { val y = it / numCompY val j = it % numCompY - cos(Math.PI * y * j / height) + cos(PI * y * j / height) }.also { cacheCosinesY.put(height * numCompY, it) } @@ -73,7 +74,7 @@ object CosineCache { DoubleArray(width * numCompX) { val x = it / numCompX val i = it % numCompX - cos(Math.PI * x * i / width) + cos(PI * x * i / width) }.also { cacheCosinesX.put(width * numCompX, it) } } else -> cacheCosinesX[width * numCompX]!! diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.kt new file mode 100644 index 000000000..2e745ca8e --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.kt @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.blurhash + +/** + * Platform-agnostic image wrapper for blurhash encoding/decoding. + * + * On Android: wraps android.graphics.Bitmap + * On Desktop JVM: wraps java.awt.image.BufferedImage + */ +expect class PlatformImage { + val width: Int + val height: Int + + /** + * Read ARGB pixels into the provided array. + * Pixels are in ARGB format (0xAARRGGBB). + */ + fun getPixels( + pixels: IntArray, + offset: Int, + stride: Int, + x: Int, + y: Int, + width: Int, + height: Int, + ) + + /** + * Create a scaled copy of this image. + */ + fun scale( + width: Int, + height: Int, + ): PlatformImage + + companion object { + /** + * Create a new image from ARGB pixel array. + */ + fun create( + pixels: IntArray, + width: Int, + height: Int, + ): PlatformImage + } +} diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/SRGB.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/SRGB.kt similarity index 100% rename from commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/SRGB.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/SRGB.kt diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImagePlatform.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImagePlatform.jvm.kt new file mode 100644 index 000000000..6c523a33e --- /dev/null +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/base64Image/Base64ImagePlatform.jvm.kt @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.base64Image + +import com.vitorpamplona.amethyst.commons.blurhash.PlatformImage +import com.vitorpamplona.amethyst.commons.blurhash.toPlatformImage +import java.io.ByteArrayInputStream +import java.util.Base64 +import javax.imageio.ImageIO + +/** + * Converts a base64 image data URI to a PlatformImage (BufferedImage wrapper). + */ +fun Base64Image.toPlatformImage(content: String): PlatformImage { + val matcher = pattern.matcher(content) + + if (matcher.find()) { + val base64String = matcher.group(2) + val byteArray = Base64.getDecoder().decode(base64String) + val bufferedImage = + ImageIO.read(ByteArrayInputStream(byteArray)) + ?: throw Exception("Unable to decode base64 image: $content") + return bufferedImage.toPlatformImage() + } + + throw Exception("Unable to convert base64 to image $content") +} diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.jvm.kt new file mode 100644 index 000000000..7cb20fc93 --- /dev/null +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/BitmapUtils.jvm.kt @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.blurhash + +import java.awt.image.BufferedImage + +/** + * Encodes this BufferedImage to a blurhash string. + * Delegates to PlatformImage.toBlurhash() for the actual encoding. + */ +fun BufferedImage.toBlurhash(): String = this.toPlatformImage().toBlurhash() diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.jvm.kt new file mode 100644 index 000000000..8d4c990f6 --- /dev/null +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/blurhash/PlatformImage.jvm.kt @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.blurhash + +import java.awt.Image +import java.awt.image.BufferedImage + +actual class PlatformImage( + val image: BufferedImage, +) { + actual val width: Int get() = image.width + actual val height: Int get() = image.height + + actual fun getPixels( + pixels: IntArray, + offset: Int, + stride: Int, + x: Int, + y: Int, + width: Int, + height: Int, + ) { + image.getRGB(x, y, width, height, pixels, offset, stride) + } + + actual fun scale( + width: Int, + height: Int, + ): PlatformImage { + val scaled = image.getScaledInstance(width, height, Image.SCALE_FAST) + val buffered = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + buffered.graphics.drawImage(scaled, 0, 0, null) + return PlatformImage(buffered) + } + + actual companion object { + actual fun create( + pixels: IntArray, + width: Int, + height: Int, + ): PlatformImage { + val image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + image.setRGB(0, 0, width, height, pixels, 0, width) + return PlatformImage(image) + } + } +} + +/** + * Extension to convert BufferedImage to PlatformImage. + */ +fun BufferedImage.toPlatformImage(): PlatformImage = PlatformImage(this) + +/** + * Extension to get the underlying BufferedImage. + */ +fun PlatformImage.toBufferedImage(): BufferedImage = this.image