简体   繁体   中英

Set pixels of BufferedImage as transparent

How can I quickly and efficiently set all pixels of a BufferedImage to transparent so that I can simply redraw what sprite graphics I want for each frame?

I am designing a simple game engine in java that updates a background and foreground BufferedImage and draws them to a composite VolatileImage for efficient scaling, to be drawn to a JPanel . This scalable model allows me to add more layers and iterate over each drawing layer.

I simplified my application into one class given below that demonstrates my issue. Use the arrow keys to move a red square over the image. The challenge is I want to decouple updating the game graphics from drawing the composite graphics to the game engine. I have studied seemingly thorough answers to this question but cannot figure out how to apply them to my application:

Here is the critical section that does not clear the pixels correctly. The commented out section is from stack-overflow answers I have read already, but they either draw the background as a non-transparent black or white. I know the foregroundImage begins with transparent pixels in my implementation as you can see the random pixel noise of the backgroundImage behind the red sprite when the application begins. Right now, the image is not cleared, so the previous drawn images remain.

/** Update the foregroundGraphics. */
private void updateGraphics(){
    Graphics2D fgGraphics = (Graphics2D) foregroundImage.getGraphics(); 

    // set image pixels to transparent
    //fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
    //fgGraphics.setColor(new Color(0,0,0,0));
    //fgGraphics.clearRect(0, 0, width, height);
    //fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

    // draw again.
    fgGraphics.setColor(Color.RED);
    fgGraphics.fillRect(sx, sy, spriteSize, spriteSize);
    fgGraphics.dispose();
}

Here is my entire example code:

/**
 * The goal is to draw two BufferedImages quickly onto a scalable JPanel, using 
 * a VolatileImage as a composite.
 */
public class Example extends JPanel implements Runnable, KeyListener
{   
    private static final long   serialVersionUID = 1L;
    private int                 width;
    private int                 height;
    private Object              imageLock;
    private Random              random;
    private JFrame              frame;
    private Container           contentPane;
    private BufferedImage       backgroundImage;
    private BufferedImage       foregroundImage;
    private VolatileImage       compositeImage;
    private Graphics2D          compositeGraphics;
    private int[]               backgroundPixels;
    private int[]               foregroundPixels;
    // throttle the framerate.
    private long                prevUpdate; 
    private int                 frameRate;
    private int                 maximumWait;
    // movement values.
    private int speed;
    private int sx;
    private int sy;
    private int dx;
    private int dy;
    private int spriteSize;

    /** Setup required fields. */
    public Example(){
        width = 512;
        height = 288;
        super.setPreferredSize(new Dimension(width, height));
        imageLock = new Object();
        random = new Random();
        frame = new JFrame("BufferedImage Example");
        frame.addKeyListener(this);
        contentPane = frame.getContentPane();
        contentPane.add(this, BorderLayout.CENTER); 
        // used to create hardware-accelerated images.
        GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        backgroundImage = gc.createCompatibleImage(width, height,Transparency.TRANSLUCENT);
        foregroundImage = gc.createCompatibleImage(width, height,Transparency.TRANSLUCENT);
        compositeImage = gc.createCompatibleVolatileImage(width, height,Transparency.TRANSLUCENT);
        compositeGraphics = compositeImage.createGraphics();
        compositeGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        compositeGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        backgroundPixels = ((DataBufferInt) backgroundImage.getRaster().getDataBuffer()).getData();
        foregroundPixels = ((DataBufferInt) foregroundImage.getRaster().getDataBuffer()).getData();     
        //initialize the background image.
        for(int i = 0; i < backgroundPixels.length; i++){
            backgroundPixels[i] = random.nextInt();
        }
        // used to throttle frames per second
        frameRate = 180;
        maximumWait = 1000 / frameRate;
        prevUpdate = System.currentTimeMillis();
        // used to update sprite state.
        speed = 1;
        dx = 0;
        dy = 0;
        sx = 0;
        sy = 0;     
        spriteSize = 32;
    }

