Java - 将图像转换为黑白 - 失败,颜色鲜艳

时间:2021-12-24 00:20:53

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:

一切都很好,直到我决定尝试更亮的颜色,例如Google徽标:

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

and it came out with this:

它出来了:

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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:

并且它似乎保存了蓝色,但不是最亮的(在这种情况下是黄色),并且您可能会看到它的质量下降:

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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.

任何建议都非常感谢;我相信我所追求的是将每种颜色转换为黑色,除了白色(这将是背景颜色),这已经在应用TYPE_BYTE_BINARY删除alpha通道时完成。


EDIT: Maybe I have not explained very clear:

编辑:也许我没有解释清楚:

  • the final image has to have White background **1
  • 最终图像必须具有白色背景** 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.

** 1 - 在某些情况下,图像实际上是白色的黑色...这很烦人(whiteOnBlackExample),因为它使这个过程变得复杂很多,我将在稍后介绍,现在优先考虑转换“正常”图像。

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

我做的是,首先剥离alpha通道(如果它存在) - >因此将alpha通道转换为白色;然后将每个其他颜色转换为黑色

2 个解决方案

#1


4  

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

如果你使用JavaFX,你可以使用ColorAdjust效果,亮度为-1(最小),这使得所有(非白色)颜色变黑:

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);
    }
}

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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. 任何不透明的像素都应该变成白色,并且

  3. any pixel which is not white should be transformed to black,
  4. 任何非白色的像素都应该变成黑色,

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);

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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.

请注意,您应用规则的顺序很重要。如果您应用1然后应用2,则透明的非白色像素将变为白色,但如果应用2然后再应用1,则它将变为黑色。这是因为预定义的WHITE和BLACK颜色是不透明的。您可以手动设置红色,绿色和蓝色值,而不是更改Alpha值。这一切都取决于您的确切要求。

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.

请记住,由于某些文件格式的有损压缩,您可能根本找不到真正的白色,但是接近真正白色的值,您的眼睛将无法区分。

#2


2  

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:

然后迭代所有像素并将rgb值与阈值进行比较:

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:

这是rgbToGray方法实现与阈值进行比较:

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:

我得到以下结果:

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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.

注意:对于您的谷歌图片,即使阈值= 1也适用,对于其他图像,您应该从范围[0..255]中选择另一个值。对于照片,最有可能的是,更合适的值约为100-150。 MODE也会影响最终结果。

#1


4  

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

如果你使用JavaFX,你可以使用ColorAdjust效果,亮度为-1(最小),这使得所有(非白色)颜色变黑:

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);
    }
}

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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. 任何不透明的像素都应该变成白色,并且

  3. any pixel which is not white should be transformed to black,
  4. 任何非白色的像素都应该变成黑色,

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);

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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.

请注意,您应用规则的顺序很重要。如果您应用1然后应用2,则透明的非白色像素将变为白色,但如果应用2然后再应用1,则它将变为黑色。这是因为预定义的WHITE和BLACK颜色是不透明的。您可以手动设置红色,绿色和蓝色值,而不是更改Alpha值。这一切都取决于您的确切要求。

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.

请记住,由于某些文件格式的有损压缩,您可能根本找不到真正的白色,但是接近真正白色的值,您的眼睛将无法区分。

#2


2  

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:

然后迭代所有像素并将rgb值与阈值进行比较:

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:

这是rgbToGray方法实现与阈值进行比较:

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:

我得到以下结果:

Java  - 将图像转换为黑白 - 失败,颜色鲜艳

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.

注意:对于您的谷歌图片,即使阈值= 1也适用,对于其他图像,您应该从范围[0..255]中选择另一个值。对于照片,最有可能的是,更合适的值约为100-150。 MODE也会影响最终结果。