I have a JTable and need to repaint very frequently each cell, where table's cells illustrate gray-scale images. Usually images need to be scaled for painting to fit painted area. I have two implementations inside JComponent's method paintComponent(Graphics g) :
Create scaled image Image.getScaledInstance(), and then draw it using Graphics.
Let Graphics to scale the image "on the fly";
The first method works faster, and requires less CPU. But it changes the colors (gray level) of images. The image below illustrates the issue. First method (wrong colors) on the left, and the second method (true colors) on the right.
Question: Why this happens and how to resolve it? Alternatively, any other solution will be much appreciated.
Note: in this code example I don't actually scale the images, but calling Image.getScaledInstance() still changes the image values.
The code:
class CellImage extends JComponent {
final boolean rescale_img;
final byte value;
CellImage(boolean flag, byte v) {
rescale_img = flag;
value = v;
}
#Override
public void paintComponent(Graphics g) {
Rectangle r = g.getClipBounds();
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_BYTE_GRAY);
byte[] bytes = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
Arrays.fill(bytes, value);
if (rescale_img){
Image scaled = image.getScaledInstance(r.width, r.height, Image.SCALE_REPLICATE);
g.drawImage(scaled, 0, 0, null);
} else {
g.drawImage(image, 0, 0, null);
}
}
}
If required, please see below the whole SSCCE:
package book_test_paint;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.util.Arrays;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
class CellImage extends JComponent {
final boolean rescale_img;
final byte value;
CellImage(boolean flag, byte v) {
rescale_img = flag;
value = v;
}
#Override
public void paintComponent(Graphics g) {
Rectangle r = g.getClipBounds();
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_BYTE_GRAY);
byte[] bytes = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
Arrays.fill(bytes, value);
if (rescale_img){
Image scaled = image.getScaledInstance(r.width, r.height, Image.SCALE_REPLICATE);
g.drawImage(scaled, 0, 0, null);
} else {
g.drawImage(image, 0, 0, null);
}
}
}
class CellText extends JComponent {
final String str;
CellText(String s){
str = s;
}
#Override
public void paintComponent(Graphics g) {
Rectangle r = g.getClipBounds();
g.setColor(Color.BLACK);
g.fill3DRect(0, 0, r.width, r.height, true);
g.setColor(Color.WHITE);
((Graphics2D) g).drawString(str, 0.1f * r.width, 0.9f * r.height);
}
}
class MyTableModel extends DefaultTableModel {
final int nrows = 17;
#Override
public int getRowCount() {
return nrows;
}
#Override
public int getColumnCount() {
return 3;
}
#Override
public Object getValueAt(int row, int column) {
Object obj = null;
// The value below represents the desired
// gray scale of the image in range [0, 255]
int val = (int)Math.min(255, row * 256.0 / (nrows - 1));
switch (column) {
case 0:
obj = new CellImage(true, (byte)val);
break;
case 1:
obj = new CellText("" + val);
break;
case 2:
obj = new CellImage(false, (byte)val);
break;
}
return obj;
}
}
public class Image_Scaling extends JTable {
public Image_Scaling(JPanel panel) {
setModel(new MyTableModel());
setRowHeight(25);
getColumnModel().getColumn(0).setPreferredWidth(200);
getColumnModel().getColumn(1).setPreferredWidth(40);
getColumnModel().getColumn(2).setPreferredWidth(200);
JTableHeader header = getTableHeader();
header.getColumnModel().getColumn(0).setHeaderValue("with getScaledInstance()");
header.getColumnModel().getColumn(1).setHeaderValue("gray");
header.getColumnModel().getColumn(2).setHeaderValue("without getScaledInstance()");
panel.setLayout(new BorderLayout());
panel.add(header, BorderLayout.NORTH);
panel.add(this, BorderLayout.CENTER);
}
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
return (Component) dataModel.getValueAt(row, column);
}
public static void main(String[] args) {
JPanel panel = new JPanel(new BorderLayout());
final Image_Scaling table = new Image_Scaling(panel);
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel, BorderLayout.CENTER);
frame.pack();
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
frame.setVisible(true);
}
});
}
}
Related
I'm trying to make my own, very inefficient "image copier". I'm doing it by reading the pixels from the original image on to an array and then resetting those same pixels in my default BufferedImage. And then repaint the frame. Row by row.
I'm trying to repaint the frame every after each row of pixels has been stored in the array. But the frame only gets updated once; when it finishes storing the pixels.
My code is all over the place, and I'm probably doing a lot of stuff wrong. This is for an assignment and I've been going at it for a while now and would really appreciate some help.
Here is my code:
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class pixelReloc extends JComponent {
static BufferedImage image,newImg;
static JFrame frame;
public void initialize() {
int width = getSize().width;
int height = getSize().height;
int pixels[];
int index = 0;
int j=0,i=0;
File f = new File("/path/to/file/images/shrek4life.jpg");
try{
image = ImageIO.read(f);
}catch(IOException e){}
System.out.println("checkpoint 1");
image = createResizedCopy(image,500,500,true);
newImg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
pixels = new int[(image.getWidth()) * (image.getHeight())];
System.out.println("checkpoint 2");
for(i= 0; i < newImg.getWidth(); i++){
for(j = 0; j < newImg.getHeight(); j++){
//get the rgb color of the old image
Color c = new Color(image.getRGB(i, j));
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
pixels[index++] = (r<<16) | (g<<8) | b;
}
newImg.setRGB(0, 0, i, j, pixels, 0, 0);
frame.getContentPane().validate();
frame.getContentPane().repaint();
}
System.out.println("checkpoint 4");
//image.setRGB(0, 0, width, height, data, 0, width);
}
public BufferedImage createResizedCopy(BufferedImage originalImage,
int scaledWidth, int scaledHeight,
boolean preserveAlpha)
{
System.out.println("resizing...");
int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
Graphics2D g = scaledBI.createGraphics();
if (preserveAlpha) {
g.setComposite(AlphaComposite.Src);
}
g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
g.dispose();
return scaledBI;
}
public void paint(Graphics g) {
if (image == null)
initialize();
g.drawImage(newImg, 0, 0, this);
}
public static void main(String[] args) {
frame = new JFrame("P I X E L S");
frame.getContentPane().add(new pixelReloc ());
frame.setSize(500, 500);
frame.setLocation(100, 100);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
}
}
Here is the picture I'm reading the pixels from:
and this is how it's coming out:
The program doesn't give any errors or anything.
The basic answer to your question is, you're blocking the Event Dispatching Thread.
Swing is both single threaded and NOT thread safe. This means you can't run long running or blocking operations from within the EDT AND you should't not update the UI or some state the UI depends on from outside the EDT.
I recommend that you start by having a look at Concurrency in Swing.
This leaves with three basic options:
Use another Thread. This is problematic as you need to ensure that any state that the UI relies on is only updated from within the context of the EDT
Use a SwingWorker. This is basically the previous option, but with built in management which allows you to publish updates which are process'ed on the EDT
Use a Swing Timer. In your case, this is probably not the best solution, but it is the simplest.
For example
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new PixelReloc());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Pixel {
private int x, y;
private int color;
public Pixel(int x, int y, int color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getColor() {
return color;
}
}
public class PixelReloc extends JComponent {
private BufferedImage image;
private BufferedImage newImg;
public PixelReloc() {
SwingWorker<Integer[], List<Pixel>> worker = new SwingWorker<Integer[], List<Pixel>>() {
Integer pixels[];
#Override
protected Integer[] doInBackground() throws Exception {
pixels = new Integer[image.getWidth() * image.getHeight()];
int index = 0;
List<Pixel> pixies = new ArrayList<>(image.getWidth());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int color = image.getRGB(x, y);
pixels[index++] = color;
pixies.add(new Pixel(x, y, color));
}
publish(new ArrayList<Pixel>(pixies));
pixies = new ArrayList<>(image.getWidth());
Thread.sleep(100);
}
return pixels;
}
#Override
protected void process(List<List<Pixel>> chunks) {
for (List<Pixel> pixels : chunks) {
for (Pixel pixel : pixels) {
newImg.setRGB(pixel.getX(), pixel.getY(), pixel.getColor());
}
}
repaint();
}
};
File f = new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/chaotic_megatokyo_by_fredrin-d9k84so.jpg");
try {
image = ImageIO.read(f);
} catch (IOException e) {
}
System.out.println("checkpoint 1");
image = createResizedCopy(image, 200, 200, true);
newImg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
worker.execute();
// pixels = new int[(image.getWidth()) * (image.getHeight())];
// System.out.println("checkpoint 2");
// for (i = 0; i < newImg.getWidth(); i++) {
// for (j = 0; j < newImg.getHeight(); j++) {
// //get the rgb color of the old image
// Color c = new Color(image.getRGB(i, j));
// int r = c.getRed();
// int g = c.getGreen();
// int b = c.getBlue();
// pixels[index++] = (r << 16) | (g << 8) | b;
// }
// }
// System.out.println("checkpoint 4");
//image.setRGB(0, 0, width, height, data, 0, width);
}
#Override
public Dimension getPreferredSize() {
return image == null ? new Dimension(200, 200) : new Dimension(image.getWidth(), image.getHeight());
}
public BufferedImage createResizedCopy(BufferedImage originalImage,
int scaledWidth, int scaledHeight,
boolean preserveAlpha) {
System.out.println("resizing...");
Image scaled = originalImage.getScaledInstance(scaledWidth, -1, Image.SCALE_SMOOTH);
BufferedImage scaledBI = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = scaledBI.createGraphics();
g.drawImage(scaled, 0, 0, null);
g.dispose();
return scaledBI;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(newImg, 0, 0, this);
}
}
}
The Thread.sleep in the SwingWorker is intended to do two things:
Give time for the EDT to process the results from the process call and
Slow down the worker so that the results can be updated on the UI.
In my testing without it, the image was pretty much updated instantly.
I also recommend you take the time to better understand the paint process in Swing, start by having a look at:
Performing Custom Painting
Painting in AWT and Swing
The scaling mechanism you were using (and the one I've implemented) aren't the best solutions. I recommend having look at:
Java: maintaining aspect ratio of JPanel background image
Quality of Image after resize very low -- Java
for some better ideas.
You made a couple of mistakes, the first one is using 0 as scansize which should be the width of the image, also in the same line you should use the width and height instead of i and j.
newImg.setRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
There is one other mistake which I will let you find on your own, it should be obvious after you see the image with the above fixed line.
I am using a JSeparator in my java swing application. The normal implementation makes the separator normal line; but what I need is the separator should be dashed(like we create dashed border). Is there any way we can do that?
Thanks
To create a custom JSeparator, you can override the paint() method of BasicSeparatorUI, discussed here, and draw the line using a dashed Stroke, illustrated here.
Addendum: A more familiar approach overrides paintComponent(), as shown in the accepted answer and encapsulated conveniently in this StrokedSeparator. The variation below replaces drawLine() with draw() using a Line2D, which takes advantage of the stroke's geometry.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import static javax.swing.JSeparator.*;
/**
* #see https://stackoverflow.com/a/74657060/230513
*/
public class StrokeSepTest {
private static final int N = 10;
private void display() {
var f = new JFrame("StrokeSepTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
var stroke = new BasicStroke(8.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10.0f, new float[]{5.0f}, 0.0f);
var panel = new JPanel(new GridLayout(0, 1)) {
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
};
panel.setBackground(Color.white);
for (int i = 0; i < N; i++) {
Color color = Color.getHSBColor((float) i / N, 1, 1);
panel.add(new StrokedSeparator(stroke, HORIZONTAL, color));
}
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
// #see https://stackoverflow.com/a/74657060/230513 */
private static class StrokedSeparator extends JSeparator {
private Stroke stroke;
public StrokedSeparator() {
this(new BasicStroke(1F), HORIZONTAL);
}
public StrokedSeparator(int orientation) {
this(new BasicStroke(1F), orientation);
}
public StrokedSeparator(Stroke stroke) {
this(stroke, HORIZONTAL);
}
public StrokedSeparator(Stroke stroke, int orientation) {
super(orientation);
this.stroke = stroke;
}
public StrokedSeparator(Stroke stroke, int orientation, Color color) {
super(orientation);
super.setForeground(color);
this.stroke = stroke;
}
#Override
public void paintComponent(Graphics g) {
var graphics = (Graphics2D) g;
var s = getSize();
graphics.setStroke(stroke);
graphics.setColor(getForeground());
if (getOrientation() == JSeparator.VERTICAL) {
graphics.draw(new Line2D.Double(0, 0, 0, s.height));
} else // HORIZONTAL
{
graphics.draw(new Line2D.Double(0, 0, s.width, 0));
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new StrokeSepTest()::display);
}
}
You can use the following code snippet to create a dashed line.
import java.awt.Container;
import java.awt.Graphics;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSeparator;
public class SeparatorSample {
public static void main(String args[]) {
JFrame f = new JFrame("JSeparator Sample");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = f.getContentPane();
content.setLayout(new GridLayout(0, 1));
JLabel above = new JLabel("Above Separator");
content.add(above);
JSeparator separator = new JSeparator() {
private static final long serialVersionUID = 1L;
public void paintComponent(Graphics g) {
for (int x = 0; x < 300; x += 15)
g.drawLine(x, 0, x + 10, 0);
}
};
content.add(separator);
JLabel below = new JLabel("Below Separator");
content.add(below);
f.setSize(300, 100);
f.setVisible(true);
}
}
With a slight modification to trashgod's answer, I found that using paintComponent() rather than paint() works very well for me:
Stroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[] { 5.0f },
0.0f);
JSeparator separator = new StrokedSeparator(stroke);
// Add separator to container
And here's the StrokedSeparator class:
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import javax.swing.JSeparator;
public class StrokedSeparator extends JSeparator {
private static final long serialVersionUID = 1L;
private Stroke stroke;
public StrokedSeparator() {
this(new BasicStroke(1F), HORIZONTAL);
}
public StrokedSeparator(int orientation) {
this(new BasicStroke(1F), orientation);
}
public StrokedSeparator(Stroke stroke) {
this(stroke, HORIZONTAL);
}
public StrokedSeparator(Stroke stroke, int orientation) {
super(orientation);
this.stroke = stroke;
}
#Override
public void paintComponent(Graphics g) {
Dimension s = getSize();
Graphics2D graphics = (Graphics2D) g;
graphics.setStroke(stroke);
if (getOrientation() == JSeparator.VERTICAL) {
graphics.setColor(getForeground());
graphics.drawLine(0, 0, 0, s.height);
graphics.setColor(getBackground());
graphics.drawLine(1, 0, 1, s.height);
} else // HORIZONTAL
{
graphics.setColor(getForeground());
graphics.drawLine(0, 0, s.width, 0);
graphics.setColor(getBackground());
graphics.drawLine(0, 1, s.width, 1);
}
}
}
I have a problem with JApplet. The code was working just fine, but when I converted it from JFrame to JApplet, the render part stopped working properly. Basicly what I'm trying to do is simplistic draw app. When launching applet, half of time repaint() is not working (There is no gray background; you have to put mouse over button for it to update its color etc), furtheremore the pixel rendering part is not shown up at all. Here's the code:
The Frame class (JApplet)
package painter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Frame extends JApplet {
public JPanel panel;
private JButton plus, minus, buttonColor;
private int scaleSize;
private JLabel labelScale;
private final Timer updateTimer;
private static boolean painting = false;
public static Color currentColor;
public static int mode = 0;
// 0 = draw; 1 = setcolor; 2 = erase
private ArrayList<Pixel> pixelArray;
public Frame() {
pixelArray = new ArrayList<>();
for (int i = 1; i <= 8; i++) {
for (int j = 1; j <= 8; j++) {
pixelArray.add(new Pixel(i, j));
}
}
setLayout(new BorderLayout());
panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//g.fillRect(10, 10, 100, 100); <- test if fillRect works at all. Yus it does.
for (int i = 0; i < pixelArray.size(); i++) {
pixelArray.get(i).render(g);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
};
//panel.setBounds(0, 0, 800, 800);
//add(panel);
getContentPane().add(panel);
//panel.setLayout(null);
//panel.setOpaque(true);
//panel.setDoubleBuffered(true);
currentColor = Color.yellow;
buttonColor = new JButton("Choose color");
buttonColor.setBounds(10, 10, 128, 64);
buttonColor.setBackground(currentColor);
buttonColor.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
currentColor = JColorChooser.showDialog(null, "JColorChooser Sample", Color.gray);
}
});
updateTimer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonColor.setBackground(currentColor);
repaint();
}
});
updateTimer.start();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
painting = true;
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
painting = false;
}
});
panel.add(buttonColor);
repaint();
}
public static boolean getPaint() {
return painting;
}
public static void main(String[] args) {
new Frame();
}
}
And here is the Pixel class:
package painter;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.MouseInfo;
import java.awt.Point;
public class Pixel {
private Color color;
private int size;
private int x, y, relativex, relativey;
public Pixel(int relx, int rely) {
color = new Color(0x999999, false);
size = 32;
x = relx * size + 64;
y = rely * size + 64;
}
public boolean mouseOver() {
Point pos, mousepos;
pos = new Point(x, y);
mousepos = MouseInfo.getPointerInfo().getLocation();
if ((mousepos.x > pos.x)
&& (mousepos.x < pos.x + size)
&& (mousepos.y > pos.y)
&& (mousepos.y < pos.y + size)) {
return true;
} else {
return false;
}
}
public void render(Graphics g) {
g.setColor(color);
if (mouseOver() && Frame.getPaint()) {
if (Frame.mode == 0) {
color = Frame.currentColor;
}
if (Frame.mode == 1) {
Frame.currentColor = color;
}
if (Frame.mode == 2) {
color = new Color(0xffffffff, true);
}
}
g.fillRect(x, y, size, size);
if (mouseOver()) {
g.setColor(Color.black);
g.drawRect(x, y, size - 1, size - 1);
g.setColor(Color.yellow);
g.drawRect(x + 1, y + 1, size - 3, size - 3);
}
//g.fillRect(10, 10, 250, 250);
}
}
As a stab in the dark, don't call Graphics#dipose on a Graphics context you did not create yourself explicitly
Apart from the fact the the Graphics context is a shared resource, used by all the components that might need to be painted within a given paint cycle, it can also prevent what ever was painted to it to be displayed on some platforms
In 15 years of professional development, I've never had reason to call Toolkit.getDefaultToolkit().sync();. I doubt it'll make that big a difference, I'm just saying
Java applets provide us with these methods
[here] http://docs.oracle.com/javase/tutorial/deployment/applet/appletMethods.html
the method
public void paint(Graphics g){}
is used as alternative of
public void paintComponent(Graphics g){}
of swing.
Check out http://docs.oracle.com/javase/tutorial/uiswing/painting/index.html for the recommended way to perform custom painting of swing components
I am developing a GUI for processing images, and I have trouble with displaying the images.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class BottomLeftPanel extends JPanel {
public static BottomLeftPanel BLP;
public static BufferedImage original;
public static ImageIcon icon;
public static Polygon poly;
public static JLabel label;
public BottomLeftPanel() throws IOException {
super();
this.setBackground(new Color(255, 255, 255, 0));
original = Methods2.loadImage("bowser jr.png");
original = Methods2.toFourChannel(original);
poly = null;
icon = new ImageIcon(original);
label = new JLabel(icon);
this.add(new JLabel(icon));
this.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent me) {
}
#Override
public void mousePressed(MouseEvent me) {
Point2D P = me.getPoint();
if(poly == null) {
poly = new Polygon(new int[]{(int) P.getX()}, new int[]{(int) P.getY()}, 1);
return;
}
int[] B = poly.xpoints;
int[] C = poly.ypoints;
int[] X = new int[poly.npoints + 1];
int[] Y = new int[poly.npoints + 1];
System.arraycopy(B, 0, X, 0, B.length);
System.arraycopy(C, 0, Y, 0, C.length);
X[B.length] = (int) P.getX();
Y[C.length] = (int) P.getY();
poly = new Polygon(X, Y, poly.npoints + 1);
System.out.println(poly.toString());
BLP.getGraphics().clearRect(0, 0, BLP.getHeight(), BLP.getWidth());
BLP.repaint(BLP.getGraphics());
}
#Override
public void mouseReleased(MouseEvent me) {
}
#Override
public void mouseEntered(MouseEvent me) {
}
#Override
public void mouseExited(MouseEvent me) {
}
});
BLP = this;
}
public void repaint(Graphics g) {
g.setColor(Color.black);
g.drawPolygon(poly);
icon = new ImageIcon(original);
label.setIcon(icon);
}
}
In the method mousePressed, polygon poly is updated, and the updated version is shown. However, after a few clicks, the ImageIcon which was part of the JLabel which was loaded onto the screen is no longer visible. How do I fix this while keeping the clearRect method in place (I need the clearRect method in order to remove the already drawn polygon and draw the new polygon)?
I was able to solve the problem. I first converted BottomLeftPanel to BottomLeftLabel and put it in a panel of its own. Then I did the painting in the paint(Graphics g) method. In the paint method, I used super.paint(Graphics g), but this is not important because the JLabel (BottomLeftLabel) would not clear the ImageIcon it held no matter what was painted. I do not mind using a static reference, since if I did not use a static reference, I would either have to make the class implement MouseListener, or create a seperate class which implements MouseListener, and since I am only running 1 GUI at a time, it does not make sense to do that (the static reference would not cause any problems here). Here is the code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class BottomLeftLabel extends JLabel {
public static BottomLeftLabel BLP;
public static BufferedImage original;
public static ImageIcon icon;
public static Polygon poly;
// public static JLabel label;
public BottomLeftLabel() throws IOException {
super();
// this.setBackground(new Color(255, 255, 255, 0));
original = Methods2.loadImage("crash bandicoot picture.jpg");
// original = Methods2.loadImage("bowser jr.png");
// original = Methods2.loadImage("devil's tooth.jpg");
original = Methods2.toFourChannel(original);
// int[][] p = Methods.toIntegerArray(original);
// p = Methods.adjustTransparency(p, (float) 1.0);
// original = Methods.toBufferedImage(p);
// this.setSize(new Dimension(original.getWidth(), original.getHeight()));
// Graphics g = this.getGraphics();
poly = null;
icon = new ImageIcon(original);
// label = new JLabel(icon);
this.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent me) {
}
#Override
public void mousePressed(MouseEvent me) {
Point2D P = me.getPoint();
if(poly == null) {
poly = new Polygon(new int[]{(int) P.getX()}, new int[]{(int) P.getY()}, 1);
return;
}
int[] B = poly.xpoints;
int[] C = poly.ypoints;
int[] X = new int[poly.npoints + 1];
int[] Y = new int[poly.npoints + 1];
System.arraycopy(B, 0, X, 0, B.length);
System.arraycopy(C, 0, Y, 0, C.length);
X[B.length] = (int) P.getX();
Y[C.length] = (int) P.getY();
poly = new Polygon(X, Y, poly.npoints + 1);
System.out.println(poly.toString());
// BLP.getGraphics().clearRect(0, 0, BLP.getHeight(), BLP.getWidth());
// BLP.removeAll();
// icon = new ImageIcon(original);
// BLP.add(new JLabel(icon));
BLP.paint(BLP.getGraphics());
}
#Override
public void mouseReleased(MouseEvent me) {
}
#Override
public void mouseEntered(MouseEvent me) {
}
#Override
public void mouseExited(MouseEvent me) {
}
});
this.setIcon(icon);
BLP = this;
// repaint(this.getGraphics());
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.clearRect(0, 0, WIDTH, HEIGHT);
if(poly != null) {
g.drawPolygon(poly);
}
}
// /**
// *
// * #param g
// */
// public void repaint(Graphics g) {
//// g.clearRect(0, 0, WIDTH, HEIGHT);
//
// g.setColor(Color.black);
// g.drawPolygon(poly);
// this.removeAll();
// icon = new ImageIcon(original);
// this.add(new JLabel(icon));
// }
// public void repaint(Graphics g) {
//
// }
}
I created a background using a relatively bland texture (it repeats well, so that's a bonus). However, on top of that, I am trying to add two images in random positions, each five times. So I tried that out with this -
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class RepeatDiagonals {
public static Image whiteOverlayStreak;
public static Image blackOverlayStreak;
public static JFrame framePanel;
public static DiagonalImages diagTest;
public static void createAndInitGUI() {
diagTest = new DiagonalImages();
framePanel = new JFrame("Diagonal Testing");
framePanel.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
framePanel.setPreferredSize(new Dimension(1020, 720));
framePanel.add(diagTest);
framePanel.pack();
framePanel.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndInitGUI();
} // public void run() Closing
}); // SwingUtilities Closing
}
}
// TODO Add in constructor for better image import
class DiagonalImages extends JPanel {
public static final String IMAGE_PATH_WHITESTREAK = "imageFolder/whiteBackgroundStreakOverlay.png";
public static final String IMAGE_PATH_BLACKSTREAK = "imageFolder/blackBackgroundStreakOverlay.png";
public static Image whiteOverlayStreak;
public static Image blackOverlayStreak;
public static Image overlayStreak;
DiagonalImages() {
loadImages();
setVisible(true);
setOpaque(false);
};
public void loadImages() {
try {
whiteOverlayStreak = ImageIO.read(new File(IMAGE_PATH_WHITESTREAK));
blackOverlayStreak = ImageIO.read(new File(IMAGE_PATH_BLACKSTREAK));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
Dimension size = this.getSize();
Insets insets = this.getInsets();
int w = size.width - insets.left - insets.right;
int h = size.height - insets.top - insets.bottom;
Random randomInteger = new Random();
randomInteger.nextInt(900);
for (int i = 0; i < 3; i++) {
int x = randomInteger.nextInt() % w;
int y = randomInteger.nextInt() % h;
g2d.drawImage(blackOverlayStreak, x, y, null);
}
for (int i2 = 0; i2 < 5; i2++){
int x2 = randomInteger.nextInt() % w;
int y2 = randomInteger.nextInt() % h;
g2d.drawImage(whiteOverlayStreak, x2, y2, null);
}
}
}
The relevant part of the main code:
// Makes the Initial BorderLayout
allContent = new ImagePanel(image);
allContent.setLayout(new BorderLayout());
allContent.add(new DiagonalImages());
allContent.add(tabbedPane, BorderLayout.CENTER);
allContent.add(logoImage, BorderLayout.NORTH);
allContent.setVisible(true);
allContent.setOpaque(false);
// Add ScrollPane
scrollPane = new JScrollPane(allContent);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.setOpaque(false);
scrollPane.getViewport().setOpaque(false);
scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
scrollPane.setWheelScrollingEnabled(true);
// JFrame programFrame Constructors
programFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
programFrame.setLayout(new BorderLayout());
programFrame.add(scrollPane);
programFrame.pack();
programFrame.setVisible(true);
programFrame.setResizable(true);
programFrame.setSize(1280, 720);
programFrame.setLocationRelativeTo(null);
And here's the ImagePanel I have:
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
class ImagePanel extends JPanel {
private Image image;
private boolean tile;
ImagePanel(Image image) {
this.image = image;
this.tile = false;
};
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int iw = image.getWidth(this);
int ih = image.getHeight(this);
if (iw > 0 && ih > 0) {
for (int x = 0; x < getWidth(); x += iw) {
for (int y = 0; y < getHeight(); y += ih) {
g.drawImage(image, x, y, iw, ih, this);
}
}
}
}
}
Thanks for the (future) help!
EDIT: Made a small change based on the answer given, and it's still not working.
Okay, so the problem is that the image that's supposed to be repeated isn't actually even showing up.
EDIT2: Rewrote my entire code for this, and it's still not working. Even setting the background color isn't working, which leads me to believe it's a problem with my paintComponent.
EDIT3: paintComponent is working thanks to help. My final problem is getting it to work correctly in my main method.
First JFrame.setVisible(true); should be done last, after pack() which does layouting.
framePanel.pack();
framePanel.setVisible(true);
The images maybe better reside in the application (jar) itself, then you can use getClass().getResource("...").
They should be loaded outside paint, say in the constructor. I guess, it was test code.
public static Image whiteOverlayStreak;
public static Image blackOverlayStreak;
DiagonalImages() {
loadImages();
}
private void loadImages() {
whiteOverlayStreak = new ImageIcon(
getClass().getResource("/white.jpg")).getImage();
blackOverlayStreak = new ImageIcon(
getClass().getResource("/black.jpg")).getImage();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int x = r.nextInt(w);
int y = r.nextInt(h);
Your failure was not using #Override because then you would have seen, that you miswrote Graphics2D g instead of Graphics g. The function paintComponent never got called! LoL
Additional question: adding a second panel
framePanel.setLayout(new BorderLayout());
framePanel.add(diagTest, BorderLayout.CENTER);
framePanel.add(otherPanel, BorderLayout.SOUTH);
It wasn't really an SSCCE.
I haven't tested this fully, mainly because I didn't want to have to set up an entire Eclipse project just to point out the obvious mistake.
Separate the image process from the JPanel.
Only extend Swing components when you're modifying a component method.
Here's my version of your code. I had to modify your code to read an image to get it to work. You're going to have to figure out that part of the code yourself.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
class GUIImages {
private Image whiteDiagonal;
// private Image blackDiagonal;
public GUIImages() {
loadImages();
}
private void loadImages() {
try {
whiteDiagonal = ImageIO.read(new File(
"C:/Documents and Settings/BOP00082/" +
"My Documents/My Pictures/Places-icon.png"));
} catch (IOException e) {
e.printStackTrace();
}
// whiteDiagonal = new ImageIcon(this.getClass().getResource(
// "imageFolder/whiteBackgroundStreakOverlay.png")).getImage();
// blackDiagonal = new ImageIcon(this.getClass().getResource(
// "imageFolder/blackBackgroundStreakOverlay.png")).getImage();
}
public void doDrawing(JPanel panel, Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(createDiagonalImage(panel), 0, 0, null);
}
private Image createDiagonalImage(JPanel panel) {
BufferedImage buffImg = new BufferedImage(677, 856,
BufferedImage.TYPE_INT_ARGB);
Graphics2D gbi = buffImg.createGraphics();
Dimension size = panel.getSize();
Insets insets = panel.getInsets();
int w = size.width - insets.left - insets.right;
int h = size.height - insets.top - insets.bottom;
Random r = new Random();
for (int i = 0; i < 5; i++) {
int x = Math.abs(r.nextInt()) % w;
int y = Math.abs(r.nextInt()) % h;
gbi.drawImage(whiteDiagonal, x, y, null);
}
gbi.dispose();
return buffImg;
}
}
class Surface extends JPanel {
GUIImages images;
public Surface(GUIImages images) {
this.images = images;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
images.doDrawing(this, g);
}
}
public class RepeatDiagonals implements Runnable {
JFrame frame;
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Repeat Diagonals");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Surface(new GUIImages()));
frame.setSize(350, 250);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new RepeatDiagonals());
}
}