BufferedImage swap red and blue channel - java

My goal is to swap the red and blue channels of a java BufferedImage.
Is there any way to achieve this other than inefficiently iterating over each pixel value und swapping the channels? I was thinking of some bitwise magic or some integrated function I don't know of.
Any help is appreciated.

Here's one solution, that's really fast, as it doesn't really change the data, only the way the data is displayed.
The trick is that the channel order (byte order) is controlled by the SampleModel. And you can change the sample model without actually changing the data, to make the same data display differently.
If you already have a BufferedImage, the easiest way to create a sample model with swapped channels, is creating a new child Raster, using the Raster.createWritableChild(...) method, and specifying the channel (or "band") order in the last parameter.
bgr.getRaster().createWritableChild(0, 0, bgr.getWidth(), bgr.getHeight(), 0, 0,
new int[]{2, 1, 0}); // default order is 0, 1, 2
In the example below, the image data is the same (if in doubt, try moving the painting part, after cloning the image, and see that the result is the same). Only the channels are swapped:
public static void main(String[] args) {
// Original
final BufferedImage bgr = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR);
// Paint something
Graphics2D graphics = bgr.createGraphics();
try {
graphics.setColor(Color.BLUE);
graphics.fillRect(0, 0, bgr.getWidth(), bgr.getHeight());
graphics.setColor(Color.YELLOW);
graphics.fillRect(0, 0, bgr.getWidth(), bgr.getHeight() / 3);
graphics.setColor(Color.GREEN);
graphics.fillRect(0, 0, bgr.getWidth() / 3, bgr.getHeight());
}
finally {
graphics.dispose();
}
// Clone, and swap BGR -> RGB
ColorModel colorModel = bgr.getColorModel();
WritableRaster swapped = bgr.getRaster().createWritableChild(0, 0, bgr.getWidth(), bgr.getHeight(), 0, 0,
new int[]{2, 1, 0}); // default order is 0, 1, 2
final BufferedImage rgb = new BufferedImage(colorModel, swapped, colorModel.isAlphaPremultiplied(), null);
System.err.println("bgr: " + bgr); // TYPE_3BYTE_BGR (5)
System.err.println("rgb: " + rgb); // TYPE_CUSTOM (0)
// Display it all
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new JLabel(new ImageIcon(bgr)), BorderLayout.WEST);
frame.add(new JLabel(new ImageIcon(rgb)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
PS: I know from the comments the OP don't need this, but if you really need to swap the channels of the pixel data for some reason (ie. needed by native library or so), the fastest would probably be to get the data, loop over and swap the red and blue (1st and 3rd) components:
byte[] data = ((DataBufferByte) bgr.getRaster().getDataBuffer()).getData();
for (int i = 0; i < data.length; i += 3) {
// Swap 1st and 3rd component
byte b = data[i];
data[i] = data[i + 2];
data[i + 2] = b;
}

Maybe you create an new instance of BufferedImage with the same WritableRaster raster and the same properties, but with a ColorModel with the colors swaped:
e.g:
ColorModel swappedColorModel = new DirectColorModel(24,
0x000000ff, // Red -> Blue
0x0000ff00, // Green
0x00ff0000, // Blue -> Red
0x0 // Alpha
);
BufferedImage swapedColorImage = new BufferedImage (swappedColorModel,
originalImage.getRaster(),
swappedColorModel.isAlphaPremultiplied(),
properties);
I didn't tried this code

Related

Convert RGB8888 to YUV420 with ColorMatrix.setRGB2YUV()

I'm trying to find a faster way of converting RGB to YUV using the Android SDK as the standard pixel for pixel methods are pretty slow in Java. ColorMatrices seem to be pretty efficient and I see there's a setRGB2YUV() method but I can't find any examples and the documentation simply says "Set the matrix to convert RGB to YUV" which is completely useless as usual.
Here's part of my initialization code, which is a little compicated slightly with arrays due to multithreading:
cacheBitmaps = new Bitmap[NumberOfThreads];
cacheCanvas = new Canvas[NumberOfThreads];
mRGB2YUV = new ColorMatrix();
cmfRGB2YUV = new ColorMatrixColorFilter(mRGB2YUV);
pRGB2YUV = new Paint();
pRGB2YUV.setColorFilter(cmfRGB2YUV);
for (int m=0; m< NumberOfThreads; m++) {
cacheBitmaps[m] = Bitmap.createBitmap(widthX,heightY, Config.ARGB_8888);
cacheCanvas[m] = new Canvas(cacheBitmaps[m]);
}
Later I use this to paint an RGB to a canvas with the specified paint:
cacheCanvas[n].drawBitmap(fb.frames[n].getAndroidBitmap(),0,0, pRGB2YUV);
I've also experimented using a standard matrix that shouldn't apply any changes to the RGB values like this:
float[] matrix = {
0, 1, 0, 0, 0, //red
0, 0, 1, 0, 0, //green
0, 0, 0, 1, 0, //blue
0, 0, 0, 0, 1 //alpha
};
mRGB2YUV.set(matrix);
Whatever I do I either get black, green or distorted frames in my output video (using JavaCV with FFMPEG and specifying AV_PIX_FMT_NV21 as a color format after copying the final bitmap to an IplImage).
Anyone know how to use this and if it's even possible or does what it says it does?

Change the alpha value of a pixel

I am trying to change the alpha value of a pixel to make a image half transparent. Here is my code:
for(int x=0;x<image1.getWidth();x++) {
for(int y=0;y<image1.getHeight();y++) {
int rgb = image1.getRGB(x, y);
rgb = rgb | 0x000000ff; // make the image blue.
rgb = rgb & 0x33ffffff; // make the image transparent.
System.out.println("before: " + Integer.toHexString(rgb));
image1.setRGB(x, y, rgb);
int now_rgb= image1.getRGB(x, y);
System.out.println("after: " + Integer.toHexString(now_rgb));
}
}
The output is something like that:
before: 331b1aff
after: ff1b1aff
before: 331918ff
after: ff1918ff
before: 331e1bff
after: ff1e1bff
before: 332623ff
after: ff2623ff
before: 332e29ff
after: ff2e29ff
As you can see, its seems like the setRGB omitted the alpha value and set it to "ff". How can I resolve this problem, and why does it happen in the first place?
It's probably because your BufferedImage's color model does not support alpha or possibly only uses a single bit for alpha.
Where did you get image from? Is its color model also ARGB? Use image.getColorModel().hasAlpha() to check. If not, make sure when you create your image it has an appropriate color model, and if it can't be changed, create a new image with the desired color model and copy the source image first.
Use an AlphaComposite:
BufferedImage img = //some code here
BufferedImage imgClone = //some clone of img, but let its type be BufferedImage.TYPE_INT_ARGB
Graphics2D imgCloneG = imgClone.createGraphics();
imgCloneG.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, 0.5f));
imgCloneG.drawImage(img, 0, 0, null);
//imgClone is now img at half alpha
imgClone can be made like this:
...
imgClone = new BufferedImage(img.getWidth(), img.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D imgCloneG = imgClone.createGraphics();
imgCloneG.drawImage(img, 0, 0, null);
imgCloneG.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, 0.5f));
...

