java.lang.IllegalArgumentException: More than one component per pixel - java

I'm new to image processing in Java. I'm trying to compare two images with the code below and getting the message following the code. Any help is greatly appreciated. Thanks.
BufferedImage imgOrig = ImageIO.read(new URL(imgOrigUrl));
BufferedImage imgComp = ImageIO.read(new URL(imgCompUrl));
byte[] pixelsOrig = ((DataBufferByte) imgOrig.getRaster().getDataBuffer()).getData();
byte[] pixelsComp = ((DataBufferByte) imgComp.getRaster().getDataBuffer()).getData();
//System.out.println("Number of pixels orig:"+pixelsOrig.length);
//System.out.println("Number of pixels comp:"+pixelsComp.length);
ColorModel cmImgOrig = imgOrig.getColorModel();
ColorModel cmImgComp = imgComp.getColorModel();
int sum1 = 0;
int sum2 = 0;
for(int i:pixelsOrig){
System.out.println(cmImgOrig.getGreen(i)); //ERROR OCCURS HERE
//System.out.println(i);
}
ERROR:
Testcase: testCompareImages(com.myapp.img.compare.service.CompareServiceTest): Caused an ERROR
More than one component per pixel
java.lang.IllegalArgumentException: More than one component per pixel
at java.awt.image.ComponentColorModel.getRGBComponent(ComponentColorModel.java:594)
at java.awt.image.ComponentColorModel.getGreen(ComponentColorModel.java:675)
at com.scottmacri.img.compare.service.CompareService.compareImages(CompareService.java:42)
at com.scottmacri.img.compare.service.CompareServiceTest.testCompareImages(CompareServiceTest.java:45)

Like #Nathan Villaescusa said, the method you are using is expecting a single channel. Do you need the byte array or the color channel? If you only need color components you can do the following:
BufferedImage imgOrig = ImageIO.read(new URL(imgOrigUrl));
BufferedImage imgComp = ImageIO.read(new URL(imgCompUrl));
for (int y = 0; y < imgOrig.getHeight(); y++)
{
for (int x = 0; x < imgOrig.getWidth(); x++)
{
System.out.println(imgOrig.getRGB(x, y) >> 8 & 0xff);
}
}
where the int returned by getRGB(x, y) can be shifted to get the RGB and alpha components like so:
int a = rgb >> 32 & 0xff;
int r = rgb >> 16 & 0xff;
int g = rgb >> 8 & 0xff;
int b = rgb & 0xff;

It looks like that error is being thrown because your ColorSpace has more than 1 component, yet you are only passing in a single value to check.
You want to use the getGreen() method of ColorComponentModel that accepts a Object, not the one that accepts an int. I think the method one that accepts an int is for use with gray scale.
According to this answer, here is how to get pixel data using this method:
Raster r = imgOrig.getData();
SampleModel sm = r.getSampleModel();
int width = sm.getWidth();
int height = sm.getHeight();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Object pixel = sm.getPixel(x, u, (int[])null, r.getDataBuffer());
System.out.println(cmImgOrig.getGreen(pixel));
}
}

Related

Get center pixel from image with format YUV

I made image converting from YUV(NV21 Android) to RGB
And i tried get color of pixel by x,y cordinates.
For example i tried get pixel from center. But i get color from wrong place.
Collect image YUV
ByteBuffer yBuff = image.getPlanes()[0].getBuffer();
ByteBuffer uBuff = image.getPlanes()[1].getBuffer();
ByteBuffer vBuff = image.getPlanes()[2].getBuffer();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] yByte = new byte[yBuff.remaining()];
byte[] uByte = new byte[uBuff.remaining()];
byte[] vByte = new byte[vBuff.remaining()];
yBuff.get(yByte);
uBuff.get(uByte);
vBuff.get(vByte);
// Create converting byte[] NV21
try {
outputStream.write(yByte);
for (int i=0; i < uByte.length; i++) {
outputStream.write(vByte[i]);
outputStream.write(uByte[i]);
}
} catch (Exception e) {
e.printStackTrace();
}
byte[] imageBytes = outputStream.toByteArray();
What i have in current moment.
// Coordinates
x = width / 2;
y = height / 2;
// Get YUV coordinates for x y position
int YCord = y * width + x;
int UCord = (y >> 1) * (width) + x + total + 1;
int VCord = (y >> 1) * (width) + x + total;
// Get YUV colors
int Y = (0xFF & imageBytes[YCord]) - 16;
int U = (0xFF & imageBytes[UCord]) - 128;
int V = (0xFF & imageBytes[VCord]) - 128;
I expect color from center of image. But i have color from different place
https://github.com/lirugo/screenDetector
i found solution if some one want more, see my code by link about get pixel by x,y coordinates code below
x = 210; // might be 250
y = height - 215; // might be 150
// Get YUV coordinates for x y position
int YCord = y*width+x;
int UCord = ((y/2)*width+(x&~1)+total+1);
int VCord = ((y/2)*width+(x&~1)+total);

