You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Android Compose(Coil3)应用中保存原尺寸模糊JPEG至私有存储的实现方案

在Android Compose(Coil3)应用中保存原尺寸模糊JPEG至私有存储的实现方案

嘿,我完全懂你现在的困扰——之前用Compose做了模糊预览,结果保存的时候只能拿到界面上显示的小尺寸图,根本没法和原图保持一样的分辨率和比例对吧?别担心,咱们换个思路就能解决,核心就是不要从UI组件里提取模糊图,而是直接对原始尺寸的Bitmap应用模糊滤镜后再保存,下面一步步给你讲具体实现:

一、为什么之前的方法不行?

你之前用graphicsLayer.toImageBitmap()拿到的是Compose UI层渲染后的Bitmap,它的尺寸完全取决于你在界面上设置的Modifier.size(200.dp),而不是原图的真实尺寸。所以要拿到和原图一致的模糊版本,必须从原始图片文件入手,而不是渲染后的UI元素。

二、具体实现步骤

1. 加载原始尺寸的Bitmap

首先,我们需要把用户选择的原图以原始分辨率加载到内存中(Coil3会帮我们处理缓存和基础内存优化):

val context = LocalContext.current
val imageLoader = LocalImageLoader.current
// 存储原始尺寸的Bitmap
val originalBitmap = remember { mutableStateOf<Bitmap?>(null) }
// 替换成你实际获取用户选择图片的Uri/路径
val selectedImageUri = remember { mutableStateOf<Uri?>(null) }

// 当用户选择图片后,加载原始尺寸的Bitmap
LaunchedEffect(selectedImageUri.value) {
    selectedImageUri.value ?: return@LaunchedEffect
    val request = ImageRequest.Builder(context)
        .data(selectedImageUri.value)
        .size(OriginalSize) // 关键:强制加载原始尺寸,不做压缩
        .allowHardware(false) // 禁用硬件Bitmap,方便后续模糊处理
        .build()
    val result = imageLoader.execute(request)
    if (result is SuccessResult) {
        originalBitmap.value = result.drawable.toBitmap()
    }
}

2. 实现Bitmap模糊工具函数

我们用Android原生的RenderScript来做模糊处理,它处理大Bitmap效率很高,而且模糊效果和Compose的Modifier.blur()完全一致:

import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import android.graphics.Bitmap

/**
 * 对Bitmap应用模糊滤镜
 * @param radius 模糊半径,范围0-25f(和Compose的blur半径参数对应)
 */
private fun Bitmap.applyBlur(context: Context, radius: Float): Bitmap {
    val rs = RenderScript.create(context)
    val inputAllocation = Allocation.createFromBitmap(
        rs, this, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT
    )
    val outputAllocation = Allocation.createTyped(rs, inputAllocation.type)
    val blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
    
    blurScript.setRadius(radius)
    blurScript.setInput(inputAllocation)
    blurScript.forEach(outputAllocation)
    
    // 将模糊后的结果复制回原Bitmap
    outputAllocation.copyTo(this)
    
    // 释放RenderScript资源
    rs.destroy()
    return this
}

3. 保留原有的模糊预览逻辑

你的预览代码可以完全保留,因为预览只需要展示效果,不需要原图尺寸:

var blurRadius by remember { mutableStateOf(1f) }
val coroutineScope = rememberCoroutineScope()

// 模糊预览区域
AsyncImage(
    model = selectedImageUri.value ?: R.drawable.app_background,
    contentScale = ContentScale.Fit,
    modifier = Modifier
        .size(200.dp)
        .blur(blurRadius.dp, BlurredEdgeTreatment.Rectangle),
    placeholder = painterResource(R.drawable.ic_android_black_24dp),
    contentDescription = stringResource(R.string.image_preview)
)

// 模糊度调整Slider
Slider(
    value = blurRadius,
    onValueChange = { blurRadius = it },
    valueRange = 0f..10f,
    steps = 11,
    modifier = Modifier.padding(16.dp)
)

4. 保存原尺寸模糊图到私有存储

现在,当用户点击保存按钮时,我们对原始Bitmap应用当前的模糊半径,然后保存到应用的私有存储(不需要申请外部存储权限):

// 保存Bitmap到应用私有存储的扩展函数
private suspend fun Bitmap.saveToPrivateStorage(context: Context) {
    withContext(Dispatchers.IO) {
        // 创建私有存储目录下的文件
        val privateDir = context.filesDir
        val outputFile = File(privateDir, "blurred_${System.currentTimeMillis()}.jpg")
        
        // 写入JPEG文件
        outputFile.outputStream().use { out ->
            this@saveToPrivateStorage.compress(Bitmap.CompressFormat.JPEG, 100, out)
            out.flush()
        }
    }
}

// 保存按钮
Button(
    onClick = {
        coroutineScope.launch {
            originalBitmap.value?.let { original ->
                // 复制原始Bitmap,避免修改缓存中的原图
                val blurredBitmap = original.copy(original.config, true)
                    .applyBlur(context, blurRadius)
                blurredBitmap.saveToPrivateStorage(context)
                // 这里可以加保存成功的提示逻辑
            }
        }
    },
    enabled = originalBitmap.value != null,
    modifier = Modifier.padding(top = 16.dp)
) {
    Text("保存模糊图")
}

三、关键注意事项

  1. 内存优化:如果你的原图特别大(比如20MP以上),加载原始Bitmap可能会触发OOM,这时候可以考虑用BitmapFactory.Options的inPreferredConfig = Bitmap.Config.RGB_565减少内存占用,或者对原图进行适度采样压缩(但要保证保存后的尺寸符合需求)。
  2. 资源释放:记得在合适的时机(比如页面销毁时)清空originalBitmap,避免内存泄漏。
  3. 权限合规:保存到应用私有存储(context.filesDir或context.cacheDir)不需要申请WRITE_EXTERNAL_STORAGE权限,完全符合Android 10+的存储政策。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:37:59