Search code examples
javacolorsrgbgrayscale

How can I convert an RGB image to grayscale but keep one color? - Java


I am trying to create an effect similar to Sin City or other movies where they remove all colors except one from an image.

I have an RGB image which I want to convert to grayscale but I want to keep one color.

This is my picture: Image to edit

I want to keep the red color. The rest should be grayscale.

Here is my code so far:

package poza;
import java.io.*;

public class poza {

    public static void main(String[] args) {
        
        try {
            FileInputStream fis=new FileInputStream("poza.bmp");
            BufferedInputStream dis=new BufferedInputStream(fis);
            FileOutputStream sif=new FileOutputStream("poza1.bmp");
            BufferedOutputStream bos=new BufferedOutputStream(sif);
            byte[] sti=new byte[54];
            dis.read(sti,0,54);
            bos.write(sti);
            while(dis.available()>0)
            {
                int b,g,r;
                b=dis.read();
                g=dis.read();
                r=dis.read();
                System.out.print(b+" "+g+" "+r+"\n");
                int gri=(int)(0.114*b+0.587*g+0.299*r);
                if(r>=b && r>=g)
                {
                    bos.write(gri);
                    bos.write(gri);
                    bos.write(r);
                }
                else
                {
                    bos.write(b);
                    bos.write(g);
                    bos.write(r);
                    
                }
                
                System.out.print(b+" "+g+" "+r+"\n");

            }
            dis.close();
            bos.close();
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
        
    }

}

Solution

  • You need to note that every color is represented by 3 values or channels, i.e. red, green and blue. If you only keep one of those channels you'll skew the results.

    Instead you need to decide whether a pixel should retain its original color or become grayscale. So assuming your code already does the conversion to grayscale correctly, it comes down to this:

    if( keepColor ) {
      //I'll keep the order of the components that your example uses, make sure this is correct
      bos.write(b);
      bos.write(g);
      bos.write(r);
    } else {
       bos.write(gri); //b
       bos.write(gri); //g
       bos.write(gri); //r
    }
    

    So what is left is how keepColor is defined and that depends on your requirements. A simple option might be to pick a color and check if the pixel is within a certain threshold of that color, e.g. like this:

    /**
      * value - the value to check
      * base - the base value to check against, i.e. the center of the range, expressed in range [0, 255]
      * difference - the allowable difference in percent, expressed in range [0.0, 1.0]
      */ 
    boolean withinThreshold(int value, int base, double difference) {
       return value>= base * (1-difference) //check lower bound
         && value <= base * (1+difference); //check upper bound
    }  
    

    Then pick a value and a difference, e.g. like this:

    boolean keepColor = withinThreshold(r, 228, 0.6) //40%-160% of 228 
                      && withinThreshold(g, 95, 0.6) //40%-160% of 95
                      && withinThreshold(b, 78, 0.6); //40%-160% of 78
    

    Of course there are many other possibilities you can play around with, i.e. different thresholds, different colors, based on brightness (grayscale value in a certain range) etc.