Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/compilation-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
java-version: 17
- name: Check build
run: ./gradlew detektWithoutTests build publishToMavenLocal
- name: Check unit tests
run: ./gradlew test
- name: Install pods
run: cd sample/ios-app && pod install
if: matrix.os == 'macOS-latest'
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ allprojects {
project build.gradle
```groovy
dependencies {
commonMainApi("dev.icerock.moko:graphics:0.9.0")
commonMainApi("dev.icerock.moko:graphics:0.10.1")
}
```

Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[versions]
androidAppCompatVersion = "1.6.1"
androidAnnotationVersion = "1.8.0"
mokoGraphicsVersion = "0.10.0"
mokoGraphicsVersion = "0.10.1"

[libraries]
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
annotation = { module = "androidx.annotation:annotation", version.ref = "androidAnnotationVersion" }
mokoGraphics = { module = "dev.icerock.moko:graphics", version.ref = "mokoGraphicsVersion" }

kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test" }
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,67 @@

package dev.icerock.moko.graphics

/**
* Parses a hexadecimal color string into a [Color] object.
*
* This function supports multiple hex color formats with optional hash prefix:
* - **3 digits (RGB)**: Each digit is expanded to two digits (e.g., "F0A" → "FF00AA")
* - **4 digits (ARGB)**: Each digit is expanded to two digits (e.g., "8F0A" → "88FF00AA")
* - **6 digits (RRGGBB)**: Standard RGB format with full alpha (255)
* - **8 digits (AARRGGBB)**: Full ARGB format with explicit alpha channel
*
* The hash prefix (#) is optional and will be automatically removed if present.
* All input is converted to uppercase for consistent parsing.
*
* @param colorHEX
* The hexadecimal color string to parse. Can include optional '#' prefix.
* Supports formats: RGB, ARGB, RRGGBB, AARRGGBB (case-insensitive).
*
* @return A [Color] object with ARGB values in the range 0-255.
*
* @throws IllegalArgumentException
* if the input string is not a valid hex color format or contains invalid hexadecimal characters.
*
*/
@Suppress("MagicNumber")
fun Color.Companion.parseColor(colorHEX: String): Color {
require(colorHEX[0] != '#') { "Unknown color" }
public fun Color.Companion.parseColor(colorHEX: String): Color {
val clean = colorHEX.removePrefix("#").uppercase()

var colorARGB = colorHEX.substring(1).toLong(16)
if (colorHEX.length == 7) {
colorARGB = colorARGB or 0x00000000ff000000
} else {
require(colorHEX.length != 9) { "Unknown color" }
}
return when (clean.length) {
3 -> {
// RGB -> RRGGBB
val r = clean[0].digitToInt(16) * 17
val g = clean[1].digitToInt(16) * 17
val b = clean[2].digitToInt(16) * 17
Color(red = r, green = g, blue = b, alpha = 255)
}

4 -> {
// ARGB
val a = clean[0].digitToInt(radix = 16) * 17
val r = clean[1].digitToInt(16) * 17
val g = clean[2].digitToInt(16) * 17
val b = clean[3].digitToInt(16) * 17
Color(alpha = a, red = r, green = g, blue = b)
}

return Color(
alpha = (colorARGB.shr(24) and 0xFF).toInt(),
red = (colorARGB.shr(16) and 0xFF).toInt(),
green = (colorARGB.shr(8) and 0xFF).toInt(),
blue = (colorARGB.shr(0) and 0xFF).toInt(),
)
6 -> {
// RRGGBB
val r = clean.substring(0, 2).toInt(16)
val g = clean.substring(2, 4).toInt(16)
val b = clean.substring(4, 6).toInt(16)
Color(red = r, green = g, blue = b, alpha = 255)
}

8 -> {
// AARRGGBB
val a = clean.substring(0, 2).toInt(16)
val r = clean.substring(2, 4).toInt(16)
val g = clean.substring(4, 6).toInt(16)
val b = clean.substring(6, 8).toInt(16)
Color(alpha = a, red = r, green = g, blue = b)
}

else -> throw IllegalArgumentException("Invalid Hex color: $colorHEX")
}
}
2 changes: 2 additions & 0 deletions sample/mpp-library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ kotlin {

dependencies {
commonMainApi(projects.graphics)

commonTestImplementation(libs.kotlin.test)
}

android {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2025 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package com.icerockdev.library

import dev.icerock.moko.graphics.Color
import dev.icerock.moko.graphics.parseColor
import kotlin.test.Test
import kotlin.test.assertEquals

class GraphicsTests {

@Test
fun parseWithoutAlphaTest() {
val colorRGB = Color.parseColor("#88AAFF")
val colorFull = Color.parseColor("#FF88AAFF")
val colorRGBShort = Color.parseColor("#8AF")
val colorAlphaShort = Color.parseColor("#F8AF")
assertEquals(
expected = colorRGB.argb,
actual = 0xFF88AAFF
)
assertEquals(
expected = colorFull.argb,
actual = 0xFF88AAFF
)
assertEquals(
expected = colorRGBShort.argb,
actual = 0xFF88AAFF
)
assertEquals(
expected = colorAlphaShort.argb,
actual = 0xFF88AAFF
)
}

@Test
fun parseWithAlphaTest() {
val colorFull = Color.parseColor("#7788AAFF")
val colorAlphaShort = Color.parseColor("#78AF")
assertEquals(
expected = colorAlphaShort.argb,
actual = 0x7788AAFF
)
assertEquals(
expected = colorFull.argb,
actual = 0x7788AAFF
)
}

@Test
fun parseWithoutPrefixTest() {
val colorRGB = Color.parseColor("88AAFF")
val colorFull = Color.parseColor("FF88AAFF")
val colorRGBShort = Color.parseColor("8AF")
val colorAlphaShort = Color.parseColor("F8AF")
assertEquals(
expected = colorRGB.argb,
actual = 0xFF88AAFF
)
assertEquals(
expected = colorFull.argb,
actual = 0xFF88AAFF
)
assertEquals(
expected = colorRGBShort.argb,
actual = 0xFF88AAFF
)
assertEquals(
expected = colorAlphaShort.argb,
actual = 0xFF88AAFF
)
}
}