Image compositing using black/white mask and image

I previously wrote a Macromedia Fireworks script to do this, but due to some requirements for tools I need to automate this as a Java process. I’ve figured out everything except the critical step and can’t seem to understand how to do it.

I have an image, which is 64x64 pixels. It is an RGB image, no Alpha channel.

I have a second image, which is also 64x64 pixels. This one is black and white.

What I need to do is to take the full color image and mask it. Specifically, black areas of the second image should retain color information from the first one, while white areas of the second image should be transparent (therefore, the destination image is RGBA).

I can’t seem to composite them properly and the articles I read at Sun about Advanced Imaging and other articles don’t help much.

Any thoughts? I tried using clipping regions, but can’t seem to get an irregular clip region based on color.

Thanks.

Edit:

http://www.gergis.net/color.jpg

http://www.gergis.net/bw.jpg

Is it speed critical?

if not, you can simply subclass RGBImageFilter and add the appropriate behaviour.

something like…
(i’ve expanded it from a 1 liner, so u can understand it :D)


//get the image pixel
int imgPixel = image.getRGB(x,y);
//get the mask pixel
int maskPixel = mask.getRGB(x,y);
//now, get rid of everything but the blue channel
//and shift the blue channel into the alpha channels sample space.
maskPixel = (maskPixel &0xFF)<<24
//now, merge img and mask pixels and copy them back to the image
image.setRGB(x,y,imgPixel|maskPixel);

I got it now. Thanks. This should do the trick JUST fine. :slight_smile:

Seems Abuse beat me to it. Here’s my solution:


import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;

import javax.imageio.*;
import javax.swing.*;

public class ImageMasker
{
    public static BufferedImage createTileMask(BufferedImage tile, BufferedImage mask)
    {
        GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        
        BufferedImage result = gc.createCompatibleImage(tile.getWidth(null), tile.getHeight(null), Transparency.BITMASK);
        BufferedImage temp = gc.createCompatibleImage(tile.getWidth(null), tile.getHeight(null), Transparency.BITMASK);
        
        WritableRaster raster = result.getRaster();
        Raster maskData = mask.getRaster();
        Raster tileData = tile.getRaster();
        
        Graphics g;
        
        int[] pixel = new int[4];
        int width = tile.getWidth(null);
        int height = tile.getHeight(null);
        
        for(int y=0; y<height; y++)
        {
            for(int x=0; x<width; x++)
            {
                pixel = maskData.getPixel(x, y, pixel);
                
                if(pixel[0] == 0) 
                {
                    tileData.getPixel(x, y, pixel);
                    pixel[3] = 255;
                    raster.setPixel(x, y, pixel);
                    
                    pixel = tileData.getPixel(x, y, pixel);
                }
            }
        }
        
        result.setData(raster);
        
        g = temp.createGraphics();
        g.drawImage(result, 0, 0, null);
        g.dispose();
        
        return temp;
    }
    
    public static void main(String[] args)
    {
        try
        {
            BufferedImage tile = ImageIO.read(new File("color.jpg"));
            BufferedImage mask = ImageIO.read(new File("bw.jpg"));
            BufferedImage masked = createTileMask(tile, mask);
            
            JFrame frame = new JFrame();
            ImageIcon icon = new ImageIcon(masked);
            JLabel label = new JLabel(icon);
            
            frame.getContentPane().add(label);
            frame.pack();
            frame.setVisible(true);
            
        }
        catch(Exception e) 
        {
            e.printStackTrace();
        }
    }
}

It’s inefficient, but it works. BTW, I highly recommend not using JPGs for game images unless you are dealing with photographs. You’ll cause all kinds of unpredictable artifacts.

P.S. Anyone know how to get this forum to actually preserve spacing? It keeps mucking with my code.

Thanks to the both of you. For some reason I just could NOT get the idea of having to manipulate the image on the pixel level; I was hung up on some crazy idea that I could just call this magical method that would composite the two images. Now that I’m past that, it’s working to my satisfaction.

JPEGs suck when it comes to any image that isn’t continuous tone. I exclusively use PNGs lately (since that’s what Fireworks supports, and since I like PNG anyway).

