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;
}