Why BufferedImage is not working well?!! Is it because I misused it?

I want to copy a gray image using BufferedImage from getRGB() to int[][] and then to setRGB(). The problem is that the size of image is different from the size of the one that the program outputs it. The original image has file size = 176 KB, whereas the output image has file size = 154 KB. I have to say that when you see the two image, all of the human-being would say it is the same, but in terms of binary bits, there are different in something that I would like to know.
Maybe some of you will say it doesn't matter, as long as image is the same when you look at it. In fact, during the processing of some noise project, this is a huge problem, and I suspect that this is the reason why I have the problem.
I just want to know if there are other method than BufferedImage to produce int[][] and then to create the output?
This is the code that I'm using:
public int[][] Read_Image(BufferedImage image)
{
width = image.getWidth();
height = image.getHeight();
int[][] result = new int[height][width];
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++)
result[row][col] = image.getRGB(row, col);
return result;
}
public BufferedImage Create_Gray_Image(int [][] pixels)
{
BufferedImage Ima = new BufferedImage(512,512, BufferedImage.TYPE_BYTE_GRAY);
for (int x = 0; x < 512; x++)
{
for (int y = 0; y < 512; y++)
{
int rgb = pixels[x][y];
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
int grayLevel = (r + g + b) / 3;
int gray = (grayLevel << 16) + (grayLevel << 8) + grayLevel;
Ima.setRGB(x, y, pixels[x][y]);
}
}
return Ima;
}
public void Write_Image(int [][] pixels) throws IOException
{
File outputfile;
outputfile = new File("Y0111.png");
BufferedImage BI = this.Create_Gray_Image(pixels);
ImageIO.write(BI, "png", outputfile);
System.out.println("We finished writing the file");
}
See the figure, you see file size = 176 KB (this is the original image) and file size = 154 KB (this is the output image).
The difference of size is not a problem. It's certainly because of different compression/encoding.
A BufferedImage is in fact a 1D array of size width * height * channel. getRGB is not the easiest/fastest way to manipulate a BufferedImage. You can use the Raster (faster than getRGB, not the fastest, but it takes care of the encoding for you). For a gray level image:
int[][] my array = new int[myimage.getHeight()][myimage.getWidth()] ;
for (int y=0 ; y < myimage.getHeight() ; y++)
for (int x=0 ; x < myimage.getWidth() ; x++)
myarray[y][x] = myimage.getRaster().getSample(x, y, 0) ;
The opposite way:
for (int y=0 ; y < myimage.getHeight() ; y++)
for (int x=0 ; x < myimage.getWidth() ; x++)
myimage.getRaster().setSample(x, y, 0, myarray[y][x]) ;
The fastest way to do it is to use the DataBuffer, but then you have to handle the image encoding.

Two-dimensional RGB array to BufferedImage