How do I add 2 same size imagicons into one jlabel and combine them soone is on top of another?

does anyone know how to do this?
the imageicons are same demension but have transparency on one so that you can see the background icon.
tiles[w][h] = new JLabel();
if(tiles[10][15] == tiles[w][h]){
icon2 = new ImageIcon(Map.tileGrid[8][11]);
icon = new ImageIcon(Map.map[w][h]);
top.setIcon(icon2);
bottom.setIcon(icon);
top.setBounds(2, 0, 30, 30);
bottom.setBounds(0, 0, 30, 30);
resources.add(top, new Integer(1));
resources.add(bottom, new Integer(2));
tiles[w][h].add(resources);
}
Something like this, and because I haven't implemented a layout manager is that the reason why it doesn't show on my map?
Build a new ImageIcon from the combination:
// create the new image, canvas size is the max. of both image sizes (a and b are ImageIcons)
int w = a.getIconWidth();
int h = a.getIconHeight();
BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
// paint both images, preserving the alpha channels
Graphics g = combined.getGraphics();
g.drawImage(a.getImage(), 0, 0, null);
g.drawImage(b.getImage(), 0, 0, null);
ImageIcon result = new ImageIcon(combined);
You would then use result as the label's icon
you can use JLayer (Java7) based on JXLayer (Java6)
you can use OverlayLayout
use JLayeredPane (maybe not good way, but bunch of examples is based on)
JLabel haven't implemented any LayoutManager(in compare with JFrame or JPanel), you have to add proper LayoutManager

Java Convert ColorSpace of BufferedImage to CS_GRAY without using ConvertColorOp

I know its possible to convert an image to CS_GRAY using
public static BufferedImage getGrayBufferedImage(BufferedImage image) {
BufferedImageOp op = new ColorConvertOp(ColorSpace
.getInstance(ColorSpace.CS_GRAY), null);
BufferedImage sourceImgGray = op.filter(image, null);
return sourceImgGray;
}
however, this is a chokepoint of my entire program. I need to do this often, on 800x600 pixel images and takes about 200-300ms for this operation to complete, on average. I know I can do this alot faster by using one for loop to loop through the image data and set it right away. The code above on the other hand constructs a brand new 800x600 BufferedImage that is gray scale. I would rather just transform the image I pass in.
Does any one know how to do this with a for loop and given that the image is RGB color space?
ColorConvertOp.filter takes two parameters. The second parameter is also a BufferedImage, which will be the destination. If you pass a correct BufferedImage to the filter method it saves you from the hassle to create a fresh BufferedImage.
private static int grayscale(int rgb) {
int r = rgb >> 16 & 0xff;
int g = rgb >> 8 & 0xff;
int b = rgb & 0xff;
int cmax = Math.max(Math.max(r, g),b);
return (rgb & 0xFF000000) | (cmax << 16) | (cmax << 8) | cmax;
}
public static BufferedImage grayscale(BufferedImage bi) {
BufferedImage bout = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);
int[] rgbArray = new int[bi.getWidth() * bi.getHeight()];
rgbArray = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), rgbArray, 0, bi.getWidth());
for (int i = 0, q = rgbArray.length; i < q; i++) {
rgbArray[i] = grayscale(rgbArray[i]);
}
bout.setRGB(0, 0, bout.getWidth(), bout.getHeight(), rgbArray, 0, bout.getWidth());
return bout;
}
Whatever you're doing you are likely doing something wrong. You shouldn't be regenerating a buffered image over and over again. But, rather figuring out a scheme to simply update the buffered image, or take the original pixels from the original and just using the grayscale which is the max of the RGB components, in each of the sections.

