1

I'm trying to build a program which can remove an single-colored border form an image.

The border is always white but the width of the border on the left and right side might differ from the width of the border at the top and bottom. So the image I want to extract is centered within the source image.

So from the following image I want to extract the green rectangle.

enter image description here

At the moment I don't know how to start solving this problem.

UPDATE

So finally calsign's code snippet and some improvements on it, solves my problem. I realized that the border around the inner image may not be completely single colored but can vary slightly. This leads to the behavior that for some images were left with a small border.

I solved this problem by improving the comparison of the color of two pixels by comparing the color distance of the two colors with a threshold. When the distance is below the threshold then the colors are handled as equally.

public Bitmap cropBorderFromBitmap(Bitmap bmp) {
            //Convenience variables
    int width = bmp.getWidth();
    int height = bmp.getHeight();

    int[] pixels = new int[height * width];

    //Load the pixel data into the pixels array
    bmp.getPixels(pixels, 0, width, 0, 0, width, height);

    int length = pixels.length;

    int borderColor = pixels[0];

    //Locate the start of the border
    int borderStart = 0;
    for(int i = 0; i < length; i ++) {

        // 1. Compare the color of two pixels whether they differ
        // 2. Check whether the difference is significant    
        if(pixels[i] != borderColor && !sameColor(borderColor, pixels[i])) {
            Log.i(TAG,"Current Color: " + pixels[i]);                   
            borderStart = i;
            break;
        }
    }

    //Locate the end of the border
    int borderEnd = 0;
    for(int i = length - 1; i >= 0; i --) {
        if(pixels[i] != borderColor && !sameColor(borderColor, pixels[i])) {
            Log.i(TAG,"Current Color: " + pixels[i]);
            borderEnd = length - i;
            break;
        }
    }

    //Calculate the margins
    int leftMargin = borderStart % width;
    int rightMargin = borderEnd % width;
    int topMargin = borderStart / width;
    int bottomMargin = borderEnd / width;

    //Create the new, cropped version of the Bitmap
    bmp = Bitmap.createBitmap(bmp, leftMargin, topMargin, width - leftMargin - rightMargin, height - topMargin - bottomMargin);
    return bmp;
}

private boolean sameColor(int color1, int color2){
    // Split colors into RGB values
    long r1 = (color1)&0xFF;
    long g1 = (color1 >>8)&0xFF;
    long b1 = (color1 >>16)&0xFF;

    long r2 = (color2)&0xFF;
    long g2 = (color2 >>8)&0xFF;
    long b2 = (color2 >>16)&0xFF;

    long dist = (r2 - r1) * (r2 - r1) + (g2 - g1) * (g2 - g1) + (b2 - b1) *(b2 - b1);

    // Check vs. threshold 
    return dist < 200;
}
Flo
  • 27,355
  • 15
  • 87
  • 125

4 Answers4

2

Perhaps not the best use of the APIs to find a solution, but the one that came to mind: directly modify the image's pixels.

You can get a Bitmap's pixels with getPixels() and then create a new, cropped Bitmap with createBitmap(). Then, it's just a matter of finding the dimensions of the border.

