Is there a way to calculate the length of a string in pixels, given a certain java.awt.Font object, that does not use any GUI components?
that does not use any GUI components?
It depends on what you mean here. I'm assuming you mean you want to do it without receiving a HeadlessException.
The best way is with a BufferedImage. AFAIK, this won't throw a HeadlessException:
Font font = ... ;
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
FontMetrics fm = img.getGraphics().getFontMetrics(font);
int width = fm.stringWidth("Your string");
Other than using something like this, I don't think you can. You need a graphics context in order to create a FontMetrics and give you font size information.
You can use the Graphics2D object to get the font bounds (including the width):
Graphics2D g2d = ...
Font font = ...
Rectangle2D f = font.getStringBounds("hello world!", g2d.getFontRenderContext());
But that depends on how you will get the Graphics2D object (for example from an Image).
This gives the output of (137.0, 15.09375) for me. I have no idea what the units are, but it certainly looks proportionally correct and doesn't use Graphics2D directly.
Font f = new Font("Ariel", Font.PLAIN, 12);
Rectangle2D r = f.getStringBounds("Hello World! Hello World!", new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT));
System.out.println("(" + r.getWidth() + ", " + r.getHeight() + ")");
I needed to get length and width of a string before paintComponent was called so I could size the enclosing panel to the text dimensions. None of these techniques provided a sufficiently width and I did not have a Graphics object available. I resolved the issue by setting my font to "Monospaced".
Related
This code snippet creates an image which contains text. I set the font to Serif. However, when I query the resulting image later on for its font face, it returns Dialog. I don't understand why this is.
BufferedImage img = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(img, 0, 0, 200, 100, this); // "this" refers to my custom JPanel which I am setting in the JFrame.
g2d.setPaint(Color.red);
g2d.setFont(new Font("Serif", Font.BOLD, 20));
System.out.println("from g2d object: " + g2d.getFont().getFamily()); // outputs "Serif"
String s = "Hello, world!";
FontMetrics fm = g2d.getFontMetrics();
int x = img.getWidth() - fm.stringWidth(s) - 5;
int y = fm.getHeight();
g2d.drawString(s, x, y);
g2d.dispose();
System.out.println("from image: " + img.getGraphics().getFont().getFamily()); // outputs "Dialog" (expected "Serif")
I understand that Dialog is one of the logical fonts in Java, but if the font is set to be something else, and Font.getFontName() returns the font face for the given font, why isn't it returning Serif as set in the Graphics2D object?
UPDATE: Calling g2d.dispose() before or after the last System.out.println() makes no difference. Both ways, it still prints out Dialog.
BufferedImage.getGraphics() returns the result of BufferedImage.createGraphics(). And, following the trail, we end up at that method of SunGraphicsEnvironment:
/**
* Returns a Graphics2D object for rendering into the
* given BufferedImage.
* #throws NullPointerException if BufferedImage argument is null
*/
public Graphics2D createGraphics(BufferedImage img) {
if (img == null) {
throw new NullPointerException("BufferedImage cannot be null");
}
SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
}
We can clearly see that it uses the defaultFont, hardly connected to the image (unless getPrimarySurface() does change the defaultFont - I guess not [I could not find it being changed]).
Source code can be found here
Setting the Font of a Graphics will not change anything in a BufferedImage. The Font is used by the Graphics to draw the text onto the image. If a new Graphics is obtained from the image (using getGraphics() or createGraphics()), it will have the defaultFont as defined in the GraphicsEnvironment.
Do this:
BufferedImage img = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(img, 0, 0, 200, 100, null); // "this" refers to my custom JPanel which I am setting in the JFrame.
g2d.setPaint(Color.red);
g2d.setFont(new Font("Serif", Font.BOLD, 20));
System.out.println("from g2d object: " + g2d.getFont().getFamily()); // outputs "Serif"
String s = "Hello, world!";
FontMetrics fm = g2d.getFontMetrics();
int x = img.getWidth() - fm.stringWidth(s) - 5;
int y = fm.getHeight();
g2d.drawString(s, x, y);
System.out.println(g2d.getFont().toString()+"-"+img.getGraphics().getFont().toString()+" from image: " + img.getGraphics().getFont().getFamily());
g2d.dispose();
System.out.println("from image: " + img.getGraphics().getFont().getFamily());
As you can see g2d retains its beloved Serif notation. The image is a thing of itself. Each time you call createGraphics on the image you get a new thing.
Everyone is making this more complicated than it needs to be. The answer is quite simple: the getGraphics() method of Image always creates a brand new Graphics object. From the documentation:
Creates a graphics context for drawing to an off-screen image.
That’s all there is to it. You set the font in one Graphics object, then you created another Graphics object. Of course the new Graphics object doesn’t know anything about the state of the first Graphics object. That would be like expecting that turning on one car’s headlights could somehow cause all cars to turn on their headlights.
You can get the expected result by only calling getGraphics() once.
System.out.println("from image: " + g2d.getFont().getFamily());
TL:DR The font is not accessible in Graphics or Graphics2D.
Whether you invoke BufferedImage#createGraphics() directly or BufferedImage#getGraphics() the end result is the same: they both create a new graphics object. More specifically, the former returns a new Graphics object and the latter returns a Graphics2D object. Both are abstractions of the concrete type returned which is an instance of SunGraphics2D. Unfortunately, this object does not store any information set, related to the font, in the graphics object. So basically, I cannot get the data I need from the image object.
In contrast, ProxyGraphics2D and PeekGraphics - both subtypes of the abstract class Graphics2D class do store such information in a Graphics2D global variable called mGraphics. This object is accessed via the getDelegate() method, which is obviously not part of the Graphics or Graphics2D API. Unfortunately, both of these subclasses are part of the sun package which is no longer accessible. Examining these classes clearly shows that methods like setFont() do save the font in this delegate (Graphics2D) object.
since I work on a LWJGL project, it encountered a problem to display text.
In theory I'd like to iterate through the (extended) ascii table and save each texture of all the chars inside of a ArrayList.
But my problem is that it won't get the right dimensions while creating a char image. The dimensions seem to be 1 (width) x 3 (height)
Code:
private static BufferedImage createCharImage(Font font, char c) {
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
// As you see, I set the font size to 100 for testing
font.awtFont.deriveFont(100);
graphics.setFont(font.awtFont);
FontMetrics fontMetrics = graphics.getFontMetrics();
graphics.dispose();
// Here the problem appears that the dimensions seem to be w: 1 and h: 3
Vector2i charDimensions = new Vector2i(fontMetrics.charWidth(c), fontMetrics.getHeight());
if (charDimensions.x == 0) {
return null;
}
image = new BufferedImage(charDimensions.x, charDimensions.y, BufferedImage.TYPE_INT_ARGB);
graphics = image.createGraphics();
graphics.setFont(font.awtFont);
graphics.setPaint(java.awt.Color.WHITE);
graphics.drawString(String.valueOf(c), 0, fontMetrics.getAscent());
graphics.dispose();
return image;
}
May anyone help me out there please?
Or if you have another idea to fix this, just let me know
The line font.awtFont.deriveFont(100); in your code currently literally does nothing except waste time by creating a new font and then immediately discarding it and using the original font instead.
Derive the new font and then use it:
....
Font bloodyHuge = font.awtFont.deriveFont(100);
graphics.setFont(bloodyHuge);
...
I have some java code that needs to programmatically render text onto an image. So I use BufferedImage, and write text on the Graphics object.
However, when configuring the font instance, one would specify the font size in points. When a piece of text is rendered onto an image, AWT will translate the points into pixels, based on the resolution of the Graphics object. I don't want to get myself involved in computing the pixel/point ratio, since it's really the task for the AWT. The image that is being produced is for a high resolution device (higher than any desktop monitors).
But, I don't seem to find a way to specify what the resolution of the Graphics is. It inherits it from the local graphics environment, which is beyond my control. I don't really want this code to be dependent on anything local, and I'm not even sure it's "sane", to use local graphics environment for determining the resolution of off screen rasters, who knows what people would want them for.
So, any way I can specify the resolution for an off screen image of any kind (preferably the one that can create Graphics object so I can use standard AWT rendering API)?
(update)
Here is a (rather long) sample problem that renders a piece of text on an image, with predefined font size in pixels (effectively, the target device DPI is 72). What bugs me, is that I have to use local screen DPI to make the calculation of the font size in points, though I'm not using the screen in any way, so it's not relevant, and plain fails on headless systems all together. What I would loved in this case instead, is being able to create an off screen image (graphics, raster), with DPI of 72, which would make points, by value, be equal to pixels.
Sample way to run the code:
$ java FontDisplay Monospace 150 "Cat in a bag" 1.png
This would render "Cat in a bag message", with font size of 150 pixels, on a 150 pixel tall image, and save the result in 1.png.
import java.awt.*;
import java.awt.image.*;
import java.awt.font.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import java.io.*;
import java.util.*;
public class FontDisplay {
public static void main(String a[]) throws Exception {
// args: <font_name> <pixel_height> <text> <image_file>
// image file must have supported extension.
int height = Integer.parseInt(a[1]);
String text = a[2];
BufferedImage bi = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_ARGB);
int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
System.out.println("dpi : "+dpi);
float points = (float)height * 72.0F / (float)dpi;
System.out.println("points : "+points);
Map m = new HashMap();
m.put(TextAttribute.FAMILY, a[0]);
m.put(TextAttribute.SIZE, points);
Font f = Font.getFont(m);
if (f == null) {
throw new Exception("Font "+a[0]+" not found on your system");
}
Graphics2D g = bi.createGraphics();
FontMetrics fm = g.getFontMetrics(f);
int w = fm.charsWidth(text.toCharArray(), 0, text.length());
bi = new BufferedImage(w, height, BufferedImage.TYPE_INT_ARGB);
g = bi.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, height);
g.setColor(Color.WHITE);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
g.setFont(f);
g.drawString(text, 0, fm.getMaxAscent());
String fName = a[3];
String ext = fName.substring(fName.lastIndexOf('.')+1).toLowerCase();
File file = new File(fName);
ImageWriter iw = ImageIO.getImageWritersBySuffix(ext).next();
ImageOutputStream ios = ImageIO.createImageOutputStream(file);
iw.setOutput(ios);
iw.write(bi);
ios.flush();
ios.close();
}
}
Comparing points to pixels is like kg to Newton where the acceleration may give varying conversions. AWT lets you elect a device (screen, printer), but in your case you definitely have to determine your ratio.
You may of course use Photoshop or Gimp and create a normative image for java.
After elaborated question:
Ah, I think I see the misunderstanding. An image does only concern pixels, never points, mm, DPI, or whatever. (Sometimes only as metainfo added separately to the image.)
So if you know the DPI of your device, the inches you want to use, then the dots/pixels are clear. points/dpi may shed more light.
How to get FontMetrics without use Graphics ? I want to get FontMetrics in constructor, now I do this way:
BufferedImage bi = new BufferedImage(5, 5, BufferedImage.TYPE_INT_RGB);
FontMetrics fm = bi.getGraphics().getFontMetrics(font);
int width = fm.stringWidth(pattern);
int height = fm.getHeight();
No you do not necessarily need to get/use the graphics object:
Font font = new Font("Helvetica",Font.PLAIN,12);
Canvas c = new Canvas();
FontMetrics fm = c.getFontMetrics(font);
If you would now call c.getGraphics() it would return null. The canvas solution on the other hand will also work in headless mode.
Hmm... It is quite logical that you need graphics to get FontMetrics. Font height, width etc. can differ on various displays.
If you have some Component, you can use it for getting FontMetrics:
component.getFontMetrics(font);
Is there a friendlier way to get an instance of FontMetrics than
FontMetrics fm = Graphics.getFontMetrics(Font);
I hate this way because of the following example:
If you want to create in a game a menu and you want all the menuitems in the center of the screen you need fontmetrics. But, mostly, menuitems are clickable. So I create an array of Rectangles and all the rectangles fits around the items, so when the mouse is pressed, I can simply use
for (int i = 0; i < rects.length; i++)
if (rects[i].contains(mouseX, mouseY)) { ... }
But to create the rects I also need FontMetrics for their coordinates. So this mean that I have to construct all my rectangles in the paint-method of my menu.
So I want a way to get the FontMetrics so I can construct the Rectangles in a method called by the constructor.
For me the easiest way was to:
Font font = new Font("Helvetica",Font.PLAIN,12);
Canvas c = new Canvas();
FontMetrics fm = c.getFontMetrics(font);
Benefits:
If you call c.getGraphics() it will return null (thus there is no graphics object)
This (canvas) will also work in headless mode.
Now you can easily get height and width...
The really correct answer is to use Toolkit.
Font font = new Font("Courier New", Font.PLAIN, 14);
FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
Once the background component, i.e. whatever is behind your menu, has been rendered, it has a Graphics object that you can use to get the metrics for a given font, just once.
You certainly don't want to be doing this in the paint method, which should be as lightweight as possible. I'd hang this code on a listener that gets called when the component is first rendered. It can store the resulting FontMetrics object somewhere where you can later access it, either in a paint method for drawing those menu item boxes.
Rather than determining the measurements of your menu graphics at the last moment, i.e. when painting, it might be a good idea instead to create some components to represent your menu. You can place those components on the Glass Pane more info here so they'll float above everything else, and you'll have the added bonus that those components are all capable of accepting mouse clicks and firing listener events on them, and since they only capture events on their own geometry you don't even have to figure out which part of menu was hit by the click, if at all.
Another advantage of using components here is that you may entirely get around the requirement for fiddling with font metrics. There are ready-made menu items, or you could just use JLabels, and you can specify their alignment, you can use a LayoutManager to size the boxes to the width of the biggest label, and so forth.
Assuming the menu text is fixed, you could pre-draw the text to a BufferedImage with alpha transparency and make your calculations then. Then, when you need the menu text, just draw the image.
You'll still have to do some offset calculations to centre the image (assuming the panel size can change), but these should be relatively lightweight.
I think this is a good solution
private static HashMap<Font, FontMetrics> fontmetrics = new HashMap<Font, FontMetrics>();
public static FontMetrics getFontMetrics(Font font)
{
if (fontmetrics.containsKey(font))
{
return fontmetrics.get(font);
}
FontMetrics fm = createFontMetrics(font);
fontmetrics.put(font, fm);
return fm;
}
private static FontMetrics createFontMetrics(Font font)
{
BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = bi.getGraphics();
FontMetrics fm = g.getFontMetrics(font);
g.dispose();
bi = null;
return fm;
}
Adding to what Lonzak said, how about this:
public static FontMetrics getFontMetrics(Font font){
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration config = gd.getDefaultConfiguration();
Canvas c = new Canvas(config);
return c.getFontMetrics(font);
}
You could store the 'config' variable as a static variable so it is constructed once in some utility font class that contains other font related information for your game/development environment. I guess you could also do this with the canvas variable.
Updated recommendation. FontMetrics is deprecated. Use LineMetrics instead.
String text = "some string";
FontRenderContext frc = new FontRenderContext(font.getTransform(), true, true);
LineMetrics lm = font.getLineMetrics(text, frc);
However, some methods such as SwingUtilities.computeStringWidth require a FontMetrics instance. Another option is to compute the bounds of the String.
Rectangle2D bounds = font.getStringBounds(text, frc);
Then the width and height may be obtained from the bounds.