Creating a text image on the fly?

I can currently paint strings no problem using.


Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
g.setColor(Color.RED);
g.setFont(new Font("Arial",Font.BOLD,8));
g.drawString("BooM",x,y);

That easy, but now I want to create an Image, that is text. Like a score, kill monster a score value appears and floats upwards.

The acting and all that I got no problem, but I can’t seem to figure out how to create a dynamic text image. Since I don’t want to create a gif for each score value. I’d rather just create it on the fly.

So the question is:
Is this possible to do?
Which class should I use?

I assume I should be able to create it as a Graphics2D and some how change that graphic into a new image.

You do that like you did it in that snipped… the only difference is that you should create the font object outside the loop.

That’s all.

The other way is to use bitmap fonts like this one:

Yea thanks, hit me to after I went and grabbed a sandwich.

Add the text grapics to a HashMap and just manage them in there. Thanks.

yea just creating a method called updateFloatingMessages()

That will cycle and handle it all. Thanks.

For anyone else wonderring, create a class called FloatingMessage with a string, x and a y value. Then used these methods.


    public void paintFloatingMessages(){
        FloatingMessage fm;
        
        for(int i=0; i<messages.size(); i++){
            fm = (FloatingMessage)messages.get(i);
            Graphics2D g = (Graphics2D)strategy.getDrawGraphics(); 
            g.setColor(Color.RED); 
            g.setFont(new Font("Arial",Font.BOLD,8)); 
            g.drawString(fm.getMessage(),fm.getX(),fm.getX()); 
        }
    }

    public void updateFloatingMessages(){
        FloatingMessage fm;
        
        for(int i=0; i<messages.size(); i++){
            fm = (FloatingMessage)messages.get(i);
            if(fm.isMarkedForRemoval()){
                messages.remove(i);
            }
            fm.move();
        }
    }

that move() method should probably handle the floating up and fading out too. When the Alpha for the fade is <= 0, isMarkedForRemoval() should return true. Also, when you call remove(), you should decriment i by 1 otherwise you’ll be skipping the next entry.

Yea thats all in the FloatingMessage class


/*
 * Created on Dec 2, 2004
 *
 */
package game;


public class FloatingMessage {
    protected String message;
    protected int x;
    protected int y;
    protected int vY = -3;
    protected boolean markedForRemoval;
    
    public FloatingMessage(String m, int x, int y){
        message = m;
        this.x = x;
        this.y = y;
    }

    public FloatingMessage(){
        message = null;
        this.x = 0;
        this.y = 0;
    }
    
    public static void createFloatingMessage(Stage stage, String m, int x, int y){
        FloatingMessage fm = new FloatingMessage(m,x,y);
        stage.addFloatingMessage(fm);
    }
    
    public void setMessage(String m){
        message = m;
    }
    
    public String getMessage(){
        return message;
    }
    
    public void setX(int i){
        x = i;
    }
    
    public int getX(){
        return x;
    }
    
    public void setY(int i){
        y = i;
    }
    
    public int getY(){
        return y;
    }
    
    /**
     * Am I going to be deleted?
     * @return true or false
     */
    public boolean isMarkedForRemoval(){
       return markedForRemoval;    
    }
    
    public void move(){
        y+=vY;
        
        if(y < 0){
            remove();
        }
    }
    
    public void remove(){
        markedForRemoval = true;
    }
}

The only problem is the Y position is sometimes where I think it should be, but sometimes its way way off like right on my head, when it should be at like y 100, on a 800x600 screen.

I can’t seem to figure out why that is. I can’t step through in eclipse because the window is blank, so its hard to tell.

DOH!

Found the error, I pasted above there.

g.drawString( fm.getMessage(), fm.getX(), fm.getX() )

Passed in getX where it should have been my getY.

[quote]That easy, but now I want to create an Image, that is text.

I assume I should be able to create it as a Graphics2D and some how change that graphic into a new image.
[/quote]
The opposite way around, I believe: i.e. you’d create an Image and get the Graphics 2D from this.


BufferedImage floatingImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2d g = floatingImage.createGraphics();
g.setColor(Color.RED); 
g.setFont(new Font("Arial",Font.BOLD,8)); 
g.drawString("BooM",x,y); 
g.dispose();

If you did the above in the constructor of your FloatingMessage class, you’d then have a cached (and almost definitely “managed”) image that you could just draw into your BufferStrategy’s graphics rather than drawing the string every time. I don’t know if copying a managed image is quicker than drawing the text each time, but I assume it would be. (Anyone know?) The downside (there’s always one) is that all these managed images will use your precious video memory.

