Poor performance with drawString from Graphics library - java

I'm making a 2D card game. In it I have a custom font uses drawString from the Graphics library to draw it in with custom color and size. Problem is that this seems incredibly unoptimized. I can't seem to find out why. With 5 cards on screen, each one using this method 4 times each, I got from 3,800 fps down to 350 fps. Here's the way I draw text:
public static void drawString(Graphics g, String text, int xPos, int yPos, boolean center, Color c, Font font) {
g.setColor(c);
g.setFont(font);
int x = xPos;
int y = yPos;
if (center) {
FontMetrics fm = g.getFontMetrics(font);
x = xPos - fm.stringWidth(text) / 2;
y = (yPos - fm.getHeight() / 2) + fm.getAscent();
}
g.drawString(text, x, y);
}

After a bit of googling here's how I did it. This image is created when a Card UI element is added to the screen. Then is used to just overlay on the card's image. The x and y scaling is done in a separate class. The code mentioned in the original comment is in the Text class, along with the other drawRestrictedText method:
public BufferedImage createTextOverlayImage(){
BufferedImage overlay = new BufferedImage(imageWidth, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = overlay.createGraphics();
Text.drawString(g, name,
(imageWidth / 2) + wd.getScaleX(18),
wd.getScaleY(21),
true,
Color.black, Assets.font20Bold);
Text.drawString(g, cost + "",
wd.getScaleX(28),
wd.getScaleY(28),
true,
Color.blue, Assets.font28Bold);
Text.drawRestrictedString(g, keyWords,
wd.getScaleX(20),
wd.getScaleY(162),
imageWidth,
Color.magenta,
Assets.font18Bold);
Text.drawRestrictedString(g, description,
wd.getScaleX(20),
wd.getScaleY(185),
imageWidth - wd.getScaleX(35),
Color.black,
Assets.font18);
return overlay;
}
Here's the site I used for reference: http://www.java2s.com/Tutorials/Java/Graphics_How_to/Text/Write_text_onto_image.htm

Related

Java Game Dev - How do I create a vertical health bar?

I need some assistance fixing my vertical health bar. It tracks health correctly and updates, but because of how Java draws (from the top left corner, i.e. (0,0)), the bar appears to be upside down. I would like to flip the health bar so it would appear correctly but I am unsure how to do so.
The division in the codes x and y is just to place the bar in the correct position on the canvas. The multiplier is to scale up the health bar to fit a graphical overlay I made for it.
private void drawHealthBar(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.fillRect(WIDTH - (WIDTH / 50), HEIGHT / 2, 10, ships.get(0).getHealth() * 6);
}
Setup the coordinate system how you want, using transform() and translate(). So:
you want the origin to be at (0, height); bottom left.
then you want to flip the Y axis.
Example code:
AffineTransform tform = AffineTransform.getTranslateInstance( 0, height);
tform.scale( 1, -1);
g2.setTransform( tform);
you need to shift bar depending on amount of health left, like this:
private void drawHealthBar(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
final int w = 10;
final int h = ships.get(0).getHealth() * 6; // calculate height first
final int x = WIDTH - (WIDTH / 50);
final int y = HEIGHT / 2 + (MAX_HEIGHT - h); // shift by height of whitespace
g2.fillRect(x, y, w, h);
}

Make string centered in rectangle

I have coordinates of rectangle to draw and I want to centre some text inside this rectangle.
int x, y, width, height;
String str = "This is a text";
x = 15;
y = 15;
width = 20;
heights = 30;
g.drawRect(x, y, width, height);
g.drawString(str, x + width/2, y + height/2);
If you want to center the text then you need to know the length of the text so you know its width relative to the width of the rectangle. This is done by getting the FontMetrics instance from the Graphics object.
So the basic code would be:
FontMetrics fm = g.getFontMetrics();
int stringWidth = fm.getStringWidth(...);
int xDiff = (width - stringWidth) / 2;
g.drawString(str, x + xDiff, ...);
Of course you will also need to center based on the height.
Doc says...
public abstract void drawString(String theString,
int x,
int y)
Renders the text of the specified iterator applying its attributes in accordance with the specification of the TextAttribute class.
The baseline of the leftmost character is at position (x, y) in this graphics context's coordinate system.
So... you are drawing your string starting at the center point of the rect but not the string.
https://docs.oracle.com/javase/7/docs/api/java/awt/FontMetrics.html - get the font metrics from the gc and figure out how wide the string is. Then subtract half of that from the start x. Do something similar for y (remember that height doesn't start at base.)

How can I create a non-square BufferedImage from a non-square image file?

I'm trying to create a BufferedImage from an arbitrary image file and then center that image in the background of a JPanel. I don't have any problems with square images, but I can't figure out how to handle non-square images.
Some debugging indicates that the (immediate) problem is that when I use ImageIO to create a BufferedImage from a rectangular input file, say one that's 256x128, BufferedImage.getHeight() returns 256 rather than 128.
Here's a snippet approximating my code:
class ExtendedPanel extends JPanel {
static final int WIDTH = 400;
static final int HEIGHT = 400;
BufferedImage image;
public ExtendedPanel(File f) {
super();
setPreferredSize(new Dimension(WIDTH,HEIGHT));
image = ImageIO.read(f);
}
#Override
public void paintComponent(Graphics g) {
int x = (WIDTH - image.getWidth())/2;
int y = (HEIGHT - image.getHeight())/2;
Graphics2D g2d = (Graphics2d)g;
g2d.drawRenderedImage(image,AffineTransform.getTranslateInstance(x,y));
}
}
As I said, this is fine for square image files. But with rectangular images that are wider than they are tall, the image is displayed higher than it should be. I haven't tried it yet with images taller than they are wide but I'm afraid that it that case the image would be displayed too far to the left. What can I do?
It is more a problem of (understanding) the right calculation.
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2d)g;
// How to scale the image:
double xscale = ((double)WIDTH) / image.getWidth();
double yscale = ((double)HEIGHT) / image.getHeight());
// When scaling proportionally:
double scale = Math.min(xscale, yscale); // max for covering entire panel.
xscale = scale;
yscale = scale;
double w = scalex * image.getWidth();
double h = scaley * image.getHeight();
double x = (getWidth() - w) / 2;
double y = (getHeight() - h) / 2;
g.drawImage(img, (int)x, (int)y, (int)w, (int)h, Color.BLACK, null);
//g2d.translate(x, y);
//g2d.scale(xscale, yscale);
//g2d.draw...;
}
Using the simple (scaling) version of drawImage what is needed is entirely clear.
To be considered is proportionally scaling, filling entirely (loss of image part) or upto maximal size (seeing background).

