Skip to content

Commit

Permalink
Implement BlurTransformationPlugin (#959)
Browse files Browse the repository at this point in the history
* Implement BlurTransformationPlugin

* Update gitignore and add missing aar file

* Update to compileOnly
  • Loading branch information
skydoves authored Dec 8, 2023
1 parent 4ff02f3 commit 071370f
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 14 deletions.
3 changes: 2 additions & 1 deletion stream-video-android-ui-compose/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build
/build
!/libs/**
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> ()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 {
Expand Down
8 changes: 5 additions & 3 deletions stream-video-android-ui-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ 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
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
Expand Down Expand Up @@ -120,7 +120,7 @@ private fun OutgoingCallBackground(callUsers: List<CallUser>, isVideoType: Boole
public fun ParticipantImageBackground(
userImage: String?,
modifier: Modifier = Modifier,
blurRadius: Dp = 20.dp,
blurRadius: Int = 20,
) {
if (!userImage.isNullOrEmpty()) {
CoilImage(
Expand All @@ -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,
Expand All @@ -142,6 +140,7 @@ public fun ParticipantImageBackground(
),
component = rememberImageComponent {
+CrossfadePlugin()
+BlurTransformationPlugin(blurRadius)
},
)
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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<Paint>(2)

0 comments on commit 071370f

Please sign in to comment.