GeeCON Prague 2014*
The moose likes Swing / AWT / SWT and the fly likes Is there a better way to blend colors when painting with Graphics2D? Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


JavaRanch » Java Forums » Java » Swing / AWT / SWT
Bookmark "Is there a better way to blend colors when painting with Graphics2D?" Watch "Is there a better way to blend colors when painting with Graphics2D?" New topic
Author

Is there a better way to blend colors when painting with Graphics2D?

Kerry Shetline
Greenhorn

Joined: Feb 09, 2006
Posts: 14
I've recently been using Java to produce some simple anaglyph 3-D images -- "anaglyph" being the type of 3-D where you paint images for the left eye in one color (typically red) and for the right eye in another color (typically cyan) so, if you put on a pair of goofy looking 3-D glasses and look at the combined results, a stereoscopic 3-D image appears.

I currently have a working example at http://www.skyviewcafe.com/skyview.php (in the Orbits tab of the applet), but I'm not satisfied with the rendering speed. In order to blend the left eye and right eye images I ended up painting to two different offscreen BufferedImages, then looping through those images to blend pixels one pixel at a time.

I'd much, MUCH rather simply paint one color on top of the other to the original Graphics2D target, but I haven't found a way to make the colors blend as they should where the colors overlap.

Painting is being done on a black background. If I paint a red line, then paint a cyan line crossing the red line, the intersection of the two lines should appear white. As I am drawing antialiased lines, pixels where lines overlap need to come out in a variety of mixes of red and cyan as well.

If I weren't trying to antialias, if all I needed as an end result was a mix of pure black, white, red, and cyan pixels, using an AlphaComposite might have done the trick. I thought about painting with semi-transparent colors by specifying alpha components for the colors I'm painting with, but that results in a weighted-average blending of color components -- lines drawn across a black background, for example, would end up darker than they should be.

I tried creating my own java.awt.CompositeContext implementation, but every single drawing operation called the compose() method with larger rasters in need of processing, with no way I could see to easily and quickly deal with the few pixels actually being drawn in need of blending.

What I really need is a painting mode that does color mixing on RGB components individually such that:

D.r = max(D.r, S.r)
D.g = max(D.g, S.g)
D.b = max(D.b, S.b)

...where D is the destination pixel and S is the source pixel being drawn over the existing D pixel.

While not needed for my current project, other useful painting modes would be an additive mode (draw a dark blue pixel on top of another dark blue pixel, and the result is a brighter blue pixel -- with 8-bit components, adding source and destination values, limiting the result to a maximum value of 255), a minimum mode (lowest component value has precedence -- this would be more like painting with pigments), and a subtractive mode (D.x = 255 - min((255 - D.x) + (255 - S.x), 255)) -- even more like pigments, where succesive painting over one spot makes it darker. Averaging components would be a useful mode too (although this is equivalent to painting in the normal paining mode with 50% alpha).

I can think of other useful painting modes (such as HSB-based blending rules), and all the complications alpha channels add to the weighting of component values, but even the above five painting modes would add a great deal of intelligence and flexibility to the kind of image rendering you can do with Graphics2D. If I were proposing an update for the API, it might look something like this:

public void setPaintMode(PaintMode mode)

...where mode was one of the following values: REPLACE_PIXELS (what setPaintMode() already does by default), BLEND_MAXIMUM, BLEND_ADDITIVE, BLEND_MINIMUM, BLEND_SUBTRACTIVE, and BLEND_AVERAGE.
Rob Camick
Ranch Hand

Joined: Jun 13, 2009
Posts: 2191
    
    7
I think you need to use an AlphaComposite. Here is an example I found on the net a while ago:

Kerry Shetline
Greenhorn

Joined: Feb 09, 2006
Posts: 14
Rob Camick wrote:I think you need to use an AlphaComposite...

Thanks for trying to help, but I can't see how this example applies to my problem.

I ran the demo and all it does is overlay one image with a variable level of transparency on top of another image (I managed to find the Duke image that goes with the demo, I had to use a random JPEG for the other image). The overlay effect doesn't get me at all closer to what I need to do. If I draw the right eye image over the left image eye, with alpha < 1.0, with all of my formerly black pixels being transparent pixels, some color blending will indeed occur where lines overlap, but the overall right eye image won't be as bright as it's supposed to be, even at the overlapping points. If I set alpha to 1.0, the cyan lines would simple overwrite any red pixels behind them, not combine to form white.

Here's a demo applet for various other things you can do with alpha composite: http://download.oracle.com/javase/tutorial/2d/advanced/compositing.html

