0

I need to toggle on/off RGB channels of an image, but I am stuck and my code is buggy.

Can you help me figure out how to do this the right way? This is my code:

The function channels is called when 1 of 3 checkboxes has changed its state and provides the arguments which are true == selected

public void channels(boolean red, boolean green, boolean blue) {
    if (this.img != null) {// checks if the image is set
        char r = 0xFF, g = 0xFF, b = 0xFF;
        if (red == false) {
            r = 0x00;
        }
        if (green == false) {
            g = 0x00;
        }
        if (blue == false) {
            b = 0x00;
        }
        BufferedImage tmp = new BufferedImage(
                img.getWidth(),
                img.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        for (int i = 0; i < img.getWidth(); i++) {
            for (int j = 0; j < img.getHeight(); j++) {
                int rgb = img.getRGB(i, j);
                int red = (rgb >> 16) & r;
                int green = (rgb >> 8) & g;
                int blue = (rgb >> 0) & b;
                int gbr = (red << 16) | (green << 8) | blue;// EDITED
                tmp.setRGB(i, j, gbr);
            }
        }
        img = tmp;
        repaint();
    } else {
        //show error
    }
}

Thank you for your help!

rayryeng
  • 102,964
  • 22
  • 184
  • 193
Mark
  • 253
  • 1
  • 5
  • 22
  • looks good at first glance, what actually happens now when you turn one channel off? – Octopus May 15 '14 at 23:42
  • 2
    It looks like you're shifting in the bits wrong. Shouldn't it be: `int gbr = (red << 16) | (green << 8) | blue;`? You basically want to shift back in the same order as how you shifted out to begin with...right? Let me know if this works. If it does, I'll turn this into an answer so you can accept! :) – rayryeng May 15 '14 at 23:43
  • when I turn one channel off it is ok, but when I try to turn on/off other channels it gets very messy. For example I can't turn off and then on one channel and get the same image – Mark May 15 '14 at 23:44
  • @rayryeng yes, that's one problem and the other is that now I can't switch the channel on... – Mark May 15 '14 at 23:47
  • 2
    That's because once you have cleared the corresponding colour, there's no way for you to get it back. You'll need to store a copy of the original image somewhere. When it's time to turn the channel back on, simply copy the original pixel from the original image back. – rayryeng May 15 '14 at 23:48
  • thank you a lot @rayryeng! I am just a beginner at image processing, but this sure is fun to work with – Mark May 15 '14 at 23:54
  • You're very welcome! If I made my comment into an answer, would you consider accepting it? – rayryeng May 15 '14 at 23:56
  • of course, it helped me solve the problem – Mark May 15 '14 at 23:56
  • Placed my answer. Thanks! – rayryeng May 15 '14 at 23:57
  • 2
    Also consider `LookupOp`, seen in examples cited [here](http://stackoverflow.com/a/3528196/230513). – trashgod May 16 '14 at 00:41
  • @trashgod: Woah! I didn't know about that class. Cool! – rayryeng May 16 '14 at 00:43
  • 1
    @rayryeng: There's a snippet and illustration [here](http://stackoverflow.com/a/21764828/230513). – trashgod May 16 '14 at 00:52
  • @trashgod: Just looked at the example. Awesome. Thanks! – rayryeng May 16 '14 at 00:54

2 Answers2

2

How about this optimized version, with a lot less bit shifting?

public void channels(boolean showRed, boolean showGreen, boolean showBlue) {
    if (this.origImg!= null) {// checks if the image is set
        int channelMask = 0xff << 24 | (showRed ? 0xff : 0) << 16 | (showGreen ? 0xff : 0) << 8 | (showBlue ? 0xff : 0);

        BufferedImage tmp = new BufferedImage(origImg.getWidth(), origImg.getHeight(), BufferedImage.TYPE_INT_RGB);

        for (int i = 0; i < origImg.getWidth(); i++) {
            for (int j = 0; j < origImg.getHeight(); j++) {
                int rgb = origImg.getRGB(i, j);
                tmp.setRGB(i, j, rgb & channelMask);
            }
        }

        img = tmp;
        repaint();
    } else {
        //show error
    }
}

A faster approach yet, would probably be to use a channeled Raster, or at least a Raster configuration that allows band sub-sampling (see Raster.createChild(...) method, especially the last parameter).

LookupOp, as mentioned by @trashgod is also a good idea, and probably faster than the getRGB()/setRGB() approach.

Harald K
  • 26,314
  • 7
  • 65
  • 111
1

It looks like you're shifting in the bits wrong. Shouldn't it be: int gbr = (red << 16) | (green << 8) | blue;? You basically want to shift back in the same order as how you shifted out to begin with.

Also, once you have cleared the corresponding colour, there's no way for you to get it back. You'll need to store a copy of the original image somewhere. When it's time to turn the channel back on, simply copy the original pixel from the original image back.

Assuming that you have the original image stored somewhere as origImg, I would modify your for loop so that if the channel is toggled on, copy from the original image.

for (int i = 0; i < img.getWidth(); i++) {
    for (int j = 0; j < img.getHeight(); j++) {
        int rgb = img.getRGB(i, j);
        int origRGB = origImg.getRGB(i, j);              
        int redPixel = red ? (origRGB >> 16) & r : (rgb >> 16) & r;
        int greenPixel = green ? (origRGB >> 8) & g : (rgb >> 8) & g;
        int bluePixel = blue ? origRGB & b : rgb & b;
        int gbr = (redPixel << 16) | (greenPixel << 8) | bluePixel;
        tmp.setRGB(i, j, gbr); 
   }
}
rayryeng
  • 102,964
  • 22
  • 184
  • 193