Is it possible to set outer stroke for text? - java

I'm trying to make outer stroke for text. When I increase width parameter of BasicStroke my outline spreads both inside and outside text, but I need only outside part.
My result:
Are there some properties for that? Or maybe there is a way to cut stroke from within text?
Example code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import javax.swing.JPanel;
import javax.swing.JApplet;
import javax.swing.JFrame;
public class FontPaint extends JApplet {
public void init() {
FontPanel fontPanel = new FontPanel();
getContentPane().add(fontPanel, BorderLayout.CENTER);
}
public static void main(String[] args) {
FontPanel starPanel = new FontPanel();
JFrame f = new JFrame("Font");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.getContentPane().add(starPanel, BorderLayout.CENTER);
f.setSize(new Dimension(550, 200));
f.setVisible(true);
}
}
class FontPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.white);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
FontRenderContext frc = g2.getFontRenderContext();
Font f = new Font("Helvetica", 1, 60);
String s1 = "Java Source and Support.";
TextLayout textTl = new TextLayout(s1, f, frc);
AffineTransform transform1;
Shape outline1 = textTl.getOutline(null);
transform1 = g2.getTransform();
double newWidth1 = 301;
double newHeight1 = 427;
textTl.draw(g2, (int)newWidth1, (int)newHeight1);
transform1.translate(newWidth1, newHeight1);
g2.transform(transform1);
g2.setColor(Color.blue);
g2.setStroke(new BasicStroke(4.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
g2.draw(outline1);
}
}

There probably is no nice "half stroke." So first draw the outline, then fill the glyph vector. (For opaque colors only.)
transform1 = g2.getTransform();
double newWidth1 = 301;
double newHeight1 = 427;
transform1.translate(newWidth1, newHeight1);
g2.transform(transform1);
g2.setColor(Color.blue);
g2.setStroke(new BasicStroke(4.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
g2.draw(outline1);
textTl.draw(g2, 0, 0);
Which should be a short cut for:
...
transform1.translate(-newWidth1, -newHeight1);
textTl.draw(g2, (int)newWidth1, (int)newHeight1);

Related

stringWidth it's very slow

I need to write a generic method that draw a centered String. In order to do that I need to know the width of the String and in order to calculate that I have different alternatives:
Rectangle2D rect=Toolkit.getDefaultToolkit().getFontMetrics(gc.getFont()).getStringBounds(text, gc);
or
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
Rectangle2D rect=gc.getFont().getStringBounds(text, frc);
or
FontMetrics metrics = g.getFontMetrics(font);
metrics.stringWidth(text)
But does't matter which approach I use it takes around 1 second to calculate that, that's crazy I'm using Eclipse with Java 1.8.0_121 on a on Macbook 3,1 GHz Intel Core i7, that's crazy!
What is wrong with it?
First Example:
import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame jf = new JFrame("Demo");
Container cp = jf.getContentPane();
MyCanvas1 tl = new MyCanvas1();
cp.add(tl);
jf.setSize(300, 200);
jf.setVisible(true);
}
}
class MyCanvas1 extends JComponent {
public void paint(Graphics g) {
long start=System.currentTimeMillis();
Graphics2D g2 = (Graphics2D) g;
drawCenteredString(g2, "Example", getBounds(), g.getFont());
System.out.println("executed in:"+(System.currentTimeMillis()-start));
}
public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) {
FontMetrics metrics = g.getFontMetrics(font);
int x = rect.x + (rect.width - metrics.stringWidth(text)) / 2;
// Determine the Y coordinate for the text (note we add the ascent, as in java 2d 0 is top of the screen)
int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
// Set the font
g.setFont(font);
// Draw the String
g.drawString(text, x, y);
}
}
executed in:922
Example 2:
import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame jf = new JFrame("Demo");
Container cp = jf.getContentPane();
MyCanvas1 tl = new MyCanvas1();
cp.add(tl);
jf.setSize(300, 200);
jf.setVisible(true);
}
}
class MyCanvas1 extends JComponent {
public void paint(Graphics g) {
long start=System.currentTimeMillis();
Graphics2D g2 = (Graphics2D) g;
drawCenteredString(g2, "Example", getBounds(), g.getFont());
System.out.println("executed in:"+(System.currentTimeMillis()-start));
}
public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) {
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
Rectangle2D rect2=font.getStringBounds(text, frc);
// Draw the String
g.drawString(text, (int) (rect.width/2-rect2.getWidth()/2),(int) (rect.height/2-rect2.getHeight()/2));
}
}
executed in:916
Example 3:
import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame jf = new JFrame("Demo");
Container cp = jf.getContentPane();
MyCanvas1 tl = new MyCanvas1();
cp.add(tl);
jf.setSize(300, 200);
jf.setVisible(true);
}
}
class MyCanvas1 extends JComponent {
public void paint(Graphics g) {
long start=System.currentTimeMillis();
Graphics2D g2 = (Graphics2D) g;
drawCenteredString(g2, "Example", getBounds(), g.getFont());
System.out.println("executed in:"+(System.currentTimeMillis()-start));
}
public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) {
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
Rectangle2D rect2=Toolkit.getDefaultToolkit().getFontMetrics(font).getStringBounds(text, g);
// Draw the String
g.drawString(text, (int) (rect.width/2-rect2.getWidth()/2),(int) (rect.height/2-rect2.getHeight()/2));
}
}
executed in:908
It seems somebody else had a similar problem but without any solution:
Java: Fastest way to draw text?
Does somebody see where the problem could be?
EDIT:
Just an update using Java 8u221 same results, using the latest Java 13 I got a little improvement instead of 900 they are executed in around 700 milliseconds... I cannot believe that... It seems this operations are very slow on Mac... doesn't matter the Java version I use...
EDIT2:
Even executing this code:
public class Main {
public static void main(String[] args) {
long start=System.currentTimeMillis();
BufferedImage image = new BufferedImage(300, 300, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = image.createGraphics();
drawCenteredString(g2, "Example", new Rectangle (0,0,300,300), g2.getFont());
System.out.println("executed in:"+(System.currentTimeMillis()-start));
}
public static void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) {
Rectangle2D rect2=Toolkit.getDefaultToolkit().getFontMetrics(font).getStringBounds(text, g);
// Draw the String
g.drawString(text, (int) (rect.width/2-rect2.getWidth()/2),(int) (rect.height/2-rect2.getHeight()/2));
}
}
I get: executed in:1342

