0

If I have the RBG code of a number, such as -16777216 (black), how can I find other similar shades of black using this color code?

I'm trying to convert an image to monochrome by marking all pixels which are not -16777216 to be white. However, often there are varying shades of black which are found, but they are lost because they are not an exact match.

Edit: I'm having a bit of trouble. When I try to use this color to find shades of black, so I can ignore them while converting the other pixels to white, this is my result:

Source:

source

Result:

result

Code:

package test;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import javax.imageio.ImageIO;

public class Test
{       
    public static void main(String[] args)
    {
        try
        {
            BufferedImage source = ImageIO.read( new URL("http://i.imgur.com/UgdqfUY.png"));
            //-16777216 = black:
            BufferedImage dest = makeMonoChromeFast(source, -16777216);
            File result = new File("D:/result.png");
            ImageIO.write(dest, "png", result);
        }
        catch (Exception e)
        {
            e.printStackTrace();;
        }
    }

    public static BufferedImage makeMonoChromeFast(BufferedImage source, int foreground)
    {        
        int background = -1; //white;

        Color fg = new Color(foreground);

        int color = 0;
        for (int y = 0; y < source.getHeight(); y++)
        {
            for (int x = 0; x < source.getWidth(); x++)
            {
                color = source.getRGB(x, y);
                if ( color == foreground )
                    continue;
                if (! isIncluded(fg, color, 50))
                    source.setRGB(x, y, background);;
            }
        }

        return source;
    }

