Skip to main content

IndexColorModel and non-opaque black color?

20 replies [Last post]
cowwoc
Offline
Joined: 2003-08-24

Hi,

It seems it is impossible to create a IndexColorModel with two colors: one transparent (non-black) color and one black non-opaque color. It seems that regardless of how I construct IndexColorModel it always interprets the black color as transparent. Using any other color than black for my opaque color works fine.

Is this a bug?

Gili

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
flar
Offline
Joined: 2003-06-11

And finally, I found an error in our color lookups. When we find a "better alpha match" we don't reset the "best color error" to its max value so we only choose a color if it is a better alpha match *and* a better color match for all previous colors. Thus, color0, being opaque, should choose index 1 because it is the best alpha match and the closest of all color matches of all other pixels with the same alpha, but the "perfect color match" from the first pixel wins out because we forgot to reset our idea of "the best color match *so far*"...

Having said that, it still isn't clear if matching alpha above all other considerations is the best technique here, but this bug means we aren't even implementing that metric correctly...

cowwoc
Offline
Joined: 2003-08-24

Man! new Color(int rgb) is evil. I didn't notice that it drops the alpha component and the Javadoc doesn't make any effort to clear up this issue either. I'd expect us to specify the word "opaque" in bold or deprecate this constructor in favour of one of the other constructors. Any reason we haven't done that? Having "rgb" and "argb" int constructors is very confusing and I doubt most people notice the difference. What makes matter worse is that we have getRGB() -- notice the lack of A in the name -- which makes matters even more confusing.

Now, I reran the test with new Color(int rgba, boolean hasAlpha) and now I get:

color0=java.awt.Color[r=1,g=0,b=0],alpha=0
color1=java.awt.Color[r=0,g=0,b=0],alpha=255
pick(color0)=0
pick(color1)=1

Now the results look correct. Problem is, the resulting Image is still incorrect. Right now I am encoding in PNG-8 and it is coming out completely transparent. Is it possible for me to get more debugging output? Any ideas?

cowwoc
Offline
Joined: 2003-08-24

Here is my entire method. Maybe there is a mistake in code I have not quoted yet. Please tell me if you see anything that might cause full transparency.

[code]
public BufferedImage toBufferedImage()
{
// We've got to ensure that color 0 (which is transparent) differs from
// color 1 which is the corner color, otherwise the corner will become
// transparent as well. Therefore, we set color 0 relative to color 1 in
// a way that guarantees that the two are not equal.
Color backgroundColor = new Color((outerColor.getRed()+1) % 256,
outerColor.getGreen(), outerColor.getBlue());
IndexColorModel colorModel = new IndexColorModel(8, 2,
new byte[]{(byte) backgroundColor.getRed(), (byte) outerColor.getRed()},
new byte[]{(byte) backgroundColor.getGreen(), (byte) outerColor.getGreen()},
new byte[]{(byte) backgroundColor.getBlue(), (byte) outerColor.getBlue()},
0);
try
{
Color color0 = new Color(colorModel.getRGB(0), true);
System.out.println("color0=" + color0 + ",alpha=" + color0.getAlpha());
Color color1 = new Color(colorModel.getRGB(1), true);
System.out.println("color1=" + color1 + ",alpha=" + color1.getAlpha());

System.out.println("pick(color0)=" + ((byte[]) colorModel.getDataElements(color0.getRGB(), null))[0]);
System.out.println("pick(color1)=" + ((byte[]) colorModel.getDataElements(color1.getRGB(), null))[0]);
}
catch (Exception e)
{
e.printStackTrace();
}
BufferedImage result = new BufferedImage(radius, radius, BufferedImage.TYPE_BYTE_INDEXED, colorModel);
Graphics2D g2d = result.createGraphics();

// Ensure the background is transparent
g2d.setBackground(backgroundColor);
g2d.clearRect(0, 0, result.getWidth(), result.getHeight());

// Set the clip bounds to exclude the internals of the corner
Rectangle imageRect = new Rectangle(0, 0, result.getWidth(), result.getHeight());
Area clipBounds = new Area(imageRect);
clipBounds.subtract(getInternalArea());
g2d.clip(clipBounds);

g2d.setPaint(outerColor);
g2d.fillRect(0, 0, result.getWidth(), result.getHeight());
g2d.dispose();
return result;
}
[/code]

