I have a requirements to create a web service that will generate user's icon based on their initials. Similar to this Android project but on the server side using Java.
The size of that image should be dynamic. I already have the code that will create a rectangle with two letters in the middle but it is not scaling the text.
Here is my code so far:
public BufferedImage getAbbreviationImage(int height, int width, String abbreviation) throws IOException {
int centerX = width/2;
int centerY = height/2;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.SCALE_SMOOTH);
Graphics2D g = bufferedImage.createGraphics();
Font font = new Font("Helvetica", Font.BOLD, 90);
g.setFont(font);
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
g.setColor(Color.decode("#3f404c"));
g.fillRect(0, 0, width, height);
// get the bounds of the string to draw.
FontMetrics fontMetrics = g.getFontMetrics();
Rectangle stringBounds = fontMetrics.getStringBounds(abbreviation, g).getBounds();
FontRenderContext renderContext = g.getFontRenderContext();
GlyphVector glyphVector = font.createGlyphVector(renderContext, abbreviation);
Rectangle visualBounds = glyphVector.getVisualBounds().getBounds();
// calculate the lower left point at which to draw the string. note that this we
// give the graphics context the y corridinate at which we want the baseline to
// be placed. use the visual bounds height to center on in conjuction with the
// position returned in the visual bounds. the vertical position given back in the
// visualBounds is a negative offset from the basline of the text.
int textX = centerX - stringBounds.width/2;
int textY = centerY - visualBounds.height/2 - visualBounds.y;
g.setColor(Color.WHITE);
g.drawString(abbreviation, textX, textY);
g.dispose();
return bufferedImage;
}
Is there any Java library that can do something like this already so that I don't have to write my own code. If not, then what would be the best approach to scale text based on the image size?
Credits:
Some of my code was take from HERE
You need to set the size of the font which you attach to the Graphics2D object. From the oracle docs:
public abstract void drawString(String str,
int x,
int y)
Renders the text of the specified String, using the current text
attribute state in the Graphics2D context
You should set the size of the font you use appropriately to match the dimensions of the rectangle. Something like this:
int lFontSize = 90 * (originalRectangleWidth / newRectangleWidth);
Font font = new Font("Helvetica", Font.BOLD, lFontSize );
where:
90 is the reference font size (this is what you are setting in the example)
originalRectangleWidth would be the size of the rectangle you use when the font looks good with size 90
newRectangleWidth would be the new rectangle width
References:
Graphics2D (oracle ref)
Font (oracle ref)
Related
thanks for checking out my question.
I am working a menu for the game that I'm making, and I want to center the game's title on the main menu. I've looked at several questions here on Stack Overflow, but couldn't find an answer.
Problem
The problem that I'm having is that whenever I call my centerString() method, it puts it in the center of the y-axis, but not of the x-axis. What am I doing wrong here?
Code
Method code
private void centerString(String txt, int width, int height, Graphics g, Font font) {
FontMetrics metrics = g.getFontMetrics();
int x = (width - metrics.stringWidth(txt)) / 2;
int y = (metrics.getAscent() + (height - (metrics.getAscent() + metrics.getDescent())) / 2);
g.setFont(font);
g.drawString(txt, x, y);
}
Where it's called
final Font fnt = new Font("Arial", Font.BOLD, 36);
centerString("ThatMarioEngine", toInt(screenSize.getWidth()), toInt(screenSize.getHeight()), g, fnt);
I have fixed the issue by making using the windows size, instead of the screen size. The centering issue was fixed by putting g.setFont(font); before I get the FontMetrics.
There is a circle (planet), drawn through fillOval(). I need to write the text (number) on the circle, so that it must be always aligned exactly in the center. There is a center of the circle and its radius.
I understand that I need to calculate the width and length of the text (tried by getStringBounds), but does not work correctly.
protected void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Planet planet : planets) {
g2d.setColor(planet.getColor());
g2d.fillOval((int) planet.getX() - planet.getRadius(), (int) planet.getY() - planet.getRadius(),
planet.getRadius() * 2, planet.getRadius() * 2);
g.setFont(new Font("Serif", Font.PLAIN, 20));
g.setColor(Color.black);
String s = String.valueOf(250);
FontMetrics fm = g.getFontMetrics();
double textWidth = fm.getStringBounds(s, g).getWidth();
g.setColor(Color.white);
g.drawString(s, (int) ((int) planet.getX() - textWidth/2), ((int) planet.getY() + fm.getMaxAscent() / 2));}}
You're using planet.getX()-planet.getRadius() and planet.getY() - planet.getRadius() to calculate your centered point of the circle.
Then you have to add the Text you want to paint at the centered position, which is calculated
planet.getX() - planet.getRadius() - textWidth/2 //or centered point - textWidth/2
which is your x-coordinate and
planet.getY() - planet.getRadius() + fm.getMaxAscent() / 2 //or centered point + fm.getMaxAscent()/2
which will be the y-coordinate.
But you are using planet.getX() - textWidth/2
This origin point has to be the center of the circle, not planet.getX()
In the past I've had to use both the ascent/descent values to calculate actual font height and it looks like you're only using ascent. Something to consider: not all strings will have the same max ascent/descent for a given font, so while the string "Dojo" may appear to be properly vertically aligned, the strings "cujo" and "coco" might not.
Another suggestion: instead of calculating the half-width of your text and using that to offset your drawing starting position, why not just center justify the text?
This is a method that returns awt fonts character as a bufferedimage:
private BufferedImage getCharImage(char ch) {
BufferedImage sizeImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D sizeGraphics = (Graphics2D) sizeImage.getGraphics();
if (antiAlias) {
sizeGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
FontMetrics fontMetrics = sizeGraphics.getFontMetrics(font);
int charwidth = fontMetrics.charWidth(ch);
if (charwidth <= 0) {
charwidth = 1;
}
int charheight = fontMetrics.getHeight();
if (charheight <= 0) {
charheight = fontSize;
}
BufferedImage charImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB);
Graphics2D charGraphics = (Graphics2D) charImage.getGraphics();
if (antiAlias) {
charGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
charGraphics.setFont(font);
charGraphics.setColor(Color.WHITE);
charGraphics.drawString(String.valueOf(ch), 0, fontMetrics.getAscent());
return charImage;
}
The problem is that i get incorrect width, and the characters don't fit to crated buffered images. If i would create the buffered image like this new BufferedImage(charwidth + 10, charheight, BufferedImage.TYPE_INT_ARGB); the characters would fit to images (that means that i get wrong font width). Why am i facing this prolem and how ca i fix it? I'm using arialbi.ttf (bold, italic).
This is how the font renders
Edit:
This is how i define the font variable:
font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
font = font.deriveFont(fontSize);
I think this is because the advance, or charWidth() doesn't have much to do with the actual width of the character as defined by the font.
Javadoc for FontMetrics.charWidth(char ch):
The advance is the distance from the leftmost point to the rightmost
point on the character's baseline
A character like "g" in Chancery Italic, below the baseline, extends left of the minimum x. Or "s" in the same font extends right of the maximum x, above the baseline. In a straighter font, like Arial Regular, the advance (baseline width) happens to be pretty close to the actual width of the character.
I'm not sure of a good solution besides adding arbitrary padding.
See also: http://docs.oracle.com/javase/tutorial/2d/text/measuringtext.html
I believe this is what is required to get the exact width of a string :
FontMetrics fontMetricesForLabel = graphics.getFontMetrics();
double width = fontMetricesForLabel.getStringBounds(label, graphics).getWidth();
I'm looking for a way to infer a Java AWT Font size from a width. For example, I know I want to write 'hello world' within 100 pixels. I know I'm using the font "Times", in style Font.PLAIN, and I want to get the font size that fits the best with my given width of 100 pixels.
I know I could calculate it in a loop (something like while(font.getSize() < panel.getWidth()), but to be honest I don't find it very elegant.
You can get the rendered width and height of a string using the FontMetrics class (be sure to enable fractional font metrics in the Graphics2D instance to avoid rounding errors):
Graphics2D g = ...;
g.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
Font font = Font.decode("Times New Roman");
String text = "Foo";
Rectangle2D r2d = g.getFontMetrics(font).getStringBounds(text, g);
Now, when you have the width of the text using a font with the default (or actually any) size, you can scale the font, so that the text will fit within a specified width, e.g. 100px:
font = font.deriveFont((float)(font.getSize2D() * 100/r2d.getWidth()));
Similarly, you may have to limit the font size, so that you don't exceed the available panel height.
To improve the appearance of the rendered text, you should also consider enabling antialiasing for text rendering and/or kerning support in the font:
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Map<TextAttribute, Object> atts = new HashMap<TextAttribute, Object>();
atts.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
font = font.deriveFont(atts);
Have a look at these two methods I am using. It is not elegant as you say, but it works.
private static int pickOptimalFontSize (Graphics2D g, String title, int width, int height) {
Rectangle2D rect = null;
int fontSize = 30; //initial value
do {
fontSize--;
Font font = Font("Arial", Font.PLAIN, fontSize);
rect = getStringBoundsRectangle2D(g, title, font);
} while (rect.getWidth() >= width || rect.getHeight() >= height);
return fontSize;
}
public static Rectangle2D getStringBoundsRectangle2D (Graphics g, String title, Font font) {
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
Rectangle2D rect = fm.getStringBounds(title, g);
return rect;
}
How can I draw a String in Java (using Graphics2d) in monospace mode? I have a font that looks like LCD screen font, and I want to draw something like LCD label.
I am using Digital 7 Mono font.
Do you know where I can find another font that will be monospace and lcd (I wan to type only digitals)?
How can I draw a String in Java (using Graphics2d) in monospace mode?
The essential method needed to render text is drawString(), as outlined below. There is no "monospace mode", per se, but even proportionally spaced fonts typically use a constant width for digit glyphs .
private static final Font font = new Font("Monospaced", Font.PLAIN, 32);
private static final String s = "12:34";
...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(font);
int xx = this.getWidth();
int yy = this.getHeight();
int w2 = g.getFontMetrics().stringWidth(s) / 2;
int h2 = g.getFontMetrics().getDescent();
g.setColor(Color.green);
g.drawString(s, xx / 2 - w2, yy / 2 + h2);
}
There's a complete example here, and you can extend a suitable JComponent to control positioning using a layout manager, as seen here.