简体   繁体   English

将BufferedImage的像素设置为透明

[英]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? 如何快速有效地将BufferedImage所有像素设置为透明,以便我可以简单地重绘每帧想要的精灵图形?

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 . 我正在用Java设计一个简单的游戏引擎,该引擎会更新背景和前景BufferedImage并将它们绘制到复合的VolatileImage以进行有效缩放,以绘制到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. 我知道我的实现中foregroundImage图像以透明像素开始,因为您可以在应用程序启动时看到红色小精灵后面的backgroundImage的随机像素噪声。 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. -修复了for循环中将backgroundPixels初始化为随机的输入错误。

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. 我注意到我正在清理一个1像素宽的框,该框是我图形的轮廓。 This is because I accidentally used drawRect() instead of fillRect() . 这是因为我不小心使用了drawRect()而不是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): 使用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

Example using AlphaComposite.SRC_OUT (draw with any color with alpha zero): 使用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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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