diff --git a/stream-video-android-ui-compose/.gitignore b/stream-video-android-ui-compose/.gitignore index 42afabfd2a..43c1a66995 100644 --- a/stream-video-android-ui-compose/.gitignore +++ b/stream-video-android-ui-compose/.gitignore @@ -1 +1,2 @@ -/build \ No newline at end of file +/build +!/libs/** \ No newline at end of file diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index 02cc8f51a1..bc9f6a55a5 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -599,18 +599,16 @@ public final class io/getstream/video/android/compose/ui/components/avatar/UserA public final class io/getstream/video/android/compose/ui/components/background/CallBackgroundKt { public static final fun CallBackground (Landroidx/compose/ui/Modifier;Ljava/util/List;ZZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V - public static final fun ParticipantImageBackground-6a0pyJM (Ljava/lang/String;Landroidx/compose/ui/Modifier;FLandroidx/compose/runtime/Composer;II)V + public static final fun ParticipantImageBackground (Ljava/lang/String;Landroidx/compose/ui/Modifier;ILandroidx/compose/runtime/Composer;II)V } public final class io/getstream/video/android/compose/ui/components/background/ComposableSingletons$CallBackgroundKt { public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/background/ComposableSingletons$CallBackgroundKt; public static field lambda-1 Lkotlin/jvm/functions/Function3; - public static field lambda-2 Lkotlin/jvm/functions/Function3; - public static field lambda-3 Lkotlin/jvm/functions/Function2; + public static field lambda-2 Lkotlin/jvm/functions/Function2; public fun ()V public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-3$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; } public final class io/getstream/video/android/compose/ui/components/call/CallAppBarKt { diff --git a/stream-video-android-ui-compose/build.gradle.kts b/stream-video-android-ui-compose/build.gradle.kts index 4e72c51887..f68356b319 100644 --- a/stream-video-android-ui-compose/build.gradle.kts +++ b/stream-video-android-ui-compose/build.gradle.kts @@ -67,12 +67,14 @@ dependencies { implementation(libs.landscapist.coil) implementation(libs.landscapist.animation) implementation(libs.landscapist.placeholder) - implementation(libs.landscapist.transformation) + + // render scripts + compileOnly(files("libs/renderscript-toolkit.aar")) // telephoto implementation(libs.telephoto) + + // preview compileOnly(project(":stream-video-android-previewdata")) testImplementation(project(":stream-video-android-previewdata")) - - // mock } \ No newline at end of file diff --git a/stream-video-android-ui-compose/libs/renderscript-toolkit.aar b/stream-video-android-ui-compose/libs/renderscript-toolkit.aar new file mode 100644 index 0000000000..daf90d952d Binary files /dev/null and b/stream-video-android-ui-compose/libs/renderscript-toolkit.aar differ diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/background/CallBackground.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/background/CallBackground.kt index 1a516c8a01..29d735a2bf 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/background/CallBackground.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/background/CallBackground.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.skydoves.landscapist.ImageOptions import com.skydoves.landscapist.animation.crossfade.CrossfadePlugin @@ -40,6 +39,7 @@ import com.skydoves.landscapist.coil.CoilImage import com.skydoves.landscapist.components.rememberImageComponent import io.getstream.video.android.compose.theme.VideoTheme import io.getstream.video.android.compose.ui.components.avatar.AvatarImagePreview +import io.getstream.video.android.compose.ui.components.plugins.BlurTransformationPlugin import io.getstream.video.android.core.MemberState import io.getstream.video.android.core.model.CallUser import io.getstream.video.android.core.utils.toCallUser @@ -120,7 +120,7 @@ private fun OutgoingCallBackground(callUsers: List, isVideoType: Boole public fun ParticipantImageBackground( userImage: String?, modifier: Modifier = Modifier, - blurRadius: Dp = 20.dp, + blurRadius: Int = 20, ) { if (!userImage.isNullOrEmpty()) { CoilImage( @@ -130,9 +130,7 @@ public fun ParticipantImageBackground( .fillMaxSize() .blur(15.dp) } else { - modifier - .fillMaxSize() - .blur(blurRadius) + modifier.fillMaxSize() }, imageModel = { userImage }, previewPlaceholder = R.drawable.stream_video_call_sample, @@ -142,6 +140,7 @@ public fun ParticipantImageBackground( ), component = rememberImageComponent { +CrossfadePlugin() + +BlurTransformationPlugin(blurRadius) }, ) } else { diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/BlurTransformationPlugin.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/BlurTransformationPlugin.kt new file mode 100644 index 0000000000..2f257e3c69 --- /dev/null +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/BlurTransformationPlugin.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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 io.getstream.video.android.compose.ui.components.plugins + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import com.skydoves.landscapist.plugins.ImagePlugin + +/** + * Originated from [Landscapist](https://github.com/skydoves/landscapist). + * + * BlurTransformationPlugin adds blur transformation effect while rendering an image. + * An image plugin that extends [ImagePlugin.PainterPlugin] to be executed while rendering painters. + * + * @property radius The radius of the pixels used to blur, a value from 0 to infinite. Default is 10. + */ +@Immutable +internal data class BlurTransformationPlugin( + val radius: Int = 10, +) : ImagePlugin.PainterPlugin { + + /** + * Compose circular reveal painter with an [imageBitmap] to the given [painter]. + * + * @param imageBitmap A target [ImageBitmap] to be drawn on the painter. + * @param painter A given painter to be executed circular reveal animation. + */ + @Composable + override fun compose(imageBitmap: ImageBitmap, painter: Painter): Painter { + return painter.rememberBlurPainter( + imageBitmap = imageBitmap, + radius = radius, + ) + } +} diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/RememberBlurPainter.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/RememberBlurPainter.kt new file mode 100644 index 0000000000..91cc79854e --- /dev/null +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/RememberBlurPainter.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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 io.getstream.video.android.compose.ui.components.plugins + +import android.graphics.Bitmap +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import com.google.android.renderscript.Toolkit + +/** + * Originated from [Landscapist](https://github.com/skydoves/landscapist). + * + * This is an extension of the [Painter] for giving blur transformation effect to the given [imageBitmap]. + * + * @param imageBitmap an image bitmap for loading the content. + * @property radius The radius of the pixels used to blur, a value from 1 to 25. Default is 10. + */ +@Composable +internal fun Painter.rememberBlurPainter( + imageBitmap: ImageBitmap, + radius: Int, +): Painter { + var androidBitmap = imageBitmap.asAndroidBitmap() + + if (!( + androidBitmap.config == Bitmap.Config.ARGB_8888 || + androidBitmap.config == Bitmap.Config.ALPHA_8 + ) + ) { + androidBitmap = androidBitmap.copy(Bitmap.Config.ARGB_8888, false) + } + + val blurredBitmap = remember(imageBitmap, radius) { + iterativeBlur(androidBitmap, radius) + } + return remember(this) { + TransformationPainter( + imageBitmap = blurredBitmap.asImageBitmap(), + painter = this, + ) + } +} + +private fun iterativeBlur( + androidBitmap: Bitmap, + radius: Int, +): Bitmap { + val iterate = (radius + 1) / 25 + var bitmap: Bitmap = Toolkit.blur( + inputBitmap = androidBitmap, + radius = (radius + 1) % 25, + ) + + for (i in 0 until iterate) { + bitmap = Toolkit.blur( + inputBitmap = bitmap, + radius = 25, + ) + } + + return bitmap +} diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/TransformationPainter.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/TransformationPainter.kt new file mode 100644 index 0000000000..c4eb3e0df9 --- /dev/null +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/plugins/TransformationPainter.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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 io.getstream.video.android.compose.ui.components.plugins + +import android.graphics.Matrix +import android.graphics.RectF +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.ImageBitmap +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 +import androidx.core.util.Pools + +/** + * Originated from [Landscapist](https://github.com/skydoves/landscapist). + * + * TransformationPainter is a [Painter] which draws the [imageBitmap] to the given [painter]. + * + * @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 TransformationPainter( + private val imageBitmap: ImageBitmap, + private val painter: Painter, +) : Painter() { + + /** return the dimension size of the [painter]'s intrinsic width and height. */ + override val intrinsicSize: Size get() = painter.intrinsicSize + + override fun DrawScope.onDraw() { + drawIntoCanvas { canvas -> + var dx = 0f + var dy = 0f + val scale: Float + val shaderMatrix = Matrix() + val shader = ImageShader(imageBitmap, TileMode.Clamp) + val brush = ShaderBrush(shader) + val paint = paintPool.acquire() ?: Paint() + paint.asFrameworkPaint().apply { + isAntiAlias = true + isDither = true + isFilterBitmap = true + } + + // cache the paint in the internal stack. + 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 + + if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) { + scale = mDrawableRect.height() / bitmapHeight.toFloat() + dx = (mDrawableRect.width() - bitmapWidth * scale) * 0.5f + } else { + scale = mDrawableRect.width() / bitmapWidth.toFloat() + dy = (mDrawableRect.height() - bitmapHeight * scale) * 0.5f + } + + // resize the matrix to scale by sx and sy. + shaderMatrix.setScale(scale, scale) + + // post translate the matrix with the specified translation. + shaderMatrix.postTranslate( + (dx + 0.5f) + mDrawableRect.left, + (dy + 0.5f) + mDrawableRect.top, + ) + // apply the scaled matrix to the shader. + shader.setLocalMatrix(shaderMatrix) + // draw an image bitmap as a rect. + drawRect(brush = brush) + // restore canvas. + canvas.restore() + // resets the paint and release to the pool. + paint.asFrameworkPaint().reset() + paintPool.release(paint) + } + } +} + +/** paint pool which caching and reusing [Paint] instances. */ +private val paintPool = Pools.SimplePool(2)