Println(color) gives strange color number

color c=color(255,1);
println(c);

console
33554431

color c=color(200,129);
println(c);

console
-2117547832

color c=color(12,127);
println(c);

console
2131495948

The negative & positive sign is related to alpha & it turns negative at 128.I am curious to know what this number actually means & how its calculated.

1 Like

Processing.org/reference/hex_.html
Processing.org/reference/color_datatype.html

1 Like

Had gone through this pages before but it doesnā€™t explain these type of number representation of colors.


1 Like

try also

color c=color(200,100);
println(c);
println("RGBA ("+red(c)+","+green(c)+","+blue(c)+","+alpha(c)+") or HSB ("+
                 hue(c)+","+saturation(c)+","+brightness(c)+")" );
1 Like

println(c) will give you the base-ten representation of the color datatype format. To understand the encoding, view it in binary. To see the channels, call red() / green() / blue() ā€“ or bit shift it.

color c = color(0, 127, 15);

// raw value
println(c);
// -16744689

// data format:
// AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
println(binary(c));
// 11111111000000000111111100001111

// color channels
println(int(red(c)), int(green(c)), int(blue(c)));
// 0 127 15
2 Likes

Thanks @kll & @jeremydouglass. I understood 32 bit format. In the decimal format, if the alpha value is specified with 255(default) or 0 then the number ranges from 0 to 16,777,215 i.e. 2^24 colors.

My curiosity is when the alpha transparency falls between 0 & 255, the number looks different.
Also when the color value has 0 or 255 alpha transperancy, the number is same with signs changing.

color c=color(225,0);
println(c);

Console:
14803425

color c=color(30,255);
println(c);

Console:
-14803426

1 Like

int is signed. That means that any number with a leftmost bit of 1, when read as an integer, has its sign bit set ā€“ positive/negative. The left 8 bits are alpha. Thatā€™s alpha 10000000 to 11111111ā€¦

2 Likes

Yes right! But with the alpha values ranging between 0 & 255, the number is entirely different.

color c=color(130,3);
println(c);

Console:
58884738

color c=color(130,130);
println(c);

Console:
-2105376126

For the alpha values between 0 to 127, its a positive sign & for values ranging from 128 to 255, its negative bit.
Alpha representation is understood. But the number doesnā€™t quite fall in the 0 to 16,777,215 range when the alpha is between 0 to 255. This number representation is my confusion. Thanks.

You seem to have misunderstood something.

The number range for Color is in fact -1 to -16.777.216, but this only applies if you donā€˜t consider alpha.

So :

RGB = -1 / -16.777.216 or 256^3 or 256 * 256 * 256 * 1 or (Values for Red[0-255] * V for Green [0-255] * V for B [0-255] * maxxed out Alpha[only 255!!!])

ARGB = -2Billion / +2Billion or 256^4 or 256 * 256 * 256 * 256 or (VfR * VfG * VfB * Value for Alpha)

That is why, a color does only range from -1 to -16million if Alpha is at itā€™s max(255 generally).

And if you change Alpha, the value increases because the calculation gets a new Layer.

Just like you can add Layers to 2D calculations and get working 3D calculations (for example in Rotation matrixes).

Also, as for why the first half is positive and the second half is negative, that is because Numbers are saved as bits.

If the first Bit is a 1, the number is read as a negative number (if itā€˜s a signed int).

So, 127 looks like this : 0111111, while 128 is this 1000000. As for why that is so, thatā€˜s got to do with how binary works.

2 Likes

@Lexyth You are correct! But if you keep alpha down to other extreme i.e. 0, you get the same range just starting from 0 to 16,777,215.
I didnā€™t know about this, figured out while trying different values.

This makes sense for alpha between 0 to 255 as the number falls out of standard range.

Thanks:)

Actually, to be a bit more precise about my last answer, what you get from the calculations for Color is a number with 4 Billion possibilities (0-4B).

But this translates to a signed Integer of -2B to +2B (B = Billion).

And to be even more precise, the different color values (Alpha, Red, Green, Blue) are weighted in this calculation. Like this :

Alpha = a (the value you use for alpha) * 256^3
Red = r * 256^2
Green = g * 256
Blue = b

So basically only Blue increases the int of color by a 1:1 ratio.

And to be more precise againā€¦ the -2B to +2B is not reflected in the logical order.
The calculation for Color is :

color c = a * 256^3 + r * 256^2 + g * 256 + b; //this is not gonna work in code because this gives an unsigned int, not a signed one.

Now this brings a problem, because we need a signed int. To get back to your question, the weird way that the number gets to +2B and then one more and we have -2B we have to look at how it is calculated in binary.

So, to make it Short, if the result of our calculation is 0, it translates to 0 as binary (000ā€¦0) (it got 32 bits, so iā€˜ll abbreviate the binary parts).
If itā€˜s more like 64, we get 000ā€¦100ā€¦0.
If itā€˜s that 2B number, we get 0111ā€¦111.

Now, if itā€˜s that 2B number plus one, the way itā€˜s calculated in binary is, you add 1 to the rightmost bit, and if itā€˜s more than 1, you set it to 0 and change the bit to itā€˜s left to 1 more.

Going all the way to the leftmost bit now, we have to set it to 1 and all others end up as 0 (1000ā€¦000).

With this we have a 1 as the first Bit, which we already said, means a negative number.