I have a custom RGB class:
class RGB {
int R, G, B;
}
I make a two-dimensional array of RGB objects representing the image:
RGB[][] image = new RGB[HEIGHT][WIDTH];
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
int pixel = bufferedImage.getRGB(i, j);
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel) & 0xff;
image[i][j] = new RGB(red, green, blue);
}
}
Now I want to make some changes to this array and save it as a BufferedImage.
Basicly, I can make something like this:
BufferedImage newImage = new BufferedImage(HEIGHT, WIDTH, BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
newImage.setRGB(i, j, VALUE);
}
}
But I need to convert RGB fields of each pixel to one integer VALUE, which I don't know how to do.
Or maybe is there a simplier way to do this?
Be careful, the function getRGB is bufferedImage.getRGB(x, y), and you do the opposite (x and y inverted).
Now you can either work using the Raster or the DataBuffer:
newimage.getRaster().setSample(x, y, 0, VALUE)
int[] newimagebuffer = ((DataBufferInt)newimage.getRaster().getDataBuffer).getData() and then newimagebuffer[x+y*WIDTH] = VALUE.
If you don't know the image type and you don't want to duplicate the code, I recommend the Raster, but else, it definitely faster to access and modify the image values using the DataBuffer because you have direct access to the array. And TYPE_INT_RGB is not the most practical image format because you have to uncompress/compress the triplet RGB into an int at each time. You can use TYPE_3BYTE_BGR.

Why raster.setPixels() is returning a grayscale image

Here i'm trying to do a fastest method to save 3 matrix(R, G and B) into a BufferedImage.
I've found this method here at StackExchange, but it doesn't work for me because the image it's being saved in a grayscale color.
If I'm doing something wrong or if there's a way of doing this faster than bufferimage.setRGB(), please help me. Thanks!
public static BufferedImage array_rasterToBuffer(int[][] imgR,
int[][]imgG, int[][] imgB) {
final int width = imgR[0].length;
final int height = imgR.length;
int numBandas = 3;
int[] pixels = new int[width*height*numBandas];
int cont=0;
System.out.println("max: "+width*height*3);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
for (int band = 0; band < numBandas; band++) {
pixels[(((i*width)+j)*numBandas +band)] =Math.abs(( (imgR[i][j] & 0xff) >> 16 | (imgG[i][j] & 0xff) >> 8 | (imgB[i][j] & 0xff)));
cont+=1;
}
}
}
BufferedImage bufferImg = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
WritableRaster rast = (WritableRaster) bufferImg.getData();
rast.setPixels(0, 0, width, height, pixels);
bufferImg.setData(rast);
return bufferImg;
}
I think you are getting grey because the expression
Math.abs(( (imgR[i][j] & 0xff) >> 16 | (imgG[i][j] & 0xff) >> 8 | (imgB[i][j] & 0xff)));
does not depend on band, so your rgb values are all the same.
The expression looks dodgy anyway because you normally use the left shift operator << when packing rgb values into a single int.
I don't know for sure, as I'm not familiar with the classes you are using, but I'm guessing something like this might work
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
pixels[(((i*width)+j)*numBandas)] = imgR[i][j] & 0xFF;
pixels[(((i*width)+j)*numBandas + 1)] = imgG[i][j] & 0xFF;
pixels[(((i*width)+j)*numBandas + 2)] = imgB[i][j] & 0xFF;
}
}
If you want a faster approach, you need to get the "live" WritableRaster from the BufferedImage and set pixels in the "native" format of the image, which is "pixel packed" for TYPE_INT_RGB. This will save you multiple (at least two) array copies and some data conversion. It will also save you 2/3rds of the memory used for the conversion, as we only need a single array component per pixel.
The below method should be quite a bit faster:
public static BufferedImage array_rasterToBuffer(int[][] imgR, int[][] imgG, int[][] imgB) {
final int width = imgR[0].length;
final int height = imgR.length;
// The bands are "packed" for TYPE_INT_RGB Raster,
// so we need only one array component per pixel
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// "Pack" RGB values to native TYPE_INT_RGB format
// (NOTE: Do not use Math.abs on these values, and without alpha there won't be negative values)
pixels[((y * width) + x)] = ((imgR[y][x] & 0xff) << 16 | (imgG[y][x] & 0xff) << 8 | (imgB[y][x] & 0xff));
}
}
BufferedImage bufferImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// NOTE: getRaster rather than getData for "live" view
WritableRaster rast = bufferImg.getRaster();
// NOTE: setDataElements rather than setPixels to avoid conversion
// This requires pixels to be in "native" packed RGB format (as above)
rast.setDataElements(0, 0, width, height, pixels);
// No need for setData as we were already working on the live data
// thus saving at least two expensive array copies
return bufferImg;
}
// Test method, displaying red/green/blue stripes
public static void main(String[] args) {
int[][] fooR = new int[99][99];
int[][] fooG = new int[99][99];
int[][] fooB = new int[99][99];
for (int i = 0; i < 33; i++) {
Arrays.fill(fooR[i], 0xff);
Arrays.fill(fooG[i + 33], 0xff);
Arrays.fill(fooB[i + 66], 0xff);
}
BufferedImage image = array_rasterToBuffer(fooR, fooG, fooB);
showIt(image);
}
// For demonstration only
private static void showIt(final BufferedImage image) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("JPEGTest");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JScrollPane scroll = new JScrollPane(new JLabel(new ImageIcon(image)));
scroll.setBorder(BorderFactory.createEmptyBorder());
frame.add(scroll);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
It is possible to optimize this further, if you don't need a "managed" (possible hardware accelerated for display) image. The trick is to create the image directly "around" your pixels array, thus saving one more array allocation and array copy in setDataElements. The downside is that in some cases the image will be a little slower to draw onto the screen. This is mainly a concern for games or smooth animations though.
Replace the lines from BufferedImage bufferImg = new BufferedImage... until the return statement, with the following code:
DataBufferInt buffer = new DataBufferInt(pixels, pixels.length);
int[] bandMasks = {0xFF0000, 0xFF00, 0xFF}; // RGB (no alpha)
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, width, bandMasks, null);
ColorModel cm = new DirectColorModel(32,
0x00ff0000, // Red
0x0000ff00, // Green
0x000000ff, // Blue
0x00000000 // No Alpha
);
BufferedImage bufferImg = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
PS: Note that I also changed the shifts inside the x/y loop, from right to left shifts. Might have been just a minor typo. :-)

