Any better way to draw string with outline in Java? - java

I need to draw white text with black outline on a Graphics object I get from an image. I first draw the same text 4 times (moved left or right by one pixel) with black color and then once with white color. However this won't work well if outline needs to be more than 1px wide, and really seems like a hack. Is there a better way to do this?
final BufferedImage image = ImageIO.read(new File("./test.jpg"));
Graphics g = image.getGraphics();
g.setFont(g.getFont().deriveFont(45F));
// coordinates
int x = 100;
int y = 100;
String text = "Hello world";
g.setColor(Color.black);
g.drawString(text, x + 1, y - 1);
g.drawString(text, x + 1, y + 1);
g.drawString(text, x - 1, y - 1);
g.drawString(text, x - 1, y + 1);
g.setColor(Color.white);
g.drawString(text, x, y);
screenshot: https://i.imgur.com/ONLsPxy.png

The other option is to use below piece of code of creating a shape and then first drawing outline and then fill it:
Graphics2D g2d = (Graphics2D)g;
AffineTransform transform = g2d.getTransform();
transform.translate(x, y);
g2d.transform(transform);
g2d.setColor(Color.black);
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout tl = new TextLayout(text, g.getFont().deriveFont(45F), frc);
Shape shape = tl.getOutline(null);
g2d.setStroke(new BasicStroke(5f));
g2d.draw(shape);
g2d.setColor(Color.white);
g2d.fill(shape);
Output:

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);
}

Java Animation Rotation

I have very little experience with Java, and I am an amateur programmer. So mind my vocabulary.
I want to be able to stick a static rectangle on top of a rotating rectangle.
So far when I try to add another object it spins with the other image. I have tried setting the rotation to zero but that doesn't seem to work. I have also tried to create another class that draws components separately and added them to the frame using frame.add. I have also tried creating another part to the Draw class that has no effect on the GUI. Here is my current Draw class. Any help is appreciated.
class DrawRectangle extends JPanel {
#Override
public void paintComponent(Graphics g) {
int h = this.getHeight();
int w = this.getWidth();
Graphics2D g2 = (Graphics2D) g;
//draw background
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, w, h);
//draw roatiing rectangle
g2.setColor(Color.CYAN);
Rectangle rRec = new Rectangle(w / 4, h / 4, 2 * w / 4, 2 * h / 4);
double wr = rRec.getX() + rRec.getWidth() / 2;
double hr = rRec.getY() + rRec.getHeight() / 2;
g2.rotate(Math.toRadians(count), wr, hr);
g2.fill(rRec);
g2.fillRect(w / 3, h / 3, 2 * w / 3, 2 * h / 3);
}
public void paintComponent2(Graphics g) {
int h = this.getHeight();
int w = this.getWidth();
Graphics2D g2 = (Graphics2D) g;
}
}
So far when I try to add another object it spins with the other image.
Create a separate Graphics object to do the rotation so you don't affect the properties of the Graphics object passed into the painting method:
//Graphics2D g2 = (Graphics2D) g;
Graphics2D g2 = (Graphics2D)g.create();
// painting code
g2.dispose();
Move your g2.fill(rRec); BEFORE the rotate call, and it should work (I just tested it out).
This way, you will draw your static rectangle before the rotation, perform the rotation, THEN draw your second rectangle. Assuming your count variable is incremented somewhere, it should show the second rectangle being rotated.

Adding an image to a shape

So i'm learning to create a java game on eclipse. I learned adding shapes and such. I also learned how to paint the shapes (change the colours) I was wondering how to add an image to the shape. This is the code for painting the rectangle.
public void paintColumn(Graphics g, Rectangle column)
{
g.setColor(Color.blue.darker());
g.fillRect(column.x, column.y, column.width, column.height);
}
Start by having a look at Reading/Loading an Image for details about how to load a image, also, have a look at 2D Graphics for more details about 2D Graphics API.
Basically, you load the image, you draw it and then draw the shape around it.
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
g2d.setColor(Color.RED);
g2d.drawRect(x, y, img.getWidth(), img.getHeight());
g2d.dispose();
Now, this just draws the rectangle over the image, if you want to, somehow "frame" the image instead, you could fill the rectangle, making it larger then the image, but you'd have to fill first, then draw the image
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.setColor(Color.RED);
g2d.fillRect(x - 10, y - 10, img.getWidth() + 20, img.getHeight() + 20);
g2d.drawImage(img, x, y, this);
g2d.dispose();
Simply create a BufferedImage object:
BufferedImage image = ImageIO.read(filename);
Then instead of doing g.drawShape do:
g.drawImage(image, [starting x], [starting y], [image width], [image height], [image observer]);
In your case, you probably won't need an image observer, so you can just put null in that spot.
Then what would be easiest is to just draw your rectangle on top of the image. Even though the image will not actually be "inside" the the rectangle, the layered effect will make it look as if it is. You can use drawRect instead of fillRect so you just get a border around your image.
To ensure that your rectangle ends up on top of the image, and doesn't get covered since the image is the same size, make sure to put the drawRect line after the drawImage.
g.drawRect([starting x], [starting y], [width], [height]);
Check out these Graphics Java Docs for more information on drawing images.

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());

Draw rectangle border thickness

Is it possible to do draw a rectangle with a given border thickness in an easy way?
If you are drawing on a Graphics2D object, you can use the setStroke() method:
Graphics2D g2;
double thickness = 2;
Stroke oldStroke = g2.getStroke();
g2.setStroke(new BasicStroke(thickness));
g2.drawRect(x, y, width, height);
g2.setStroke(oldStroke);
If this is being done on a Swing component and you are being passed a Graphics object, you can downcast it to a Graphics2D.
Graphics2D g2 = (Graphics2D) g;
Here's how to do this : Border with colored line with thickness 5.
Border linebor = BorderFactory.createLineBorder(new Color(0xAD85FF), 5);
**Tested code with buffered image with different thickness values**:
Graphics2D g = bufferedImage.createGraphics();
int height = //image height
int width = //image height
int borderWidth = //border thickness
int borderControl = 1;
//set border color
g.setColor(Color.BLACK);
//set border thickness
g.setStroke(new BasicStroke(borderWidth));
//to fix issue for even numbers
if(borderWidth%2 == 0){
borderControl = 0;
}
g.drawLine(0, 0, 0, height);
g.drawLine(0, 0, width, 0);
g.drawLine(0, height – borderControl, width, height – borderControl);
g.drawLine(width – borderControl, height – borderControl, width – borderControl, 0);

Categories

Resources