在Android中将位图转换为灰度

65

我是这个网站的新手,我有一个关于Android的问题。

是否有任何方法将位图转换为灰度? 我知道如何绘制灰度位图(使用画布操作:http://www.mail-archive.com/android-developers@googlegroups.com/msg38890.html),但我真的需要实际的灰度位图(或至少可以在以后转换为位图的东西)。 我是否必须手动实现它(逐像素操作)?

我搜索了很多,仍然找不到。 有人知道一种简单/高效的方法吗?

非常感谢!


1
请参考此Stackoverflow问题:https://dev59.com/OHI-5IYBdhLWcg3wj5JG - Paresh Mayani
将图像转换为黑白后,图像大小会减小吗?(以KB为单位的大小) - Kalpesh Lakhani
5个回答

165

哦,是的,确实如此。 我使用方法有误,感谢您指出来。 (对于无用的问题表示抱歉) 这是最终代码(严重基于链接中的代码),因为它可能会帮助某些人:

public Bitmap toGrayscale(Bitmap bmpOriginal)
{        
    int width, height;
    height = bmpOriginal.getHeight();
    width = bmpOriginal.getWidth();    

    Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bmpGrayscale);
    Paint paint = new Paint();
    ColorMatrix cm = new ColorMatrix();
    cm.setSaturation(0);
    ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
    paint.setColorFilter(f);
    c.drawBitmap(bmpOriginal, 0, 0, paint);
    return bmpGrayscale;
}

非常欢迎对此提出任何意见或评论。

谢谢


2
这不会保留完整的8位灰度,而是将其剪裁为5或6位,并且每个像素消耗16位而不是8位。差异可能不太明显,但请查看渐变以查看最坏情况。 - Mark Ransom
15
必须说明的是,使用RGB_565会使透明区域完全变成黑色。在这种情况下,应该使用Bitmap.Config.ARGB_8888来保持灰度图像的透明度。 - IronBlossom
1
将图像转换为黑白后,图像大小会减小吗?(以 KB 为单位的大小) - Kalpesh Lakhani
2
我使用bmpOriginal.getConfig()而不是Config.RGB_565或Config.ARGB_8888。 - Jared Rummler
我建议使用ARGB_4444,因为在灰度图像中每个通道不需要8位,但仍需要保持透明度。 - RGrun
在返回灰度位图以释放资源之前,请确保调用bmpOriginal.recycle() - ManuelTS

24

如果您要在ImageView上显示位图,则可以尝试以下代码,而不是将位图转换为灰度:

ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);

ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
imageview.setColorFilter(filter);

作为参考


15

这难道不正是你所链接代码的功能吗?它获取一个彩色位图("bmp"),创建了一个副本位图("bm"),然后使用过滤器将彩色位图绘制到 "bm" 中以将其转换为灰度。从那时起,您可以将 "bm" 用作实际的灰度位图并进行任何想做的操作。

您需要稍微调整示例(它使用硬编码大小,您可能只想克隆原始位图的大小),但除此之外,根据您的需求,这似乎已经准备就绪了。


14

我想提到,采用这种方法时必须考虑一个重要方面。在Android上,位图存储在NativeHeap中。仅仅“创建位图”会最终导致内存塞满,出现 OutOfMemoryException (OOM)。

因此,必须始终对位图进行.recycled()处理。


8
你能提供一个参考文献吗?API文档指出:“[回收]是一种高级调用,通常不需要调用,因为当没有更多引用指向这个位图时,正常的GC过程会释放这段内存。” - Tamlyn
1
GC只有在Java对象被垃圾回收时才会调用recycle()。但是,GC是在虚拟机内存不足时调用的,并不是响应本地堆上的内存可用性。所以,如果你没有进行太多的图像处理,可能可以不用回收。但是,如果你进行了大量的图像处理,如果不回收,很快就会遇到麻烦。 - tomwhipple

4

这里有一种更高效的方法,我已经将其制作成支持所有Android版本的方式:

    //    https://xjaphx.wordpress.com/2011/06/21/image-processing-grayscale-image-on-the-fly/
    @JvmStatic
    fun getGrayscaledBitmapFallback(src: Bitmap, redVal: Float = 0.299f, greenVal: Float = 0.587f, blueVal: Float = 0.114f): Bitmap {
        // create output bitmap
        val bmOut = Bitmap.createBitmap(src.width, src.height, src.config)
        // pixel information
        var A: Int
        var R: Int
        var G: Int
        var B: Int
        var pixel: Int
        // get image size
        val width = src.width
        val height = src.height
        // scan through every single pixel
        for (x in 0 until width) {
            for (y in 0 until height) {
                // get one pixel color
                pixel = src.getPixel(x, y)
                // retrieve color of all channels
                A = Color.alpha(pixel)
                R = Color.red(pixel)
                G = Color.green(pixel)
                B = Color.blue(pixel)
                // take conversion up to one single value
                B = (redVal * R + greenVal * G + blueVal * B).toInt()
                G = B
                R = G
                // set new pixel color to output bitmap
                bmOut.setPixel(x, y, Color.argb(A, R, G, B))
            }
        }
        // return final image
        return bmOut
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @JvmStatic
    fun getGrayscaledBitmap(context: Context, src: Bitmap): Bitmap {
//        https://gist.github.com/imminent/cf4ab750104aa286fa08
//        https://en.wikipedia.org/wiki/Grayscale
        val redVal = 0.299f
        val greenVal = 0.587f
        val blueVal = 0.114f
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
            return getGrayscaledBitmapFallback(src, redVal, greenVal, blueVal)
        val render = RenderScript.create(context)
        val matrix = Matrix4f(floatArrayOf(-redVal, -redVal, -redVal, 1.0f, -greenVal, -greenVal, -greenVal, 1.0f, -blueVal, -blueVal, -blueVal, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f))
        val result = src.copy(src.config, true)
        val input = Allocation.createFromBitmap(render, src, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT)
        val output = Allocation.createTyped(render, input.type)
        // Inverts and do grayscale to the image
        @Suppress("DEPRECATION")
        val inverter =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                    ScriptIntrinsicColorMatrix.create(render)
                else
                    ScriptIntrinsicColorMatrix.create(render, Element.U8_4(render))
        inverter.setColorMatrix(matrix)
        inverter.forEach(input, output)
        output.copyTo(result)
        src.recycle()
        render.destroy()
        return result
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接