> 技术文档 > 【Android笔记】Android Bitmap颜色抖动处理代码详解与反混淆实践

【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. 混淆变量与函数推断

由于代码变量极度混淆,须结合上下文和代码结构还原:

混淆名 推断含义 说明 var0 inputBitmap 输入图像 var1 enableDithering 是否启用颜色抖动 var2 outputWidth 输出图像宽度 var3 outputHeight 输出图像高度 var4 paletteIndex 选择颜色调色板 e inputBitmap 输入Bitmap引用 f outputBitmap 输出Bitmap引用 g colorPalette 当前调色板颜色数组 h colorPalettes 所有调色板集合 a inputWidth 输入图宽 b inputHeight 输入图高 c outputWidth 输出图宽 d outputHeight 输出图高 var17 inputPixels 输入像素数组 var19 outputPixels 输出像素数组 a(x,y) getMappedPixel 获取对应坐标映射像素 a(double[],…) spreadError 误差扩散方法

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图像处理兴起,可以考虑结合机器学习方法优化颜色映射质量,或使用更高级的图像滤镜实现更丰富的视觉效果。


感谢阅读,欢迎交流!