6
votes

I am using tess-two library and I wish to convert all the colors other than black in my image to white (Black will be text). Thus making it easier for the tess-two to read the text. I have tried various methods but they are taking too much time as they convert pixel by pixel. Is there a way to achieve this using canvas or anything that give results faster.

UPDATE

Another problem that came up with this algorithm is that printer doesn't print with the same BLACK and White as in android. So the algorithm converts the whole picture to white.

Pixel by pixel method that I am currently using.

 binarizedImage = convertToMutable(cropped);// the bitmap is made mutable
 int width = binarizedImage.getWidth();
 int height = binarizedImage.getHeight();
 int[] pixels = new int[width * height];
 binarizedImage.getPixels(pixels, 0, width, 0, 0, width, height);

 for(int i=0;i<binarizedImage.getWidth();i++) {
     for(int c=0;c<binarizedImage.getHeight();c++) {
         int pixel = binarizedImage.getPixel(i, c);
         if(!(pixel == Color.BLACK  || pixel == Color.WHITE))
         {
              int index = c * width + i;
             pixels[index] = Color.WHITE;
             binarizedImage.setPixels(pixels, 0, width, 0, 0, width, height);
          }
     }
 }
3
you could use ColorMatrix? - 4x5 matrix for transforming the color and alpha components of a Bitmap. -Blackbelt
thanks for the comment @Blackbelt but can you please provide a little help in understanding how ColorMatrix will be helpfulRishabh Lashkari
At a minimum fix your loop. Seriously, you loop over the size of pixels (width*height), and simply do if (pixel[i] == Color.BLACK) continue then set the pixel to white. There's no need to care about it's dimensions. It's an array of integers, you want to do this to all of them.Tatarize
You could do this operation much faster in a renderscript, which would be rather trivial, and would do all the work on the GPU. But, I really also think Rishabh is right and it should be able to be done in a ColorMatrix.Tatarize

3 Answers

1
votes

Per, Rishabh's comment. Use a color matrix. Since black is black and is RGB(0,0,0,255), it's immune to multiplications. So if you multiply everything by 255 in all channels everything will exceed the limit and get crimped to white, except for black which will stay black.

       ColorMatrix bc = new ColorMatrix(new float[] {
                255, 255, 255, 0, 0,
                255, 255, 255, 0, 0,
                255, 255, 255, 0, 0,
                0, 0, 0, 1, 0,
        });
        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(bc);
        paint.setColorFilter(filter);

You can use that paint to paint that bitmap in only-black-stays-black colormatrix filter glory.

Note: This is a quick and awesome trick, but, it will ONLY work for black. While it's perfect for your use and will turn that lengthy op into something that is instant, it does not actually conform to the title question of "a particular color" my algorithm works in any color you want, so long as it is black.

0
votes

Though @Tatarize answer was perfect I was having troubles reading a printed image as its not always jet black.

This algorithm which i found on stack overflow works great, it actually checks whether the particular pixel is closer to black or white and converts the pixel to the closest color. Hence providing binarization with range. (https://stackoverflow.com/a/16534187/3710223).

What I am doing now is keeping the unwanted areas in light colors while text in black. This algorithm gives binarized image in approximately 20-35 sec. Still not that fast but efficient.

private static boolean shouldBeBlack(int pixel) {
        int alpha = Color.alpha(pixel);
        int redValue = Color.red(pixel);
        int blueValue = Color.blue(pixel);
        int greenValue = Color.green(pixel);
        if(alpha == 0x00) //if this pixel is transparent let me use TRASNPARENT_IS_BLACK
            return TRASNPARENT_IS_BLACK;

        // distance from the white extreme
        double distanceFromWhite = Math.sqrt(Math.pow(0xff - redValue, 2) + Math.pow(0xff - blueValue, 2) + Math.pow(0xff - greenValue, 2));

        // distance from the black extreme 
        double distanceFromBlack = Math.sqrt(Math.pow(0x00 - redValue, 2) + Math.pow(0x00 - blueValue, 2) + Math.pow(0x00 - greenValue, 2));

       // distance between the extremes
        double distance = distanceFromBlack + distanceFromWhite;

        return ((distanceFromWhite/distance)>SPACE_BREAKING_POINT);
    }

If the return value is true then we convert the pixel to black else we convert it to white.

I know there can be better/faster answers and more answers are welcomed :)

0
votes

Same thing but done in renderscript, times about 60-100ms. You won't even notice the delay.

    Bitmap blackbitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),bitmap.getConfig());
    RenderScript mRS = RenderScript.create(TouchEmbroidery.activity);
    ScriptC_blackcheck script = new ScriptC_blackcheck(mRS);

    Allocation allocationRaster0 = Allocation.createFromBitmap(
            mRS,
            bitmap,
            Allocation.MipmapControl.MIPMAP_NONE,
            Allocation.USAGE_SCRIPT
    );
    Allocation allocationRaster1 = Allocation.createTyped(mRS, allocationRaster0.getType());
    script.forEach_root(allocationRaster0, allocationRaster1);
    allocationRaster1.copyTo(blackbitmap);

Does the allocation, uses renderscript to write out the data to blackbitmap.

#pragma version(1)
#pragma rs java_package_name(<YOUR PACKAGENAME GOES HERE>)

void root(const uchar4 *v_in, uchar4 *v_out) {
    uint32_t value = (v_in->r * v_in->r);
    value = value + (v_in->g * v_in->g);
    value = value + (v_in->b * v_in->b);
    if (value > 1200) {
        v_out->r = 255;
        v_out->g = 255;
        v_out->b = 255;
    }
    else {
        v_out->r = 0;
        v_out->g = 0;
        v_out->b = 0;
    }
    v_out->a = 0xFF;
}

Note the 1200 is just the threshold I used, should be all three components less than 20 (or, like 0, 0, sqrt(1200) aka (~34)). You can set the 1200 limit up or down accordingly.

And the build gradle needs Renderscript:

renderscriptTargetApi 22

Last few things of the build tools claims to have fixed a bunch of the renderscript headaches. So it might be perfectly reasonable to do this kind of stuff in mission critical places like yours. 20 seconds is too long to wait, 60 milliseconds is not.