Skip to content

jordond/kmpalette

Repository files navigation

logo


Maven Central Kotlin Build License

Compose Multiplatform badge-android badge-ios badge-desktop badge-js

A Compose Multiplatform library for generating color palettes from images, including the dominant color. You can use this library in combination with MaterialKolor to generate dynamic Material themes based on images.

Supports loading images from several sources, see sources.

Note: This is a port of the androidx.palette library.

You can checkout a web demo at demo.kmpalette.com.

Table of Contents

Platforms

This library is written for Compose Multiplatform, and can be used on the following platforms:

Artifact Android Desktop iOS macOS Browser
core βœ… βœ… βœ… βœ… βœ…
extensions-base64 βœ… βœ… βœ… βœ… βœ…
extensions-bytearray βœ… βœ… βœ… βœ… βœ…
extensions-libres βœ… βœ… βœ… βœ… βœ…
extensions-network βœ… βœ… βœ… βœ… βœ…
extensions-file βœ… βœ… βœ… βœ… ❌

Inspiration

I created this library because I wanted to use the androidx.palette library in a Compose Multiplatform app. But that library is not multiplatform, so I decided to port it.

Dynamic Material Themes

Want to create a dynamic Material theme based on the dominant color in an image?

Check out my other Compose Multiplatform library MaterialKolor!

Setup

You can add this library to your project using Gradle. There are several optional extension libraries, see sources.

Version Catalog

In libs.versions.toml:

[versions]
kmpalette = "3.1.0"

[libraries]
kmpalette-core = { module = "com.kmpalette:kmpalette-core", version.ref = "kmpalette" }
# Optional source libraries
kmpalette-extensions-base64 = { module = "com.kmpalette:extensions-base64", version.ref = "kmpalette" }
kmpalette-extensions-bytearray = { module = "com.kmpalette:extensions-bytearray", version.ref = "kmpalette" }
kmpalette-extensions-libres = { module = "com.kmpalette:extensions-libres", version.ref = "kmpalette" }
kmpalette-extensions-network = { module = "com.kmpalette:extensions-network", version.ref = "kmpalette" }
kmpalette-extensions-file = { module = "com.kmpalette:extensions-file", version.ref = "kmpalette" }

To add to a multiplatform project, add the dependency to the common source-set:

kotlin {
    sourceSets {
        commonMain {
            dependencies {
                // Core library
                implementation(libs.kmpalette.core)

                // Optional extensions based on your image source
                implementation(libs.kmpalette.extensions.base64)
                implementation(libs.kmpalette.extensions.bytearray)
                implementation(libs.kmpalette.extensions.libres)
                implementation(libs.kmpalette.extensions.network)
                implementation(libs.kmpalette.extensions.file)
            }
        }
    }
}

Usage

To see the generated KDocs, visit docs.kmpalette.com

In order to use this library you first must have a ImageBitmap or a Painter object.

To get an ImageBitmap you can use one of the sources or by using a library that creates one for you.

Since this library is a port of the androidx.palette library, the usage is very similar. However this library provides some helpful extension functions and composables.

Look in kmpalette-core for the main library, including extensions for the Palette and Swatch objects.

Included are two helpful @Composeable-ready State objects:

  • DominantColorState - A state object that holds a generated dominant Color object.
  • PaletteState - A state object that holds a generated Palette object.

They can be used like so:

Dominant Color

You can generate a dominant color from an ImageBitmap using the rememberDominantColorState composeable. This will also provide a onColor for you to use as a text color.

@Composable
fun SomeComposable(bitmap: ImageBitmap) {
    val dominantColorState = rememberDominantColorState()
    LaunchedEffect(bitmap) {
        dominantColorState.updateFrom(bitmap)
    }

    Box(
        modifier = Modifier
            .width(200.dp)
            .height(100.dp)
            .background(dominantColorState.color)
    ) {
        Text("Some Text", color = dominantColorState.onColor)
    }
}

You can also use a Painter object by specifying the DominantColorState.loader parameter:

@Composable
fun SomeComposable(painter: Painter) {
    val loader = rememberPainterLoader()
    val dominantColorState = rememberDominantColorState(loader = loader)
    LaunchedEffect(painter) {
        dominantColorState.updateFrom(painter)
    }

    // ...
}