Converting grayscale image pixels to defined scale

I'm looking to use a very crude heightmap I've created in Photoshop to define a tiled isometric grid for me:
Map:
http://i.imgur.com/jKM7AgI.png
I'm aiming to loop through every pixel in the image and convert the colour of that pixel to a scale of my choosing, for example 0-100.
At the moment I'm using the following code:
try
{
final File file = new File("D:\\clouds.png");
final BufferedImage image = ImageIO.read(file);
for (int x = 0; x < image.getWidth(); x++)
{
for (int y = 0; y < image.getHeight(); y++)
{
int clr = image.getRGB(x, y) / 99999;
if (clr <= 0)
clr = -clr;
System.out.println(clr);
}
}
}
catch (IOException ex)
{
// Deal with exception
}
This works to an extent; the black pixel at position 0 is 167 and the white pixel at position 999 is 0. However when I insert certain pixels into the image I get slightly odd results, for example a gray pixel that's very close to white returns over 100 when I would expect it to be in single digits.
Is there an alternate solution I could use that would yield more reliable results?
Many thanks.
Since it's a grayscale map, the RGB parts will all be the same value (with range 0 - 255), so just take one out of the packed integer and find out what percent of 255 it is:
int clr = (int) ((image.getRGB(x, y) & 0xFF) / 255.0 * 100);
System.out.println(clr);
getRGB returns all channels packed into one int so you shouldn't do arithmetic with it. Maybe use the norm of the RGB-vector instead?
for (int x = 0; x < image.getWidth(); ++x) {
for (int y = 0; y < image.getHeight(); ++y) {
final int rgb = image.getRGB(x, y);
final int red = ((rgb & 0xFF0000) >> 16);
final int green = ((rgb & 0x00FF00) >> 8);
final int blue = ((rgb & 0x0000FF) >> 0);
// Norm of RGB vector mapped to the unit interval.
final double intensity =
Math.sqrt(red * red + green * green + blue * blue)
/ Math.sqrt(3 * 255 * 255);
}
}
Note that there is also the java.awt.Color class that can be instantiated with the int returned by getRGB and provides getRed, getGreen and getBlue methods if you don't want to do the bit manipulations yourself.

Categories