I have the same question asked by Alex Ghilas here (https://stackoverflow.com/questions/66421996/how-to-put-double-width-and-double-height-characters-in-a-jtextpane#new-answer). I have tried the solution based on the AffineTransform proposed by user1324109, but it in my case it does not work for all the font sizes.
Here is a snippet of code that shows what I mean:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
public class Test {
static JTextPane textPane = new JTextPane();
private static void displayText(String s, Font font) throws Exception {
displayTextLine(s, font, 1.0, 1.0); // normal (48 characters)
displayTextLine(s, font, 1.0, 2.0); // double height (48 characters)
displayTextLine(s.substring(0, 24), font, 2.0, 1.0); // double width (24 characters)
displayTextLine(s.substring(0, 24), font, 2.0, 2.0); // double height and width (24 characters)
displayTextLine("================================================", font, 1.0, 1.0);
}
private static void displayTextLine(String s, Font font, double scaleX, double scaleY) throws Exception {
AffineTransform at = new AffineTransform();
at.scale(scaleX, scaleY);
Canvas c = new Canvas();
Font renderFont = font.deriveFont(font.getStyle(), at);
FontMetrics fm = c.getFontMetrics(renderFont);
BufferedImage img = new BufferedImage(fm.stringWidth(s), fm.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setFont(renderFont);
g.setColor(Color.black);
g.drawString(s, 0, fm.getHeight() - fm.getDescent());
textPane.insertIcon(new ImageIcon(img));
Document doc = textPane.getDocument();
doc.insertString(doc.getLength(), "\n", null);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
Container container = frame.getContentPane();
container.add(new JScrollPane(textPane));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 300);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
for (int size = 10; size <= 26; size += 2) {
try {
displayText("ABCDEFGHIJKLMNOabcdefghxijklmnopqrstu01234567890",
new Font(Font.MONOSPACED, Font.PLAIN, size));
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
}
The test code print 4 line of text (normal, double height, double width and double height and width) with increasing font sizes. The normal and double height text is composed by 48 characters and the double width and double height and width text is composed by 24 characters, so I was expecting the same width in pixel for all the 4 string messages, for each font size.
The output shown by the above snippet is the following:
output produced by the above code snippet
Only at font size 10, 12, 18 and 20 the result is what I was expecting. The same is true for other font sizes. It seems there is a pattern: two sizes are OK, the next two not, the next two again are OK, and so on...
I have tried with different JRE version (1.6, 1.8 and 19), but the effect is always the same.
Someone has a clue about which could be the reason or I'm doing something wrong?
Related
I am creating a retro arcade game in Java. The screen resolution for the game is 304 x 256, which I want to keep to preserve the retro characteristics of the game (visuals, animations, font blockiness, etc.).
But when I render this on a large desktop display, it is too small, as one would expect.
I'd like to be able to scale the window up say by a constant factor, without having to code the various paint(Graphics) methods to be knowledgeable about the fact that there's a scale-up. That is, I'd like the rendering code believe that the screen is 304 x 256. I also don't want to have to change my desktop resolution or go into full screen exclusive mode. Just want a big window with scaled up pixels, essentially.
I'd be looking for something along the following lines:
scale(myJFrame, 4);
and have all the contents automatically scale up.
UPDATE: Regarding input, my game happens to use keyboard input, so I don't myself need the inverse transform that trashgod describes. Still I can imagine that others would need that, so I think it's an appropriate suggestion.
One approach, suggested here, is to rely on drawImage() to scale an image of the content. Your game would render itself in the graphics context of a BufferedImage, rather than your implementation of paintComponent(). If the game includes mouse interaction, you'll have to scale the mouse coordinates as shown. In the variation below, I've given the CENTER panel a preferred size that is a multiple of SCALE = 8 and added the original as an icon in the WEST of a BorderLayout. As the default, CENTER, ignores a component's preferred size, you may want to add it to a (possibly nested) panel having FlowLayout. Resize the frame to see the effect.
f.setLayout(new FlowLayout());
f.add(new Grid(NAME));
//f.add(new JLabel(ICON), BorderLayout.WEST);
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
/**
* #see https://stackoverflow.com/a/44373975/230513
* #see http://stackoverflow.com/questions/2900801
*/
public class Grid extends JPanel implements MouseMotionListener {
private static final String NAME = "OptionPane.informationIcon";
private static final Icon ICON = UIManager.getIcon(NAME);
private static final int SCALE = 8;
private final BufferedImage image;
private int imgW, imgH, paneW, paneH;
public Grid(String name) {
super(true);
imgW = ICON.getIconWidth();
imgH = ICON.getIconHeight();
image = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) image.getGraphics();
ICON.paintIcon(null, g2d, 0, 0);
g2d.dispose();
this.addMouseMotionListener(this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(imgW * SCALE, imgH * SCALE);
}
#Override
protected void paintComponent(Graphics g) {
paneW = this.getWidth();
paneH = this.getHeight();
g.drawImage(image, 0, 0, paneW, paneH, null);
}
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
int x = p.x * imgW / paneW;
int y = p.y * imgH / paneH;
int c = image.getRGB(x, y);
this.setToolTipText(x + "," + y + ": "
+ String.format("%08X", c));
}
#Override
public void mouseDragged(MouseEvent e) {
}
private static void create() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new Grid(NAME));
f.add(new JLabel(ICON), BorderLayout.WEST);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
create();
}
});
}
}
One approach, suggested here, is to rely on the graphics context's scale() method and construct to an inverse transform to convert between mouse coordinates and image coordinates. In the example below, note how the original image is 256 x 256, while the displayed image is scaled by SCALE = 2.0. The mouse is hovering over the center the image; the tooltip shows an arbitrary point in the display and the center point (127, 127) in the original.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see https://stackoverflow.com/a/2244285/230513 */
public class InverseTransform {
private static final double SCALE = 2.0;
public static void main(String[] args) {
JFrame frame = new JFrame("Inverse Test");
BufferedImage image = getImage(256, 'F');
AffineTransform at = new AffineTransform();
at.scale(SCALE, SCALE);
frame.add(new ImageView(image, at));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static BufferedImage getImage(int size, char c) {
final Font font = new Font("Serif", Font.BOLD, size);
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.white);
g2d.fillRect(0, 0, size, size);
g2d.setPaint(Color.blue);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int x = (size - fm.charWidth(c)) / 2;
int y = fm.getAscent() + fm.getDescent() / 4;
g2d.drawString(String.valueOf(c), x, y);
g2d.setPaint(Color.black);
g2d.drawLine(0, y, size, y);
g2d.drawLine(x, 0, x, size);
g2d.fillOval(x - 3, y - 3, 6, 6);
g2d.drawRect(0, 0, size - 1, size - 1);
g2d.dispose();
return bi;
}
private static class ImageView extends JPanel {
private BufferedImage image;
private AffineTransform at;
private AffineTransform inverse;
private Graphics2D canvas;
private Point oldPt = new Point();
private Point newPt;
#Override
public Dimension getPreferredSize() {
return new Dimension( // arbitrary multiple of SCALE
(int)(image.getWidth() * SCALE * 1.25),
(int)(image.getHeight() * SCALE * 1.25));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
try {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
inverse = g2d.getTransform();
inverse.invert();
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.transform(at);
g2d.translate(-image.getWidth() / 2, -image.getHeight() / 2);
inverse.concatenate(g2d.getTransform());
g2d.drawImage(image, 0, 0, this);
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace(System.err);
}
}
ImageView(final BufferedImage image, final AffineTransform at) {
this.setBackground(Color.lightGray);
this.image = image;
this.at = at;
this.canvas = image.createGraphics();
this.canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
this.canvas.setColor(Color.BLACK);
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point m = e.getPoint();
Point i = e.getPoint();
try {
inverse.inverseTransform(m, i);
setToolTipText("<html>Mouse: " + m.x + "," + m.y
+ "<br>Inverse: " + i.x + "," + i.y + "</html>");
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
});
}
}
}
Thanks to trashgod for pointing me in the right direction with his two answers. I was able to combine elements of both answers to arrive at something that works for what I need to do.
So first, my goal was to scale up an entire UI rather than scaling up a single icon or other simple component. By "an entire UI" I specifically mean a JPanel containing multiple custom child components laid out using a BorderLayout. There are no JButtons or any other interactive Swing components, and no mouse input (it's all keyboard-based input), so really I just need to scale a 304 x 256 JPanel up by a factor of 3 or 4.
Here's what I did:
package bb.view;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import static bb.BBConfig.SCREEN_WIDTH_PX; // 304
import static bb.BBConfig.SCREEN_HEIGHT_PX; // 256
public class Resizer extends JPanel {
private static final int K = 3;
private static final Dimension PREF_SIZE =
new Dimension(K * SCREEN_WIDTH_PX, K * SCREEN_HEIGHT_PX);
private static final AffineTransform SCALE_XFORM =
AffineTransform.getScaleInstance(K, K);
public Resizer(JComponent component) {
setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
add(component);
}
#Override
public Dimension getPreferredSize() {
return PREF_SIZE;
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setTransform(SCALE_XFORM);
super.paint(g2);
}
}
Some important elements of the solution:
Using a FlowLayout here shrinkwraps the child component, which is what I want. (Thanks trashgod for that.) That is, I don't want the child to expand to fill the Resizer preferred size, because that wrecks the child component's layout. Specifically it was creating this huge gap between the child component's CENTER and SOUTH regions.
I configured the FlowLayout with left alignment and hgap, vgap = 0. That way my scale transform would have the scaled up version anchored in the upper left corner too.
I used an AffineTransform to accomplish the scaling. (Again thanks trashgod.)
I used paint() instead of paintComponent() because the Resizer is simply a wrapper. I don't want to paint a border. I basically want to intercept the paint() call, inserting the scale transform and then letting the JPanel.paint() do whatever it would normally do.
I didn't end up needing to render anything in a separate BufferedImage.
The end result is that the UI is large, but the all the code other than this Resizer thinks the UI is 304 x 256.
As we are using JDesktopPane with JInternalFrames for our software, I am wondering if we can have Shortcut Icons (to particular Frame) placed inside the desktop pane (Similar to Windows desktop shortcuts). I searched for this but no luck.
Any idea guys to make this happen??
The following is not a "good" solution, but maybe OK-ish for certain application cases. The main difficult is that the central class that comes into play here is JInternalFrame.JDesktopIcon, and the documentation of this class contains a
Warning:
This API should NOT BE USED by Swing applications, as it will go away in future versions of Swing as its functionality is moved into JInternalFrame.
However, the corresponding functionality in JInternalFrame simply is not there. And although one has to accept that the JDesktopIcon class might be removed in future versions, it seems very unlikely for me, considering the broad usage of this class in the internal Swing UI implementations.
However: One option to achieve this is to create a custom extension of the BasicDesktopIconUI. Fortunately, this class handles most of the janitorial work, like dragging support and un-iconifying the frame on double clicks.
As a result, one can easily sneak custom icons into such an implementation (I only used a placeholder here: A red cross on a black background. But it can be an arbitrary Image.)
This is implemented here as a MCVE. The general UI handling may be different in an actual application, but the basic idea is to create the custom UI class and assign it to the internal frame icons.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicDesktopIconUI;
class SimpleDesktopIconUI extends BasicDesktopIconUI
{
private final Icon icon;
SimpleDesktopIconUI(Icon icon)
{
this.icon = icon;
}
#Override
protected void installComponents()
{
frame = desktopIcon.getInternalFrame();
String title = frame.getTitle();
JLabel label = new JLabel(title, icon, SwingConstants.CENTER);
label.setVerticalTextPosition(JLabel.BOTTOM);
label.setHorizontalTextPosition(JLabel.CENTER);
desktopIcon.setBorder(null);
desktopIcon.setOpaque(false);
desktopIcon.setLayout(new GridLayout(1, 1));
desktopIcon.add(label);
}
#Override
protected void uninstallComponents()
{
desktopIcon.setLayout(null);
desktopIcon.removeAll();
frame = null;
}
#Override
public Dimension getMinimumSize(JComponent c)
{
LayoutManager layout = desktopIcon.getLayout();
Dimension size = layout.minimumLayoutSize(desktopIcon);
return new Dimension(size.width + 15, size.height + 15);
}
#Override
public Dimension getPreferredSize(JComponent c)
{
return getMinimumSize(c);
}
#Override
public Dimension getMaximumSize(JComponent c)
{
return getMinimumSize(c);
}
}
public class InternalFrameIconTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Icon icon = new ImageIcon(createImage());
JDesktopPane desktopPane = new JDesktopPane();
for (int i = 0; i < 5; i++)
{
String title = "Test " + i;
if (i == 2)
{
title = "Test 2 with longer title";
}
JInternalFrame internalFrame =
new JInternalFrame(title, true, true, true, true);
internalFrame.setBounds(20 + 50 * i, 300 - 40 * i, 160, 80);
internalFrame.setVisible(true);
desktopPane.add(internalFrame);
internalFrame.getDesktopIcon().setUI(new SimpleDesktopIconUI(icon));
}
f.getContentPane().add(desktopPane);
f.setSize(600, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static BufferedImage createImage()
{
int w = 50;
int h = 50;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.RED);
g.drawLine(0, 0, w, h);
g.drawLine(0, h, w, 0);
g.dispose();
return image;
}
}
Just trying to draw some lines to the screen.
I've checked to ensure all the relevant code's being run
I've tried calling repaint (and ensuring that's being run)
Since this is a JSplitPane, the layout must be the JSplitPane layout
I'm setting the color to ensure it isn't drawing using the background color.
I've checked the height and width to ensure its size isn't 0 or something
I've tried drawing text as well; same result
I've changed the coordinates all over the place, tried both arbitrary and proportional values
Or at least I think. Swing is unintuitively quirky. I'd use AWT, but I need the specificity Swing offers. Anyway, the code. It's just a split pane, which is actually displaying - resizable and all - but the contents of the top pane (the only one I've attempted to put anything in) don't show.
package derange;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.*;
public class Derange {
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Derange");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Display the window.
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
frame.pack();
frame.setVisible(true);
//Create a split pane with the two scroll panes in it.
PanelScore scorePane = new PanelScore();
JScrollPane instrumentPane = new JScrollPane();
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
scorePane, instrumentPane);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation((frame.getHeight() / 4) * 3 );// Three-quarters of the way down
splitPane.setDividerSize(20);
//Provide minimum sizes for the two components in the split pane
Dimension minimumSize = new Dimension(frame.getWidth(), frame.getHeight()/ 2);//width, height
scorePane.setMinimumSize(minimumSize); //Score takes up at least half the screen
instrumentPane.setMinimumSize(new Dimension(0,0));//no minimum size on the instrument panel; collapsible
frame.getContentPane().add(splitPane);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
.
package derange;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import javax.swing.JScrollPane;
#SuppressWarnings("serial")//wtf is this needed for?
public class PanelScore extends JScrollPane{
public int strings = 6;
public void drawStaffTablature(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
int xStart = 30;//insets.left;
int xEnd = getParent().getWidth() - 30;
int yCoord = this.getHeight() / 2;
System.out.println(this.isShowing());
//Space between tablature lines
int lineSpacing = 15;
//Space between staffs.
int staffSpacing = 60;`enter code here`
for(int x = 0; x < strings; x++){
g2d.drawLine(xStart, yCoord + (lineSpacing * x), xEnd, yCoord + (lineSpacing * x));
//System.out.println("String: " + (x + 1));
g.drawString("Test", xStart, yCoord); //change the co-odrinates
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
drawStaffTablature(g);
}
}
Short answer, don't extend from JScrollPane, a JScrollPane contains single component known as a JViewport, which covers the most of the scroll pane (the rest is taken up by the JScrollBars
Instead, try extending from something like JPanel.
I'd also advise you against using anything like int xEnd = getParent().getWidth() - 30; within your paint code, the Graphics context is translated to the components location, making the top/left corner 0x0 and clipped to the components current width and height
I want to create a custom border with rounded corners.
Code -
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.border.AbstractBorder;
class JRoundedCornerBorder extends AbstractBorder
{
private static final long serialVersionUID = 7644739936531926341L;
private static final int THICKNESS = 5;
JRoundedCornerBorder()
{
super();
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if(c.hasFocus())
{
g2.setColor(Color.BLUE);
}
else
{
g2.setColor(Color.BLACK);
}
g2.setStroke(new BasicStroke(THICKNESS, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.drawRect(x, y, width - 1, height - 1);
g2.dispose();
}
#Override
public Insets getBorderInsets(Component c)
{
return new Insets(THICKNESS, THICKNESS, THICKNESS, THICKNESS);
}
#Override
public Insets getBorderInsets(Component c, Insets insets)
{
insets.left = insets.top = insets.right = insets.bottom = THICKNESS;
return insets;
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// Add button with custom border
final JButton button = new JButton("Hello");
button.setBorder(new JRoundedCornerBorder());
frame.add(button);
// Add button without custom border
frame.add(new JButton("Goodbye"));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Result -
As you can see, Graphics.drawRect completely ignores the BasicStroke.CAP_ROUND and BasicStroke.JOIN_ROUND attributes. Why?
As explained at Learning Java 2D, Part 1:
java.awt.BasicStroke.CAP_ROUND: This makes a circular cap centered on the endpoint, with a diameter of the pen width.
The key word is "centered". I believe that it is always the case that when drawing with thick strokes, Java2D will center the thickness of the line along the hypothetical, infinitesimally-thin line between the centers of the pixels at the starting and ending coordinates. For example, when drawing a vertical blue line 7 pixels thick, Java2D paints 3 pixels on each side of the hypothetical line segment that is being drawn.
In your example, the thickness is 5 pixels. You need to offset the coordinates to draw the stroke completely within the graphics clip. By moving in 2 pixels (or THICKNESS/2), the rounded corners become visible:
//...
g2.setStroke(new BasicStroke(THICKNESS, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.drawRect(x + THICKNESS/2, y + THICKNESS/2, width - 2*(THICKNESS/2) - 1, height - 2*(THICKNESS/2) - 1);
g2.dispose();
}
#Override
public Insets getBorderInsets(Component c) {
return new Insets(THICKNESS + THICKNESS/2, THICKNESS + THICKNESS/2, THICKNESS + THICKNESS/2, THICKNESS + THICKNESS/2);
}
//...
the problem is the offset: you'r effectively cutting-off the border in the middle so the corners appear to be not rounded. Taking it into account (here only for the offset, need to adjust width as well)
g2.drawRect(x + thickness/2, y + thickness/2,
width - 1 - thickness, height - 1 - thickness);
Edit
fixed sloppy pixel counting :-)
Edit:
I submitted a bug for the below (it may take a a few days to become approved though):
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7043319
Some more details:
It works with Windows Sun JDK 1.6 versions 13 and 17
It fails on Ubuntu 11.04 x64 with both OpenJDK 1.6.0_22 and Sun JDK 1.6.0_24
What I want is to make a background image panel with additional panels on top of it (with additional components - e.g. JButtons, custom shapes, etc. - in them) and draw all that correctly. I'm using JLayeredPane for that purpose in my app, but for the sake of an example the below code should suffice. I'm open to suggestions about how to do what I want regardless of the below problem.
I'm running into the issue that the painting is behaving really weird. It doesn't repaint fully (e.g. only the top part above the image), it repaints in - from what I've noticed increasingly spaced - steps (e.g. 1st paint, 3rd paint, 9th paint, 21st paint, 64th paint, etc.). My guess is that I'm going too much into implementation here - is there anything obviously wrong with the below?
On a separate note, there are three commented lines below. Interestingly enough, uncommenting any of them and commenting the following line solves the problem. The images are with the following attributes (and it seems it doesn't matter which image - just the size):
cat.jpg JPEG 640x533 640x533+0+0 8-bit DirectClass 110KB 0.000u 0:00.000
cat-small.jpg JPEG 200x167 200x167+0+0 8-bit DirectClass 7.99KB 0.000u 0:00.000
Here's the Java code I'm having issues with:
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class SwingDrawingPrb {
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
final JFrame frame = new JFrame("SwingDrawingPrb");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Container contentPane = frame.getContentPane();
frame.setLocation(550, 50);
frame.setSize(1000, 800);
frame.setVisible(true);
// ImageIcon image = new ImageIcon(SwingDrawingPrb.class.getResource("/cat-small.jpg"));
ImageIcon image = new ImageIcon(SwingDrawingPrb.class.getResource("/cat.jpg"));
final JPanel imagePanel = new JPanel() {
// Color trans = new Color(255, 0, 0, 255);
Color trans = new Color(255, 0, 0, 64);
protected void paintComponent(Graphics g) {
System.out.println("painting");
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(trans);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.blue);
g.drawLine(0, 0, 1000, 1000);
}
};
imagePanel.setBounds(0, 0, image.getIconWidth() + 200, image.getIconHeight() + 200);
imagePanel.setLayout(null);
// JLabel imageLabel = new JLabel("Hello, world!");
JLabel imageLabel = new JLabel(image);
imageLabel.setBounds(100, 100, image.getIconWidth(), image.getIconHeight());
imageLabel.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
System.out.println("mouseMoved");
imagePanel.repaint();
}
});
imagePanel.add(imageLabel);
contentPane.add(imagePanel);
}
}
You need to add:
imagePanel.setOpaque(false);
See Backgrounds With Transparency for more information.