Jackpack Compose 图片合成

204 阅读1分钟

效果展示

image.png

核心代码

这里采用的混合模式是 BlendMode.Overlay

此模式根据目标颜色的亮度来混合源颜色。如果目标颜色较暗,则使用类似于multiply的模式;如果目标颜色较亮,则使用类似于screen的模式。这种模式可以在暗色和亮色之间创建平滑的过渡。

package com.lujianfei.composeui.page.image.widget

import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toSize
import kotlin.math.roundToInt


/**
 * Author: lujianfei
 * Date: 2024/5/6 17:54
 * Description:
 */

class OverlayImagePainter(
    private val image: ImageBitmap,
    private val imageOverlay: ImageBitmap,
    private val srcOffset: IntOffset = IntOffset.Zero,
    private val srcSize: IntSize = IntSize(image.width, image.height),
    private val overlaySize: IntSize = IntSize(imageOverlay.width, imageOverlay.height)
) : Painter() {

    private val size: IntSize = validateSize(srcOffset, srcSize)
    override fun DrawScope.onDraw() {
        // draw the first image without any blend mode
        drawImage(
            image,
            srcOffset,
            srcSize,
            dstSize = IntSize(
                this@onDraw.size.width.roundToInt(),
                this@onDraw.size.height.roundToInt()
            )
        )
        // draw the second image with an Overlay blend mode to blend the two together
        drawImage(
            imageOverlay,
            srcOffset,
            overlaySize,
            dstSize = IntSize(
                this@onDraw.size.width.roundToInt(),
                this@onDraw.size.height.roundToInt()
            ),
            blendMode = BlendMode.Overlay
        )
    }

    /**
     * Return the dimension of the underlying [ImageBitmap] as it's intrinsic width and height
     */
    override val intrinsicSize: Size get() = size.toSize()

    private fun validateSize(srcOffset: IntOffset, srcSize: IntSize): IntSize {
        require(
            srcOffset.x >= 0 &&
                    srcOffset.y >= 0 &&
                    srcSize.width >= 0 &&
                    srcSize.height >= 0 &&
                    srcSize.width <= image.width &&
                    srcSize.height <= image.height
        )
        return srcSize
    }
}

完整代码

package com.lujianfei.composeui.page.image

import MyTopAppBar
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.lujianfei.composeui.R
import com.lujianfei.composeui.page.image.widget.OverlayImagePainter
import com.lujianfei.composeui.router.Router
import com.lujianfei.composeui.widget.CodeView


/**
 * Author: lujianfei
 * Date: 2024/3/27 11:25
 * Description:
 */

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun ImageOverlayPage() {
    Scaffold(
        topBar = {
            MyTopAppBar(
                title = {
                    Text("图片合成")
                },
                navigationIcon = {
                    IconButton(onClick = { Router.instance.popBack() }) {
                        Icon(Icons.Filled.ArrowBack,"")
                    }
                }
            )
        },
    ) { padding ->
        Column(modifier = Modifier
            .padding(padding)
            .verticalScroll(rememberScrollState())) {
            Example1()
        }
    }
}

@Composable
private fun Example1() {
    val image1 = ImageBitmap.imageResource(id = R.drawable.car)
    val image2 = ImageBitmap.imageResource(id = R.drawable.avatar)
    val imageOverlay = remember {
        OverlayImagePainter(image1, image2)
    }
  Column {
      CustomTitle(title = "图片1")
      Image(
          bitmap = image1,
          contentDescription = ""
      )
      CustomTitle(title = "图片2")
      Image(
          bitmap = image2,
          contentDescription = ""
      )
      CustomTitle(title = "图片合成")
      Image(
          painter = imageOverlay,
          contentDescription = "",
          contentScale = ContentScale.Crop,
          modifier = Modifier.wrapContentSize()
      )      
  }
}

@Composable
private fun CustomTitle(title:String) {
    Text(text = title, modifier = Modifier
        .fillMaxWidth()
        .padding(all = 10.dp), fontSize = 20.sp)
}

Jackpack Compose 图片合成底部: 上一篇 Jetpack Compose 图形