6

I have a code that turns a bitmap that has the grey colors into a bitmap of black and white colors, using this code:

// scan through all pixels
for (int x = 0; x < width; ++x) {                                                                                                                                                                                               
  for (int y = 0; y < height; ++y) {                                                                                                                                                                                            
    // get pixel color
    pixel = bitmap.getPixel(x, y);                                                                                                                                                                                              
    A = Color.alpha(pixel);                                                                                                                                                                                                     
    R = Color.red(pixel);                                                                                                                                                                                                       
    G = Color.green(pixel);                                                                                                                                                                                                     
    B = Color.blue(pixel);                                                                                                                                                                                                      
    int gray = (int) (0.2989 * R + 0.5870 * G + 0.1140 * B);                                                                                                                                                                    

    // use 128 as threshold, above -> white, below -> black
    if (gray > 128)
    gray = 255;                                                                                                                                                                                                                 
    else
    gray = 0;                                                                                                                                                                                                                   
    // set new pixel color to output bitmap
    bmOut.setPixel(x, y, Color.argb(A, gray, gray, gray));                                                                                                                                                                      
  }
}   

As you can see I travel all the pixels points of the original bitmap and then I compare the components of the color with a given threshold, in this case 128, and then if its above I say its white otherwise it will be a black pixel.

What I want to do now, is a Spinner that can change that threshold value, and then the BW image will be different.

To do this, I would need to draw all the image again, and that is very cpu costing time, its takes time to travel all the pixels again.

Is therey any way to change the image using a different BW threshold in real-time?

Someone told me to use a GIF, and then what I would do, was just changing the lookup table values of the GIF, does anyone has knowledge about this on Android?

vidit
  • 6,293
  • 3
  • 32
  • 50
TiagoM
  • 3,458
  • 4
  • 42
  • 83
  • DarkLink, why in your instance is the CPU time costly? Is this because you're drawing the bitmap on the screen? – Tom Apr 03 '14 at 13:10
  • Just so I can tempt you into humouring me, I'm pretty confident you don't need GL or OpenCV to do this! But I'd rather not take the guess- you *might* need these tools in some circumstances! Best. – Tom Apr 04 '14 at 09:52
  • I don't understand Tom, what you need to know ? yes this code is CPU time costly, after setting all the pixels into black and white, I show the bitmap on the screen. – TiagoM Apr 04 '14 at 14:58
  • That's what I needed confirmation on. Could you please post the code exactly how you draw this on the screen (for example `canvas.drawBitmap(bitmap, 0, 0, paint...)` etc...? Much appreciated. – Tom Apr 04 '14 at 17:01
  • how about multithreading ? – Beep.exe Apr 07 '14 at 08:48
  • Yes @Tom thats how I do it ;) – TiagoM Apr 09 '14 at 08:50
  • What about it @Beep.exe ? I guess I can't use multithread on bitmap, because is not thread safe, I cannot set the pixels using more than one thread, I guess – TiagoM Apr 09 '14 at 08:50
  • @DarkLink Just a hypothesis,,, bitmap to raw byte array, do the operation, (using multithreading if possible) then back to bitmap,,, refer http://stackoverflow.com/a/13758637/2756352 and raw bitmap processing : http://stackoverflow.com/a/9470843/2756352 (please do note the benchmark) – Beep.exe Apr 11 '14 at 06:09

3 Answers3

6

A little time passed since this question was asked, but I bumped into this looking for something else and happened to have the solution. You can achieve this without OpenCV or any other third party library, using only ColorMatrixColorFilter available since API level 1.

Here are matrices that You can use:

//matrix that changes picture into gray scale
public static ColorMatrix createGreyMatrix() {
    ColorMatrix matrix = new ColorMatrix(new float[] {
            0.2989f, 0.5870f, 0.1140f, 0, 0,
            0.2989f, 0.5870f, 0.1140f, 0, 0,
            0.2989f, 0.5870f, 0.1140f, 0, 0,
            0, 0, 0, 1, 0
    });
    return matrix;
}

