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:
and it came out with this:
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:
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:
**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
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);
}
}
These Effect
s are optimized so they are probably faster than what you would achieve by applying them manually.
Edit
Since your requirements are that
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);
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With