【Android笔记】Android Bitmap颜色抖动处理代码详解与反混淆实践
Android Bitmap颜色抖动处理代码详解与反混淆实践
文章目录
- Android Bitmap颜色抖动处理代码详解与反混淆实践
-
- 1. 背景介绍
- 2. 原始代码分析
-
- 代码结构梳理
- 3. 混淆变量与函数推断
- 4. 核心处理流程详解
-
- 4.1 初始化
- 4.2 非抖动处理流程
- 4.3 启用抖动处理流程
- 5. 颜色抖动算法原理解析
- 6. 反混淆重构代码展示
- 7. 调色板与辅助函数设计
- 8. 应用场景与优化建议
-
- 8.1 应用场景
- 8.2 优化建议
- 9. 总结与展望
1. 背景介绍
在安卓应用开发中,图像处理是一项常见需求,尤其是对位图(Bitmap)进行颜色格式转换、压缩、滤镜处理等操作。为了减小图像文件体积或实现某种视觉效果,常需要将图像颜色限制在固定调色板内。此时,颜色抖动(dithering)算法成为经典方案,它通过在图像中巧妙分布误差,模拟更多颜色层次,从而避免因颜色减少而产生的严重带状效果。
本文将结合一段典型的安卓Bitmap颜色处理代码,详细剖析其功能实现,包含颜色抖动过程,并基于代码的混淆情况进行反向推断和重构,力求让读者清晰理解底层实现原理及复用方式。
2. 原始代码分析
该段代码功能为:
- 接收一个输入Bitmap,及布尔标志决定是否启用颜色抖动
- 生成指定大小的新Bitmap
- 通过指定索引选择颜色调色板
- 根据是否启用抖动,分别执行简单映射或基于 Floyd–Steinberg 算法的误差扩散颜色量化
- 输出处理后的Bitmap
代码结构梳理
public static Bitmap a(Bitmap var0, boolean var1, int var2, int var3, int var4) { e = var0; Bitmap var10001 = f = Bitmap.createBitmap(var2, var3, var0.getConfig()); g = h[var4]; // 选调色板 c = var10001.getWidth(); d = f.getHeight(); a = e.getWidth(); b = var15 = e.getHeight(); int[] var17 = new int[a * b]; // 输入像素 int[] var19 = new int[c * d]; // 输出像素 var4 = 0; e.getPixels(var17, 0, a, 0, 0, a, b); if (!var1) { // 简单映射 } else { // 颜色抖动映射,含误差扩散 } f.setPixels(var19, 0, c, 0, 0, c, d); return f;}
其中,var1
为是否启用抖动,var4
为调色板索引,a,b
是输入宽高,c,d
是输出宽高。
3. 混淆变量与函数推断
由于代码变量极度混淆,须结合上下文和代码结构还原:
4. 核心处理流程详解
4.1 初始化
- 根据输入图像尺寸和指定输出尺寸创建新的Bitmap对象
- 从输入Bitmap提取像素ARGB数组
- 根据调色板索引取得目标颜色集合
4.2 非抖动处理流程
- 遍历输出图像像素,直接通过
getMappedPixel(x,y)
取得映射像素赋值 - 这一步通常用于直接缩放或颜色映射,不做误差扩散
4.3 启用抖动处理流程
- 初始化双行误差缓存
errorBuffer
(两个行的RGB误差值) - 按行扫描输出图像坐标
- 对每个像素:
- 取输入图像对应像素RGB值,加上当前误差缓存值
- 通过调色板找出最接近的颜色
- 计算误差(实际RGB - 目标颜色RGB)
- 将误差按权重分发给邻近像素的误差缓存(当前行和下一行)
- 边界外像素用调色板中两色交替填充(类似棋盘格)
5. 颜色抖动算法原理解析
本文代码使用了类似 Floyd–Steinberg 算法的误差扩散技术:
- 误差在像素间扩散,避免颜色量化时出现明显带状块状
- 误差权重不完全是标准 FS 权重,但效果类似
- 误差缓存为二维数组,一行存两行数据交替使用
- 误差分配权重值体现了对周围像素不同程度影响
误差传播示意图(近似):
当前像素(x,y) → 分发误差到:(x+1, y) 权重7.0(x-1, y+1) 权重1.5 或 7.0(x, y+1) 权重2.5 或 9.0(x+1, y+1) 权重3.5 或 2.0
此算法通过对误差分布,提升调色板有限色时图像视觉平滑度。
6. 反混淆重构代码展示
以下为经过重命名和注释后,更易读的代码示例:
public static Bitmap processBitmap(Bitmap inputBitmap, boolean enableDithering, int outputWidth, int outputHeight, int paletteIndex) { Bitmap input = inputBitmap; Bitmap output = Bitmap.createBitmap(outputWidth, outputHeight, input.getConfig()); int[] colorPalette = colorPalettes[paletteIndex]; int inputWidth = input.getWidth(); int inputHeight = input.getHeight(); int[] inputPixels = new int[inputWidth * inputHeight]; int[] outputPixels = new int[outputWidth * outputHeight]; input.getPixels(inputPixels, 0, inputWidth, 0, 0, inputWidth, inputHeight); int index = 0; if (!enableDithering) { // 直接映射,不抖动 for (int y = 0; y < outputHeight; ++y) { for (int x = 0; x < outputWidth; ++x) { outputPixels[index++] = getMappedPixel(x, y); } } } else { // 启用抖动,初始化误差缓存 double[][] errorBuffer = new double[2][outputWidth * 3]; int currentRow = 0; for (int y = 0; y < outputHeight; ++y) { int nextRow = (currentRow + 1) & 1; // 清零下一行误差缓存 for (int x = 0; x < outputWidth; ++x) { int base = x * 3; errorBuffer[nextRow][base] = 0.0; errorBuffer[nextRow][base + 1] = 0.0; errorBuffer[nextRow][base + 2] = 0.0; } for (int x = 0; x < outputWidth; ++x) { if (x >= inputWidth || y >= inputHeight) { // 超出原图边界,填充双色棋盘格 outputPixels[index++] = colorPalette[(x + y) % 2]; } else { int pixelColor = inputPixels[y * inputWidth + x]; int base = x * 3; double r = Color.red(pixelColor) + errorBuffer[currentRow][base]; double g = Color.green(pixelColor) + errorBuffer[currentRow][base + 1]; double b = Color.blue(pixelColor) + errorBuffer[currentRow][base + 2]; int nearestColor = colorPalette[getNearestColorIndex(r, g, b)]; outputPixels[index++] = nearestColor; double errorR = r - Color.red(nearestColor); double errorG = g - Color.green(nearestColor); double errorB = b - Color.blue(nearestColor); // 误差分发权重示例 if (x == 0) { spreadError(errorBuffer[currentRow], x, errorR, errorG, errorB, 7.0); spreadError(errorBuffer[nextRow], x + 1, errorR, errorG, errorB, 2.0); spreadError(errorBuffer[nextRow], x, errorR, errorG, errorB, 7.0); } else if (x == outputWidth - 1) { spreadError(errorBuffer[currentRow], x - 1, errorR, errorG, errorB, 7.0); spreadError(errorBuffer[currentRow], x, errorR, errorG, errorB, 9.0); } else { spreadError(errorBuffer[currentRow], x - 1, errorR, errorG, errorB, 1.5); spreadError(errorBuffer[currentRow], x, errorR, errorG, errorB, 2.5); spreadError(errorBuffer[currentRow], x + 1, errorR, errorG, errorB, 1.0); spreadError(errorBuffer[nextRow], x + 1, errorR, errorG, errorB, 3.5); } } } currentRow = nextRow; } } output.setPixels(outputPixels, 0, outputWidth, 0, 0, outputWidth, outputHeight); return output;}
7. 调色板与辅助函数设计
为了完整运行,还需要配合以下辅助函数:
// 返回 (x,y) 对应输入图像映射后的像素(可根据需求实现缩放、插值)private static int getMappedPixel(int x, int y) { // 示例简单返回黑色,实际请替换为对应采样 return Color.BLACK;}// 误差扩散,将误差乘权重加到误差缓存对应像素RGB通道private static void spreadError(double[] errorBuffer, int x, double rErr, double gErr, double bErr, double weight) { int idx = x * 3; errorBuffer[idx] += rErr * weight / 16.0; errorBuffer[idx + 1] += gErr * weight / 16.0; errorBuffer[idx + 2] += bErr * weight / 16.0;}// 根据RGB值,找出调色板中距离最小颜色索引private static int getNearestColorIndex(double r, double g, double b) { int index = 0; double minDist = Double.MAX_VALUE; for (int i = 0; i < colorPalette.length; i++) { int color = colorPalette[i]; double dr = r - Color.red(color); double dg = g - Color.green(color); double db = b - Color.blue(color); double dist = dr * dr + dg * dg + db * db; if (dist < minDist) { minDist = dist; index = i; } } return index;}// 示例调色板,常用黑白双色private static int[][] colorPalettes = new int[][] { {Color.BLACK, Color.WHITE}, {Color.RED, Color.BLUE}, // 更多调色板...};
8. 应用场景与优化建议
8.1 应用场景
- 图像颜色量化:减小颜色数,便于压缩、显示在低彩色设备
- 图像特效:复古像素风、黑白漫画风
- 硬件限制场景:嵌入式设备或显示屏只能显示有限颜色
- 图像缩放重采样:配合抖动防止缩放锯齿
8.2 优化建议
- 多线程加速:分区处理,利用线程池提高性能
- 内存复用:减少像素数组新建,降低GC压力
- 硬件加速:利用 RenderScript 或 GPU 着色器实现实时抖动
- 调色板预处理:构建快速索引结构如Kd-Tree,优化最近邻搜索
9. 总结与展望
本文通过对一段安卓Bitmap处理代码的细致分析,揭示了其内在的颜色量化与抖动处理逻辑。通过变量和函数的推断与重构,提升代码可读性与维护性。颜色抖动作为经典图像处理技术,在现代图像应用中仍然大有用武之地,理解其算法细节有助于开发高效且视觉友好的图像应用。
未来,随着硬件性能提升与AI图像处理兴起,可以考虑结合机器学习方法优化颜色映射质量,或使用更高级的图像滤镜实现更丰富的视觉效果。
感谢阅读,欢迎交流!