Turn an array of pixels into an Image object with Java's ImageIO?

I'm currently turning an array of pixel values (originally created with a java.awt.image.PixelGrabber object) into an Image object using the following code:
public Image getImageFromArray(int[] pixels, int width, int height) {
MemoryImageSource mis = new MemoryImageSource(width, height, pixels, 0, width);
Toolkit tk = Toolkit.getDefaultToolkit();
return tk.createImage(mis);
}
Is it possible to achieve the same result using classes from the ImageIO package(s) so I don't have to use the AWT Toolkit?
Toolkit.getDefaultToolkit() does not seem to be 100% reliable and will sometimes throw an AWTError, whereas the ImageIO classes should always be available, which is why I'm interested in changing my method.
You can create the image without using ImageIO. Just create a BufferedImage using an image type matching the contents of the pixel array.
public static Image getImageFromArray(int[] pixels, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
WritableRaster raster = (WritableRaster) image.getData();
raster.setPixels(0,0,width,height,pixels);
return image;
}
When working with the PixelGrabber, don't forget to extract the RGBA info from the pixel array before calling getImageFromArray. There's an example of this in the handlepixelmethod in the PixelGrabber javadoc. Once you do that, make sure the image type in the BufferedImage constructor to BufferedImage.TYPE_INT_ARGB.
Using the raster I got an ArrayIndexOutOfBoundsException even when I created the BufferedImage with TYPE_INT_ARGB. However, using the setRGB(...) method of BufferedImage worked for me.
JavaDoc on BufferedImage.getData() says: "a Raster that is a copy of the image data."
This code works for me but I doubt in it's efficiency:
// Получаем картинку из массива.
int[] pixels = new int[width*height];
// Рисуем диагональ.
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
if (i == j) {
pixels[j*width + i] = Color.RED.getRGB();
}
else {
pixels[j*width + i] = Color.BLUE.getRGB();
//pixels[j*width + i] = 0x00000000;
}
}
}
BufferedImage pixelImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
pixelImage.setRGB(0, 0, width, height, pixels, 0, width);
I've had good success using java.awt.Robot to grab a screen shot (or a segment of the screen), but to work with ImageIO, you'll need to store it in a BufferedImage instead of the memory image source. Then you can call one static method of ImageIO and save the file. Try something like:
// Capture whole screen
Rectangle region = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage capturedImage = new Robot().createScreenCapture(region);
// Save as PNG
File imageFile = new File("capturedImage.png");
ImageIO.write(capturedImage, "png", imageFile);
As this is one of the highest voted question tagged with ImageIO on SO, I think there's still room for a better solution, even if the question is old. :-)
Have a look at the BufferedImageFactory.java class from my open source imageio project at GitHub.
With it, you can simply write:
BufferedImage image = new BufferedImageFactory(image).getBufferedImage();
The other good thing is that this approach, as a worst case, has about the same performance (time) as the PixelGrabber-based examples already in this thread. For most of the common cases (typically JPEG), it's about twice as fast. In any case, it uses less memory.
As a side bonus, the color model and pixel layout of the original image is kept, instead of translated to int ARGB with default color model. This might save additional memory.
(PS: The factory also supports subsampling, region-of-interest and progress listeners if anyone's interested. :-)
I had the same problem of everyone else trying to apply the correct answer of this question, my int array actually get an OutOfboundException where i fixed it adding one more index because the length of the array has to be widht*height*3 after this i could not get the image so i fixed it setting the raster to the image
public static Image getImageFromArray(int[] pixels, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
WritableRaster raster = (WritableRaster) image.getData();
raster.setPixels(0,0,width,height,pixels);
image.setData(raster);
return image;
}
And you can see the image if u show it on a label on a jframe like this
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(new JLabel(new ImageIcon(image)));
frame.pack();
frame.setVisible(true);
setting the image on the imageIcon().
Last advice you can try to change the Bufferedimage.TYPE_INT_ARGB to something else that matches the image you got the array from this type is very important i had an array of 0 and -1 so I used this type BufferedImage.TYPE_3BYTE_BGR

Categories

Resources