Original question:
This method is supposed to change the image being displayed on a JFrame gradually into another image. However, without some way to slow it down, it just seems to change from one image to the new image. In order to slow it down, I put in a Thread.sleep(1000) so the changes wouldn't happen instantly. However, with this line in there, my program freezes completely. No error message, no nothing. Can anyone please help me out? Suggest a better method to slow it down, or how this can be fixed.
For clarification: int k is the number of gradual steps in the change. k = 1 would be an instant change. Anything greater would be gradual changes. int l meanwhile controls the ratio of how much of each image is displayed.
public void morphImg(int width, int height, BufferedImage morphImage, int k) {
//creates new image from two images of same size
BufferedImage image2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
//get color from original image
Color c = new Color(image.getRGB(i, j));
//get colors from morph image
Color c2 = new Color(morphImage.getRGB(i, j));
for (int l = 1; l <= k; l++) {
//gets colors at different stages
int r = ((k-l)*c.getRed()/k) + (l*c2.getRed()/k);
int g = ((k-l)*c.getGreen()/k) + (l*c2.getGreen()/k);
int b = ((k-l)*c.getBlue()/k) + (l*c2.getBlue()/k);
Color newColor = new Color(r, g, b);
//set colors of new image to average of the two images
image2.setRGB(i, j, newColor.getRGB());
//display new image
try {
imageLabel.setIcon(new ImageIcon(image2));
Thread.sleep(1000);
}
catch (InterruptedException e){
System.out.println("Exception caught.");
}
}
}
}
//sets modified image as "original" for further manipulation
setImage(image2);
}
UPDATED CODE: Using a Timer also causes the program to freeze...Am I not using it right?
public void morphImg(int width, int height, BufferedImage morphImage, int k) {
//creates new image from two images of same size
final BufferedImage image2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int l = 1; l <= k; l++) {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
//get color from original image
Color c = new Color(image.getRGB(i, j));
//get colors from morph image
Color c2 = new Color(morphImage.getRGB(i, j));
//gets colors at different stages
int r = ((k-l)*c.getRed()/k) + (l*c2.getRed()/k);
int g = ((k-l)*c.getGreen()/k) + (l*c2.getGreen()/k);
int b = ((k-l)*c.getBlue()/k) + (l*c2.getBlue()/k);
Color newColor = new Color(r, g, b);
//set colors of new image to average of the two images
image2.setRGB(i, j, newColor.getRGB());
//display new image
imageLabel.setIcon(new ImageIcon(image2));
final Timer t = new Timer(500,null);
t.setInitialDelay(500);
t.start();
}
}
}
//sets modified image as "original" for further manipulation
setImage(image2);
}
Never use Thread.sleep() when code is executing on the Event Dispatch Thread.
Instead you should use a Swing Timer to schedule your animation.
See the sections from the Swing tutorial on:
Concurrency in Swing
How to Use Timers
Or if you don't want to use a Timer, then you can use a SwingWorker (as described in the tutorial on concurrency) and then just publish() the image after you change it. Then you can use a Thread.sleep() since the SwingWorker doesn't execute on the EDT.
Simple Timer example:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class TimerTime extends JPanel implements ActionListener
{
private JLabel timeLabel;
private int count = 0;
public TimerTime()
{
timeLabel = new JLabel( new Date().toString() );
add( timeLabel );
Timer timer = new Timer(1000, this);
timer.setInitialDelay(1);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e)
{
// Update the time
timeLabel.setText( new Date().toString() );
count++;
// Stop after 10 events have been generated
if (count == 10)
{
Timer timer = (Timer)e.getSource();
timer.stop();
System.out.println( "Timer stopped" );
}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("TimerTime");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new TimerTime() );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
}
}
The loop over k should be the outermost loop. Right now you are calling Thread.sleep k*width*height times.
If the intention is to show progressive animation of the morph effect, below is the test code I did without using Timer or Thread.sleep(), using the latest morph code given by OP:
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JApplet;
import javax.swing.JFrame;
class MorphComponent extends Component {
/**
*
*/
private static final long serialVersionUID = 1L;
private BufferedImage bi;
private URL imageSrc1;
private URL imageSrc2;
public MorphComponent(URL imageSrc1, URL imageSrc2) {
this.imageSrc1 = imageSrc1;
this.imageSrc2 = imageSrc2;
try {
BufferedImage img1 = ImageIO.read(imageSrc1);
//BufferedImage img2 = ImageIO.read(imageSrc2);
int w = img1.getWidth(null);
int h = img1.getHeight(null);
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
g.drawImage(img1, 0, 0, null);
} catch (IOException e) {
System.out.println("Image could not be read");
System.exit(1);
}
}
public Dimension getPreferredSize() {
return new Dimension(bi.getWidth(null), bi.getHeight(null));
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.white);
g2d.fillRect(0,0, getWidth(), getHeight());
try {
BufferedImage img1 = ImageIO.read(imageSrc1);
BufferedImage img2 = ImageIO.read(imageSrc2);
int w = img1.getWidth(null);
int h = img1.getHeight(null);
bi = morphImg(g, img1, img2, w, h, 10);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private long start = System.currentTimeMillis();
public BufferedImage morphImg(Graphics gp, BufferedImage originalImage, BufferedImage morphImage, int width, int height, int k) {
//creates new image from two images of same size
final BufferedImage image2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int l = 1; l <= k; l++) {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
long elapsed = System.currentTimeMillis() - start;
//get color from original image
Color c = new Color(originalImage.getRGB(i, j));
//get colors from morph image
Color c2 = new Color(morphImage.getRGB(i, j));
//gets colors at different stages
int r = ((k-l)*c.getRed()/k) + (l*c2.getRed()/k);
int g = ((k-l)*c.getGreen()/k) + (l*c2.getGreen()/k);
int b = ((k-l)*c.getBlue()/k) + (l*c2.getBlue()/k);
Color newColor = new Color(r, g, b);
//set colors of new image to average of the two images
image2.setRGB(i, j, newColor.getRGB());
if( elapsed > 100 ) {
gp.drawImage(image2, 0, 0, null);
start = System.currentTimeMillis();
repaint();
}
}
}
}
return image2;
}
}
public class MorphImageApplet extends JApplet {
/**
*
*/
private static final long serialVersionUID = 1L;
static String imageFileName1 = "image_1.jpg";
static String imageFileName2 = "image_2.jpg";
private URL imageSrc1;
private URL imageSrc2;
public MorphImageApplet () {
}
public MorphImageApplet (URL imageSrc1, URL imageSrc2) {
this.imageSrc1 = imageSrc1;
this.imageSrc2 = imageSrc2;
}
public void init() {
try {
imageSrc1 = new URL(getCodeBase(), imageFileName1);
imageSrc2 = new URL(getCodeBase(), imageFileName2);
} catch (MalformedURLException e) {
}
buildUI();
}
public void buildUI() {
final MorphComponent st = new MorphComponent(imageSrc1, imageSrc2);
add("Center", st);
}
public static void main(String s[]) {
JFrame f = new JFrame("See Through Image");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
URL imageSrc1 = null;
URL imageSrc2 = null;
try {
imageSrc1 = ((new File(imageFileName1)).toURI()).toURL();
imageSrc2 = ((new File(imageFileName2)).toURI()).toURL();
} catch (MalformedURLException e) {
}
MorphImageApplet sta = new MorphImageApplet(imageSrc1, imageSrc2);
sta.buildUI();
f.add("Center", sta);
f.pack();
f.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 tried resizing the buffered image using AffineTransform as well as Scalr.resize
Here are my codes for both of them.
using Scalr.resize:
BufferedImage buff = robot.createScreenCapture(new Rectangle(bufx,bufy,bufwidth,bufheight)); // x-coord, y-coord, width, height
BufferedImage scrCapt = Scalr.resize(buff, Method.BALANCED, scrwidth, scrheight);
using AffineTransform:
BufferedImage buff = robot.createScreenCapture(new Rectangle(bufx,bufy,bufwidth,bufheight)); // x-coord, y-coord, width, height
BufferedImage scrCapt = new BufferedImage(bufwidth,bufheight,BufferedImage.TYPE_INT_ARGB);
AffineTransform atscr = new AffineTransform();
atscr.scale(aspectRatioWidth,aspectRatioHeight);
AffineTransformOp scaleOp = new AffineTransformOp(atscr, AffineTransformOp.TYPE_BILINEAR);
scrCapt = scaleOp.filter(buff, scrCapt);
the variables have been declared in the beginning inside class:
static int bufx = 0;
static int bufy = 0;
static int bufwidth = 1;
static int bufheight = 1;
static int scrwidth = 0;
static int scrheight = 0;
static float aspectRatioWidth = 0;
static float aspectRatioHeight = 0;
I am getting the values for for all the variables dynamically inside a different method:
aspectRatioWidth = bufwidth/scrwidth;
aspectRatioHeight = bufheight/scrheight;
However when I run this code I get an error in both the functions AffineTransform as well as Scalr.resize:
Scalr.resize:
Exception in thread "Thread-2" java.lang.IllegalArgumentException: Width (0) and height (0) cannot be <= 0
at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1016)
at java.awt.image.BufferedImage.<init>(BufferedImage.java:331)
at org.imgscalr.Scalr.createOptimalImage(Scalr.java:2006)
at org.imgscalr.Scalr.scaleImage(Scalr.java:2133)
at org.imgscalr.Scalr.resize(Scalr.java:1667)
at org.imgscalr.Scalr.resize(Scalr.java:1415)
AffineTransform:
Exception in thread "Thread-2" java.awt.image.ImagingOpException: Unable to invert transform AffineTransform[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
at java.awt.image.AffineTransformOp.validateTransform(AffineTransformOp.java:558)
at java.awt.image.AffineTransformOp.<init>(AffineTransformOp.java:151)
How do I go about this?
I understand that this is happening because I am changing the variable in a different method and accessing them in another one.
But those two methods can't be combined.
Is there any way I can make this work?
EDIT:
I changed the method of resizing
Here's what I did now
public static BufferedImage resizeImage(BufferedImage image, double scalewidth, double scaleheight){
BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(),BufferedImage.SCALE_FAST);
Graphics2D g = img.createGraphics();
g.scale(scalewidth, scaleheight);
g.drawImage(image, null, 0, 0);
g.dispose();
return image;
}
EDIT(2):
For a clearer idea of what is happening exactly:
This is a method which returns scrwidth and scrheight
public static void showOnScreen( int screen, JFrame framenew )
{
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
GraphicsDevice[] gs = ge.getScreenDevices();
for (int i = 0; i < gs.length; i++) {
screenwidth.add(gs[i].getDisplayMode().getWidth());
screenheight.add(gs[i].getDisplayMode().getHeight());
}
scrwidth = screenwidth.get(screenwidth.size()-1);
scrheight = screenheight.get(screenheight.size()-1);
System.out.print(ge);
System.out.print(gs);
if( screen > -1 && screen < gs.length )
{gs[screen].setFullScreenWindow( framenew );}
else if( gs.length > 0 )
{gs[0].setFullScreenWindow( framenew );}
else
{throw new RuntimeException( "No Screens Found" );}}
And this is the actionlistener which returns bufwidth and bufheight:
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
//Execute when button is pressed
System.out.println("You clicked the button");
int ind = c.getSelectedIndex();
bufx = capx.get(ind);
bufy = capy.get(ind);
bufwidth = capwidth.get(ind);
bufheight = capheight.get(ind);
frame.setVisible(false);
framenew.setVisible(true);
showOnScreen(1,framenew);
aspectRatioWidth = (double) bufwidth/scrwidth;
aspectRatioHeight = (double) bufheight/scrheight;
System.out.print("aspectRatioWidth: ");
System.out.println(aspectRatioWidth);
System.out.print("aspectRatioHeight: ");
System.out.println(aspectRatioHeight);
}
});
And aspectRatios are used inside run:
public void run() {
System.out.print("aspectRatioWidth: ");
System.out.println(aspectRatioWidth);
System.out.print("aspectRatioHeight: ");
System.out.println(aspectRatioHeight);
while(true){
BufferedImage buff = robot.createScreenCapture(new Rectangle(bufx,bufy,bufwidth,bufheight)); // x-coord, y-coord, width, height
BufferedImage resizedbuff = resizeImage(buff, aspectRatioWidth, aspectRatioHeight);}
You're doing int division which always returns an int, here 0 since your screen dimensions will likely be greater than your image dimensions:
aspectRatioWidth = bufwidth/scrwidth;
aspectRatioHeight = bufheight/scrheight;
Solution: convert the numbers to double and then do double division.
aspectRatioWidth = (double) bufwidth/scrwidth;
aspectRatioHeight = (double) bufheight/scrheight;
Edit
Not sure what you're ultimately trying to do -- post the image of the computer screen in your GUI? If so, perhaps something like...
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.util.List;
import javax.swing.*;
public class ChangeVars extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int DELAY = 20;
public BufferedImage displayImage;
private MyWorker myWorker;
public ChangeVars() {
try {
myWorker = new MyWorker(DELAY);
myWorker.execute();
} catch (AWTException e) {
e.printStackTrace();
}
}
#Override
// to initialize the panel to something
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (displayImage != null) {
g.drawImage(displayImage, 0, 0, null);
}
}
public void stopWorker() {
if (myWorker != null && !myWorker.isDone()) {
myWorker.setRunning(false);
myWorker.cancel(true);
}
}
private class MyWorker extends SwingWorker<Void, BufferedImage> {
private volatile boolean running = true;
private Robot robot;
private int delay;
public MyWorker(int delay) throws AWTException {
this.delay = delay;
robot = new Robot();
}
#Override
protected Void doInBackground() throws Exception {
while (running) {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
Rectangle screenRect = new Rectangle(0, 0, d.width, d.height);
BufferedImage img = robot.createScreenCapture(screenRect);
publish(img);
Thread.sleep(delay);
}
return null;
}
#Override
protected void process(List<BufferedImage> chunks) {
for (BufferedImage image : chunks) {
Dimension sz = getSize();
double scaleX = (double) sz.width / image.getWidth();
double scaleY = (double) sz.height / image.getHeight();
AffineTransform transform = AffineTransform.getScaleInstance(
scaleX, scaleY);
AffineTransformOp transformOp = new AffineTransformOp(transform,
AffineTransformOp.TYPE_BILINEAR);
displayImage = new BufferedImage(sz.width, sz.height,
BufferedImage.TYPE_INT_ARGB);
displayImage = transformOp.filter(image, displayImage);
repaint();
}
}
public void setRunning(boolean running) {
this.running = running;
}
public boolean getRunning() {
return running;
}
}
private static void createAndShowGui() {
final ChangeVars changeVars = new ChangeVars();
JFrame frame = new JFrame("ChangeVars");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
if (changeVars != null) {
changeVars.stopWorker();
}
System.exit(0);
}
});
frame.getContentPane().add(changeVars);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Although simpler would be to just let paintComponent do the scaling:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (displayImage != null) {
int width = getWidth();
int height = getHeight();
g.drawImage(displayImage, 0, 0, width, height, null);
}
}
// ....
#Override
protected void process(List<BufferedImage> chunks) {
for (BufferedImage image : chunks) {
displayImage = image;
repaint();
}
}
My current code for grid to show video stream is as follow
//Function for displaying grid window//
public void createMap(int maxX, int maxY) {
gameGrid = new JPanel(new GridLayout(maxX, maxY, 1, 1));
gameGrid.setBackground(Color.GREEN);
for (int i = 0; i < maxX; i++) {
for (int j = 0; j < maxY; j++) {
JPanel panel = new JPanel();
panel.setPreferredSize(PREF_SIZE);
String name = String.format("[%d, %d]", i, j);
panel.setName(name);
panel.setBackground(Color.black);
gameGrid.add(panel);
}
}
}
I call this function as createMap(2,2) so it create grid for 2x2(4 window).
I want to show video stream in each of these grid.
I receiving the video stream through 4 cctv cameras attached with DVR.
I used following code for Showing video streams using opencv
package cctvmonitorsystem;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
// DB connectivity classes
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.ResultSet;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.imgproc.Imgproc;
import java.util.Arrays;
public class Video {
public static void main(String args[]) throws SQLException {
int maxCamNo = 4;
//create connection
DbConnect dbcon = new DbConnect();
Connection con = dbcon.openCon();
Statement stmt = con.createStatement();
String getDevice="select * from device order by d_id limit 1";
ResultSet res=stmt.executeQuery(getDevice);
// DECLARE VARIABLES FOR DEVICE ADDRESS & PORT
String devAddress = null;
int httpPort=0;
while (res.next())
{
devAddress=res.getString("d_address");
httpPort=res.getInt("d_http_port");
}
dbcon.closeCon(con); // Connection closed
VideoCapture vcam[] = new VideoCapture[maxCamNo];
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
for (int i = 0; i < vcam.length; i++) {
try {
vcam[i] = new VideoCapture();
vcam[i].open("http://"+devAddress+":"+httpPort+"/cgi-bin/view.cgi?chn=" + i + "&u=admin&p=");
//vcam[i]=new VideoCapture(i);
} catch (Exception e) {
e.printStackTrace();
}
if (!vcam[i].isOpened()) {
System.out.println("Camera " + i + " error");
}
}
AccessVideo av = new AccessVideo(vcam);
//Initialize swing components
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(av);
//frame.setSize(1024,768);
frame.setMaximumSize(null);
frame.setExtendedState(java.awt.Frame.MAXIMIZED_BOTH);
frame.setVisible(true);
while (vcam[0].isOpened()) {
av.repaint();
}
}
}
class AccessVideo extends JPanel {
VideoCapture[] camera = new VideoCapture[6];
AccessVideo(VideoCapture[] camView) {
camera = camView;
}
public BufferedImage Mat2BufferedImage(Mat m) {
System.out.println("Channels - "+m.channels());
int type = BufferedImage.TYPE_BYTE_GRAY;
if (m.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = m.channels() * m.cols() * m.rows();
byte[] b = new byte[bufferSize];
m.get(0, 0, b); // get all the pixels
BufferedImage img = new BufferedImage(m.cols(), m.rows(), type);
final byte[] targetPixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
System.arraycopy(b, 0, targetPixels, 0, b.length);
return img;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Mat[] mat = new Mat[camera.length];
int width=400;
int height=300;
int xpos = 0;
int ypos = 0;
int y = 1;
for (int i = 0; i < camera.length; i++) {
mat[i] = new Mat();
camera[i].read(mat[i]);
BufferedImage image = Mat2BufferedImage(mat[i]);
g.drawImage(image, xpos, ypos, width, height, null);
xpos = xpos + width;
if (y == 3) {
xpos = 0;
ypos = ypos + height;
y = 0;
}
y++;
}
}
}
Here the video streams will show according to x & y co-ordinates.
But I want to show these video streams in grids which i mention above.
Can anyone help me to resolve this issue.
I did something similar here is my code, but this is hardcoded to only 6 cameras.
This is the main function where I called the addComponentsToPane.
public static void main(String[] args) {
CamtestMonitor f = new CamtestMonitor("Monitor");
f.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//Set up the content pane.
f.addComponentsToPane(f.getContentPane());
//Display the window.
f.pack();
f.setVisible(true);
}
This is the addComponentsToPane function where we add 6 different ServerUI's. Each one extends the JLabel class and implements DataListener.
public void addComponentsToPane(final Container pane) {
final JPanel jp = new JPanel();
jp.setLayout(gl);
//Set up components preferred size
jp.add(new ServerUI(8880));
jp.add(new ServerUI(8891));
jp.add(new ServerUI(8892));
jp.add(new ServerUI(8893));
jp.add(new ServerUI(8894));
jp.add(new ServerUI(8895));
jp.setPreferredSize(new Dimension(1600, 900));
//Set up the horizontal gap value
gl.setHgap(gapSize);
//Set up the vertical gap value
gl.setVgap(gapSize);
//Set up the layout of the buttons
gl.layoutContainer(jp);
pane.add(jp, BorderLayout.NORTH);
pane.add(new JSeparator(), BorderLayout.SOUTH);
}
There you have the Constructor of the ServerUI:
public ServerUI(int port) {
SocketServer server = new SocketServer(port);
server.setOnDataListener(this);
server.start();
}
Finally, here we have the overrrided function that refresh the image on each JLabel.
#Override
public void paint(Graphics g) {
synchronized (queue) {
if (queue.size() > 0) {
lastFrame = queue.poll();
}
}
if (lastFrame != null) {
g.drawImage(lastFrame, 0, 0, null);
}
}
You can have one thread for each camera, and also you extend JPanel class and override the paintComponent() method, like example below
public class MyJPanel extends JPanel {
private BufferedImage image;
public MyJPanel() {
super();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (image != null) {
g2.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), this);
}
}
public void refresh(BufferedImage image) {
this.image = image;
this.repaint();
}
}
public class CameraHandler implements Runnable {
VideoCapture vCapture;
MyJPanel mPanel;
public CameraHandler(VideoCapture vCapture, MyJPanel mPanel) {
this.vCapture = vCapture;
this.mPanel = mPanel;
}
public void run() {
while(vCapture.canReadFrame...) {
then convert the Mat to BufferedImage;
then call this:
mPanel.refresh(convertedImage);
}
}
}
public class MainClass {
public static void main(String[] args) {
VideoCapture vcam[] = new VideoCapture[maxCamNo];
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
for (int i = 0; i < vcam.length; i++) {
try {
vcam[i] = new VideoCapture();
vcam[i].open("http://"+devAddress+":"+httpPort+"/cgi-bin/view.cgi?chn=" + i + "&u=admin&p=");
//vcam[i]=new VideoCapture(i);
if(vcam[i].isOpen) {
new Thread(new CameraHandler(vcam[i], and you MyJPanel reference)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
if (!vcam[i].isOpened()) {
System.out.println("Camera " + i + " error");
}
}
}
}
The code above is just an example to show the way, if you use this then need to arrange the code as your need. Hope this code help you!
I am trying to make a java desktop application. I have a JLabel where I am shuffling image but all image sizes are different so I want to fix size of the image on JLabel.
How can I do this?
Here is my code :
public class ImageShuffle1 extends JPanel {
private List<Icon> list = new ArrayList<Icon>();
private List<Icon> shuffled;
private JLabel label = new JLabel();
private Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
}
});
public ImageShuffle1() {
this.setLayout(new GridLayout(1, 0));
list.add(new ImageIcon("E:\\SOFTWARE\\TrainPIS\\res\\drawable\\e.jpg"));
list.add(new ImageIcon("E:\\SOFTWARE\\TrainPIS\\res\\drawable\\d.jpg"));
list.add(new ImageIcon("E:\\SOFTWARE\\TrainPIS\\res\\drawable\\yellow.png"));
list.add(new ImageIcon("E:\\SOFTWARE\\TrainPIS\\res\\drawable\\f.jpg"));
list.add(new ImageIcon("E:\\SOFTWARE\\TrainPIS\\res\\drawable\\l.jpg"));
//label.setIcon(UIManager.getIcon("OptionPane.informationIcon"));
for(Icon icon: list){
Image img = icon.getImage() ;
// put here the size properties
Image newimg = img.getScaledInstance( 45, 34, java.awt.Image.SCALE_SMOOTH ) ;
icon = new ImageIcon(newimg);
}
shuffled = new ArrayList<Icon>(list);
Collections.shuffle(shuffled);
timer.start();
}
private void update() {
if (shuffled.isEmpty()) {
shuffled = new ArrayList<Icon>(list);
Collections.shuffle(shuffled);
}
Icon icon = shuffled.remove(0);
label.setIcon(icon);
}
private void display() {
JFrame f = new JFrame("ImageShuffle");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(label);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ImageShuffle1().display();
}
});
}
}
I am getting error herein this.
line/variable getimage can not found mage img = icon.getImage() ;
Thanks in advance
Use BufferedImage in place of Icon that has a functionality to re size it.
Here is the code
import java.awt.EventQueue;
import java.awt.Graphics2D;
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.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class ImageShuffle1 extends JPanel {
private List<BufferedImage> list = new ArrayList<BufferedImage>();
private List<BufferedImage> shuffled;
private JLabel label = new JLabel();
private int width = 50;
private int height = 100;
private Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
}
});
public ImageShuffle1() {
try {
list.add(resizeImage(ImageIO.read(new File("resources/1.png"))));
list.add(resizeImage(ImageIO.read(new File("resources/2.png"))));
list.add(resizeImage(ImageIO.read(new File("resources/6.png"))));
list.add(resizeImage(ImageIO.read(new File("resources/Tulips.jpg"))));
} catch (IOException e) {
e.printStackTrace();
}
shuffled = new ArrayList<BufferedImage>(list);
Collections.shuffle(shuffled);
timer.start();
}
private BufferedImage resizeImage(BufferedImage originalImage) throws IOException {
BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
private void update() {
if (shuffled.isEmpty()) {
shuffled = new ArrayList<BufferedImage>(list);
Collections.shuffle(shuffled);
}
BufferedImage icon = shuffled.remove(0);
label.setIcon(new ImageIcon(icon));
}
private void display() {
JFrame f = new JFrame("ImageShuffle");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(label);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ImageShuffle1().display();
}
});
}
}
Lets start with this...
for(Icon icon: list){
Image img = icon.getImage() ;
Icon does not have a method getImage, there is actually no way to get the "image" data maintained by the Icon class without first rendering it to something (like a BufferedImage)
A better solution might be to load the images into a List that supports BufferedImage. BufferedImage is a more versatile starting point and because it extends from Image, it can be used with ImageIcon. For example...
private List<BufferedImage> list = new ArrayList<BufferedImage>();
//...
list.add(ImageIO.read("E:\\SOFTWARE\\TrainPIS\\res\\drawable\\e.jpg"));
Take a look at Reading/Loading an Image
For scaling you might like to take a look at
The Perils of Image.getScaledInstance
Java: maintaining aspect ratio of JPanel background image
Quality of Image after resize very low -- Java
First you resize every image to a fixed size and it must be fit in JLabel
public static Boolean resizeImage(String sourceImage, String destinationImage, Integer Width, Integer Height) {
BufferedImage origImage;
try {
origImage = ImageIO.read(new File(sourceImage));
int type = origImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : origImage.getType();
//*Special* if the width or height is 0 use image src dimensions
if (Width == 0) {
Width = origImage.getWidth();
}
if (Height == 0) {
Height = origImage.getHeight();
}
int fHeight = Height;
int fWidth = Width;
//Work out the resized width/height
if (origImage.getHeight() > Height || origImage.getWidth() > Width) {
fHeight = Height;
int wid = Width;
float sum = (float)origImage.getWidth() / (float)origImage.getHeight();
fWidth = Math.round(fHeight * sum);
if (fWidth > wid) {
//rezise again for the width this time
fHeight = Math.round(wid/sum);
fWidth = wid;
}
}
BufferedImage resizedImage = new BufferedImage(fWidth, fHeight, type);
Graphics2D g = resizedImage.createGraphics();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(origImage, 0, 0, fWidth, fHeight, null);
g.dispose();
ImageIO.write(resizedImage, "png", new File(destinationImage));
...
After reading image from JFilechooser, I am trying to read pixel of an image one by one and display it to JPanel after some delay in sequential manner. can't update the background of JPanel.
public class ImageMain extends JFrame implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 2916361361443483318L;
private JFileChooser fc = null;
private JMenuItem item1, item2;
private BufferedImage image = null;
private JPanel panel = null;
private int width = 0;
private int height = 0;
private BorderLayout card;
private Container contentPane;
//private int loopcount = 0;
//private int counter = 0;
public ImageMain() {
JFrame frame = new JFrame("Image Extraction Tool");
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane = frame.getContentPane();
panel = new JPanel();
card = new BorderLayout();
panel.setLayout(card);
panel.setBackground(Color.white);
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menuBar.add(menu);
item1 = new JMenuItem("Browse an image");
item2 = new JMenuItem("Exit");
item1.addActionListener(this);
item2.addActionListener(this);
menu.add(item1);
menu.add(item2);
frame.setJMenuBar(menuBar);
contentPane.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
ImageMain img = new ImageMain();
}
});
} catch (InvocationTargetException | InterruptedException e) {
e.printStackTrace();
}
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == item1) {
if (fc == null)
fc = new JFileChooser();
int retVal = fc.showOpenDialog(null);
if (retVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
try {
image = ImageIO.read(file);
height = image.getHeight();
width = image.getWidth();
// final int[][] pixelData = new int[height * width][3];
// int[] rgb;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
System.out.println(i + " " + j);
Color c = new Color(image.getRGB(j, i));
panel.setBackground(c);
panel.invalidate();
panel.validate();
panel.repaint();
}
}
} catch (IOException e1) {
System.out.println("IO::" + e1.getMessage());
} catch (Exception e1) {
System.out.println("Exception::" + e1.getMessage());
}
}
}
if (e.getSource() == item2) {
System.exit(0);
}
}}
Inside ActionPerformed, I got Color object by reading RGB values and then I am stuck at displaying them to JApplet. Suggestion are welcome if there is a better way to achieve this.
Thanks in advance.
The main problem is your performing a long running task within the context of the Event Dispatching Thread, which is responsible for, amongst other things, processing repaint requests.
#Override
public void actionPerformed(ActionEvent e) {
//...
// Nothing will be updated until after the
// actionPerformed method exists
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
System.out.println(i + " " + j);
Color c = new Color(image.getRGB(j, i));
panel.setBackground(c);
panel.invalidate();
panel.validate();
panel.repaint();
}
}
The other problem you have is that you are required to only modify the state of the UI from within the context of the EDT
Depending on your exact needs, you could use a SwingWorker, which would allow you to process the pixels in the background while updating the UI from within the context of the EDT, however, because SwingWorker consolidates it's updates, you could miss color changes.
A better solution might be to use a java.swing.Timer which would allow you to trigger updates at a specified period, which are triggered within the context of the EDT.
See Concurrency in Swing for more details...
Updated with example
In order to draw pixels, you need something to draw them on. Now, you could simply add each pixel you want to paint to an array and loop that array each time you need to repaint the component, but that's kind of expensive...
Instead, it would be simpler to have a backing buffer of some kind, onto which you paint the pixels and then paint that buffer to the component, which should be faster.
Basically, the allows you to supply the height and width of the expected image and then supply each pixel as you need..
public class ImagePane extends JPanel {
private BufferedImage img;
public ImagePane() {
}
public void reset(int width, int height) {
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
revalidate();
}
public void reset() {
img = null;
revalidate();
}
public void setPixelAt(int x, int y, int pixel) {
img.setRGB(x, y, pixel);
}
#Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (img != null) {
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
}
g2d.dispose();
}
}
Take a look at Performing Custom Painting for more details...
Then you need some way to process the original image and update the image panel...Now, based on the updated requirements, I would use a SwingWorker, the reason for this, is that the SwingWorker can cache what is passed back to the EDT, this allows the background thread to continue processing and caching the output until the EDT (and system) is ready to process it...
public class PixelExposerWorker extends SwingWorker<Void, Pixel> {
private final BufferedImage img;
private final ImagePane imagePane;
private final List<Point> points;
public PixelExposerWorker(BufferedImage img, ImagePane imagePane) {
this.img = img;
this.imagePane = imagePane;
points = new ArrayList<>(img.getWidth() * img.getHeight());
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
points.add(new Point(x, y));
}
}
}
#Override
protected void process(List<Pixel> chunks) {
System.out.println("Publish " + chunks.size());
for (Pixel pixel : chunks) {
imagePane.setPixelAt(pixel.getX(), pixel.getY(), pixel.getColor());
}
imagePane.repaint();
}
#Override
protected Void doInBackground() throws Exception {
int pixelCount = (int) (points.size() * 0.005);
while (!points.isEmpty()) {
for (int count = 0; count < pixelCount && !points.isEmpty(); count++) {
int index = (int) (Math.random() * (points.size() - 1));
Point p = points.remove(index);
Pixel pixel = new Pixel(p.x, p.y, img.getRGB(p.x, p.y));
publish(pixel);
}
Thread.yield();
}
return null;
}
}
Basically, this SwingWorker builds a List of "pixel points", it uses the list to randomly remove points from the list, generate a virtual Pixel and publish back to the EDT for processing. The worker will process around 0.5% of the pixels at a time, meaning that the work is always trying to send a "bunch" of pixels back to the EDT, rather the one at a time.
Finally, it will yield to allow other threads to run (and the hopefully for the EDT to update it self)
An image of 650x975 takes roughly 1m and 10s to fully renderer
And the full code...
import core.util.StopWatch;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
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.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
public class PixelShower implements ActionListener {
private static final long serialVersionUID = 2916361361443483318L;
private JFileChooser fc = null;
private JMenuItem item1, item2;
private BufferedImage image = null;
private ImagePane panel = null;
private int width = 0;
private int height = 0;
private BorderLayout card;
private Container contentPane;
public PixelShower() {
JFrame frame = new JFrame("Image Extraction Tool");
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane = frame.getContentPane();
panel = new ImagePane();
card = new BorderLayout();
panel.setLayout(card);
panel.setBackground(Color.white);
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menuBar.add(menu);
item1 = new JMenuItem("Browse an image");
item2 = new JMenuItem("Exit");
item1.addActionListener(this);
item2.addActionListener(this);
menu.add(item1);
menu.add(item2);
frame.setJMenuBar(menuBar);
contentPane.add(panel);
frame.setVisible(true);
}
public static void main(String[] args) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
PixelShower img = new PixelShower();
}
});
} catch (InvocationTargetException | InterruptedException e) {
e.printStackTrace();
}
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == item1) {
if (fc == null) {
fc = new JFileChooser();
}
int retVal = fc.showOpenDialog(null);
if (retVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
try {
image = ImageIO.read(file);
panel.reset(image.getWidth(), image.getHeight());
PixelExposerWorker worker = new PixelExposerWorker(image, panel);
worker.execute();
} catch (IOException e1) {
e1.printStackTrace();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
if (e.getSource() == item2) {
System.exit(0);
}
}
public class ImagePane extends JPanel {
private BufferedImage img;
public ImagePane() {
}
public void reset(int width, int height) {
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
revalidate();
}
public void reset() {
img = null;
revalidate();
}
public void setPixelAt(int x, int y, int pixel) {
img.setRGB(x, y, pixel);
}
#Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (img != null) {
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
}
g2d.dispose();
}
}
public class Pixel {
private int x;
private int y;
private int color;
public Pixel(int x, int y, int color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getColor() {
return color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
public class PixelExposerWorker extends SwingWorker<Void, Pixel> {
private final BufferedImage img;
private final ImagePane imagePane;
private final List<Point> points;
public PixelExposerWorker(BufferedImage img, ImagePane imagePane) {
this.img = img;
this.imagePane = imagePane;
points = new ArrayList<>(img.getWidth() * img.getHeight());
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
points.add(new Point(x, y));
}
}
}
#Override
protected void process(List<Pixel> chunks) {
System.out.println("Publish " + chunks.size());
for (Pixel pixel : chunks) {
imagePane.setPixelAt(pixel.getX(), pixel.getY(), pixel.getColor());
}
imagePane.repaint();
}
#Override
protected Void doInBackground() throws Exception {
StopWatch sw = StopWatch.newInstance().start();
int pixelCount = (int) (points.size() * 0.005);
System.out.println("pixelCount = " + pixelCount + "; " + points.size());
while (!points.isEmpty()) {
StopWatch sw1 = StopWatch.newInstance().start();
for (int count = 0; count < pixelCount && !points.isEmpty(); count++) {
int index = (int) (Math.random() * (points.size() - 1));
Point p = points.remove(index);
Pixel pixel = new Pixel(p.x, p.y, img.getRGB(p.x, p.y));
publish(pixel);
}
Thread.yield();
}
System.out.println("Took " + sw.stop());
return null;
}
}
}