How to draw out a JLabel? - Java

I want to draw out a JLabel in a "public void paint(Graphics g) {}" and all that stuff. How do I take a JLabel from outside of the class (within the main method) and draw it out in the paint method?
static JLabel drawThisOut = new JLabel("draw this out");
public static void main(String[] args) {
class paintJLabel extends JComponent {
public void paint(Graphics g) {
//what goes here to draw the JLabel?????
}
}
}
And no, I cannot just use the regular JLabel instead of drawing it out. I need to draw something else over the JLabel and I assumed this was the most efficient way to do it. (Is there a more efficient way?) Any help at all would be greatly appreciated. Please be specific. Thanks!
In the "simplest" terms, you could use a Border of some kind, the following presents a custom rounded border, but the concept is relatively simple
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.AbstractBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JLabel label = new JLabel("This is a test");
label.setBorder(new RoundedBorder(Color.BLACK, 20));
add(label);
}
}
public class RoundedBorder extends AbstractBorder {
private final Color color;
private final int gap;
public RoundedBorder(Color c, int g) {
color = c;
gap = g;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.setColor(color);
g2d.draw(new RoundRectangle2D.Double(x, y, width - 1, height - 1, gap, gap));
g2d.dispose();
}
#Override
public Insets getBorderInsets(Component c) {
return (getBorderInsets(c, new Insets(gap, gap, gap, gap)));
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
insets.left = insets.top = insets.right = insets.bottom = gap / 2;
return insets;
}
#Override
public boolean isBorderOpaque() {
return false;
}
}
}
You could also use a custom component. This provides you with the added benefit of been able to "fill" the shape with your own background color, but is, otherwise, essentially the same thing
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.AbstractBorder;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JLabel label = new JLabel("This is a test");
label.setBorder(new EmptyBorder(10, 10, 10, 10));
RoundedPane pane = new RoundedPane();
pane.add(label);
add(pane);
}
}
public class RoundedPane extends JPanel {
public RoundedPane() {
setLayout(new BorderLayout());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.setColor(Color.BLACK);
g2d.draw(new RoundRectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1, 20, 20));
g2d.dispose();
}
}
}