Coloring the outside of a circle using arcAngle

Question about the circumference of circles. In order to change the outside color of the circle (circumference) I would use
drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
Just not exactly how to start off after the following code below..after Public void drawArc I dont know where to go
public void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension d = getSize();
for(int i = 0; i < 100; ++i) {
Color color = new Color(generator.nextInt(255), generator.nextInt(255), generator.nextInt(255));
g.setColor(color);
int circleSize = generator.nextInt(d.width / 4);
int x = generator.nextInt(d.width - circleSize);
int y = generator.nextInt(d.height - circleSize);
g.fillOval(x, y, circleSize, circleSize);
g.drawArc(x, y, circleSize, circleSize, 0, 360);
}
}
You are drawing the body of a circle, then drawing its outline, without changing the colour in between. That means you can't actually see the outline of the circle.
I think you should change the colour of the graphics context, before you draw the outline. One way would be to insert
color = new Color(generator.nextInt(255), generator.nextInt(255), generator.nextInt(255));
g.setColor(color);
before the call to drawArc.
You don't need your own drawArc method, you should be calling the Graphics.drawArc() method. x and y are the center of the circle, width and height are the diameter of the circle, and startAngle and arcAngle are where to start and stop drawing the circle. 0 is 3 o'clock. So to draw a complete circle you would use 0 and 360 for startAngle and arcAngle.

How to get the correct String width from FontMetrics in JAVA

