Overview of kmPalette
mainandroidx.palette library. It can be used standalone for pure Kotlin projects or in combination with MaterialKolor to create dynamic Material themes based on image colors.repository·main·Indexed 43 days ago
https://github.com/jordond/kmpaletteA Kotlin Multiplatform library for generating color palettes from images, ported from the AndroidX Palette library. It supports Android, iOS, Desktop, JS, and WASM. The library provides a dependency-free core for raw ARGB pixel data and optional extensions for Compose Multiplatform, including loaders for Base64 strings, network URLs via Ktor, and files via FileKit.
androidx.palette library. It can be used standalone for pure Kotlin projects or in combination with MaterialKolor to create dynamic Material themes based on image colors.The library is layered to allow you to include only the dependencies you need:
androidx-palette: Pure Kotlin palette generation. No Compose dependency. Works on raw IntArray pixels.kmpalette-loader: Defines the ImageBitmapLoader<T> interface. Depends only on Compose UI graphics.core: Provides Compose state objects, Palette/Swatch extensions, and built-in loaders. It exposes androidx-palette and kmpalette-loader via api.extensions-*: Optional loaders and composables for specific image sources (e.g., Base64, Network, File). These depend on core via implementation, so you must declare core explicitly if you use an extension.kmpalette 4.0 introduces significant architectural changes to improve portability and reduce dependencies:
androidx-palette module no longer depends on Compose. It operates on raw IntArray pixel data.Rect-based region selection with plain Int coordinates.defaultColor and defaultOnColor.Asynchronous palette operations report their status through the PaletteResult sealed interface. This allows you to handle loading, success, and error states in your UI.
Success(val palette: Palette): Contains the generated palette.Loading: Indicates the operation is in progress.Error(val cause: Exception): Contains the exception if generation failed.paletteOrNull: A convenience property to get the Palette if the result is Success, otherwise null.To use Base64Loader for loading base64 encoded images into an ImageBitmap, add the following dependencies to your build.gradle.kts file:
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.kmpalette.core)
implementation(libs.kmpalette.extensions.base64)
}
}
}The Maven group has changed from com.kmpalette to com.materialkolor.palette. Update your libs.versions.toml or build.gradle.kts using the following mapping:
| Module | Old Coordinate | New Coordinate |
|---|---|---|
| Core | com.kmpalette:kmpalette-core | com.materialkolor.palette:core |
| Base64 extension | com.kmpalette:extensions-base64 | com.materialkolor.palette:extensions-base64 |
| Network extension | com.kmpalette:extensions-network | com.materialkolor.palette:extensions-network |
| File extension | com.kmpalette:extensions-file | com.materialkolor.palette:extensions-file |
| Palette (no Compose) | com.kmpalette:androidx-palette | com.materialkolor.palette:androidx-palette |
| Loader interface | com.kmpalette:kmpalette-bitmap-loader | com.materialkolor.palette:loader |
| Resources extension | com.kmpalette:extensions-resources | Removed, now part of core |
| Libres extension | com.kmpalette:extensions-libres | Removed, no replacement |
| ByteArray extension | com.kmpalette:extensions-bytearray | Removed, now part of core |
// Example: Updating Core dependency
implementation("com.materialkolor.palette:core:4.x.x")You usually do not need to declare this dependency explicitly because both core and extensions-* artifacts expose it via api.
Add it explicitly only if you are writing a custom loader in a module that does not depend on core or any extension modules.
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.materialkolor.palette:kmpalette-loader:<version>")
}
}
}The androidx-palette module is Compose-free and operates on raw ARGB pixel data. You must provide an IntArray of pixels along with the source dimensions (width and height).
val palette = Palette
.from(pixels, width, height)
.maximumColorCount(16)
.generate()
val vibrant = palette.vibrantSwatch
val dominant = palette.dominantSwatchYou can also construct a Palette directly from a collection of existing swatches:
val palette = Palette.from(swatches)val palette = Palette
.from(pixels, width, height)
.maximumColorCount(16)
.generate()
val vibrant = palette.vibrantSwatch
val dominant = palette.dominantSwatchFor cross-platform support (Android, iOS, macOS (Apple Silicon), Desktop, JS, WASM), use PlatformFile from FileKit along with the provided composables.
Use rememberPlatformFileDominantColorState to extract a single dominant color. You must provide defaultColor and defaultOnColor which are used until generation succeeds or if it fails.
Use rememberPlatformFilePaletteState to generate a full palette. You can access swatches like vibrantSwatch from the palette property.
Monitor paletteState.state to handle the loading lifecycle and errors. The state can be null (nothing generated yet), PaletteResult.Loading, PaletteResult.Error (contains the cause), or PaletteResult.Success (contains the palette).
@Composable
fun MyComposable(file: PlatformFile) {
val paletteState = rememberPlatformFilePaletteState()
LaunchedEffect(file) {
paletteState.generate(file)
}
val vibrant = paletteState.palette?.vibrantSwatch
when (val state = paletteState.state) {
null -> Unit // nothing generated yet
PaletteResult.Loading -> CircularProgressIndicator()
is PaletteResult.Error -> Text("Failed to read file: ${state.cause.message}")
is PaletteResult.Success -> PaletteDisplay(state.palette)
}
}The library provides specialized composables for different input types. Each input type has a corresponding pair of Dominant Color and Palette composables.
| Input | Dominant color | Palette |
|---|---|---|
ImageBitmap | rememberDominantColorState() | rememberPaletteState() |
Painter | rememberPainterDominantColorState()¹ | rememberPainterPaletteState()¹ |
DrawableResource | rememberResourceDominantColorState() | rememberResourcePaletteState() |
@DrawableRes Int | rememberDrawableDominantColorState() | rememberDrawablePaletteState() |
String (Base64) | rememberBase64DominantColorState() | rememberBase64PaletteState() |
Url | rememberNetworkDominantColorState() | rememberNetworkPaletteState() |
PlatformFile | rememberPlatformFileDominantColorState() | rememberPlatformFilePaletteState() |
java.io.File | rememberFileDominantColorState() | rememberFilePaletteState() |
¹ In com.kmpalette.extensions.painter.
Example: Using Network URL
@Composable
fun SomeComposable(url: Url) {
val dominantColorState = rememberNetworkDominantColorState(
defaultColor = MaterialTheme.colorScheme.primary,
defaultOnColor = MaterialTheme.colorScheme.onPrimary,
)
LaunchedEffect(url) {
dominantColorState.updateFrom(url)
}
Box(
modifier = Modifier
.width(200.dp)
.height(100.dp)
.background(dominantColorState.color)
) {
Text("Some Text", color = dominantColorState.onColor)
}
}Version 4.0 introduces dedicated composable functions for different loader types, removing the need to manually create and pass loaders.
Note: Every dominant-color composable requires defaultColor and defaultOnColor parameters. Palette-state equivalents do not require these parameters.
Use rememberPainterPaletteState() and rememberPainterDominantColorState(). These are located in com.kmpalette.extensions.painter.
Use rememberResourcePaletteState() and rememberResourceDominantColorState(). These are located in com.kmpalette.extensions.resource.
Use rememberBase64PaletteState() and rememberBase64DominantColorState().
Use rememberNetworkPaletteState() and rememberNetworkDominantColorState(). The latter allows passing a custom httpClient.
// Painter Example
val paletteState = rememberPainterPaletteState()
LaunchedEffect(painter) {
paletteState.generate(painter)
}
// Resource Example
val paletteState = rememberResourcePaletteState()
LaunchedEffect(Res.drawable.my_image) {
paletteState.generate(Res.drawable.my_image)
}
// Network Example
val dominantColorState = rememberNetworkDominantColorState(
defaultColor = Color.Black,
defaultOnColor = Color.White,
httpClient = myHttpClient
)
LaunchedEffect(url) {
dominantColorState.updateFrom(url)
}To extract a single dominant color and a contrasting text color (onColor), use rememberDominantColorState (for ImageBitmap) or rememberPainterDominantColorState (for Painter).
Important: These composables require defaultColor and defaultOnColor. These values are displayed while the generation is loading or if it fails. The library does not provide defaults to avoid making assumptions about your design system.
To filter which color is chosen as dominant, use the isSwatchValid lambda. Swatches are checked in descending population order; the first one satisfying the predicate is used.
@Composable
fun SomeComposable(bitmap: ImageBitmap) {
val dominantColorState = rememberDominantColorState(
defaultColor = Color.Black,
defaultOnColor = Color.White,
isSwatchValid = { swatch -> swatch.population > 100 },
)
LaunchedEffect(bitmap) {
dominantColorState.updateFrom(bitmap)
}
Box(
modifier = Modifier
.width(200.dp)
.height(100.dp)
.background(dominantColorState.color)
) {
Text("Some Text", color = dominantColorState.onColor)
}
}