// matrix that changes gray scale picture into black and white at given threshold.
// It works this way:
// The matrix after multiplying returns negative values for colors darker than threshold
// and values bigger than 255 for the ones higher. 
// Because the final result is always trimed to bounds (0..255) it will result in bitmap made of black and white pixels only
public static ColorMatrix createThresholdMatrix(int threshold) {
    ColorMatrix matrix = new ColorMatrix(new float[] {
            85.f, 85.f, 85.f, 0.f, -255.f * threshold,
            85.f, 85.f, 85.f, 0.f, -255.f * threshold,
            85.f, 85.f, 85.f, 0.f, -255.f * threshold,
            0f, 0f, 0f, 1f, 0f
    });
    return matrix;
}

And here's how to use them:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;

//load source bitmap and prepare destination bitmap
Bitmap pic = BitmapFactory.decodeResource(getResources(), R.drawable.thePicture, options);
Bitmap result = Bitmap.createBitmap(pic.getWidth(), pic.getHeight(),  Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(result);

//first convert bitmap to grey scale:
bitmapPaint.setColorFilter(new ColorMatrixColorFilter(createGreyMatrix()));
c.drawBitmap(pic, 0, 0, bitmapPaint);

//then convert the resulting bitmap to black and white using threshold matrix 
bitmapPaint.setColorFilter(new ColorMatrixColorFilter(createThresholdMatrix(128)));
c.drawBitmap(result, 0, 0, bitmapPaint);

//voilà! You can now draw the result bitmap anywhere You want:
bitmapPaint.setColorFilter(null);
otherCanvas.drawBitmap(result, null, new Rect(x, y, x + size, y + size), bitmapPaint);

Hope this will help someone.

Arsen
  • 664
  • 1
  • 11
  • 29
  • This does not work with `ARGB_8888`. When I use `Bitmap.Config.RGB_565` it works, why? – FindOut_Quran Nov 27 '15 at 15:42
  • @user3441905 It's been almost a year since my last adventure with Android, but when I look at my old code, I'm using Bitmap.Config.ARGB_8888 everywhere and the code works as expected. Maybe You're already using RGB_565 somewhere for initializing some of the bitmaps and it doesn't work well, because you're mixing up ARGB_8888 and RGB_565 bitmaps? – Arsen Nov 29 '15 at 13:59
  • I do not think so. I think your images do not contain alpha channel (i.e. only `rgb`, not `argb`). When I use transparent images, it causes unexpected results. – FindOut_Quran Nov 30 '15 at 16:31
  • @user3441905 They do. Both the matrices leave the alpha channel untouched, so the final result should actually be made of black and white semi-transparent pixels if you have some transparency in the bitmaps. If you want to completely get rid of transparency you can change the last line of the threshold matrix to "0, 0, 0, 0, 255" (or "0, 0, 0, 0, 0" can't remember now If 0 or 255 was for full transparent; gonna check it later). How do your "unexpected results" look like? – Arsen Dec 01 '15 at 09:13
  • @Arsen your matrix is setup for `ARGB_8888` if I wanted to use it for `RGB_565` I assume I'd change the `-255.f` to `-32.f`, `-64.f`, `-32.f`? – Blundell Jul 30 '17 at 18:53
  • @Blundell I'd assume the same, but I'm not sure. I guess you will have to try and check. – Arsen Jul 30 '17 at 22:08
3

I recommend you look at using the OpenCV library for Android. Once you have it installed you can use it for quick thresholding among many other commonly used operations on images. The following code fragment will convert your colour bitamp into a gray image and threshold at gray level 128.

// first convert bitmap into OpenCV mat object
Mat imageMat = new Mat (bitmap.getHeight(), bitmap.getWidth(),
                CvType.CV_8U, new Scalar(4));
Bitmap myBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
Utils.bitmapToMat(myBitmap, imageMat);

// now convert to gray
Mat grayMat = new Mat ( bitmap.getHeight(), bitmap.getWidth(),
            CvType.CV_8U, new Scalar(1));
Imgproc.cvtColor(imageMat, grayMat, Imgproc.COLOR_RGB2GRAY, 1);

// get the thresholded image
Mat thresholdMat = new Mat ( bitmap.getHeight(), bitmap.getWidth(),
            CvType.CV_8U, new Scalar(1));
Imgproc.threshold(grayMat, thresholdMat , 128, 255, Imgproc.THRESH_BINARY);

// convert back to bitmap for displaying
Bitmap resultBitmap = Bitmap.createBitmap(bitmap.cols(), bitmap.rows(),
                Bitmap.Config.ARGB_8888);
thresholdMat.convertTo(thresholdMat, CvType.CV_8UC1);
Utils.matToBitmap(thresholdMat, resultBitmap);
JacoFourie
  • 41
  • 4
  • Thanks for your reply, is this faster than doing the code I posted ? how much faster? did you tested it ? thanks ;) – TiagoM Apr 04 '14 at 14:57
  • 1
    I had a similar method that also processed the image by manually looping through the image pixel by pixel. By using the OpenCV methods I increased my speed from something that took almost 5 minutes to a few seconds. The method was more involved than just thresholding but I would say you can expect the OpenCV methods to be at least 100 times faster than looping through the image and using getPixel to get each pixel individually. I adapted the code above from a larger method that I did test but I did not directly test the code I submitted. – JacoFourie Apr 07 '14 at 02:50
  • Thanks for your reply JacoFourie, I will test it, when I get home ;) – TiagoM Apr 09 '14 at 08:51
  • Hi there @JacoFourie I tested now using your way, imported the OpenCV library on my project, and used this code you posted, but didn't notice any less time in performance :/ – TiagoM Apr 09 '14 at 20:35
  • I'm surprised that you did not see any speed improvement. Are you still using getPixel or setPixel anywhere in a loop? What size images are you working on? The images I worked on were 3264x2448 pixels. If your images are much smaller the overhead of calling the extra libraries may cancel any speed improvement you got from avoiding the manual looping. – JacoFourie Apr 09 '14 at 23:21
  • Actually I couldn't use OpenCV library, and the tests I done, was using the same algorithm I had before, didn't notice that the application didn't build on my tablet. I can't use OpenCV library on my project because of this error: http://stackoverflow.com/questions/22974308/android-couldnt-load-opencv-java248 @JacoFourie – TiagoM May 08 '14 at 11:05
  • what is cols() and rows() – Karthik Mar 25 '16 at 13:49
