Android开发:如何加载.cube文件并对Bitmap执行LUT操作?
Android加载.cube文件实现Bitmap LUT滤镜方案
一、先搞定.cube文件的正确解析
你的.cube文件开头的LUT_3D_SIZE 16表示这是16x16x16的3D查找表,总共有16*16*16=4096个RGB颜色项,每个项是0-1范围的浮点值。解析时要跳过注释(#开头)、空行,先提取尺寸,再读取所有颜色值。
解析代码(Kotlin)
fun loadCubeFromAssets(assets: AssetManager, fileName: String): FloatArray? { return try { assets.open(fileName).bufferedReader().use { reader -> var lutSize = 0 val colorValues = mutableListOf<Float>() reader.lineSequence().forEach { line -> val trimmedLine = line.trim() // 跳过空行和注释 if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach // 提取LUT尺寸 if (trimmedLine.startsWith("LUT_3D_SIZE")) { lutSize = trimmedLine.split(" ")[1].toInt() } else { // 读取RGB三个浮点值 trimmedLine.split(" ").filter { it.isNotEmpty() }.forEach { value -> colorValues.add(value.toFloat()) } } } // 验证数据长度:3*lutSize^3 if (lutSize > 0 && colorValues.size == 3 * lutSize * lutSize * lutSize) { colorValues.toFloatArray() } else { null // 解析失败,数据不完整 } } } catch (e: Exception) { e.printStackTrace() null } }
二、高效实现:用RenderScript做LUT处理
RenderScript是Android官方的高性能图像处理框架,适合大尺寸Bitmap,效率比直接遍历像素高很多。
1. 创建RenderScript内核文件(lut_script.rs,放在src/main/rs/目录下)
#pragma version(1) #pragma rs java_package_name(com.your.package.name) // 3D LUT数组,长度为3*size^3 rs_allocation lut; int lutSize; // 将0-255的颜色值转成0-1范围的比例 static float norm(float x) { return x / 255.0f; } uchar4 __attribute__((kernel)) applyLUT(uchar4 in, uint32_t x, uint32_t y) { // 提取输入像素的RGB(注意Bitmap是ARGB,所以in.r是红色,in.g绿色,in.b蓝色) float r = norm(in.r); float g = norm(in.g); float b = norm(in.b); // 计算LUT中的索引(3D转一维) float sizeMinus1 = lutSize - 1.0f; int rIndex = clamp((int)(r * sizeMinus1), 0, lutSize - 1); int gIndex = clamp((int)(g * sizeMinus1), 0, lutSize - 1); int bIndex = clamp((int)(b * sizeMinus1), 0, lutSize - 1); int baseIndex = (rIndex * lutSize + gIndex) * lutSize + bIndex; // 从LUT中读取转换后的RGB值 float outR = rsGetElementAt_float(lut, baseIndex * 3 + 0); float outG = rsGetElementAt_float(lut, baseIndex * 3 + 1); float outB = rsGetElementAt_float(lut, baseIndex * 3 + 2); // 转成0-255的uchar值,返回ARGB格式 return uchar4( (outR * 255.0f + 0.5f) as uchar, (outG * 255.0f + 0.5f) as uchar, (outB * 255.0f + 0.5f) as uchar, in.a // 保留原透明度 ); }
2. Java/Kotlin调用RenderScript代码
fun applyLUTWithRenderScript(context: Context, sourceBitmap: Bitmap, lutArray: FloatArray, lutSize: Int): Bitmap? { // 确保Bitmap是ARGB_8888格式 val inputBitmap = if (sourceBitmap.config!= Bitmap.Config.ARGB_8888) { sourceBitmap.copy(Bitmap.Config.ARGB_8888, true) } else { sourceBitmap } val outputBitmap = Bitmap.createBitmap(inputBitmap.width, inputBitmap.height, Bitmap.Config.ARGB_8888) val rs = RenderScript.create(context) val inputAllocation = Allocation.createFromBitmap(rs, inputBitmap) val outputAllocation = Allocation.createFromBitmap(rs, outputBitmap) // 加载LUT脚本 val script = ScriptC_lut_script(rs) // 创建LUT的Allocation val lutAllocation = Allocation.createSized(rs, Element.F32(rs), lutArray.size) lutAllocation.copyFrom(lutArray) // 设置脚本参数 script._lut = lutAllocation script._lutSize = lutSize // 执行内核 script.forEach_applyLUT(inputAllocation, outputAllocation) // 将结果写入Bitmap outputAllocation.copyTo(outputBitmap) // 释放资源 inputAllocation.destroy() outputAllocation.destroy() lutAllocation.destroy() script.destroy() rs.destroy() return outputBitmap }
三、兼容方案:直接遍历Bitmap像素替换
如果你的App需要兼容低版本(API<17)或者不想用RenderScript,可以用这种方法,缺点是大尺寸Bitmap会比较慢。
fun applyLUTDirectly(sourceBitmap: Bitmap, lutArray: FloatArray, lutSize: Int): Bitmap? { val inputBitmap = if (sourceBitmap.config!= Bitmap.Config.ARGB_8888) { sourceBitmap.copy(Bitmap.Config.ARGB_8888, true) } else { sourceBitmap } val outputBitmap = Bitmap.createBitmap(inputBitmap.width, inputBitmap.height, Bitmap.Config.ARGB_8888) val pixels = IntArray(inputBitmap.width * inputBitmap.height) inputBitmap.getPixels(pixels, 0, inputBitmap.width, 0, 0, inputBitmap.width, inputBitmap.height) val sizeMinus1 = lutSize - 1.0f for (i in pixels.indices) { val pixel = pixels[i] // 提取ARGB val a = pixel shr 24 and 0xFF val r = pixel shr 16 and 0xFF val g = pixel shr 8 and 0xFF val b = pixel and 0xFF // 转成0-1范围,计算LUT索引 val rNorm = r / 255.0f val gNorm = g / 255.0f val bNorm = b / 255.0f val rIndex = (rNorm * sizeMinus1).toInt().coerceIn(0, lutSize - 1) val gIndex = (gNorm * sizeMinus1).toInt().coerceIn(0, lutSize - 1) val bIndex = (bNorm * sizeMinus1).toInt().coerceIn(0, lutSize - 1) val baseIndex = (rIndex * lutSize + gIndex) * lutSize + bIndex * 3 // 读取LUT中的RGB值,转成0-255 val outR = (lutArray[baseIndex] * 255 + 0.5f).toInt().coerceIn(0, 255) val outG = (lutArray[baseIndex + 1] * 255 + 0.5f).toInt().coerceIn(0, 255) val outB = (lutArray[baseIndex + 2] * 255 + 0.5f).toInt().coerceIn(0, 255) // 重新组合像素 pixels[i] = a shl 24 or (outR shl 16) or (outG shl 8) or outB } outputBitmap.setPixels(pixels, 0, outputBitmap.width, 0, 0, outputBitmap.width, outputBitmap.height) return outputBitmap }
四、常见问题排查
- 解析错误:检查.cube文件是否完整,有没有额外的空行或注释,确保颜色值总数是
3*lutSize^3。比如你的16尺寸,应该有3*4096=12288个浮点值。 - 颜色偏差:注意.cube里的数值是0-1范围,转成Bitmap的0-255时要乘255并加0.5f做四舍五入,避免精度丢失。
- 索引计算错误:3D LUT的索引顺序通常是R→G→B,也就是
(R索引 * 尺寸 + G索引) * 尺寸 + B索引,别搞反顺序。 - Bitmap格式:必须用
ARGB_8888格式,其他格式(如RGB_565)会导致颜色通道读取错误。
内容的提问来源于stack exchange,提问作者白小花
相关产品推荐
相关产品推荐