In the above code, getInternalArea() returns the internal area of an arc. This this is supposed to dynamically generate round corners for web sites. So basically I specify which of the four kinds of corners I am generating, and its color and the code is supposed to paint it. I am clarifying this because the clip bounds should never end up being empty -- I don't think that's the problem; especially since the problem seems to be 100% dependant on outerColor's value, not on anything else.

cowwoc
Offline
Joined: 2003-08-24

uh, I'm still waiting on an answer for this problem... Did anyone figure out whether this is a Java2D bug or not and how to fix it?

Thanks,
Gili

flar
Offline
Joined: 2003-06-11

I already pointed out a bug on finding the correct pixel in an earlier post which is likely affecting you, but there are other problems in this example.

For one, you set the background color to an opaque color and then expect it to clear the background to transparent - that isn't likely to work very well even if this code was an exact science. Given the limitations of trying to figure out the best "color" for a non-opaque color, I wouldn't rely on rendering to clear an indexed image to a specific non-opaque index in any case.

Also, the image starts out transparent when you create it. If you just deleted the code that tries to "clear" it, you'd probably bet better results...

cowwoc
Offline
Joined: 2003-08-24

What is the suggested fix then?

1) Don't clear image
2) Set background color to transparent color

Thing is, I remember that I didn't use to have the code that clears the background or sets the background color and it *still* did not work. Something else is at play.

Maybe the problem is that IndexColorModel constructor I am using to generate the two colors? Is the constructor I am using safe or should I be using another one? Ideas?

Thanks,
Gili

flar
Offline
Joined: 2003-06-11

At this point I need a complete reproducible test case, not a code snippet. It's too hard to diagnose partial code. If you could provide a very small, but complete Java program ready to compile and run that demonstrates the problem, that would help.

General suggestions:

- Avoid using indexed images for any kind of work that involves alpha

- If you create a BufferedImage, it should be initialized to all 0 pixels so if the ICM you use has a transparent color at index 0 it should start out life transparent. Trying to "render" transparency to it is not likely to work very well.

flar
Offline
Joined: 2003-06-11

I'm sorry, I spoke without double-checking the code. It only sets the alpha of the transparent index to 0, it doesn't touch the color components - my mistake. So, the resulting IndexColorModel really will contain:

index 0: (1, 0, 0, 0)
index 1: (0, 0, 0, 255)

As far as the terminology of "pixel", that terminology is more general than "index". All types of formats of storing colors in images involve "pixel data", but only a paletted format involves an "index". We have lots of different kinds of ColorModel in the JDK, the IndexColorModel is only one of them. They all have "pixels", but only the ICM has "indices". Thus, we use the general term that applies to all ColorModels.

flar
Offline
Joined: 2003-06-11

You say that "color" is (0,0,0). That is only 3 components - the red, green and blue. What about its alpha? The alpha is the part that tells you whether it is transparent, translucent, or opaque.

Also, in your code, you fill in the values of the red, green and blue arrays for color 0 that are relative to "color 1", but those values are ignored by the IndexColorModel constructor. The last part of constructing the IndexColorModel says "Oh look, the caller asked me to ensure that the color in index 0 is transparent so I will completely overwrite all of the values I have for that color and make it (0,0,0,0) and any values the caller supplied for that index are now irrelevant. If you don't want it to overwrite the values you supply, don't give a valid index for the transparent index.

Thus, if "color" is opaque black then the IndexColorModel that your example code will construct will have 2 colors which will be (0,0,0,0) and (0,0,0,255) because you told it to set index 0 to all 0's.

shan-man
Offline
Joined: 2006-02-17