Please note that no matter what parameters you put in, the blue and the red never combine in an additive way to make nice, bright magenta - the color that should come from an additive combination of red and blue. You can adjust the alpha setting to get some color blending, only to a darker, not a lighter color where the two colors overlap. When you get that effect, the part of the red drawn against the white background is faded, not full red.

If AlphaComposite could do what I want to do, then some setting of the parameters should produce a blue rectangle with a red oval and a bright magenta overlap between the two shapes.
Darryl Burke
Bartender

Joined: May 03, 2008
Posts: 4573
    
    5

I tried creating my own java.awt.CompositeContext implementation, but every single drawing operation called the compose() method with larger rasters in need of processing, with no way I could see to easily and quickly deal with the few pixels actually being drawn in need of blending.

Did you try setting the clip of the Graphics?


luck, db
There are no new questions, but there may be new answers.
Kerry Shetline
Greenhorn

Joined: Feb 09, 2006
Posts: 14
Darryl Burke wrote:Did you try setting the clip of the Graphics?

How would the clipping region change the way colors blend, anything other than the where of where pixels are drawn, and actually change the result of the colors that get drawn?
Kerry Shetline
Greenhorn

Joined: Feb 09, 2006
Posts: 14
Just to make it more clear what kind of image blending has to happen, I have to blend this:



...with this:



...and get this:



...NOT this:



Notice how in the correct image (which I'm currently generating via a slow iteration through all of the pixels in two offscreen pixmap) the places where cyan is drawn on top of red a lighter color is produced (not quite white because using full green in the green/blue mix of the cyan was so bright the color bled through the red filter of the 3-D glasses, causing too much "ghosting"). Those blended colors show up as active pixels to both the left and right eye at the same time.

In the bad image, cyan simply overwrites red where the two colors intersect. The left eye will not be able to see pixels it should be seeing because they will have been lost by being painted over. This is only one variety of bad result -- but XOR drawing and AlphaComposite only provide other variations on bad so far, and least without using tricks and techniques of which I'm not aware.
Kerry Shetline
Greenhorn

Joined: Feb 09, 2006
Posts: 14
I found one easy way to almost double my frame rate: Using BufferedImage.TYPE_INT_RGB instead of BufferedImage.TYPE_3BYTE_BGR.

I didn't think about how the different kinds of BufferedImages were constructed behind the scenes when I first wrote my code, but if I'd thought about how this loop works:

...it might have occurred to me that TYPE_3BYTE_BGR means separate byte arrays for each RGB component, and that accessing those components as ints means a lot of byte shifting and shuffling. Not only does my loop to merge the two BufferedImages run faster with TYPE_INT_RGB, the drawing to the BufferedImages before the merge step is faster too. The Graphics2D drawing routines are probably better optimized for the TYPE_INT_RGB data format.

Once I was thinking more about how the BufferedImages are constructed, I tried an experiment with creating a buffered image that would put all of the bytes for red, green, and blue components into separate byte arrays for each color component. If that worked, the thought was I could merge the images using System.arraycopy(...) to quickly copy all of the red bytes from the left eye image into the right eye image, a technique I hoped would merge the two images much faster than nested x/y for loops reading and writing lots of individual pixel values.

There were two problems:

One big problem was that using this kind of BufferedImage turned out to be a great way to hang the JVM. Drawing beyond the bounds of the image seems to be what got the JVM in trouble and locked up my application.

Even without that bugginess, however, drawing to one of these BufferedImages was slooooow. Whatever speed up I might have gotten out of being able to quickly copy of one color component's bytes was lost to the sluggish drawing.

Going back to the idea of trying to speed things up using Graphics2D.setComposite(), I found this code from the swingx library...

http://www.java2s.com/Open-Source/Java-Document/Swing-Library/swingx/org/jdesktop/swingx/graphics/BlendComposite.java.htm

...which provides a lot of the flexible color blending I'd hope for. Instead of pulling all of this code into my project, however, I wrote a simplified version to do just the part I needed:

Two problems yet again... First, although it didn't crash the JVM, the results were buggy. When drawing shapes like ovals and rectangles, the results were correct, just what I'd expect. But for some reason text and line drawing behaved very weirdly, with line drawing causing portions of the image beyond particular lines I was drawing to be modified.

This was also slow. Not so terribly slow as my custom BufferedImage experiment, but not as fast as the gain I'm finding I get simply by switching to standard TYPE_INT_RGB BufferedImages.
 
GeeCON Prague 2014
 
subject: Is there a better way to blend colors when painting with Graphics2D?