.png is great, now if only IE would support them properly (full alpha channel instead of single bit) then we’d all be happy. Unfortunatly it looks like the next major version of IE will be with longhorn and so quite a while off (yes its probably on Opera and others, but theres no point in making a site that looks substandard on 80% of peoples machines :-[ ).

Good to see you around, Orangy Tang.

I agree with you on the PNG in IE thing, however, I’ve only been using it for 1-bit alpha. I don’t do anything that really requires multi-channel alpha though I know it can be done. (Though you have just given me a REALLY neat idea for transition tiles).

if the AND and OR composition operations were supported, it would be exactly that ez.

  1. Blit the mask with a bitwise AND onto the target.
  2. Then using a bitwise OR, blit the image over the top.

I still don’t understand why the AND and OR drawing operations arn’t supported ???

Infact, back when I only knew VB, and wrote stuff using the win32 api, those were the only composition operations I knew of!!

any1 know why java doesn’t support them?

Is there REALLY no way to do this with the supported compositing rules?
Perhaps we should file a RFE? Seems dumb to have that fancy compositing stuff in there and yet we have to resort to manipulating things at the pixel data level. The compositing stuff could even be accelerated with SIMD instructions under the hood (do the JAI performance packs use SIMD instructions?)

This took me a while to find, but I think you are right, there is a way to do this in the current rules. It lies in the java.awt.AlphaComposite class, and so far the BEST explanation I have seen of what it can do is here:
http://www.redbrick.dcu.ie/help/reference/java/2d/display/compositing.html

I have no idea how to use the AlphaComposite class, but it’s not a big deal. I can figure it out later. Now it’s almost 1AM here and time for sleep, while dreaming of data structures.
8)

all the AlphaCompositing rules deal with compositing the Alpha channel (hence the name :D)

What we need for this particular problem is a ColorCompositing class (and yeah, you don’t need to check, it doesn’t exist >:()

See example 11-12 CompositeEffects.java from the O’Reilly “Java Examples in a Nutshell” book:

The last effect uses a complex shape for a clipping region.

  • Craig

[quote]See example 11-12 CompositeEffects.java from the O’Reilly “Java Examples in a Nutshell” book:

The last effect uses a complex shape for a clipping region.

  • Craig
    [/quote]
    Hmm, spose you could do it like that…

make your own class that implements the Shape interface, and implement the contains(x,y) method as a simple collision map (using the b&w bitmask as the collision map)

It would then be a simple case of using your BitMaskShape object as the clip shape for your Graphics context.

Thats actually quite a neat solution, and very reusable.
Though its pretty slow. (even slower than the suggestions earier :()

Have you checked the demos in the jfc/java2d. Click the composition tab and see how it works.

I think the mode you need is SrcAtop or DstAtop.

[quote]Have you checked the demos in the jfc/java2d. Click the composition tab and see how it works.

I think the mode you need is SrcAtop or DstAtop.
[/quote]
that is AlphaCompositing only, it deals with the src and dst Alpha channel, not the pixel color.

See reply below: it wasn’t showing up well on the white contrast, so I’m double posting just to get it on blue.

I think I need to clarify one thing.

www.gergis.net/color.jpg
+
www.gergis.net/bw.jpg

www.gergis.net/result.gif

Does tht modify any of the suggestions made so far? I’m somewhat liking the RGBImageFilter solution myself, but I wanted to post this to see if that might change the AlphaComposite attempted solutions.

Thanks.

I tried my earlier idea, and it doesn’t work.

setClip uses the PathIterator of the shape for determining ‘insideness’ of a point, and because the shape described by a bitmask image cannot be represented as a Path, there is no way this idea will work.

It could be done using the the Composite interface , however the Composite interface is fairly complicated, so I aint gonna bother trying :smiley:

As you mentioned, its prolly eziest to stick with the RGBFilter.

Perhaps an RFE is in order. (Something like a ColorComposite or PixelComposite class)

I think the key here is that we need to get an image that is either 1-bit or 8-bit greyscale to be interpreted as an Alpha channel instead of the image colour content.
This might be possible with existing image and compositing classes.

Isn’t there a way to make a custom image format where you can say which bits are part of which ARGB component?

The existing AlphaComposite class is fine, as long as we can tell it that the binary or greyscale image is nothing but alpha data.

GK - It looks like you figured this out already, but I will mention it in this thread just for completness. A white (FF) alpha channel indicates 100% opaque, a black (0) alpha channel indicates 100% transparent. The images used in this thread are backwards from that standard

I looked into this a bit more.

It looks like the main thing to do is to change the SampleModel of the BufferedImage’s Raster so that there is a band for Alpha. The BufferedImage’s ColorModel would need to change too. The question is. Can you make these changes on a BufferedImage and the related classes to remap the data in the Raster? It would be great if you could do this without needing to copy image data around.
Incidentally, that would be the next step I would try… copy that mask to the alpha channel of another image when you first load it, then use the built-in AlpahComposite stuff as needed.