    /** Renders the compositeImage to the Example, scaling to fit. */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        // draw the composite, scaled to the JPanel.
        synchronized (imageLock) {
            ((Graphics2D) g).drawImage(compositeImage, 0, 0, super.getWidth(), super.getHeight(), 0, 0, width, height, null);
        }
        // force repaint.
        repaint();
    }

    /** Update the BufferedImage states. */
    @Override
    public void run() {
        while(true){
            updateSprite();
            updateGraphics();
            updateComposite();
            throttleUpdateSpeed();
        }
    }

    /** Update the Sprite's position. */
    private void updateSprite(){
        // update the sprite state from the inputs.
        dx = 0;
        dy = 0;         
        if (Command.UP.isPressed()) dy -= speed;
        if (Command.DOWN.isPressed()) dy += speed;
        if (Command.LEFT.isPressed()) dx -= speed;
        if (Command.RIGHT.isPressed()) dx += speed;
        sx += dx;
        sy += dy;
        // adjust to keep in bounds.
        sx = sx < 0 ? 0 : sx + spriteSize >= width ? width - spriteSize : sx;
        sy = sy < 0 ? 0 : sy + spriteSize >= height ? height - spriteSize : sy;
    }

    /** Update the foregroundGraphics. */
    private void updateGraphics(){
        Graphics2D fgGraphics = (Graphics2D) foregroundImage.getGraphics(); 

        // set image pixels to transparent
        //fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
        //fgGraphics.setColor(new Color(255, 255, 255, 255));
        //fgGraphics.clearRect(0, 0, width, height);
        //fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        // draw again.
        fgGraphics.setColor(Color.RED);
        fgGraphics.fillRect(sx, sy, spriteSize, spriteSize);
        fgGraphics.dispose();
    }

    /** Draw the background and foreground images to the volatile composite. */
    private void updateComposite(){
        synchronized (imageLock) {
            compositeGraphics.drawImage(backgroundImage, 0, 0, null);
            compositeGraphics.drawImage(foregroundImage, 0, 0, null);
        }

    }

    /** Keep the update rate around 60 FPS. */
    public void throttleUpdateSpeed(){
        try {
            Thread.sleep(Math.max(0, maximumWait - (System.currentTimeMillis() - prevUpdate)));
            prevUpdate = System.currentTimeMillis();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /** Ignore key typed events. */
    @Override
    public void keyTyped(KeyEvent e) {}

    /** Handle key presses. */
    @Override
    public void keyPressed(KeyEvent e) {
        setCommandPressedFrom(e.getKeyCode(), true);
    }

    /** Handle key releases. */
    @Override
    public void keyReleased(KeyEvent e) {
        setCommandPressedFrom(e.getKeyCode(), false);
    }

    /** Switch over key codes and set the associated Command's pressed value. */
    private void setCommandPressedFrom(int keycode, boolean pressed){
        switch (keycode) {
        case KeyEvent.VK_UP:
            Command.UP.setPressed(pressed);
            break;
        case KeyEvent.VK_DOWN:
            Command.DOWN.setPressed(pressed);
            break;
        case KeyEvent.VK_LEFT:
            Command.LEFT.setPressed(pressed);
            break;
        case KeyEvent.VK_RIGHT:
            Command.RIGHT.setPressed(pressed);
            break;
        }
    }
    /** Commands are used to interface with key press values. */
    public enum Command{
        UP, DOWN, LEFT, RIGHT;      
        private boolean pressed;

        /** Press the Command. */
        public void press() {
            if (!pressed) pressed = true;
        }
        /** Release the Command. */
        public void release() {
            if (pressed) pressed = false;
        }       
        /** Check if the Command is pressed. */
        public boolean isPressed() {
            return pressed;
        }       
        /** Set if the Command is pressed. */
        public void setPressed(boolean pressed) {
            if (pressed) press();
            else release();
        }
    }

    /** Begin the Example. */
    public void start(){
        try {           
            // create and display the frame.
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    Example e = new Example();
                    frame.pack();
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });         
            // start updating from key inputs.
            Thread t = new Thread(this);
            t.start();          
        }
        catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /** Start the application. */
    public static void main(String[] args){
        Example e = new Example();
        e.start();
    }   
}

Edits:

- Fixed a typo in the for-loop initializing the backgroundPixels to random.

Turns out I goofed in my method selection. I noticed I was clearing a one-pixel wide box that was the outline of my graphics. This is because I accidentally used drawRect() instead of fillRect() . Upon changing my code it works now. Here are examples I was able to get to work.

Example using AlphaComposite.CLEAR (draw with any opaque color):

    // clear pixels
    fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
    fgGraphics.setColor(new Color(255,255,255,255));
    fgGraphics.fillRect(0, 0, width, height);
    fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
    // draw new graphics

Example using AlphaComposite.SRC_OUT (draw with any color with alpha zero):

    // clear pixels
    fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT));
    fgGraphics.setColor(new Color(255,255,255,0));
    fgGraphics.fillRect(0, 0, width, height);
    fgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
    // draw new graphics

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM