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

如何基于颜色映射HashMap快速重绘Bitmap?逐像素方法性能不佳

高效实现Bitmap颜色映射替换的方案

兄弟,我太懂这种逐像素遍历卡成狗的痛苦了!你现在用的getPixel()和setPixel()方法效率极低——它们每次调用都要在Java层和Native层之间来回切换,对于稍微大一点的Bitmap来说,这种频繁的JNI调用就是性能杀手,真机上慢到离谱完全正常。

下面给你几种高效稳定的替代方案,按实现复杂度和性能排序:

方案1:批量像素数组处理(最快实现,性能提升显著)

这个方法通过一次性读取所有像素到Java数组,在内存中完成替换后再批量写回Bitmap,把JNI调用从width*height次降到2次,性能提升非常明显。

public void recolorFast(Bitmap origBitmap, Bitmap newBitmap, Map<Integer, Integer> colorReplace) {
    int width = origBitmap.getWidth();
    int height = origBitmap.getHeight();
    int[] pixels = new int[width * height];
    
    // 一次性读取所有像素到数组(仅1次JNI调用)
    origBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    
    // 遍历数组完成颜色替换(纯Java内存操作,无JNI开销)
    for (int i = 0; i < pixels.length; i++) {
        Integer targetColor = colorReplace.get(pixels[i]);
        if (targetColor != null) {
            pixels[i] = targetColor;
        }
        // 无匹配颜色则保留原像素
    }
    
    // 一次性写入处理后的像素(仅1次JNI调用)
    newBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
}

额外优化点:

  • 把HashMap<Integer, Integer>换成SparseIntArray,它是Android专门为int键值对设计的容器,内存占用更小,查找速度比HashMap更快:
    // 替换示例:把HashMap转成SparseIntArray
    SparseIntArray colorReplace = new SparseIntArray();
    colorReplace.put(0xFF0000FF, 0xFFFF0000); // 蓝色转红色
    

方案2:RenderScript并行处理(超大Bitmap最优解)

如果你的Bitmap尺寸特别大(比如4K级别的图片),RenderScript是更好的选择——它会自动利用CPU多核心甚至GPU进行并行计算,性能比Java数组方法还要强,而且内存压力更小。

步骤1:编写RenderScript内核脚本

在src/main/rs/目录下创建script_color_replace.rs文件:

#pragma version(1)
#pragma rs java_package_name(com.your.package.name) // 替换成你的包名

rs_allocation colorMapKeys;
rs_allocation colorMapValues;
int colorMapSize;

// 每个像素的处理逻辑
uchar4 __attribute__((kernel)) replaceColor(uchar4 in, uint32_t x, uint32_t y) {
    // 把输入像素转换为ARGB int格式
    int origColor = (in.a << 24) | (in.r << 16) | (in.g << 8) | in.b;
    
    // 查找颜色映射表
    for (int i = 0; i < colorMapSize; i++) {
        int key = rsGetElementAt_int(colorMapKeys, i);
        if (key == origColor) {
            int targetColor = rsGetElementAt_int(colorMapValues, i);
            // 把目标颜色转回uchar4格式
            uchar4 out;
            out.a = (targetColor >> 24) & 0xFF;
            out.r = (targetColor >> 16) & 0xFF;
            out.g = (targetColor >> 8) & 0xFF;
            out.b = targetColor & 0xFF;
            return out;
        }
    }
    
    // 无匹配则返回原像素
    return in;
}

步骤2:Java层调用RenderScript

public void recolorWithRenderScript(Bitmap origBitmap, Bitmap newBitmap, Map<Integer, Integer> colorReplace) {
    RenderScript rs = RenderScript.create(origBitmap.getContext());
    
    // 把颜色映射表转为数组
    int[] keys = new int[colorReplace.size()];
    int[] values = new int[colorReplace.size()];
    int index = 0;
    for (Map.Entry<Integer, Integer> entry : colorReplace.entrySet()) {
        keys[index] = entry.getKey();
        values[index] = entry.getValue();
        index++;
    }
    
    // 创建RenderScript分配对象
    Allocation inputAlloc = Allocation.createFromBitmap(rs, origBitmap);
    Allocation outputAlloc = Allocation.createFromBitmap(rs, newBitmap);
    Allocation keysAlloc = Allocation.createSized(rs, Element.I32(rs), keys.length);
    Allocation valuesAlloc = Allocation.createSized(rs, Element.I32(rs), values.length);
    
    // 复制数据到分配对象
    keysAlloc.copyFrom(keys);
    valuesAlloc.copyFrom(values);
    
    // 加载自定义脚本并设置参数
    ScriptC_color_replace script = new ScriptC_color_replace(rs);
    script.set_colorMapKeys(keysAlloc);
    script.set_colorMapValues(valuesAlloc);
    script.set_colorMapSize(keys.length);
    
    // 执行并行处理
    script.forEach_replaceColor(inputAlloc, outputAlloc);
    
    // 把处理结果写回Bitmap
    outputAlloc.copyTo(newBitmap);
    
    // 释放资源(避免内存泄漏)
    inputAlloc.destroy();
    outputAlloc.destroy();
    keysAlloc.destroy();
    valuesAlloc.destroy();
    script.destroy();
    rs.destroy();
}

关于ColorFilter的说明

你提到的PorterDuffColorFilter或ColorMatrixColorFilter更适合批量颜色变换(比如调整色调、亮度、透明度,或者把所有颜色按公式混合),但没办法直接实现你需要的“精确键值对颜色替换”——因为它们是基于连续的数学变换逻辑,而非离散的映射表。所以如果你的需求是替换特定的几种颜色,上面两种方案更合适。

最后补充的优化建议

  1. 确保origBitmap和newBitmap的颜色格式一致(推荐用ARGB_8888),避免格式转换带来的额外开销。
  2. 如果不需要保留原Bitmap,可以直接在原Bitmap上修改(前提是origBitmap.isMutable()返回true),节省内存。
  3. 处理大Bitmap时,尽量避免在主线程执行,放到子线程中防止UI卡顿。

内容的提问来源于stack exchange,提问作者Bogdan Samchuk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:26:12