将BufferedImage的像素设置为透明

时间:2015-06-30 21:54:34

标签: java swing game-engine bufferedimage alpha

如何快速有效地将BufferedImage的所有像素设置为透明,以便我可以简单地重绘每帧所需的精灵图形?

我正在设计一个简单的游戏引擎,用于更新背景和前景BufferedImage并将它们绘制到复合VolatileImage以进行有效缩放,以便绘制到JPanel。这种可扩展的模型允许我添加更多图层并迭代每个绘图层。

我将我的应用程序简化为下面给出的一个演示我的问题的类。使用箭头键在图像上移动一个红色方块。挑战是我想要将游戏图形的更新从绘制复合图形到游戏引擎分离。我已经研究了这个问题的看似彻底的答案,但无法弄清楚如何将它们应用到我的应用程序中:

这是关键部分,无法正确清除像素。注释掉的部分来自我已经读过的堆栈溢出答案,但它们要么将背景绘制为不透明的黑色或白色。我知道foregroundImage在我的实现中以透明像素开头,因为您可以在应用程序开始时看到红色精灵后面backgroundImage的随机像素噪声。目前,图像未被清除,因此之前绘制的图像仍然存在。

/** 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();
}

以下是我的完整示例代码:

/**
 * 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();
    }   
}

编辑:

- 修正了for循环中的拼写错误,将backgroundPixels初始化为随机。

1 个答案:

答案 0 :(得分:4)

原来我在方法选择中傻了。我注意到我正在清理一个像素宽的盒子,这是我的图形轮廓。这是因为我不小心使用了drawRect()而不是fillRect()。在更改我的代码后,它现在有效。以下是我能够开展工作的例子。

使用AlphaComposite.CLEAR的示例(使用任何不透明颜色绘制):

    // 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

使用AlphaComposite.SRC_OUT的示例(使用alpha零的任何颜色绘制):

    // 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