Hi Gili,

The experts on this type of question are more likely to be found in the Java 2D forum? Mind if I move your post over there?

Thanks!
Shannon

cowwoc
Offline
Joined: 2003-08-24

Please do.

shan-man
Offline
Joined: 2006-02-17

Done! :)

Shannon

cowwoc
Offline
Joined: 2003-08-24

Here is the code I am using. It is supposed to create a BufferedImage of two colors, where "color" denotes the opaque color and the other one is supposed to be transparent. What I am seeing is that if color is [0, 0, 0] then it renders as if it was transparent as well. It is possible my actual code is incorrect so please take a look and see if you spot anything wrong.

Thank you,
Gili

[code]
// We've got to ensure that color 0 (which is transparent) differs from
// color 1 which is the corner color, otherwise the corner will become
// transparent as well. Therefore, we set color 0 relative to color 1 in
// a way that guarantees that the two are not equal.
IndexColorModel colorModel = new IndexColorModel(8, 2,
new byte[]{(byte) ((color.getRed()+1) % 255), (byte) actualColor.getRed()},
new byte[]{0, (byte) color.getGreen()},
new byte[]{0, (byte) color.getBlue()},
0);
BufferedImage result = new BufferedImage(radius, radius, BufferedImage.TYPE_BYTE_INDEXED, colorModel);
Graphics g = result.getGraphics();

// Set the clip bounds to exclude the internals of the corner
Rectangle imageRect = new Rectangle(0, 0, result.getWidth(), result.getHeight());
Area clipBounds = new Area(imageRect);
clipBounds.subtract(getInternalArea());
g.setClip(clipBounds);

g.setColor(color);
g.fillRect(0, 0, result.getWidth(), result.getHeight());
g.dispose();
[/code]

flar
Offline
Joined: 2003-06-11

What is the alpha value of "color"?

In your first message you claimed that the second color was a non-opaque color, but in the second message you refer to it as "opaque". It's alpha value makes a difference because it is taken into account when the system looks up the best index to use for the color you set in the graphics.

Also, when you specify a transparent index in the constructor to IndexColorModel, all color and alpha information you supply for that entry is ignored and it is replaced with all 0s. Thus, your colormap really has a transparent black as the first entry.

Now, if "color" has an alpha value that is closer to 0 than 255, then the first index will be chosen as the closest color, otherwise the second index should be chosen. Even if the first color was a transparent non-black color, the alpha comparison is used as a higher priority match than the color comparisons (perhaps not the best choice, but that is the way it has been implemented since 1.2 for lack of any existing examples of how to do pixel lookups on palettes containing alpha).

You can use the "getDataElements()" method on IndexColorModel to test which index is chosen as the best representation for a given color.

cowwoc
Offline
Joined: 2003-08-24

Ok, I will clarify:

"color" is the color I am drawing with. It is (0,0,0) which, as far as I know, is a non-transparent black.

Now, if you look at the code, color 0 is always created in reference to color 1 in that we add 1 to its red component. So in the above code, if I set color 1 to (0,0,0) then color 0 is (1,0,0). When I try painting later on, I try doing so with (0,0,0). I'm expecting the color table to contain (1,0,0,0) and (0,0,0,255). I will check out getDataElements() now.

cowwoc
Offline
Joined: 2003-08-24

Something doesn't make sense here.

getDataElement() is telling me that color 0 is (0,0,0,0) and color 1 has more than four components (!!) yet the first four are (0,0,0,0) as well and when I feed the non-transparent color into getDataElements(color.getRGB(), null) it returns a byte[] of size 1 whose element contains the value 1. What does all this mean? and why?

flar
Offline
Joined: 2003-06-11

It appears that you are using the words "non-transparent color" to mean something different than I use them. Colors can be transparent (alpha is 0x00), translucent (alpha is 0x01 through 0xfe) or opaque (alpha is 0xff). When you say "non-transparent" I read that as "any alpha value other than 0x00". Is that the way you are using the term?