Since the generation of the dominant color is an asynchronous operation that can fail, you can track the results of the operation using the DominantColorState.result object.

If you want to filter the dominant color, you can use the pass a lambda to rememberDominantColorState():

val dominantColorState = rememberDominantColorState(
    isSwatchValid = { swatch ->
        swatch.color.contrastAgainst(MaterialTheme.colorScheme.surfaceColor) >= MinContrastRatio
    }
)
LaunchedEffect(bitmap) {
    dominantColorState.updateFrom(bitmap)
}

For more examples of getting a dominant color see the demo app

Generate a color Palette

If you want a whole color palette instead of just a dominate color, you can use the rememberPaletteState composeable. This will provide a Palette object which contains a few different color Swatchs, each have their own color and onColor.

Using an ImageBitmap:

fun SomeComposable(bitmap: ImageBitmap) {
    val paletteState = rememberPaletteState()
    LaunchedEffect(bitmap) {
        paletteState.generate(bitmap)
    }

    Box(
        modifier = Modifier
            .width(200.dp)
            .height(100.dp)
            .background(paletteState.vibrantSwatch?.color ?: Color.White)
    ) {
        Text(
            "Some Text",
            color = paletteState.vibrantSwatch?.onColor ?: Color.Black,
        )
    }
}

Or using a Painter:

fun SomeComposable(painter: Painter) {
    val loader = rememberPainterLoader()
    val paletteState = rememberPaletteState(loader = loader)
    LaunchedEffect(painter) {
        paletteState.generate(painter)
    }

    // ...
}

Since the generation of the dominant color is an asynchronous operation that can fail, you can track the results of the operation using the DominantColorState.result object.

For more examples of generating a Palette see the demo app

Sources

The kmpalette-core library provides the core functionality for generating color palettes from a ImageBitmap or a Painter object.

This library provides some extensions artifacts for some popular sources.

Artifact Library Loader Input Class Demo
extensions-base64 N/A Base64Loader String Base64DemoScreen
extensions-bytearray N/A ByteArrayLoader ByteArray N/A
extensions-libres libres LibresLoader Image LibresPaletteScreen
extensions-network ktor NetworkLoader Url NetworkDemoScreen
extensions-file okio PathLoader, FilePathLoader Path, String N/A

Each of these extensions provides a ImageBitmapLoader object that can be used to generate an ImageBitmap from the input class. For example, the NetworkLoader can be used to generate an ImageBitmap from a Url:

@Composable
fun SomeComposable(url: Url) {
    val networkLoader = rememberNetworkLoader()
    val dominantColorState = rememberDominantColorState(loader = networkLoader)
    LaunchedEffect(url) {
        dominantColorState.updateFrom(url)
    }

    Box(
        modifier = Modifier
            .width(200.dp)
            .height(100.dp)
            .background(dominantColorState.color)
    ) {
        Text("Some Text", color = dominantColorState.onColor)
    }
}

Compose Multiplatform Resources

As of Compose Multiplatform 1.6.0 the way Resources are handled has changed. Thus this library has deleted it's extension as it is no longer needed.

To generate a palette from a DrawableResource you can use the @Composable imageResource() to get an ImageBitmap then pass that to the default loader.

@Composable
fun MyComposeable() {
    val image = imageResource(R.drawable.my_image)
    val dominantColorState = rememberDominantColorState()
    LaunchedEffect(url) {
        dominantColorState.updateFrom(image)
    }
}

Demo

A demo app is available in the demo directory. It is a Compose Multiplatform app that runs on Android, iOS and Desktop.

See the README for more information.

Feature Requests

If you have a feature request, please open an issue. If you would like to implement a feature request refer to the Contributing section.

Contributing

Contributions are always welcome!. If you'd like to contribute, please feel free to create a PR or open an issue.

License

The module androidx-palette is licensed under the Apache License, Version 2.0. See their LICENSE and their repository here for more information.

Changes from original source

  • Convert Java code to Kotlin
  • Convert library to Kotlin Multiplatform

For the remaining code see LICENSE for more information.