[英]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.