diff --git a/landscapist-animation/api/android/landscapist-animation.api b/landscapist-animation/api/android/landscapist-animation.api new file mode 100644 index 00000000..7cf66065 --- /dev/null +++ b/landscapist-animation/api/android/landscapist-animation.api @@ -0,0 +1,34 @@ +public abstract interface class com/skydoves/landscapist/animation/circular/CircularRevealFinishListener { + public abstract fun onFinish ()V +} + +public final class com/skydoves/landscapist/animation/circular/CircularRevealImageKt { + public static final field DefaultCircularRevealDuration I + public static final fun CircularRevealImage (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Ljava/lang/String;FLandroidx/compose/ui/graphics/ColorFilter;Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;Landroidx/compose/runtime/Composer;II)V +} + +public final class com/skydoves/landscapist/animation/circular/CircularRevealPlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin { + public static final field $stable I + public fun ()V + public fun (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)V + public synthetic fun (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener; + public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; + public final fun copy (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin; + public static synthetic fun copy$default (Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILjava/lang/Object;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin; + public fun equals (Ljava/lang/Object;)Z + public final fun getDuration ()I + public final fun getOnFinishListener ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/skydoves/landscapist/animation/crossfade/CrossfadePlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin { + public static final field $stable I + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; +} + diff --git a/landscapist-animation/api/desktop/landscapist-animation.api b/landscapist-animation/api/desktop/landscapist-animation.api new file mode 100644 index 00000000..7cf66065 --- /dev/null +++ b/landscapist-animation/api/desktop/landscapist-animation.api @@ -0,0 +1,34 @@ +public abstract interface class com/skydoves/landscapist/animation/circular/CircularRevealFinishListener { + public abstract fun onFinish ()V +} + +public final class com/skydoves/landscapist/animation/circular/CircularRevealImageKt { + public static final field DefaultCircularRevealDuration I + public static final fun CircularRevealImage (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Ljava/lang/String;FLandroidx/compose/ui/graphics/ColorFilter;Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;Landroidx/compose/runtime/Composer;II)V +} + +public final class com/skydoves/landscapist/animation/circular/CircularRevealPlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin { + public static final field $stable I + public fun ()V + public fun (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)V + public synthetic fun (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener; + public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; + public final fun copy (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin; + public static synthetic fun copy$default (Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILjava/lang/Object;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin; + public fun equals (Ljava/lang/Object;)Z + public final fun getDuration ()I + public final fun getOnFinishListener ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/skydoves/landscapist/animation/crossfade/CrossfadePlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin { + public static final field $stable I + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; +} + diff --git a/landscapist-animation/build.gradle.kts b/landscapist-animation/build.gradle.kts index 2cdfdf37..c02277fe 100644 --- a/landscapist-animation/build.gradle.kts +++ b/landscapist-animation/build.gradle.kts @@ -16,7 +16,7 @@ import com.github.skydoves.landscapist.Configuration plugins { - id("landscapist.library.compose") + id("landscapist.library.compose.multiplatform") id("landscapist.spotless") } @@ -35,6 +35,31 @@ mavenPublishing { } } +kotlin { + sourceSets { + all { + languageSettings.optIn("kotlin.RequiresOptIn") + languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + languageSettings.optIn("com.skydoves.landscapist.InternalLandscapistApi") + } + val commonMain by getting { + dependencies { + api(project(":landscapist")) + + implementation(compose.ui) + implementation(compose.runtime) + implementation(compose.foundation) + } + } + + val androidMain by getting { + dependencies { + implementation(libs.androidx.core.ktx) + } + } + } +} + android { namespace = "com.skydoves.landscapist.animation" compileSdk = Configuration.compileSdk @@ -52,17 +77,14 @@ baselineProfile { } } -dependencies { - api(project(":landscapist")) - - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.compose.ui) - implementation(libs.androidx.compose.runtime) - implementation(libs.androidx.compose.foundation) - - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.androidx.test.runner) - androidTestImplementation(libs.androidx.test.junit) - androidTestImplementation(libs.androidx.compose.ui) - androidTestImplementation(libs.androidx.compose.ui.test) -} \ No newline at end of file +tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += listOf( + "-Xexplicit-api=strict", + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=com.skydoves.landscapist.InternalLandscapistApi", + ) + } +} diff --git a/landscapist-animation/src/main/AndroidManifest.xml b/landscapist-animation/src/androidMain/AndroidManifest.xml similarity index 100% rename from landscapist-animation/src/main/AndroidManifest.xml rename to landscapist-animation/src/androidMain/AndroidManifest.xml diff --git a/landscapist-animation/src/main/baseline-prof.txt b/landscapist-animation/src/androidMain/baseline-prof.txt similarity index 100% rename from landscapist-animation/src/main/baseline-prof.txt rename to landscapist-animation/src/androidMain/baseline-prof.txt diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt b/landscapist-animation/src/androidMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt similarity index 91% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt rename to landscapist-animation/src/androidMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt index 053cd4c5..9a898628 100644 --- a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt +++ b/landscapist-animation/src/androidMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.graphics.ImageShader import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.painter.Painter @@ -42,12 +41,12 @@ import androidx.core.util.Pools * @param imageBitmap an image bitmap for loading for the content. * @param painter an image painter to draw an [ImageBitmap] into the provided canvas. */ -internal class CircularRevealPainter( - private val imageBitmap: ImageBitmap, - private val painter: Painter, +internal actual class CircularRevealPainter actual constructor( + actual val imageBitmap: ImageBitmap, + actual val painter: Painter, ) : Painter() { - var radius by mutableStateOf(0f, policy = neverEqualPolicy()) + actual var radius by mutableStateOf(0f, policy = neverEqualPolicy()) override fun DrawScope.onDraw() { var dx = 0f @@ -68,8 +67,8 @@ internal class CircularRevealPainter( canvas.saveLayer(size.toRect(), paint) val mDrawableRect = RectF(0f, 0f, size.width, size.height) - val bitmapWidth: Int = imageBitmap.asAndroidBitmap().width - val bitmapHeight: Int = imageBitmap.asAndroidBitmap().height + val bitmapWidth: Int = imageBitmap.width + val bitmapHeight: Int = imageBitmap.height if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) { scale = mDrawableRect.height() / bitmapHeight.toFloat() diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt b/landscapist-animation/src/androidMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt similarity index 95% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt rename to landscapist-animation/src/androidMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt index 53cd6a73..968be7a8 100644 --- a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt +++ b/landscapist-animation/src/androidMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt @@ -40,16 +40,16 @@ import androidx.core.util.Pools * @param imageBitmap an image bitmap for loading for the content. * @param painter an image painter to draw an [ImageBitmap] into the provided canvas. */ -internal class CrossfadePainter( - private val imageBitmap: ImageBitmap, - private val painter: Painter, +internal actual class CrossfadePainter actual constructor( + actual val imageBitmap: ImageBitmap, + actual val painter: Painter, ) : Painter() { /** return the dimension size of the [painter]'s intrinsic width and height. */ override val intrinsicSize: Size get() = painter.intrinsicSize /** color filter that will be applied to draw the [imageBitmap]. */ - var transitionColorFilter by mutableStateOf(null) + actual var transitionColorFilter by mutableStateOf(null) override fun DrawScope.onDraw() { drawIntoCanvas { canvas -> diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealAnimation.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealAnimation.kt similarity index 100% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealAnimation.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealAnimation.kt diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealFinishListener.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealFinishListener.kt similarity index 100% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealFinishListener.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealFinishListener.kt diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealImage.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealImage.kt similarity index 98% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealImage.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealImage.kt index 07dba31b..b1e0c4e7 100644 --- a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealImage.kt +++ b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealImage.kt @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:JvmName("CircularRevealImage") -@file:JvmMultifileClass - package com.skydoves.landscapist.animation.circular import androidx.compose.foundation.Image diff --git a/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt new file mode 100644 index 00000000..eadf18a1 --- /dev/null +++ b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt @@ -0,0 +1,35 @@ +/* + * Designed and developed by 2020-2023 skydoves (Jaewoong Eum) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.skydoves.landscapist.animation.circular + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter + +/** + * CircularRevealPainter is a [Painter] which animates a clipping circle to reveal an image. + * Reveal animations provide users visual continuity when we show an image. + * + * @param imageBitmap an image bitmap for loading for the content. + * @param painter an image painter to draw an [ImageBitmap] into the provided canvas. + */ +internal expect class CircularRevealPainter( + imageBitmap: ImageBitmap, + painter: Painter, +) : Painter { + internal val imageBitmap: ImageBitmap + internal val painter: Painter + internal var radius: Float +} diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPlugin.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPlugin.kt similarity index 100% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPlugin.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPlugin.kt diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeAnimation.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeAnimation.kt similarity index 100% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeAnimation.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeAnimation.kt diff --git a/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt new file mode 100644 index 00000000..45e0b17f --- /dev/null +++ b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt @@ -0,0 +1,35 @@ +/* + * Designed and developed by 2020-2023 skydoves (Jaewoong Eum) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.skydoves.landscapist.animation.crossfade + +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter + +/** + * CrossfadePainter is a [Painter] that applies crossfade filter effect on the given [imageBitmap]. + * + * @param imageBitmap an image bitmap for loading for the content. + * @param painter an image painter to draw an [ImageBitmap] into the provided canvas. + */ +internal expect class CrossfadePainter( + imageBitmap: ImageBitmap, + painter: Painter, +) : Painter { + internal val imageBitmap: ImageBitmap + internal val painter: Painter + internal var transitionColorFilter: ColorFilter? +} diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePlugin.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePlugin.kt similarity index 100% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePlugin.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePlugin.kt diff --git a/landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeTransition.kt b/landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeTransition.kt similarity index 100% rename from landscapist-animation/src/main/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeTransition.kt rename to landscapist-animation/src/commonMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadeTransition.kt diff --git a/landscapist-animation/src/skiaMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt b/landscapist-animation/src/skiaMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt new file mode 100644 index 00000000..b511e3e2 --- /dev/null +++ b/landscapist-animation/src/skiaMain/kotlin/com/skydoves/landscapist/animation/circular/CircularRevealPainter.kt @@ -0,0 +1,100 @@ +/* + * Designed and developed by 2020-2023 skydoves (Jaewoong Eum) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.skydoves.landscapist.animation.circular + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.asSkiaBitmap +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.painter.Painter +import org.jetbrains.skia.Image +import org.jetbrains.skia.Matrix33 + +/** + * CircularRevealPainter is a [Painter] which animates a clipping circle to reveal an image. + * Reveal animations provide users visual continuity when we show an image. + * + * @param imageBitmap an image bitmap for loading for the content. + * @param painter an image painter to draw an [ImageBitmap] into the provided canvas. + */ +internal actual class CircularRevealPainter actual constructor( + actual val imageBitmap: ImageBitmap, + actual val painter: Painter, +) : Painter() { + + actual var radius by mutableStateOf(0f, policy = neverEqualPolicy()) + + override fun DrawScope.onDraw() { + var dx = 0f + var dy = 0f + var scale: Float + val paint = Paint() + + paint.asFrameworkPaint().apply { + isAntiAlias = true + isDither = true + } + + drawIntoCanvas { canvas -> + // cache the paint in the internal stack. + canvas.saveLayer(size.toRect(), paint) + + val mDrawableRect = Rect(offset = Offset.Zero, size = Size(size.width, size.height)) + val bitmapWidth: Int = imageBitmap.width + val bitmapHeight: Int = imageBitmap.height + + if (bitmapWidth * mDrawableRect.size.height > mDrawableRect.size.width * bitmapHeight) { + scale = mDrawableRect.size.height / bitmapHeight.toFloat() + dx = (mDrawableRect.size.width - bitmapWidth * scale) * 0.5f + } else { + scale = mDrawableRect.size.width / bitmapWidth.toFloat() + dy = (mDrawableRect.size.height - bitmapHeight * scale) * 0.5f + } + + // resize the matrix to scale by sx and sy. + val m1 = Matrix33.makeScale(scale, scale) + val m2 = Matrix33.makeTranslate( + (dx + 0.5f) + mDrawableRect.left, + (dy + 0.5f) + mDrawableRect.top, + ) + val shaderMatrix = m1.makeConcat(m2) + val shader = Image.makeFromBitmap(imageBitmap.asSkiaBitmap()).makeShader( + localMatrix = shaderMatrix, + ) + val brush = ShaderBrush(shader) + // calculate radius and draw an image bitmap as a circle. + val calculatedRadius = size.width.coerceAtLeast(size.height) * radius + drawCircle(brush, calculatedRadius, Offset(size.width / 2, size.height / 2)) + // restore canvas. + canvas.restore() + // resets the paint and release to the pool. + paint.asFrameworkPaint().reset() + } + } + + /** return the dimension size of the [painter]'s intrinsic width and height. */ + override val intrinsicSize: Size get() = painter.intrinsicSize +} diff --git a/landscapist-animation/src/skiaMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt b/landscapist-animation/src/skiaMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt new file mode 100644 index 00000000..c47d862d --- /dev/null +++ b/landscapist-animation/src/skiaMain/kotlin/com/skydoves/landscapist/animation/crossfade/CrossfadePainter.kt @@ -0,0 +1,98 @@ +/* + * Designed and developed by 2020-2023 skydoves (Jaewoong Eum) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.skydoves.landscapist.animation.crossfade + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.asSkiaBitmap +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.painter.Painter +import org.jetbrains.skia.Image +import org.jetbrains.skia.Matrix33 + +/** + * CrossfadePainter is a [Painter] that applies crossfade filter effect on the given [imageBitmap]. + * + * @param imageBitmap an image bitmap for loading for the content. + * @param painter an image painter to draw an [ImageBitmap] into the provided canvas. + */ +internal actual class CrossfadePainter actual constructor( + actual val imageBitmap: ImageBitmap, + actual val painter: Painter, +) : Painter() { + + /** return the dimension size of the [painter]'s intrinsic width and height. */ + override val intrinsicSize: Size get() = painter.intrinsicSize + + /** color filter that will be applied to draw the [imageBitmap]. */ + actual var transitionColorFilter by mutableStateOf(null) + + override fun DrawScope.onDraw() { + drawIntoCanvas { canvas -> + var dx = 0f + var dy = 0f + val scale: Float + val paint = Paint() + paint.asFrameworkPaint().apply { + isAntiAlias = true + isDither = true + } + + // cache the paint in the internal stack. + canvas.saveLayer(size.toRect(), paint) + + val mDrawableRect = Rect(offset = Offset.Zero, size = Size(size.width, size.height)) + val bitmapWidth: Int = imageBitmap.width + val bitmapHeight: Int = imageBitmap.height + + if (bitmapWidth * mDrawableRect.size.height > mDrawableRect.size.width * bitmapHeight) { + scale = mDrawableRect.size.height / bitmapHeight.toFloat() + dx = (mDrawableRect.size.width - bitmapWidth * scale) * 0.5f + } else { + scale = mDrawableRect.size.width / bitmapWidth.toFloat() + dy = (mDrawableRect.size.height - bitmapHeight * scale) * 0.5f + } + + // resize the matrix to scale by sx and sy. + val m1 = Matrix33.makeScale(scale, scale) + val m2 = Matrix33.makeTranslate( + (dx + 0.5f) + mDrawableRect.left, + (dy + 0.5f) + mDrawableRect.top, + ) + val shaderMatrix = m1.makeConcat(m2) + val shader = Image.makeFromBitmap(imageBitmap.asSkiaBitmap()).makeShader( + localMatrix = shaderMatrix, + ) + val brush = ShaderBrush(shader) + // draw an image bitmap as a rect. + drawRect(brush = brush, colorFilter = transitionColorFilter) + // restore canvas. + canvas.restore() + // resets the paint and release to the pool. + paint.asFrameworkPaint().reset() + } + } +}