getDataElements turns a color into a pixel value. It can't tell you what color is in an index, but it can try to choose the best index for a given color. If you want to see what colors the IndexColorModel has in the various entries then use the "getRGB(int pixel)" or "getRGBs(int rgb[])" methods. Having said that, I'm not sure what you mean when you say that "getDataElements() is telling me ... color 1 has more than four components" since that isn't the information it supplies...?

getDataElements(rgb, null) should return a byte array containing the index of the best color in the colormap to match the integer "rgb" value you supplied. The format for the integer "rgb" is 0xAARRGGBB where AA are two hex digits of alpha, RR are two hex digits of red, GG are two hex digits of green and BB are two hex digits of blue.

If it returns a byte array which contains the value 1 then it is saying that the color in the LUT at index 1 is the best match for the color you asked about.

cowwoc
Offline
Joined: 2003-08-24

When I said non-transparent, I meant opaque. Sorry for the confusion. When I said that getDataElements() is telling me color 1 has more than 4 components I mean that when I pass in an array of size 4 into the method, it throws an IndexOutOfBoundsException which indicates it needed more than four components.

So, lets see if I understand you correctly:

I pass in:

1) Color0 = 1,0,0
2) Color1 = 0,0,0

and specify the transparent index is 0

IndexColorModel is constructed with this in the color table:

1) Color0 = 0,0,0,0
2) Color1 = 0,0,0,255

and if I use Graphics2D.setColor(new Color(0,0,0)), where the Graphics2D is associated with a BufferedImage using the IndexColorModel, then it should (in theory) pick Color1 since 0,0,0 should map to 0,0,0,255. Am I correct?

I'm going to try getRGBs() like you suggested and see what I get.

PS: Why does IndexColorModel refer to color indicies as "color pixel"? This terminology is very confusing. Could we not use the terminology of "color index" across the entire Javadoc?

Message was edited by: cowwoc

cowwoc
Offline
Joined: 2003-08-24

Flar,

I'm getting results that contradict what you said. Here is the sample code:

[code]
Color backgroundColor = new Color((outerColor.getRed()+1) % 256,
outerColor.getGreen(), outerColor.getBlue());
IndexColorModel colorModel = new IndexColorModel(8, 2,
new byte[]{(byte) backgroundColor.getRed(), (byte) outerColor.getRed()},
new byte[]{(byte) backgroundColor.getGreen(), (byte) outerColor.getGreen()},
new byte[]{(byte) backgroundColor.getBlue(), (byte) outerColor.getBlue()},
0);
try
{
Color color0 = new Color(colorModel.getRGB(0));
System.out.println("color0=" + color0 + ",alpha=" + color0.getAlpha());
Color color1 = new Color(colorModel.getRGB(1));
System.out.println("color1=" + color1 + ",alpha=" + color1.getAlpha());

System.out.println("pick(color0)=" + ((byte[]) colorModel.getDataElements(color0.getRGB(), null))[0]);
System.out.println("pick(color1)=" + ((byte[]) colorModel.getDataElements(color1.getRGB(), null))[0]);
}
catch (Exception e)
{
e.printStackTrace();
}
[/code]

and the output:

color0=java.awt.Color[r=1,g=0,b=0],alpha=255
color1=java.awt.Color[r=0,g=0,b=0],alpha=255
pick(color0)=0
pick(color1)=1

Now, in the above code, backgroundColor is supposed to be transparent and outerColor to be opaque. The problematic case occurs when outerColor = (0,0,0)

What do you think?

flar
Offline
Joined: 2003-06-11

Beware, read the javadocs for new Color(int rgb). That constructor comes from an older version of the JDK before alpha was supported and was used by older applets to construct opaque colors without bothering to set the alpha bits - so it always constructs an opaque color. It's one of the annoying warts of maintaining backwards compatibility.

To construct a non-opaque color you must use a different constructor, such as "new Color(int rgba, boolean hasalpha)", or "new Color(int r, int g, int b, int a)".