Only 1 Corner is Rounded when I call fillRoundRect()

When run this code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JLabel;
public class CustomButton extends JButton {
int width = 100;
int height = 50;
int radius = 10;
JLabel lab;
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.ORANGE);
g2.fillRoundRect(0, 0, width, height, radius, radius);
g2.dispose();
super.paintComponent(g2);
super.paintComponent(g);
}
}
And my other class:
package custom.frame;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CustomFrame {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
f.setSize(500,500);
f.setLocationRelativeTo(null);
JPanel pane = new JPanel();
pane.setBounds(0,0,500,500);
CustomButton btn = new CustomButton();
pane.add(btn);
f.add(btn);
f.setVisible(true);
}
}
I get a get a regular rectangle with only 1 rounded side. Please see the image below.
Is this the expected function?
If not, how can I fix this.
Edit
I can get 2 rounded corners if I do this:
g2.fillRoundRect(0, 0, 50, 50, 7, 7);
The only thing I can really think of is that the window containing the rectangle is too small and is cutting off the other three corners, but that seems pretty unlikely.

Assigning a image to a String

Is there any way that I could assign a specific image to a String so that the output would be something like this (im using the String with a JLabel):
The basic process is to create an outline of the text and render it...easy ;)
This uses the TextLayout class to generate a Shape of the text, which can filled and drawn.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TextOutline {
public static void main(String[] args) {
new TextOutline();
}
public TextOutline() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new FontPaint());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class FontPaint extends JPanel {
public FontPaint() {
setFont(getFont().deriveFont(Font.BOLD, 48f));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
Graphics2D g2d = (Graphics2D) g;
FontRenderContext fontRendContext = g2d.getFontRenderContext();
// Not required, but it will make it look nice
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
String st = "Hello World.";
// Create an layout of the text
TextLayout text = new TextLayout(st, getFont(), fontRendContext);
// Generate a shape of the layout
Shape shape = text.getOutline(null);
// Align the shape to the center
Rectangle rect = shape.getBounds();
AffineTransform affineTransform = new AffineTransform();
affineTransform = g2d.getTransform();
affineTransform.translate(w / 2 - (rect.width / 2), h / 2
+ (rect.height / 2));
g2d.transform(affineTransform);
// Fill in blue
g2d.setColor(Color.BLUE);
g2d.fill(shape);
// Outline in red
g2d.setColor(Color.red);
g2d.draw(shape);
g2d.dispose();
}
}
}

Disable standard repainting of JToggleButton when It is selected

I want my JToggleButton not to repaint when It is selected. I indicate state changing by pair of words ("check/next").
Standard behavior is blue lighting but I want to disable it.
Perhaps you could show the words on ImageIcons. For example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
public class ToggleFun {
private static final Color BACKGROUND_COLOR = new Color(200, 200, 255);
public static void main(String[] args) {
int biWidth = 60;
int biHeight = 30;
BufferedImage checkImg = new BufferedImage(biWidth, biHeight, BufferedImage.TYPE_INT_RGB);
BufferedImage nextImg = new BufferedImage(biWidth, biHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = checkImg.createGraphics();
g2.setColor(BACKGROUND_COLOR);
g2.fillRect(0, 0, biWidth, biHeight);
g2.setColor(Color.black);
g2.drawString("Check", 10, 20);
g2.dispose();
g2 = nextImg.createGraphics();
g2.setColor(BACKGROUND_COLOR);
g2.fillRect(0, 0, biWidth, biHeight);
g2.setColor(Color.black);
g2.drawString("Next", 15, 20);
g2.dispose();
ImageIcon checkIcon = new ImageIcon(checkImg);
ImageIcon nextIcon = new ImageIcon(nextImg);
JToggleButton toggleBtn = new JToggleButton(checkIcon);
toggleBtn.setSelectedIcon(nextIcon);
toggleBtn.setContentAreaFilled(false);
toggleBtn.setBorder(BorderFactory.createLineBorder(Color.black));
JPanel panel = new JPanel();
panel.add(toggleBtn);
JOptionPane.showMessageDialog(null, panel);
}
}
See: AbstractButton.setContentAreaFilled(false).
But note that users generally prefer a GUI element that follows the 'path of least surprise'. This type of rendering might be better described as going off on a bit of a crash-bang through the undergrowth beside that path.

Categories