Java's Primitive Datatypes are Signed!

It took me a whole 6 hours to write a color conversion from the YUV420P color space to the RGB color space. I thought I had a fairly good understanding of the various color formats (YUV420P, SP, 422, etc etc) and how to access individual Y, U, and V components; but I spent the better half of the 6 hours reading and re-reading the conversions and theory. The converted output's colors were completely messed up, and the images looked something like this:

The colors are all messed up; but notice you don't see the 4 quadrant ghosts as you'd usually see in YUV conversions that go wrong. This hinted that my conversions/element access wasn't wrong.*

Note, you needn't know what each of these really is or the formula of the conversion behind this. The crux of the problem was converting the given byte[] array input, to an int[] array output. I struggled because of a very simple yet hair-pulling gotcha; but it took me hours to realize where I was going wrong. All primitives in Java are signed! If you come from a Python-like world, or are ignorant of signed-ness, you'd expect the byte 0x0 to print 0, and 0xFF to print 255.

In Java however, where everything is signed, ((byte) 0xFF) is -1, and not 0x255. Further, simply trying to print 0xFF prints 255; which is because 0xFF is an int. Now, what I wanted to do was simply access a signed byte as an unsigned byte.
sout(0xFF); // outputs 255
sout(((byte)(0xFF))); // outputs -1
sout((0xFF & (byte)0xFF)); // outputs 255

I am however super embarrassed at spending 6 hours on this. This shows how developers tend to forget the underlying mechanisms because of all the abstractions high-level languages. On the upside, I recapped all color formats and the formulas behind them!

Here's the source I've put on Github Gists. Note, there's room for more optimization; the conversion to int and then back to byte for each R, G, and B component isn't necessary.
/**
* It took me a whole 6 hours to finally get the color conversion right.
* I have a fairly good understanding of the various color formats (YUV420P, SP, 422, etc etc),
* and how to access individual Y, U, and V components.
* I however struggled because of a very simple yet hair-pulling gotcha.
* All primitives in Java are signed! If you come from a Python-like world where 0xFF prints 255,
* you see yourself struggle just the same. I am however embarrassed at spending 6 hours on this.
*
* @author rish
*/
public class ColorConversions {
/**
* Converts yuv420p to rgba 8888
* The tricky bit here is:
* Java has signed primitives only. This means 0xFF is -1, and not 0x255, as you'd expect
* in an unsigned python like world.
* <p>
* And-ing with 0xFF (0xFF is an int, not a byte ;)) converts the individual Y, U, V, bits
* from an signed byte to a signed int, but for our purposes behaves as an unsigned byte.
* This means, (byte)0xFF=-1, whereas 0xFF & (byte)0xFF = 255.
**/
public void yuv420pToRGBA8888(int[] out, byte[] yuv, int width, int height) {
if (out.length < width * height) {
throw new IllegalArgumentException("Size of out must be " + width * height);
}
if (yuv.length < width * height * 3.0 / 2) {
throw new IllegalArgumentException("Size of yuv must be " + width * height * 3.0 / 2);
}
int size = width * height;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
/*accessing YUV420P elements*/
int indexY = j * width + i;
int indexU = (size + (j / 2) * (width / 2) + i / 2);
int indexV = (int) (size * 1.25 + (j / 2) * (width / 2) + i / 2);
// todo; this conversion to int and then later back to int really isn't required.
// There's room for better work here.
int Y = 0xFF & yuv[indexY];
int U = 0xFF & yuv[indexU];
int V = 0xFF & yuv[indexV];
/*constants picked up from http://www.fourcc.org/fccyvrgb.php*/
int R = (int) (Y + 1.402f * (V - 128));
int G = (int) (Y - 0.344f * (U - 128) - 0.714f * (V - 128));
int B = (int) (Y + 1.772f * (U - 128));
/*clamping values*/
R = R < 0 ? 0 : R;
G = G < 0 ? 0 : G;
B = B < 0 ? 0 : B;
R = R > 255 ? 255 : R;
G = G > 255 ? 255 : G;
B = B > 255 ? 255 : B;
out[width * j + i] = 0xff000000 + (R << 16) + (G << 8) + B;
}
}
}
}
*Okay, this is an image I picked up online; source: stackexchange. I felt lazy in redoing the mistake and screenshotting it to put it up here.

Comments

Popular posts from this blog

Finding My Parter

Work Week: Sprints without the Jargon