And the others all being 0, means we got the -2B number. Now we can go back all the way to -1 or (111ā€¦111) in binary.

Well, i hope that last part wasnā€˜t too confusingā€¦ thatā€˜s just the simplest way i could think of desrcibing it practicly.

1 Like

You may be imagining that a signed int counts like this:

-4 -3 -2 -1 0 1 2 3

But it actually counts like this (example of a 3-bit signed twos compliment):

0 1 2 3 -4 -3 -2 -1

Like this:

000 : 0
001 : 1
010 : 2
011 : 3
100 : -4
101 : -3
110 : -2
111 : -1

So, if you take 111 (-1) and add 1, you get 000 (0); if you take 000 (0) and subtract 1, you get 111 (-1). On the other hand, if you take 011 (max 3) and add 1, you get 100 (min -4).

2 Likes

Thanks @jeremydouglass & @Lexyth. Finally understood! It is complex to understand but very interesting in how this numbers work in the back end.
Just one query, is all this hassle of negative bit to store the colors efficiently without taking additional storage as in colors could be stored from 0 to 4B directly but that would double up the size but -2B to +2B makes the number(magnitude) stay the same with the 8 leftmost bit changing?

They are always stored the same way from (0000ā€¦00) to (1111ā€¦11) in 32 Bits.

Now, if you read them as Numbers from 0-4B or -2B to 2B doesnā€˜t affect the storage (Btw, you could also read [000ā€¦00] with 32 Bits as a String and it would still give a result, so maybe that makes it clearer).

The only reason why they are read as -2B to 2B is because int is a signed primitive (meaning it checks if the first Bit is 1 [-] or 0 [+]).

You can just as well read the same 32 Bits using an unsigned Integer, just that Java doesnā€˜t have oneā€¦

In the end it all comes down to how you read the Bits, so the result doesnā€˜t change the storage at all.

EDIT : A mathematical approach to explain the difference between signed and unsigned would pretty much be :

int unsInt = 0,256; //unsigned Integer range using only 8 Bits (such a number variable doesnā€˜t exist, shortest is Short with 16 Bits)
int sInt = 0 - (unsInt / 2), 256 - (unsInt / 2);
2 Likes

in this respect generally good to know might be
( run it )

void setup() {
  numinfo();
}

void numinfo() {
  println( "__NUM INFO :");
  println( "byte     "+Byte.SIZE+  " bit | min: "+Byte.MIN_VALUE+   "\t\t\t max: "+Byte.MAX_VALUE);
  println( "short   "+Short.SIZE+  " bit | min: "+Short.MIN_VALUE+  "\t\t\t max: "+Short.MAX_VALUE);
  println( "int     "+Integer.SIZE+" bit | min: "+Integer.MIN_VALUE+"\t\t max: "  +Integer.MAX_VALUE);
  println( "long    "+Long.SIZE+   " bit | min: "+Long.MIN_VALUE+   "\t max: "    +Long.MAX_VALUE);
  println( "float   "+Float.SIZE+  " bit | min: "+Float.MIN_VALUE+  "\t\t\t max: "+Float.MAX_VALUE);
  println( "double  "+Double.SIZE+ " bit | min: "+Double.MIN_VALUE+ "\t\t\t max: "+Double.MAX_VALUE);
}


2 Likes

Although Javaā€™s primitive int is always a signed 32-bit (4-byte) value, we can convert it to unsigned: :coffee:

Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Integer.html#toUnsignedLong(int)
Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Integer.html#toUnsignedString(int)

final color c = color(200, 129);
println(c); // -2117547832

final long l = Integer.toUnsignedLong(c);
println(l); // 2177419464

final String s = Integer.toUnsignedString(c);
println(s); // 2177419464

exit();

16-bit (2-byte) char in the only Javaā€™s primitive datatype which is unsigned: :heavy_plus_sign:

3 Likes

An important thing to understand is that, from Processingā€™s perspective, there is no ā€œnegative bitā€ in a color. The bits are AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB.

Because the actual storage for a color is an int, if you println() the int, it is interpreted as having a sign bit ā€“ but that is just an interpretation of a list of 0s and 1s, and not a helpful one. As long as you donā€™t try to println(c), there isnā€™t a hastle ā€“ looking at the default print of a color is confusing, because Javaā€™s default interpretation of that primitive value isnā€™t the way that the bits are meant to be interpreted. However, wrapping each color value (e.g. a pixel) in an object with an attached print method doesnā€™t make sense for high speed graphics processing ā€“ they are stored in ints (rather than Color or Pixel objects) for performance, and you use alpha() red() green() blue() helper methods ā€“ rather than Pixel.print() or print(Pixel). But the fastest way to do it is with bit-shifting, and this is generally what Processing is doing when working with color channels ā€“ it is grabbing the relevant bits directly. There is an example on the reference page for >> (right shift):

Here is a slightly expanded version of that example:

color c = color(127, 63, 31);
int a = c >> 24 & 0xff;
int r = c >> 16 & 0xff;
int g = c >> 8 & 0xff;
int b = c >> 0 & 0xff;

println(a, r, g, b);
// 255, 63, 127, 255

println(binary(a));
println(binary(r));
println(binary(g));
println(binary(b));
// 00000000000000000000000011111111
// 00000000000000000000000001111111
// 00000000000000000000000000111111
// 00000000000000000000000000011111
4 Likes