0

I'm trying to tackle a problem where an image is read. I need to exclude the Blue channel in the pixel so the it will result in an RG image.

This is what I have so far and not working D;

Would I need to create a new BufferedImage and apply each averaged pixel (Without the blue channel) to it? I'm not sure how to do it though.

  • So you want to take an image which is 2mX2n pixels and convert it to an image that is mXn and each pixel (in the final) is the average pixels of the original minus the blue component (i.e. the blue component will always be zero)? – Jared Apr 13 '14 at 04:43
  • Yes that is basically it! But I cannot seem to figure out how to do it. :S – user3526114 Apr 13 '14 at 05:02
  • A comment: averaging RGB values worsens image quality slightly. RGB values do not have a linear relation with brightness (a gamma curve has been applied). The result is darker than it should be. But doing it right is involved and much slower. Have a look if you're interested: http://www.4p8.com/eric.brasseur/gamma.html – Erwin Bolwidt Apr 13 '14 at 05:22
  • @ErwinBolwidt Averaging the RGB values is good enough to recreate an image with images (thumbnails) as each pixel fairly faithfully so I question the merits of doing it "correctly". – Jared Apr 13 '14 at 16:23

2 Answers2

2

For starters, change your second-to-last line of code to this and see if it helps:

image.setRGB(i, j, (redData[i][j] << 16) | (greenData[i][j] << 8));

But with this, you will end up with an image that is the same size as the original, except that you drop the blue channel and you average over the other channels (sort-of like a blur), but only in the top left corner of each 2x2-block.

If you want to actually create an image of a quarter size, you do indeed need to create a new BufferedImage with half the width and height and then change the above mentioned line to:

newImage.setRGB(i/2, j/2, (redData[i][j] << 16) | (greenData[i][j] << 8));

Here's an updated version of your code that should do it (untested):

