60

I am new to this site, and I come with a question about Android.

Is there any way to convert a Bitmap to grayscale? I know how to draw a grayscale bitmap (using canvas operations: http://www.mail-archive.com/android-developers@googlegroups.com/msg38890.html) but I really need The actual bitmap in gray colors (or at least something that could be converted to a bitmap later on). Do I have to implement it by hand (pixel by pixel operations)?

I've searched a lot, and still could not find. Anyone knows a easy/efficient way to do it?

Thanks a lot!

leparlon
  • 2,584
  • 2
  • 15
  • 31

5 Answers5

160

OH, yes, it does. I was using it wrong, thanks for pointing it out to me. (Sorry for the useless question) Here is the end code (heavily based on the one linked) since it may help someone:

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;
}

Any remarks or comments on it are very welcome.

Thanks

leparlon
  • 2,584
  • 2
  • 15
  • 31
  • 1
    This doesn't retain a full 8 bits of grayscale, it clips it to 5 or 6 bits and consumes 16 bits per pixel rather than 8. The difference might not be highly visible but check out a gradient to see the worst case. – Mark Ransom Aug 10 '12 at 02:49
  • 14
    Must say, using RGB_565 will make the transparent area with full black. In that case use Bitmap.Config.ARGB_8888 for maintaining transparency on the grayscaled image. – IronBlossom Jul 09 '13 at 08:13
  • 1
    so after converting it to black and white will image size reduced ? (size in kb) – Kalpesh Lakhani Jun 21 '14 at 07:04
  • 2
    I use bmpOriginal.getConfig() instead of Config.RGB_565 or Config.ARGB_8888 – Jared Rummler Oct 02 '14 at 21:14
  • I'd say use ARGB_4444 because you don't need 8 bits for each channel when it's greyscale, but still need to maintain the transparency. – RGrun Dec 28 '16 at 19:45
  • Make sure to call call [bmpOriginal.recycle()](https://developer.android.com/reference/android/graphics/Bitmap#recycle()) before returning the grayscale bitmap to free resources. – ManuelTS Feb 27 '19 at 17:27
20

If you are going to show that Bitmap on ImageView. Then Instead of converting Bitmap to Gray Scale, you can try below code:

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

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

For reference

Community
  • 1
  • 1
E Player Plus
  • 1,676
  • 15
  • 19
16

Isn't that exactly what the code you're linking to does? It takes a color bitmap ("bmp"), creates a duplicate bitmap ("bm"), and then draws the color bitmap into "bm" using the filter to turn it into grayscale. From that point on, you can use "bm" as an actual grayscale bitmap and do whatever you want to do with it.

You'd need to tweak the sample a bit (it's using hard-coded sizes, you may want to just clone the size of the original bitmap), but other than that, this seems to be as ready-to-use as it gets, depending on what you want.

EboMike
  • 72,990
  • 14
  • 152
  • 157
14

I'd like to mention that with this approach one important aspect must be taken in account. BitMap's on Android are stored in the NativeHeap. By just "creating bitmaps", you'll eventually clog the memory, getting an OutOfMemoryException (OOM).

Therefor, the bitmap must always be .recycled().

Lukas Knuth
  • 24,328
  • 14
  • 80
  • 107
Code-Monkey
  • 157
  • 1
  • 2
  • 8
    Can you provide a reference? The API docs [state](http://developer.android.com/reference/android/graphics/Bitmap.html#recycle()) that "[recycle] is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap." – Tamlyn Nov 02 '11 at 12:53
  • 1
    The GC will call recycle() only when the Java object is GC'd. BUT, GC is called when the VM runs low on memory, NOT in response to memory availability on the native heap. So, if you can probably get away without recycling if you're not doing that much image work. But, if you're doing a lot you'll quickly get in trouble if you don't recycle. – tomwhipple Jul 11 '12 at 23:32
3

Here's a more efficient way, which I've made to support all versions of 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
    }
android developer
  • 106,412
  • 122
  • 641
  • 1,128