1

GIFs have a colour palette in the header that associates, for every "value", which color pertains to it (Wikipedia entry). The catch: back in the day they thought that 256 colours would be enough, and that's as many palette entries as there are. You could use the class GifDecoder.java with a local color table of 256 entries and two values: your two outputs 0 and 255. But at the end of the day, there's a loop that checks for every byte, which value to associate via palette lookup (this is the line). Not a wee faster than your code.

An alternative to make this faster could be using OpenGL to produce your thresholding (given that anyway you're going to draw the Bitmap on the screen, right?). This needs a bunch of extra code, fortunately this tutorial does exactly what you want to do, minus the thresholding part.

For the thresholding, an OpenGL program needs to be compiled (on GlRenderer::onSurfaceChanged) and used every time (on GlRenderer::onDrawFrame). The program gets two shaders that will threshold every pixel at the speed of light! (OK, perhaps a bit slower, but very fast!).

The shaders would be like the following. Let me know if I can help you with plugging them into the example code.

private static final String mVertexShader =
    "attribute vec4 aPosition;\n" +
    "attribute vec4 aTextureCoord;\n" +
    "varying vec2 vTextureCoord;\n" +
    "void main() {\n" +
    "  gl_Position = aPosition;\n" +
    "  vTextureCoord = aTextureCoord;\n" +
    "}\n";

private static final String mFragmentShader =
    "precision mediump float;\n" +
    "varying vec2 vTextureCoord;\n" +
    "uniform float threshold_in_range_0_to_1;\n" +
    "const vec4 coeff_y = vec4(0.256, 0.504, 0.097, 0.0625);\n" +
    "float y;\n" +
    "void main() {\n" +
    "  y = dot(coeff_y, texture2D(sTexture, vTextureCoord));\n" +
    "  if ( y > threshold_in_range_0_to_1)" +
    "    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0)\n" +
    "  else" +
    "    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0)\n" +
    "}\n";
miguelao
  • 772
  • 5
  • 12
  • Thanks for your reply, but this is a bit hard for me to do it. I never worked with OpenGL on Android, I dont have any clue on how to put it together :/ Can you give further help or maybe update the answer? Thanks alot in advance.. – TiagoM Apr 02 '14 at 17:54