From be5d2ee7152e03541b6144fd91c0241b6feb23d0 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 4 Jan 2024 18:14:09 +0600 Subject: [PATCH 1/4] start 0.20.25 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d200d8902..2136dc234e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.20.25 + ## 0.20.24 **Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`** diff --git a/gradle.properties b/gradle.properties index d93d99cf483..8dc3c326a3a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.20.24 -android_code_version=230 +version=0.20.25 +android_code_version=231 From ff59b0cc9ce3ff2736a56fe808b0da304a924027 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 4 Jan 2024 19:53:43 +0600 Subject: [PATCH 2/4] add colors module --- CHANGELOG.md | 4 + colors/common/build.gradle | 40 +++++ .../common/src/commonMain/kotlin/HEXAColor.kt | 138 ++++++++++++++++ .../src/commonTest/kotlin/HexColorTests.kt | 150 ++++++++++++++++++ settings.gradle | 1 + 5 files changed, 333 insertions(+) create mode 100644 colors/common/build.gradle create mode 100644 colors/common/src/commonMain/kotlin/HEXAColor.kt create mode 100644 colors/common/src/commonTest/kotlin/HexColorTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2136dc234e2..93a9d687b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.20.25 +* `Colors`: + * `Common`: + * Module inited + ## 0.20.24 **Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`** diff --git a/colors/common/build.gradle b/colors/common/build.gradle new file mode 100644 index 00000000000..ba6963d2308 --- /dev/null +++ b/colors/common/build.gradle @@ -0,0 +1,40 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" + +kotlin { + sourceSets { + jvmMain { + dependencies { + api project(":micro_utils.coroutines") + } + } + androidMain { + dependencies { + api project(":micro_utils.coroutines") + api libs.android.fragment + } + dependsOn jvmMain + } + + linuxX64Main { + dependencies { + api libs.okio + } + } + mingwX64Main { + dependencies { + api libs.okio + } + } + linuxArm64Main { + dependencies { + api libs.okio + } + } + } +} diff --git a/colors/common/src/commonMain/kotlin/HEXAColor.kt b/colors/common/src/commonMain/kotlin/HEXAColor.kt new file mode 100644 index 00000000000..e145e2f16b2 --- /dev/null +++ b/colors/common/src/commonMain/kotlin/HEXAColor.kt @@ -0,0 +1,138 @@ +package dev.inmo.micro_utils.colors.common + +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline +import kotlin.math.floor + +/** + * Wrapper for RGBA colors. Receiving [UInt] in main constructor. Each part in main constructor + * configured with `0x00 - 0xff` range. Examples: + * + * * Red: `0xff0000ffu` + * * Red (0.5 capacity): `0xff000088u` + * + * Anyway it is recommended to use + */ +@Serializable +@JvmInline +value class HEXAColor ( + val uint: UInt +) : Comparable { + val hexa: String + get() = "#${uint.toString(16).padStart(8, '0')}" + val hex: String + get() = hexa.take(7) + val shortHex: String + get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}" + val shortHexa: String + get() = "$shortHex${a.shortPart()}" + val rgbInt: Int + get() = (uint shr 2).toInt() + val alphaOfOne: Float + get() = (uint and 0xffu).toFloat() / 256f + + val r: Int + get() = ((uint and 0xff000000u) / 0x1000000u).toInt() + val g: Int + get() = ((uint and 0x00ff0000u) / 0x10000u).toInt() + val b: Int + get() = ((uint and 0x0000ff00u) / 0x100u).toInt() + val a: Int + get() = ((uint and 0x000000ffu)).toInt() + val aOfOne: Float + get() = a.toFloat() / (0xff) + init { + require(uint in 0u ..0xffffffffu) + } + + constructor(r: Int, g: Int, b: Int, a: Int) : this( + ((r * 0x1000000).toLong() + g * 0x10000 + b * 0x100 + a).toUInt() + ) { + require(r in 0 ..0xff) + require(g in 0 ..0xff) + require(b in 0 ..0xff) + require(a in 0 ..0xff) + } + + constructor(r: Int, g: Int, b: Int, aOfOne: Float = 1f) : this( + r = r, g = g, b = b, a = (aOfOne * 0xff).toInt() + ) + + override fun toString(): String { + return hexa + } + + override fun compareTo(other: HEXAColor): Int = (uint - other.uint).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt() + + fun copy( + r: Int = this.r, + g: Int = this.g, + b: Int = this.b, + aOfOne: Float = this.aOfOne + ) = HEXAColor(r, g, b, aOfOne) + fun copy( + r: Int = this.r, + g: Int = this.g, + b: Int = this.b, + a: Int + ) = HEXAColor(r, g, b, a) + + companion object { + /** + * Parsing color from [color] + * + * Supported formats samples (on Red color based): + * + * * `#f00` + * * `#f00f` + * * `#ff0000` + * * `#ff0000ff` + * * `rgb(255, 0, 0)` + * * `rgba(255, 0, 0, 1)` + */ + fun parseStringColor(color: String): HEXAColor = when { + color.startsWith("#") -> color.removePrefix("#").let { color -> + when (color.length) { + 3 -> color.map { "$it$it" }.joinToString(separator = "", postfix = "ff") + 4 -> color.take(3).map { "$it$it" }.joinToString(separator = "", postfix = color.takeLast(1).let { "${it}0" }) + 6 -> "${color}ff" + 8 -> color + else -> error("Malfurmed color string: $color. It is expected that color started with # will contains 3, 6 or 8 valuable parts") + } + } + color.startsWith("rgb(") -> color + .removePrefix("rgb(") + .removeSuffix(")") + .replace(Regex("\\s"), "") + .split(",") + .map { it.toInt().toString(16) } + .joinToString("", postfix = "ff") + color.startsWith("rgba(") -> color + .removePrefix("rgba(") + .removeSuffix(")") + .replace(Regex("\\s"), "") + .split(",").let { + it.take(3).map { it.toInt().toString(16) } + (it.last().toFloat() * 0xff).toInt().toString(16) + } + .joinToString("") + else -> color + }.lowercase().toUInt(16).let(::HEXAColor) + + /** + * Parsing color from [color] + * + * Supported formats samples (on Red color based): + * + * * `#f00` + * * `#ff0000` + * * `#ff0000ff` + * * `rgb(255, 0, 0)` + * * `rgba(255, 0, 0, 1)` + */ + operator fun invoke(color: String) = parseStringColor(color) + + private fun Int.shortPart(): String { + return (floor(toFloat() / 16)).toInt().toString(16) + } + } +} diff --git a/colors/common/src/commonTest/kotlin/HexColorTests.kt b/colors/common/src/commonTest/kotlin/HexColorTests.kt new file mode 100644 index 00000000000..3c3cf668c9c --- /dev/null +++ b/colors/common/src/commonTest/kotlin/HexColorTests.kt @@ -0,0 +1,150 @@ +package dev.inmo.micro_utils.colors.common + +import kotlin.math.floor +import kotlin.math.round +import kotlin.test.Test +import kotlin.test.assertEquals + +class HexColorTests { + class TestColor( + val color: HEXAColor, + val shortHex: String?, + val shortHexa: String?, + val hex: String, + val hexa: String, + val r: Int, + val g: Int, + val b: Int, + val a: Int, + ) + val testColors: List + get() = listOf( + TestColor( + color = HEXAColor(uint = 0xff0000ffu), + shortHex = "#f00", + shortHexa = "#f00f", + hex = "#ff0000", + hexa = "#ff0000ff", + r = 0xff, + g = 0x00, + b = 0x00, + a = 0xff, + ), + TestColor( + color = HEXAColor(uint = 0x00ff00ffu), + shortHex = "#0f0", + shortHexa = "#0f0f", + hex = "#00ff00", + hexa = "#00ff00ff", + r = 0x00, + g = 0xff, + b = 0x00, + a = 0xff, + ), + TestColor( + color = HEXAColor(0x0000ffffu), + shortHex = "#00f", + shortHexa = "#00ff", + hex = "#0000ff", + hexa = "#0000ffff", + r = 0x00, + g = 0x00, + b = 0xff, + a = 0xff, + ), + TestColor( + color = HEXAColor(0xff000088u), + shortHex = "#f00", + shortHexa = "#f008", + hex = "#ff0000", + hexa = "#ff000088", + r = 0xff, + g = 0x00, + b = 0x00, + a = 0x88, + ), + TestColor( + color = HEXAColor(0x00ff0088u), + shortHex = "#0f0", + shortHexa = "#0f08", + hex = "#00ff00", + hexa = "#00ff0088", + r = 0x00, + g = 0xff, + b = 0x00, + a = 0x88, + ), + TestColor( + color = HEXAColor(0x0000ff88u), + shortHex = "#00f", + shortHexa = "#00f8", + hex = "#0000ff", + hexa = "#0000ff88", + r = 0x00, + g = 0x00, + b = 0xff, + a = 0x88, + ), + TestColor( + color = HEXAColor(0xff000022u), + shortHex = "#f00", + shortHexa = "#f002", + hex = "#ff0000", + hexa = "#ff000022", + r = 0xff, + g = 0x00, + b = 0x00, + a = 0x22, + ), + TestColor( + HEXAColor(0x00ff0022u), + "#0f0", + "#0f02", + "#00ff00", + "#00ff0022", + 0x00, + 0xff, + 0x00, + 0x22, + ), + TestColor( + color = HEXAColor(0x0000ff22u), + shortHex = "#00f", + shortHexa = "#00f2", + hex = "#0000ff", + hexa = "#0000ff22", + r = 0x00, + g = 0x00, + b = 0xff, + a = 0x22, + ), + ) + + @Test + fun baseTest() { + testColors.forEach { + assertEquals(it.hex, it.color.hex) + assertEquals(it.hexa, it.color.hexa) + assertEquals(it.shortHex, it.color.shortHex) + assertEquals(it.shortHexa, it.color.shortHexa) + assertEquals(it.r, it.color.r) + assertEquals(it.g, it.color.g) + assertEquals(it.b, it.color.b) + assertEquals(it.a, it.color.a) + } + } + + @Test + fun testHexParseColor() { + testColors.forEach { + assertEquals(it.color, HEXAColor.parseStringColor(it.hexa)) + assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex)) + it.shortHex ?.let { _ -> + assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex)) + } + it.shortHexa ?.let { _ -> + assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa)) + } + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index d9d6508c329..79702a0bc1e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -42,6 +42,7 @@ String[] includes = [ ":serialization:mapper", ":startup:plugin", ":startup:launcher", + ":colors:common", ":resources", From 303e1e6281993c363012b66e3e3e9b54f238b253 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 4 Jan 2024 20:20:55 +0600 Subject: [PATCH 3/4] add rgb and rgba representations of HEXAColor --- .../common/src/commonMain/kotlin/HEXAColor.kt | 17 +++--- .../src/commonTest/kotlin/HexColorTests.kt | 59 +++++++++++++------ 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/colors/common/src/commonMain/kotlin/HEXAColor.kt b/colors/common/src/commonMain/kotlin/HEXAColor.kt index e145e2f16b2..f40e547121f 100644 --- a/colors/common/src/commonMain/kotlin/HEXAColor.kt +++ b/colors/common/src/commonMain/kotlin/HEXAColor.kt @@ -22,14 +22,16 @@ value class HEXAColor ( get() = "#${uint.toString(16).padStart(8, '0')}" val hex: String get() = hexa.take(7) + val rgba: String + get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})" + val rgb: String + get() = "rgb($r,$g,$b)" val shortHex: String get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}" val shortHexa: String get() = "$shortHex${a.shortPart()}" val rgbInt: Int get() = (uint shr 2).toInt() - val alphaOfOne: Float - get() = (uint and 0xffu).toFloat() / 256f val r: Int get() = ((uint and 0xff000000u) / 0x1000000u).toInt() @@ -69,13 +71,13 @@ value class HEXAColor ( g: Int = this.g, b: Int = this.b, aOfOne: Float = this.aOfOne - ) = HEXAColor(r, g, b, aOfOne) + ) = HEXAColor(r = r, g = g, b = b, aOfOne = aOfOne) fun copy( r: Int = this.r, g: Int = this.g, b: Int = this.b, a: Int - ) = HEXAColor(r, g, b, a) + ) = HEXAColor(r = r, g = g, b = b, a = a) companion object { /** @@ -105,14 +107,15 @@ value class HEXAColor ( .removeSuffix(")") .replace(Regex("\\s"), "") .split(",") - .map { it.toInt().toString(16) } - .joinToString("", postfix = "ff") + .joinToString("", postfix = "ff") { + it.toInt().toString(16).padStart(2, '0') + } color.startsWith("rgba(") -> color .removePrefix("rgba(") .removeSuffix(")") .replace(Regex("\\s"), "") .split(",").let { - it.take(3).map { it.toInt().toString(16) } + (it.last().toFloat() * 0xff).toInt().toString(16) + it.take(3).map { it.toInt().toString(16).padStart(2, '0') } + (it.last().toFloat() * 0xff).toInt().toString(16).padStart(2, '0') } .joinToString("") else -> color diff --git a/colors/common/src/commonTest/kotlin/HexColorTests.kt b/colors/common/src/commonTest/kotlin/HexColorTests.kt index 3c3cf668c9c..ea2a79a4a21 100644 --- a/colors/common/src/commonTest/kotlin/HexColorTests.kt +++ b/colors/common/src/commonTest/kotlin/HexColorTests.kt @@ -1,17 +1,20 @@ package dev.inmo.micro_utils.colors.common import kotlin.math.floor -import kotlin.math.round import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class HexColorTests { + val alphaRgbaPrecision = 5 class TestColor( val color: HEXAColor, - val shortHex: String?, - val shortHexa: String?, + val shortHex: String, + val shortHexa: String, val hex: String, val hexa: String, + val rgb: String, + val rgba: String, val r: Int, val g: Int, val b: Int, @@ -25,6 +28,8 @@ class HexColorTests { shortHexa = "#f00f", hex = "#ff0000", hexa = "#ff0000ff", + rgb = "rgb(255,0,0)", + rgba = "rgba(255,0,0,1.0)", r = 0xff, g = 0x00, b = 0x00, @@ -36,6 +41,8 @@ class HexColorTests { shortHexa = "#0f0f", hex = "#00ff00", hexa = "#00ff00ff", + rgb = "rgb(0,255,0)", + rgba = "rgba(0,255,0,1.0)", r = 0x00, g = 0xff, b = 0x00, @@ -47,6 +54,8 @@ class HexColorTests { shortHexa = "#00ff", hex = "#0000ff", hexa = "#0000ffff", + rgb = "rgb(0,0,255)", + rgba = "rgba(0,0,255,1.0)", r = 0x00, g = 0x00, b = 0xff, @@ -58,6 +67,8 @@ class HexColorTests { shortHexa = "#f008", hex = "#ff0000", hexa = "#ff000088", + rgb = "rgb(255,0,0)", + rgba = "rgba(255,0,0,0.533)", r = 0xff, g = 0x00, b = 0x00, @@ -69,6 +80,8 @@ class HexColorTests { shortHexa = "#0f08", hex = "#00ff00", hexa = "#00ff0088", + rgb = "rgb(0,255,0)", + rgba = "rgba(0,255,0,0.533)", r = 0x00, g = 0xff, b = 0x00, @@ -80,6 +93,8 @@ class HexColorTests { shortHexa = "#00f8", hex = "#0000ff", hexa = "#0000ff88", + rgb = "rgb(0,0,255)", + rgba = "rgba(0,0,255,0.533)", r = 0x00, g = 0x00, b = 0xff, @@ -91,21 +106,25 @@ class HexColorTests { shortHexa = "#f002", hex = "#ff0000", hexa = "#ff000022", + rgb = "rgb(255,0,0)", + rgba = "rgba(255,0,0,0.133)", r = 0xff, g = 0x00, b = 0x00, a = 0x22, ), TestColor( - HEXAColor(0x00ff0022u), - "#0f0", - "#0f02", - "#00ff00", - "#00ff0022", - 0x00, - 0xff, - 0x00, - 0x22, + color = HEXAColor(0x00ff0022u), + shortHex = "#0f0", + shortHexa = "#0f02", + hex = "#00ff00", + hexa = "#00ff0022", + rgb = "rgb(0,255,0)", + rgba = "rgba(0,255,0,0.133)", + r = 0x00, + g = 0xff, + b = 0x00, + a = 0x22, ), TestColor( color = HEXAColor(0x0000ff22u), @@ -113,6 +132,8 @@ class HexColorTests { shortHexa = "#00f2", hex = "#0000ff", hexa = "#0000ff22", + rgb = "rgb(0,0,255)", + rgba = "rgba(0,0,255,0.133)", r = 0x00, g = 0x00, b = 0xff, @@ -127,6 +148,8 @@ class HexColorTests { assertEquals(it.hexa, it.color.hexa) assertEquals(it.shortHex, it.color.shortHex) assertEquals(it.shortHexa, it.color.shortHexa) + assertEquals(it.rgb, it.color.rgb) + assertEquals(it.rgba, it.color.rgba) assertEquals(it.r, it.color.r) assertEquals(it.g, it.color.g) assertEquals(it.b, it.color.b) @@ -137,14 +160,12 @@ class HexColorTests { @Test fun testHexParseColor() { testColors.forEach { - assertEquals(it.color, HEXAColor.parseStringColor(it.hexa)) assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex)) - it.shortHex ?.let { _ -> - assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex)) - } - it.shortHexa ?.let { _ -> - assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa)) - } + assertEquals(it.color, HEXAColor.parseStringColor(it.hexa)) + assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb)) + assertTrue(it.color.uint.toInt() - HEXAColor.parseStringColor(it.rgba).uint.toInt() in -0x1 .. 0x1, ) + assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex)) + assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa)) } } } \ No newline at end of file From 9a0b67f938319a58a501e87a1509fb8ec6b8b392 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 4 Jan 2024 20:30:09 +0600 Subject: [PATCH 4/4] fix of tests --- colors/common/build.gradle | 33 ------------------- .../src/commonTest/kotlin/HexColorTests.kt | 6 +++- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/colors/common/build.gradle b/colors/common/build.gradle index ba6963d2308..d425197852e 100644 --- a/colors/common/build.gradle +++ b/colors/common/build.gradle @@ -5,36 +5,3 @@ plugins { } apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" - -kotlin { - sourceSets { - jvmMain { - dependencies { - api project(":micro_utils.coroutines") - } - } - androidMain { - dependencies { - api project(":micro_utils.coroutines") - api libs.android.fragment - } - dependsOn jvmMain - } - - linuxX64Main { - dependencies { - api libs.okio - } - } - mingwX64Main { - dependencies { - api libs.okio - } - } - linuxArm64Main { - dependencies { - api libs.okio - } - } - } -} diff --git a/colors/common/src/commonTest/kotlin/HexColorTests.kt b/colors/common/src/commonTest/kotlin/HexColorTests.kt index ea2a79a4a21..8d710c82a53 100644 --- a/colors/common/src/commonTest/kotlin/HexColorTests.kt +++ b/colors/common/src/commonTest/kotlin/HexColorTests.kt @@ -19,6 +19,7 @@ class HexColorTests { val g: Int, val b: Int, val a: Int, + vararg val additionalRGBAVariants: String ) val testColors: List get() = listOf( @@ -34,6 +35,7 @@ class HexColorTests { g = 0x00, b = 0x00, a = 0xff, + "rgba(255,0,0,1)", ), TestColor( color = HEXAColor(uint = 0x00ff00ffu), @@ -47,6 +49,7 @@ class HexColorTests { g = 0xff, b = 0x00, a = 0xff, + "rgba(0,255,0,1)" ), TestColor( color = HEXAColor(0x0000ffffu), @@ -60,6 +63,7 @@ class HexColorTests { g = 0x00, b = 0xff, a = 0xff, + "rgba(0,0,255,1)" ), TestColor( color = HEXAColor(0xff000088u), @@ -149,7 +153,7 @@ class HexColorTests { assertEquals(it.shortHex, it.color.shortHex) assertEquals(it.shortHexa, it.color.shortHexa) assertEquals(it.rgb, it.color.rgb) - assertEquals(it.rgba, it.color.rgba) + assertTrue(it.rgba == it.color.rgba || it.color.rgba in it.additionalRGBAVariants) assertEquals(it.r, it.color.r) assertEquals(it.g, it.color.g) assertEquals(it.b, it.color.b)