    public static boolean isIncluded(Color target, int pixelColor, int tolerance)
    {
        Color pixel = new Color(pixelColor);
        int rT = target.getRed();
        int gT = target.getGreen();
        int bT = target.getBlue();
        int rP = pixel.getRed();
        int gP = pixel.getGreen();
        int bP = pixel.getBlue();
        return(
            (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
            (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
            (bP-tolerance<=bT) && (bT<=bP+tolerance) );
    }
}
user2790209
  • 339
  • 2
  • 5
  • 17

2 Answers2

3

You might use this 'look for color with difference tolerance' method.

public static boolean isIncluded(Color target, Color pixel, int tolerance) {
    int rT = target.getRed();
    int gT = target.getGreen();
    int bT = target.getBlue();
    int rP = pixel.getRed();
    int gP = pixel.getGreen();
    int bP = pixel.getBlue();
    return(
        (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
        (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
        (bP-tolerance<=bT) && (bT<=bP+tolerance) );
}

Here it is used to get the outline (motorcycle-03.jpg) of the motorcycle (motorcycle.jpg), while stripping out the 'faint gray overlay'.

motorcycle.jpg

Original Image

motorcycle-03.png

Processed Image

ImageOutline.java

This code requires some patience (when running). See Smoothing a jagged path for code that does the same thing much faster.

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.geom.Area;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.Date;
import javax.swing.*;

/* Motorcycle image courtesy of ShutterStock
http://www.shutterstock.com/pic-13585165/stock-vector-travel-motorcycle-silhouette.html */
class ImageOutline {

    public static Area getOutline(BufferedImage image, Color color, boolean include, int tolerance) {
        Area area = new Area();
        for (int x=0; x<image.getWidth(); x++) {
            for (int y=0; y<image.getHeight(); y++) {
                Color pixel = new Color(image.getRGB(x,y));
                if (include) {
                    if (isIncluded(color, pixel, tolerance)) {
                        Rectangle r = new Rectangle(x,y,1,1);
                        area.add(new Area(r));
                    }
                } else {
                    if (!isIncluded(color, pixel, tolerance)) {
                        Rectangle r = new Rectangle(x,y,1,1);
                        area.add(new Area(r));
                    }
                }
            }
        }
        return area;
    }

    public static boolean isIncluded(Color target, Color pixel, int tolerance) {
        int rT = target.getRed();
        int gT = target.getGreen();
        int bT = target.getBlue();
        int rP = pixel.getRed();
        int gP = pixel.getGreen();
        int bP = pixel.getBlue();
        return(
            (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
            (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
            (bP-tolerance<=bT) && (bT<=bP+tolerance) );
    }

    public static BufferedImage drawOutline(int w, int h, Area area) {
        final BufferedImage result = new BufferedImage(
            w,
            h,
            BufferedImage.TYPE_INT_RGB);
        Graphics2D g = result.createGraphics();

        g.setColor(Color.white);
        g.fillRect(0,0,w,h);

        g.setClip(area);
        g.setColor(Color.red);
        g.fillRect(0,0,w,h);

        g.setClip(null);
        g.setStroke(new BasicStroke(1));
        g.setColor(Color.blue);
        g.draw(area);

        return result;
    }

    public static BufferedImage createAndWrite(
        BufferedImage image,
        Color color,
        boolean include,
        int tolerance,
        String name)
        throws Exception {
        int w = image.getWidth();
        int h = image.getHeight();

        System.out.println("Get Area: " + new Date() + " - " + name);
        Area area = getOutline(image, color, include, tolerance);
        System.out.println("Got Area: " + new Date() + " - " + name);

        final BufferedImage result = drawOutline(w,h,area);
        displayAndWriteImage(result, name);

        return result;
    }

    public static void displayAndWriteImage(BufferedImage image, String fileName) throws Exception {
        ImageIO.write(image, "png", new File(fileName));
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(image)));
    }

    public static void main(String[] args) throws Exception {
        final BufferedImage outline = ImageIO.read(new File("motorcycle.jpg"));
        BufferedImage crop = outline.getSubimage(17,35,420,270);
        displayAndWriteImage(crop, "motorcycle-01.png");

        BufferedImage crude = createAndWrite(crop, Color.white, false, 60, "motorcycle-02.png");

        BufferedImage combo = createAndWrite(crude, Color.red, true, 0, "motorcycle-03.png");
    }
}

With the code seen in the question, with a tolerance of 150, I see this.

enter image description here

Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • Any hints on what to set the tolerance as, to catch all shades of black while e.g ignoring gray? – user2790209 Sep 22 '13 at 09:24
  • Well, that example does, for certain definitions of 'black' & 'gray'. And that is what makes your comment ..vague. By 'gray' DYM a `Color(20,20,20)` is 'gray'? Is a color with one of R/G/B out by one to the other two (e.g. `Color(0,0,1)`) still 'black'? What are the exact rules being used to determine 'black' & 'gray' here? – Andrew Thompson Sep 22 '13 at 09:34
  • Just what you'd look at visually and would consider black vs gray. I don't know what the RGB values would be. – user2790209 Sep 22 '13 at 09:38
  • I'm having a bit of trouble with this, please see my edited question for details / code. – user2790209 Sep 22 '13 at 09:43
  • See edit. I used a tolerance of 150 to get a reasonable image, but did not bother trying 200. – Andrew Thompson Sep 22 '13 at 10:38
  • Thanks. Do you know what tolerance is used by `Graphics2D.drawImage()` when its used to draw a monochrome image (as in the last question)? That one seems to know perfectly what tolerance to use no matter what image I give it. But if I try to view its source code in Netbeans, it shows it as abstract and with no source. – user2790209 Sep 22 '13 at 11:10
  • *"Do you know what tolerance is used by `Graphics2D.drawImage()` when its used to draw a monochrome image (as in the last question)?"* Nope. But I'd use '50%'. Ultimately, if using a monochrome image works better, I'd recommend using it instead. – Andrew Thompson Sep 22 '13 at 11:24
  • It works better, but this is for a time sensitive app. It takes about 20-40 milliseconds, while using the one in this question takes hardly 1-2 milliseconds. – user2790209 Sep 22 '13 at 12:09
  • 1
    A lot of that code (and slowness) is due to the attempt to ascertain the ***outline*** of the color difference. The code on Smoothing a jagged path (link above) does the same much faster, but you do not need to do that at all here. Simply 1) create a new image 2) for the width X height (e.g. for each pixel) of the original image, determine if it is 'equals in range of black' 3) For the target image draw a black pixel if it is, white otherwise. -- That should be a lost faster, though I would imagine it is still not as fast as the simple 'to monochrome' formula used by the JRE. – Andrew Thompson Sep 22 '13 at 12:29
  • `the simple 'to monochrome' formula used by the JRE` where is that? The `TYPE_BYTE` method is not fast.. it takes 20-30 Milliseconds. – user2790209 Sep 22 '13 at 12:53
2

In general, I think that the way to go is use the sRGB to Grey-scale conversion formulae described on this Wikipedia page, and then choose a particular "grey" value as being the boundary between black and white. (The choice is up to you ...)

But say that you already have RGB values that represent grey-scale points, you should find that they all have equal red, green and blue values. If that is actually the case, then you simply need to pick one of the colour components of an RGB and compare it against the same colour value of your chosen "grey".

If you need to discriminate multiple shades of black, grey and white, then choose multiple boundary "colours".


Edit: I'm having a bit of trouble. When I try to use this color to find shades of black, so I can ignore them while converting the other pixels to white, this is my result:

What you are seeing there is the effects of anti-aliasing. There is actually very little "pure" black in the image. A lot of what looks black to the human eye is actually dark, or not so dark grey. You need to make your boundary colour (i.e. boundary between "black" and "not black") more grey.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • I'm sure what you said is correct, but its beyond my ability / knowledge to understand – user2790209 Sep 22 '13 at 10:11
  • Well read it again. I don't appreciate your "I'm to dumb" act :-) – Stephen C Sep 22 '13 at 10:15
  • How would I set the boundary color more gray? I.e would a tolerance of 200 do it? – user2790209 Sep 22 '13 at 10:21
  • Here's my question. This image was taken as a screenshot, copied to MS paint, and saved. If I had taken the image using `awt.Robot.captureScreenRegion, would it still have had the anti aliasing, or does the anti aliasing come from paint? – user2790209 Sep 22 '13 at 10:27
  • 1
    *"If I had taken the image using `awt.Robot.captureScreenRegion, would it still have had the anti aliasing.."* Yes, it sure would. Non-antialiased text looks horrid, and is rarely used. – Andrew Thompson Sep 22 '13 at 11:26
  • And anti aliased text looks horrid to computers trying to read it! – user2790209 Sep 22 '13 at 12:27
  • I would try various boundaries / shades of grey to see which one gives the best results. – Stephen C Sep 22 '13 at 14:39