I calculate the width of a string by using the stringWidth("str") method on the FontMetrics object. This method only gives me the width from the starting point to the ending point on the baseline. Not the overall width of the string.
Any ideas on how to calculate the overall width?
Most documentation says that I can't rely on the result by adding the width of each char in the string.
Here is my code until now:
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
FontMetrics fm = g.getFontMetrics(new Font("Arial", Font.PLAIN, 6));
int width = fm.stringWidth("Product name");
FontMetrics also has getStringBounds(), not just stringWidth().
You should ask yourself what you need the text width for. If it's for output via e.g. a paintComponent() override, then you should measure the text dimensions there, which makes sure that all factors (e.g. fractionalmetrics, antialiasing) are taken into consideration. Also, you don't have to think about disposing the graphics context - which in your example, you definitely have to, it needs g.dispose()!
The following example for use in a paintComponent() override, e.g. for the JPanel you're using as your ContentPane, draws a text at the center of the component in a font given by you and draws a rectangle around it with some distance, the text being perfectly in its center.
The text size, especially vertically, is not precise, however. A better solution is further down.
Screenshot of this imprecise solution: http://i.imgur.com/vetRjCK.png
Screenshot of precise solution further down: http://i.imgur.com/0A0EdCf.png
final int w = getWidth();
final int h = getHeight();
// CLEAR BACKGROUND
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, w, h);
// ACTIVATE ANTIALIASING AND FRACTIONAL METRICS
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// PREPARE TEXT, COLOR, FONT
final String text = "The Higgs Boson is ...";
g.setColor(Color.ORANGE);
g.setFont(yourFont);
// PREPARE COORDINATES, AND DRAW TEXT
final FontMetrics fm = g.getFontMetrics();
final Rectangle2D stringBounds = fm.getStringBounds(text, g);
final double x = (w - stringBounds.getWidth()) / 2d;
final double y = (h - stringBounds.getHeight()) / 2d;
g.drawString(text, (int) x, (int) (y + fm.getAscent()));
// TURN OFF ANTIALIASING FOR HIGHER VISUAL PRECISION OF THE LINES
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
// DRAW RECTANGLE BORDER
final double borderDistance = 10d;
final Shape borderRect = new Rectangle2D.Double(x - borderDistance * 2, y - borderDistance, stringBounds.getWidth() + borderDistance * 4, stringBounds.getHeight() + borderDistance * 2);
g.setStroke(new BasicStroke(3f));
g.draw(borderRect);
// DRAW THIN TIGHT RECTANGLE BORDER
final Shape borderRectTight = new Rectangle2D.Double(x, y , stringBounds.getWidth(), stringBounds.getHeight());
g.setStroke(new BasicStroke(1f));
g.setColor(Color.GRAY);
g.draw(borderRectTight);
The following solution is structurally just like the above, but instead of using FontMetrics-calls to loosely derive the text dimensions, it derives the precise text dimensions by converting the text into a Shape first.
// CLEAR BACKGROUND
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, w, h);
// ACTIVATE ANTIALIASING AND FRACTIONAL METRICS
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// PREPARE TEXT, COLOR
final String text = "The Higgs Boson is ...";
g.setColor(Color.ORANGE);
// CREATE GLYPHVECTOR FROM TEXT, CREATE PRELIMINARY SHAPE FOR COORDINATE CALCULATION, CALC COORDINATES
final GlyphVector gv = yourFont.createGlyphVector(g.getFontRenderContext(), text);
final Rectangle2D stringBoundsForPosition = gv.getOutline().getBounds2D();
final double xForShapeCreation = (w - stringBoundsForPosition.getWidth()) / 2d;
final double yForShapeCreation = (h - stringBoundsForPosition.getHeight()) / 2d;
// DERIVE SHAPE AGAIN, THIS TIME IN THE RIGHT PLACE (IT'S NOT THE ONLY POSSIBLE METHOD.)
final Shape textShape = gv.getOutline((float) xForShapeCreation, (float) yForShapeCreation + g.getFontMetrics(yourFont).getAscent());
g.fill(textShape);
// GET PRECISE SHAPE BOUNDS, TURN OFF ANTIALIASING FOR HIGHER VISUAL PRECISION OF THE LINES
final Rectangle2D stringBoundsForEverything = textShape.getBounds2D();// JavaDocs: "Returns a high precision [...] bounding box of the Shape [...] guarantee [...] that the Shape lies entirely within the indicated Rectangle2D."
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
// DRAW RECTANGLE BORDER
final double borderDistance = 10d;
final Shape borderRect = new Rectangle2D.Double(stringBoundsForEverything.getX() - borderDistance * 2, stringBoundsForEverything.getY() - borderDistance, stringBoundsForEverything.getWidth() + borderDistance * 4, stringBoundsForEverything.getHeight() + borderDistance * 2);
g.setStroke(new BasicStroke(3f));
g.draw(borderRect);
// DRAW THIN TIGHT RECTANGLE BORDER
final Shape borderRectTight = new Rectangle2D.Double(stringBoundsForEverything.getX(), stringBoundsForEverything.getY(), stringBoundsForEverything.getWidth(), stringBoundsForEverything.getHeight());
g.setStroke(new BasicStroke(1f));
g.setColor(Color.GRAY);
g.draw(borderRectTight);
you need a graphics object.
graphics.setFont(font);
int lenghtgraphics.getFontMetrics(graphics.getFont()).stringWidth(value);
I built an xml rendering component a few years back and rely very heavily on
SwingUtilities.computeStringWidth(fontMetrics, string);
I get the font metrics from the component that I'm measuring the string for.
myComponent.getFontMetrics(myComponent.getFont());

Categories

Resources