kmPalette

repository·main·Indexed 43 days ago

https://github.com/jordond/kmpalette

A 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.

Tokens
15.4K
Snippets
42
Records
56
Agent score
55%

What's inside kmPalette

  1. Overview of kmPalette

    main
    kmPalette is a Kotlin Multiplatform library used to generate color palettes from images, including the dominant color. It is a port of the 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.
  2. Understand the kmPalette architecture and dependency layers

    main

    The library is layered to allow you to include only the dependencies you need:

    1. androidx-palette: Pure Kotlin palette generation. No Compose dependency. Works on raw IntArray pixels.
    2. kmpalette-loader: Defines the ImageBitmapLoader<T> interface. Depends only on Compose UI graphics.
    3. core: Provides Compose state objects, Palette/Swatch extensions, and built-in loaders. It exposes androidx-palette and kmpalette-loader via api.
    4. 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.
  3. Understand the core changes in kmpalette 4.0

    main

    kmpalette 4.0 introduces significant architectural changes to improve portability and reduce dependencies:

    • Decoupling from Compose: The androidx-palette module no longer depends on Compose. It operates on raw IntArray pixel data.
    • Simplified Region Selection: The library has replaced Rect-based region selection with plain Int coordinates.
    • Pure-Kotlin Scaling: Platform-specific bitmap scaling has been replaced with a pure-Kotlin nearest-neighbour implementation.
    • Material 3 Independence: The library no longer depends on Material 3; users must provide defaultColor and defaultOnColor.
  4. Handle Palette Generation Results with PaletteResult

    main

    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.
  5. Install the Base64 Extensions

    main

    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)
            }
        }
    }
  6. Update Maven coordinates for kmpalette 4.0

    main

    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:

    ModuleOld CoordinateNew Coordinate
    Corecom.kmpalette:kmpalette-corecom.materialkolor.palette:core
    Base64 extensioncom.kmpalette:extensions-base64com.materialkolor.palette:extensions-base64
    Network extensioncom.kmpalette:extensions-networkcom.materialkolor.palette:extensions-network
    File extensioncom.kmpalette:extensions-filecom.materialkolor.palette:extensions-file
    Palette (no Compose)com.kmpalette:androidx-palettecom.materialkolor.palette:androidx-palette
    Loader interfacecom.kmpalette:kmpalette-bitmap-loadercom.materialkolor.palette:loader
    Resources extensioncom.kmpalette:extensions-resourcesRemoved, now part of core
    Libres extensioncom.kmpalette:extensions-libresRemoved, no replacement
    ByteArray extensioncom.kmpalette:extensions-bytearrayRemoved, now part of core
    // Example: Updating Core dependency
    implementation("com.materialkolor.palette:core:4.x.x")
  7. Add kmpalette-loader dependency

    main

    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>")
            }
        }
    }
  8. Generate a Palette from pixel data

    main

    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.dominantSwatch

    You 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.dominantSwatch
  9. Use PlatformFile for image loading (All Platforms)

    main

    For cross-platform support (Android, iOS, macOS (Apple Silicon), Desktop, JS, WASM), use PlatformFile from FileKit along with the provided composables.

    Dominant Color

    Use rememberPlatformFileDominantColorState to extract a single dominant color. You must provide defaultColor and defaultOnColor which are used until generation succeeds or if it fails.

    Palette Generation

    Use rememberPlatformFilePaletteState to generate a full palette. You can access swatches like vibrantSwatch from the palette property.

    Error Handling

    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)
        }
    }
  10. Generate Palette from Various Input Types

    main

    The library provides specialized composables for different input types. Each input type has a corresponding pair of Dominant Color and Palette composables.

    InputDominant colorPalette
    ImageBitmaprememberDominantColorState()rememberPaletteState()
    PainterrememberPainterDominantColorState()¹rememberPainterPaletteState()¹
    DrawableResourcerememberResourceDominantColorState()rememberResourcePaletteState()
    @DrawableRes IntrememberDrawableDominantColorState()rememberDrawablePaletteState()
    String (Base64)rememberBase64DominantColorState()rememberBase64PaletteState()
    UrlrememberNetworkDominantColorState()rememberNetworkPaletteState()
    PlatformFilerememberPlatformFileDominantColorState()rememberPlatformFilePaletteState()
    java.io.FilerememberFileDominantColorState()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)
        }
    }
  11. Use the new simplified Composable APIs in 4.0

    main

    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.

    Painter (Core)

    Use rememberPainterPaletteState() and rememberPainterDominantColorState(). These are located in com.kmpalette.extensions.painter.

    Compose Multiplatform Resources

    Use rememberResourcePaletteState() and rememberResourceDominantColorState(). These are located in com.kmpalette.extensions.resource.

    Base64

    Use rememberBase64PaletteState() and rememberBase64DominantColorState().

    Network

    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)
    }
  12. Generate a Dominant Color in Compose

    main

    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)
        }
    }