The trick with the above (the hard bit that I didn’t write for you!) would be working out what size to make your image and what y co-ordinate to paint the text at (x will almost definitely be zero).
I’m pretty sure you can work it out with FontMetrics, but I’ve never done it myself.

if you have a Graphics2D object handy somewhere, set the font, and call


Rectangle2D rect = g2d.getFontMetrics().getStringBounds("BooM!", g2d);

That’ll cover your dimensions. Keep in mind that Rectangle created is also effected by stuff like whether or not you have anti-aliasing turned on. As in, if g2d has it turned off, but the Graphics2D object you draw onto has it turned on, you may find that the String you draw doesn’t entirely fit. So make sure you keep that consistent.

You could also just call new FontMetrics(Font f) but you have to have a Graphics2D object to pass in later anyway for the reasons I described… so you might as well just use it I guess :wink: I dunno, maybe there’s some inefficiency in that.

[quote]I don’t know if copying a managed image is quicker than drawing the text each time, but I assume it would be. (Anyone know?)
[/quote]
It looks like I was right.
Chet Hasse recommends doing exactly this (caching rendered text in BufferedImages) in his latest article:
http://www.sys-con.com/story/?storyid=46983&DE=1

Thanks for the info all.

Good read grlea, thanks.

Another article for those interested.
http://java.sun.com/developer/technicalArticles/Media/intimages/

This works real nice, and the framerate is better as well.


/*
 * Created on Dec 2, 2004
 *
 */
package game;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.geom.Rectangle2D;

/**
 * @author Jeremy
 *
 */
public class FloatingMessage {
    protected String message;
    protected int x;
    protected int y;
    protected int vY = -3;
    protected boolean markedForRemoval = false;
    protected Image intermediateImage;
    protected static Font font = new Font("Arial",Font.BOLD,11);
    
    public FloatingMessage(String m, int x, int y){
        message = m;
        this.x = x;
        this.y = y;
    }

    public void paintComponent(Graphics2D g) {
        if (intermediateImage == null) {
            // First, measure the size of the text
            Rectangle2D rect = g.getFontMetrics().getStringBounds(message, g);
            int imageW = (int)(rect.getWidth() - rect.getX() + 2);
            int imageH = (int)(rect.getHeight() - rect.getY());
            // We must also account for text "descent" in determining where to draw string in image
            int descent = (int)(g.getFontMetrics().getDescent() + .5f);
            // Now, create the intermediate image
            GraphicsConfiguration gc = g.getDeviceConfiguration();
            intermediateImage = gc.createCompatibleImage(imageW, imageH, Transparency.BITMASK);
            // And render the transparent background and the text into the image
            Graphics2D gImg = (Graphics2D)intermediateImage.getGraphics();
            gImg.setComposite(AlphaComposite.Src);
            gImg.setColor(new Color(0,0,0,0));
            gImg.fillRect(0, 0, imageW, imageH);
            renderText(gImg, 0, imageH - descent);
            gImg.dispose();
        }
        g.drawImage(intermediateImage, x, y,null);
    }
    
    public void renderText(Graphics g, int x, int y) {
        g.setColor(Color.WHITE);
        g.setFont(font);
        g.drawString(message, x, y);
    }
    
    public static void createFloatingMessage(Stage stage, String m, int x, int y){
        FloatingMessage fm = new FloatingMessage(m,x,y);
        stage.addFloatingMessage(fm);
    }
    
    public void setMessage(String m){
        message = m;
    }
    
    public String getMessage(){
        return message;
    }
    
    public void setX(int i){
        x = i;
    }
    
    public int getX(){
        return x;
    }
    
    public void setY(int i){
        y = i;
    }
    
    public int getY(){
        return y;
    }
    
    /**
     * Am I going to be deleted?
     * @return true or false
     */
    public boolean isMarkedForRemoval(){
       return markedForRemoval;    
    }
    
    public void move(){
        y+=vY;
        
        if(y < 0){
            remove();
        }
    }
    
    public void remove(){
        markedForRemoval = true;
    }
}

In my main class I just call these subroutines when I need them.


public void paintFloatingMessages(Graphics2D g){
    FloatingMessage fm;
    
    for(int i=0; i<messages.size(); i++){
        fm = (FloatingMessage)messages.get(i);
        fm.paintComponent(g);
    }
}

public void updateFloatingMessages(){
    FloatingMessage fm;
    
    for(int i=0; i<messages.size(); i++){
        fm = (FloatingMessage)messages.get(i);
        if(fm.isMarkedForRemoval()){
            messages.remove(i);
        }
        fm.move();
    }
}