You can find the color of the border by accessing the pixel located at position 0, and then compare that value (an int) to the value of each proceeding pixel until your reach the border (the pixel that isn't that color). With a little bit of math, it can be done.

Here is some simple code that demonstrates the point:

private void cropBorderFromBitmap(Bitmap bmp) {
    int[] pixels;
    //Load the pixel data into the pixels array
    bmp.getPixels(pixels, 0, width, 0, 0, width, height);

    //Convenience variables
    int width = bmp.getWidth();
    int height = bmp.getHeight();
    int length = pixels.length;

    int borderColor = pixels[0];

    //Locate the start of the border
    int borderStart;
    for(int i = 0; i < length; i ++) {
        if(pixels[i] != borderColor) {
            borderStart = i;
            break;
        }
    }

    //Locate the end of the border
    int borderEnd;
    for(int i = length - 1; i >= 0; i --) {
        if(pixels[i] != borderColor) {
            borderEnd = length - i;
            break;
        }
    }

    //Calculate the margins
    int leftMargin = borderStart % width;
    int rightMargin = borderEnd % width;
    int topMargin = borderStart / width;
    int bottomMargin = borderEnd / width;

    //Create the new, cropped version of the Bitmap
    bmp = createBitmap(bmp, leftMargin, topMargin, width - leftMargin - rightMargin, height - topMargin - bottomMargin);
}

This is untested and lacks error checking (e.g., what if the width is 0?), but it should serve as a proof-of-concept.

EDIT: I just realized that I failed to complete the getPixels() method. The wonders of testing your code... it's fixed now.

calsign
  • 341
  • 3
  • 12
  • Hey, your solution is nearly perfect! Great! – Flo Sep 04 '12 at 20:04
  • I realized that the border color changes a little bit near the inner image and is not a pure white any more. This leads to the problem, that the border is not cropped completely. So I improved the code by not only comparing the colors but also by calculating the color distance between the both colors. I can then check whether the distance reaches a certain threshold so I can be assure that the color has really changed significant. – Flo Sep 04 '12 at 20:58
  • Perhaps you can update my response (or should I do it?) with your threshold solution. – calsign Sep 04 '12 at 21:04
  • I updated my question with the complete code. Thx again for you help! – Flo Sep 05 '12 at 08:02
2

If the frame around your picture is uniform then all you need to do is investigate when the pixels in the image change. But first thing's first - you need to have a BufferedImage object to work with. It's a class that allows you to traverse the bitmap of an image (http://docs.oracle.com/javase/6/docs/api/java/awt/image/BufferedImage.html). If you have the image saved as a file you need to call this method:

BufferedImage bimage = ImageIO.read(new File(file));

Now you can fetch the bitmap array from the bimage:

bimage.getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)

like this:

int[] rgb = bimage.getRGB(0, 0, bimage.getWidth(), bimage.getHeight(), null, 0, bimage.getWidth());

There could be some issues here with ColorModel so be sure to read up on your documentation of how to fetch the appropriate rgb from different file types.

Now that you have the rgb array you should start searching how far the frame stretches out from the middle of the picture. Keep in mind that this a single dimensional array - all the lines are written here sequentially one after another - as if you sliced the picture into lines 1pixel heigh and glued them together to form one long line.

This actually works to our advantage because the first different pixel we encounter in this table will work as a great reference point.

So now we just do something like this:

int pixel1=0,pixel2=0, i=0;
while(pixel1==pixel2 && i<bimage.getWidth()*bimage.getHeight()){
    pixel1=pixel2;
    pixel2=rgb[i++];
}

So now if the frame of your image is uniform, the top offset is the same as the bottom offset and the left offset is the same as the right offset then the number in the variable i is very likely to be the first pixel in the green rectangle.

In order to know which row and which column it is you need the following code:

 int row= i%bimage.getWidth();
 int column= i - row*bimage.getWidth();

Now the problem is that you may have an image embedded in the frame that in it's left upper corner is of the same color as the frame - so for example an image of a green rectangle with white corners in a white frame. Is this the case?

norbitheeviljester
  • 952
  • 1
  • 6
  • 17
  • Thx great answer. Good point to start. Regarding the last comment, it can happen that any side of the inner image might be white. I might need a more robust solution here. Perhaps it might be good start the checking at several positions, e.g. at the top, middle and bottom of the left side. When the x-coordinate of the first non-white pixel for the check on the top left side is larger than on the middle of the left side, then I know that the top left corner of the inner image might be white, so I take the x-coordinate of the latter check as a reference point. – Flo Sep 04 '12 at 08:12
0

You can use the public int getPixel (int x, int y) function which return for every pixel its color
It should be easy to run through the border lines and verify that the color is still the same

Izack
  • 823
  • 7
  • 13
0

This is my solution:

    private Bitmap cropBorderFromBitmap(Bitmap bmp) {

        final int borderWidth = 10; //preserved border width
        final int borderColor = -1; //WHITE

        int width = bmp.getWidth();
        int height = bmp.getHeight();

        int[] pixels = new int[width * height];
        bmp.getPixels(pixels, 0, width, 0, 0, width, height);

        int minX = -1;
        int minY = -1;
        int maxX = -1;
        int maxY = -1;

        for(int y = 0; y < height; y++) {
            for(int x = 0; x < width; x++) {
                if(bmp.getPixel(x,y) != borderColor) {
                    minX = (minX == -1) ? x : Math.min(x, minX);
                    minY = (minY == -1) ? y : Math.min(y, minY);

                    maxX = (maxX == -1) ? x : Math.max(x, maxX);
                    maxY = (maxY == -1) ? y : Math.max(y, maxY);
                }
            }
        }

        minX = Math.max(0, minX - borderWidth);
        maxX = Math.min(width, maxX + borderWidth);
        minY = Math.max(0, minY - borderWidth);
        maxY = Math.min(height, maxY + borderWidth);

        //Create the new, cropped version of the Bitmap
        return Bitmap.createBitmap(bmp, minX, minY, maxX - minX, maxY-minY);
    }
Jaro
  • 1