public static BufferedImage isolateBlueChannelAndResize(BufferedImage image) {
    int imageWidth  = image.getWidth();
    int imageHeight = image.getHeight();
    // Round down for odd-sized images to prevent indexing outside image
    if ((imageWidth  & 1) > 0) imageWidth --;
    if ((imageHeight & 1) > 0) imageHeight--;

    BufferedImage newImage = new BufferedImage(imageWidth/2, imageHeight/2, image.getType());

    for (int i = 0; i < imageWidth; i += 2) {
        for (int j = 0; j < imageHeight; j += 2) {
            int r1 = (image.getRGB(i  , j  ) >> 16) & 0xff;
            int r2 = (image.getRGB(i+1, j  ) >> 16) & 0xff;
            int r3 = (image.getRGB(i  , j+1) >> 16) & 0xff;
            int r4 = (image.getRGB(i+1, j+1) >> 16) & 0xff;
            int red = (r1 + r2 + r3 + r4 + 3) / 4;   // +3 rounds up (equivalent to ceil())

            int g1 = (image.getRGB(i  , j  ) >>  8) & 0xff;
            int g2 = (image.getRGB(i+1, j  ) >>  8) & 0xff;
            int g3 = (image.getRGB(i  , j+1) >>  8) & 0xff;
            int g4 = (image.getRGB(i+1, j+1) >>  8) & 0xff;
            int green = (g1 + g2 + g3 + g4 + 3) / 4;   // +3 rounds up (equivalent to ceil()) 

            newImage.setRGB(i/2, j/2, (red << 16) | (green << 8));
        }
    }

    return newImage;
}
Markus A.
  • 12,349
  • 8
  • 52
  • 116
  • Thanks let me try implement it ;D – user3526114 Apr 13 '14 at 05:05
  • I created a new image using: `BufferedImage newImage = new BufferedImage(imageWidth/2, imageHeight/2, image.getType());` and set the pixels using your above line: `newImage.setRGB(i/2, j/2, (redData[i][j] << 16) | (greenData[i][j] << 8));` but it outputted the original image, same size and everything without the yellowish hue that was expected – user3526114 Apr 13 '14 at 05:14
  • 1
    Did you change the line that says `return image;` to `return newImage;`? ;) – Markus A. Apr 13 '14 at 05:14
  • You're welcome! Check out the code I posted also, you can save yourself a bunch of array-creation, etc. to improve performance. :) – Markus A. Apr 13 '14 at 05:18
  • What are you going to do when Java decides to change their RGB format? This is highly unlike, I agree, but there must be some reason that Java does _not_ provide methods for converting an integer RGB value to the red/green/blue components (and I still don't get why they don't). – Jared Apr 13 '14 at 05:35
  • @Jared The docs specify the returned format (http://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html#getRGB(int,%20int)), so they can't just change it. Worst case scenario is that they deprecate this function and introduce a new one with a different name in Java 9 and then remove this one many years/decades later... :) – Markus A. Apr 13 '14 at 05:37
  • @MarkusA. I agree with you, that this is fine, I just think it's cleaner and more readable to use the `Color` class. You have to question the optimization of not using the `Color` class, OK, you're going to create a bunch of `Color` objects that may need to be garbage collected. Each `Color` object takes up 4 + 4/8 bytes, so for a an image that is say 2MB, this means 4MB/8MB worth of "garbage", but when normal RAM is on the order of GB, this is insignificant. – Jared Apr 13 '14 at 05:44
  • @Jared Technically, the Color class are the methods for converting an integer RGB value to the components. In fact, you used getRGB in your code as well and passed its result to the constructor. ;) – Markus A. Apr 13 '14 at 05:44
  • @MarkusA. Yes, I passed the `int` RGB value to the `Color` class--this is actually my objection that I don't understand. There should be static methods which get the red, green, and blue components from an `int` RGB value--like I said, I don't get why those aren't library functions--you shouldn't need to do the shifts/masks manually. – Jared Apr 13 '14 at 05:48
  • 1
    @Jared Agreed... I started programming in the 80's when the luxury of writing more readable code vs. higher performing code did not exist. So, I still feel a bit uneasy about throwing around a bunch of unnecessary objects and function calls. And especially if you try to process a bunch of huge images, speed does start to matter. Also, I just stuck with the approach that the OP used in the question... – Markus A. Apr 13 '14 at 05:48
  • 1
    @Jared No clue why they don't provide the methods... But I'm sure some Apache Commons library somewhere does. ;) My guess is: If you are using these functions to begin with (especially the array versions), it's because you need to do a lot of processing quickly. And then the last thing you want to do is replace a simple shift operation with a method call... In fact, I shouldn't even be calling getRGB(...) twice for the same pixel in my code above... – Markus A. Apr 13 '14 at 05:52
  • @MarkusA. Sorry to continue this, but there is no reason that a compiler could not optimize a static method like this so that it was inline (just like `inline` functions in C--I mean this is basically a #def function). There's no way you would need a method-call overhead for something like this. But, as it stands, Java doesn't provide those static methods so I certainly believe that doing the shifts/mask is going to be faster than calling the `Color` methods (and this _is_ significant when you're going to do it a million times--especially if you're processing several pictures). – Jared Apr 13 '14 at 05:58
  • 1
    @Jared I wish Java had an @ Inline-annotation to force the compiler to do just that. But unfortunately it doesn't and you can't rely on the compiler doing anything there: http://stackoverflow.com/questions/2096361/are-there-inline-functions-in-java . So, depending on the JVM you are going to be running on, anything may happen. No idea, for example, how Android's Dalvik would handle it. And either way it will increase start-up time. But in general, you should easily be able to write yourself these static methods and stick them somewhere in your own library collection. ;) – Markus A. Apr 13 '14 at 06:06
  • @MarkusA. http://pastebin.com/aseyyr2V I've run into another problem. So with the code you revised for me I created a program which creates 4 copies of an original image and seperates an RGB from one of the 4 (you can try running it!) and meshes them together into an image the size of the original Example: http://puu.sh/86Ny0.png, where the top left is the original image. But i've run into a problem where for some images the output is pitch black! Am i doing something wrong? – user3526114 Apr 13 '14 at 09:27
  • @user3526114 Very strange... At first glance I can't see anything obviously wrong with your code, but I can take another look later. Did you try outputting the different resized images? Did they come out OK? BTW: You can probably increase performance significantly (and fix the bug) if you don't draw the four different images separately and then stamp them into the corners of a bigger one. Instead, just draw the final image directly! That way, you're also calculating averageRed (and so on) only once rather than 3 times. – Markus A. Apr 13 '14 at 16:04
0

I really think the easier way is to use the Color class:

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class AverageOutBlue {
    public static final float AVG_FACTOR = 1f / (4f * 255f);

    public static BufferedImage processImage(final BufferedImage src) {
        final int w = src.getWidth();
        final int h = src.getHeight();

        final BufferedImage out = new BufferedImage(w / 2, h / 2, src.getType());

        for (int i = 0; i < w; i += 2)
            for (int j = 0; j < h; j += 2) {
                final Color color = avgColor(src, i, j);
                out.setRGB(i / 2, j / 2, color.getRGB());
            }

        return out;
    }

    private static Color avgColor(final BufferedImage src, final int i,
        final int j) {

        final Color c1 = new Color(src.getRGB(i, j));
        final Color c2 = new Color(src.getRGB(i + 1, j));
        final Color c3 = new Color(src.getRGB(i + 1, j));
        final Color c4 = new Color(src.getRGB(i + 1, j + 1));

        final float r = (c1.getRed() + c2.getRed() + c3.getRed() + c4.getRed())
                            * AVG_FACTOR;
        final float g = (c1.getGreen() + c2.getGreen() + c3.getGreen() + 
                            c4.getGreen()) * AVG_FACTOR;

        return new Color(r, g, 0f);
    }
}
Jared
  • 940
  • 5
  • 9
  • If you are dead set against using the `Color` class, then you can average the pixels given the `int` RGB value as you attempted to do above (with shifts and masks) and then return an integer instead of a `Color` object. – Jared Apr 13 '14 at 05:06