From 0c35513c20ca43783e7692415d1f5a9013388cab Mon Sep 17 00:00:00 2001 From: Jordon de Hoog Date: Tue, 12 Dec 2023 16:48:40 -0500 Subject: [PATCH] Chore: Add demo for new PainterLoader (#75) --- README.md | 43 +++++++++++++++++-- demo/composeApp/build.gradle.kts | 3 +- .../kotlin/com/kmpalette/demo/HomeScreen.kt | 8 ++++ .../demo/dominant/DominantPhotoColorScreen.kt | 13 +++++- .../demo/dominant/NetworkPainterDemoScreen.kt | 41 ++++++++++++++++++ .../demo/palette/LibresPaletteScreen.kt | 2 +- .../demo/palette/PainterPaletteScreen.kt | 22 ++++++++++ gradle/libs.versions.toml | 2 + kmpalette-core/api/android/kmpalette-core.api | 4 ++ kmpalette-core/api/jvm/kmpalette-core.api | 4 ++ .../kotlin/com/kmpalette/loader/Loader.kt | 16 ------- .../com/kmpalette/loader/PainterLoader.kt | 40 +++++++++++++++++ 12 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/NetworkPainterDemoScreen.kt create mode 100644 demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/PainterPaletteScreen.kt create mode 100644 kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/PainterLoader.kt diff --git a/README.md b/README.md index 4dc10d36..5764d2e9 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,10 @@ kotlin { To see the generated KDocs, visit [docs.kmpalette.com](https://docs.kmpalette.com/) -In order to use this library you first must have a `ImageBitmap` object. You can get this from using -one of the [sources](#sources) or by using a library that creates one for you. +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](#sources) or by using a library that +creates one for you. Since this library is a port of the [`androidx.palette`](https://developer.android.com/jetpack/androidx/releases/palette) library, @@ -164,6 +166,21 @@ fun SomeComposable(bitmap: ImageBitmap) { } ``` +You can also use a `Painter` object by specifying the `DominantColorState.loader` parameter: + +```kotlin +@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. @@ -190,6 +207,8 @@ 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 `Swatch`s, each have their own color and _onColor_. +Using an `ImageBitmap`: + ```kotlin fun SomeComposable(bitmap: ImageBitmap) { val paletteState = rememberPaletteState() @@ -211,6 +230,20 @@ fun SomeComposable(bitmap: ImageBitmap) { } ``` +Or using a `Painter`: + +```kotlin +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. @@ -219,8 +252,10 @@ the [demo app](demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette) ### Sources -In order to generate a color palette, you must first have an `ImageBitmap` object. This library -provides some extensions artifacts for some popular 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 | |----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|--------------------------------|------------------|------------------------------------------------------------------------------------------------------------------------| diff --git a/demo/composeApp/build.gradle.kts b/demo/composeApp/build.gradle.kts index ba199630..92df518e 100644 --- a/demo/composeApp/build.gradle.kts +++ b/demo/composeApp/build.gradle.kts @@ -1,5 +1,4 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { alias(libs.plugins.multiplatform) @@ -9,7 +8,6 @@ plugins { alias(libs.plugins.libres) } -@OptIn(ExperimentalKotlinGradlePluginApi::class) kotlin { applyDefaultHierarchyTemplate() @@ -67,6 +65,7 @@ kotlin { implementation(libs.kermit) implementation(libs.calf.filePicker) implementation(libs.ktor.client) + implementation(libs.kamel) } } diff --git a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/HomeScreen.kt b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/HomeScreen.kt index b66fcbc8..c895355e 100644 --- a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/HomeScreen.kt +++ b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/HomeScreen.kt @@ -15,7 +15,9 @@ import cafe.adriel.voyager.navigator.currentOrThrow import com.kmpalette.demo.dominant.Base64DemoScreen import com.kmpalette.demo.dominant.DominantPhotoColorScreen import com.kmpalette.demo.dominant.NetworkDemoScreen +import com.kmpalette.demo.dominant.NetworkPainterDemoScreen import com.kmpalette.demo.palette.LibresPaletteScreen +import com.kmpalette.demo.palette.PainterPaletteScreen import com.kmpalette.demo.palette.ResourcesPaletteScreen import com.kmpalette.demo.theme.AppTheme @@ -37,6 +39,12 @@ class HomeScreen : Screen { Button(onClick = { navigator.push(ResourcesPaletteScreen()) }) { Text("Palette - Resources") } + Button(onClick = { navigator.push(PainterPaletteScreen()) }) { + Text("Palette - Painter") + } + Button(onClick = { navigator.push(NetworkPainterDemoScreen()) }) { + Text("Dominant Color - Painter") + } Button(onClick = { navigator.push(DominantPhotoColorScreen()) }) { Text("Dominant Color - Photo Picker") } diff --git a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/DominantPhotoColorScreen.kt b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/DominantPhotoColorScreen.kt index a148c39c..f140f390 100644 --- a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/DominantPhotoColorScreen.kt +++ b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/DominantPhotoColorScreen.kt @@ -30,6 +30,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen @@ -89,8 +91,9 @@ class DominantPhotoColorScreen : Screen { @Composable internal fun DominantDemoContent( dominantColorState: DominantColorState, - imageBitmap: ImageBitmap?, - content: @Composable () -> Unit, + imageBitmap: ImageBitmap? = null, + painter: Painter? = null, + content: @Composable () -> Unit = {}, ) { var style: PaletteStyle by remember { mutableStateOf(PaletteStyle.TonalSpot) } @@ -114,6 +117,12 @@ internal fun DominantDemoContent( contentDescription = null, modifier = Modifier.heightIn(max = 200.dp) ) + } else if (painter != null) { + Image( + painter = painter, + contentDescription = null, + modifier = Modifier.heightIn(max = 200.dp) + ) } when (dominantColorState.result) { diff --git a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/NetworkPainterDemoScreen.kt b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/NetworkPainterDemoScreen.kt new file mode 100644 index 00000000..75dc1ce5 --- /dev/null +++ b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/dominant/NetworkPainterDemoScreen.kt @@ -0,0 +1,41 @@ +package com.kmpalette.demo.dominant + +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.graphics.painter.Painter +import cafe.adriel.voyager.core.screen.Screen +import com.kmpalette.loader.rememberPainterLoader +import com.kmpalette.rememberDominantColorState +import io.kamel.core.Resource +import io.kamel.image.asyncPainterResource +import io.ktor.http.Url + +class NetworkPainterDemoScreen : Screen { + + private val demoImageUrl = Url("https://picsum.photos/600/300") + + @Composable + override fun Content() { + when (val painterResource = asyncPainterResource(demoImageUrl)) { + is Resource.Failure -> Text("Failed to load image") + is Resource.Loading -> CircularProgressIndicator() + is Resource.Success -> SuccessContent(painterResource) + } + } + + @Composable + private fun SuccessContent(resource: Resource.Success) { + val loader = rememberPainterLoader() + val dominantColorState = rememberDominantColorState(loader) + LaunchedEffect(resource) { + dominantColorState.updateFrom(resource.value) + } + + DominantDemoContent( + dominantColorState = dominantColorState, + painter = resource.value, + ) + } +} \ No newline at end of file diff --git a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/LibresPaletteScreen.kt b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/LibresPaletteScreen.kt index 993814cf..345f969c 100644 --- a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/LibresPaletteScreen.kt +++ b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/LibresPaletteScreen.kt @@ -7,7 +7,7 @@ import com.kmpalette.loader.LibresLoader import com.kmpalette.rememberPaletteState import io.github.skeptick.libres.compose.painterResource -private val images = listOf( +internal val images = listOf( Res.image.bg_1, Res.image.bg_2, Res.image.bg_3, diff --git a/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/PainterPaletteScreen.kt b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/PainterPaletteScreen.kt new file mode 100644 index 00000000..88b4a511 --- /dev/null +++ b/demo/composeApp/src/commonMain/kotlin/com/kmpalette/demo/palette/PainterPaletteScreen.kt @@ -0,0 +1,22 @@ +package com.kmpalette.demo.palette + +import androidx.compose.runtime.Composable +import cafe.adriel.voyager.core.screen.Screen +import com.kmpalette.loader.rememberPainterLoader +import com.kmpalette.rememberPaletteState +import io.github.skeptick.libres.compose.painterResource + +class PainterPaletteScreen : Screen { + + @Composable + override fun Content() { + val painters = images.map { it.painterResource() } + + val loader = rememberPainterLoader() + PaletteScreen( + images = painters, + paletteState = rememberPaletteState(loader), + painterResource = { _, painter -> painter }, + ) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0b1dd3c..034028ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ kover = "0.7.5" poko = "0.15.1" calf-filePicker = "0.3.0" okio = "3.6.0" +kamel = "0.9.0" # These are pinned for Android instrumented tests #noinspection GradleDependency @@ -70,6 +71,7 @@ libres = { module = "io.github.skeptick.libres:libres-compose", version.ref = "l materialKolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } calf-filePicker = { module = "com.mohamedrejeb.calf:calf-file-picker", version.ref = "calf-filePicker" } +kamel = { module = "media.kamel:kamel-image", version.ref = "kamel" } [plugins] multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/kmpalette-core/api/android/kmpalette-core.api b/kmpalette-core/api/android/kmpalette-core.api index df6bbfc9..1ecadf8e 100644 --- a/kmpalette-core/api/android/kmpalette-core.api +++ b/kmpalette-core/api/android/kmpalette-core.api @@ -162,3 +162,7 @@ public final class com/kmpalette/loader/PainterLoader : com/kmpalette/loader/Ima public synthetic fun load (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class com/kmpalette/loader/PainterLoaderKt { + public static final fun rememberPainterLoader (Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/runtime/Composer;II)Lcom/kmpalette/loader/PainterLoader; +} + diff --git a/kmpalette-core/api/jvm/kmpalette-core.api b/kmpalette-core/api/jvm/kmpalette-core.api index df6bbfc9..1ecadf8e 100644 --- a/kmpalette-core/api/jvm/kmpalette-core.api +++ b/kmpalette-core/api/jvm/kmpalette-core.api @@ -162,3 +162,7 @@ public final class com/kmpalette/loader/PainterLoader : com/kmpalette/loader/Ima public synthetic fun load (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class com/kmpalette/loader/PainterLoaderKt { + public static final fun rememberPainterLoader (Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/runtime/Composer;II)Lcom/kmpalette/loader/PainterLoader; +} + diff --git a/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/Loader.kt b/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/Loader.kt index 98d97c25..5a09196a 100644 --- a/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/Loader.kt +++ b/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/Loader.kt @@ -1,11 +1,6 @@ package com.kmpalette.loader import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection -import com.kmpalette.internal.PainterImage /** * A default loader that takes an [ImageBitmap] and returns it. @@ -15,14 +10,3 @@ internal val ImageBitmapLoader = object : ImageBitmapLoader { override suspend fun load(input: ImageBitmap): ImageBitmap = input } -/** - * A default loader that takes an [Painter], draws it as an [ImageBitmap] and returns that. - */ -public class PainterLoader( - private val density: Density, - private val layoutDirection: LayoutDirection -) : ImageBitmapLoader { - override suspend fun load(input: Painter): ImageBitmap { - return PainterImage(input, density, layoutDirection).asBitmap() - } -} \ No newline at end of file diff --git a/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/PainterLoader.kt b/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/PainterLoader.kt new file mode 100644 index 00000000..9cc56b03 --- /dev/null +++ b/kmpalette-core/src/commonMain/kotlin/com/kmpalette/loader/PainterLoader.kt @@ -0,0 +1,40 @@ +package com.kmpalette.loader + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import com.kmpalette.internal.PainterImage + +/** + * A default loader that takes an [Painter], draws it as an [ImageBitmap] and returns that. + */ +public class PainterLoader( + private val density: Density, + private val layoutDirection: LayoutDirection, +) : ImageBitmapLoader { + + override suspend fun load(input: Painter): ImageBitmap { + return PainterImage(input, density, layoutDirection).asBitmap() + } +} + +/** + * Create and remember a [PainterLoader] that uses the [LocalDensity] and [LocalLayoutDirection] + * to generate a Palette from a [Painter]. + * + * @param[density] The [Density] to use when drawing the [Painter]. + * @param[layoutDirection] The [LayoutDirection] to use when drawing the [Painter]. + * @return A [PainterLoader] for the given [density] and [layoutDirection]. + */ +@Composable +public fun rememberPainterLoader( + density: Density = LocalDensity.current, + layoutDirection: LayoutDirection = LocalLayoutDirection.current, +): PainterLoader = remember(density, layoutDirection) { + PainterLoader(density, layoutDirection) +} \ No newline at end of file