Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - Convert Image to black and white - fails with bright colors

I'm attempting to convert an image to black and white only (not grey scale).

I've used this:

BufferedImage blackAndWhiteImage = new BufferedImage(
        dWidth.intValue(),
        dHeight.intValue(),
        BufferedImage.TYPE_BYTE_BINARY);
Graphics2D graphics = blackAndWhiteImage.createGraphics();
graphics.drawImage(colourImage, 0, 0, null);

return blackAndWhiteImage;

Everything fine, until I decided to try out brighter colors, like the Google logo for example:

googleLogo

and it came out with this:

goolgeLogoBroken1

Then I tried first to pass trough grey scale first using:

BufferedImage blackAndWhiteImage2 = new BufferedImage(
        dWidth.intValue(),
        dHeight.intValue(),
        BufferedImage.TYPE_USHORT_GRAY);

And it seemed to have saved the Blue color, but not the brightest (in this case yellow), and as you may see it decreased in quality:

goolgeLogoBroken1

Any suggestions are much appreciated; I believe what I am seeking for is to convert every colour to Black except White (which would be the background color), this is already done when applying TYPE_BYTE_BINARY removing the alpha channel.


EDIT: Maybe I have not explained very clear:

  • the final image has to have White background **1
  • every other color has to be converted to Black

**1 - there are some cases where the image is actually White on Black..which is annoying (whiteOnBlackExample) as it complicates a lot this process, and I will leave this later on, as priority now is to convert "normal" images.

What I did was, first strip out the alpha channel if it exists -> therefore convert the alpha channel to White; then convert every other color to Black

like image 874
4673_j Avatar asked May 11 '17 14:05

4673_j


2 Answers

If you use JavaFX you can use the ColorAdjust effect with brightness of -1 (minimum), which makes all the (non-white) colors black:

public class Main extends Application {

    Image image = new Image("https://i.stack.imgur.com/UPmqE.png");

    @Override
    public void start(Stage primaryStage) {
        ImageView colorView = new ImageView(image);
        ImageView bhView = new ImageView(image);

        ColorAdjust colorAdjust = new ColorAdjust();
        colorAdjust.setBrightness(-1);
        bhView.setEffect(colorAdjust);

        primaryStage.setScene(new Scene(new VBox(colorView, bhView)));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

enter image description here

These Effects are optimized so they are probably faster than what you would achieve by applying them manually.

Edit

Since your requirements are that

  1. any pixel which is not opaque should be transformed to white, and
  2. any pixel which is not white should be transformed to black,

the predesigned effects won't suit you as far as I can tell - they are too specific. You can do pixel by pixel manipulation:

WritableImage writableImage = new WritableImage(image.getPixelReader(), (int) image.getWidth(), (int) image.getHeight());
PixelWriter pixelWriter = writableImage.getPixelWriter();
PixelReader pixelReader = writableImage.getPixelReader();
for (int i = 0; i < writableImage.getHeight(); i++) {
    for (int j = 0; j < writableImage.getWidth(); j++) {
        Color c = pixelReader.getColor(j, i);
        if (c.getOpacity() < 1) {
            pixelWriter.setColor(j, i, Color.WHITE);
        }
        if (c.getRed() > 0 || c.getGreen() > 0 || c.getBlue() > 0) {
            pixelWriter.setColor(j, i, Color.BLACK);
        }
    }
}
ImageView imageView = new ImageView(writableImage);

enter image description here

Note that the order in which you apply the rules matter. A transparent non-white pixel will turn white if you apply 1 and then 2, but if you apply 2 and then 1 it will end up black. This is because the predefined WHITE and BLACK colors are opaque. You can manually set the red, green and blue values while not changing the alpha value instead. It all depends on your exact requirements.

Remember that due to lossy compression of some file formats you might not find true white in them at all, but a value which is close to true white and your eye won't be able to tell the difference.

like image 189
user1803551 Avatar answered Nov 02 '22 07:11

user1803551


Here is the example from my comment. At first open the input image and create a new one for output.

BufferedImage myColorImage = ImageIO.read(fileInput);
BufferedImage myBWImage = new BufferedImage(myColorImage.getWidth(), myColorImage.getHeight(), BufferedImage.TYPE_BYTE_BINARY);

Then iterate through all the pixels and compare rgb values with a threshold:

for (int x = 0; x < myColorImage.getWidth(); x++)
    for (int y = 0; y < myColorImage.getHeight(); y++)
        if (rgbToGray(myColorImage.getRGB(x, y), MODE.AVERAGE) > threshold)
            myBWImage.setRGB(x, y, 0);
        else
            myBWImage.setRGB(x, y, 0xffffff); 

Here is rgbToGray method implementation to compare with threshold:

private static int rgbToGray(int rgb, MODE mode) {
    // split rgb integer into R, G and B components
    int r = (rgb >> 16) & 0xff;
    int g = (rgb >> 8) & 0xff;
    int b = rgb & 0xff;
    int gray;
    // Select mode
    switch (mode) {
        case LIGHTNESS:
            gray = Math.round((Math.max(r, Math.max(g, b)) + Math.min(r, Math.min(g, b))) / 2);
            break;
        case LUMINOSITY:
            gray = Math.round(0.21f * r + 0.72f * g + 0.07f * b);
            break;
        case AVERAGE:
        default:
            gray = Math.round((r + g + b) / 3);
            break;
    }
    return gray;
}

An utility enum:

private enum MODE {
    LIGHTNESS, AVERAGE, LUMINOSITY
}

I got the following result:

Black and White converted

Note: for your google image even threshold = 1 is suitable, for other images you should pick another values from range [0..255]. For photos, most likely, more appropriate values are about 100-150. MODE also will affect the final result.

like image 42
Yevhen Danchenko Avatar answered Nov 02 '22 05:11

Yevhen Danchenko