Is this simple Swing-based class thread-safe? - java

I'm making a proof-of-concept game in Java and decided (for practice but mostly for fun) to make my own simple component with thread-safe double buffering. However, I am not very experienced when it comes to concurrency (specially regarding Swing) and I'm wondering if there are any problems with my implementation that I'm missing.
Implementation as follows:
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class GamePanel extends JPanel
{
private static final long serialVersionUID = 1L;
private BufferedImage mBackImage = null;
private BufferedImage mFrontImage = null;
public BufferedImage getBackImage ()
{
return mBackImage;
}
public void swap ()
{
BufferedImage new_back;
//
synchronized (this)
{
new_back = mFrontImage;
mFrontImage = mBackImage;
}
//
int width = getWidth (), height = getHeight ();
if (width > 0 && height > 0)
{
if (new_back == null || new_back.getWidth () != width
|| new_back.getHeight () != height)
new_back = new BufferedImage (width, height,
BufferedImage.TYPE_INT_ARGB);
//
mBackImage = new_back;
}
else
mBackImage = null;
}
#Override
public void paintComponent (Graphics g)
{
synchronized (this)
{
if (mFrontImage == null)
super.paintComponent (g);
else
g.drawImage (mFrontImage, 0, 0, null);
}
}
}
I'm assuming that getBackImage() and swap() will only be called by a single thread (the game loop thread). The paints are triggered via a Swing timer and so are in the EDT. I believe that the simple synchronized-blocks around the use of mFrontImage should be enough to protect against unwanted behavior, allowing the game loop thread to render to the back image and call swap without worrying about the Swing redraws. Am I missing something?

In game thread:
BufferedImage image = gamePanel.getBackPanel();
gamePanel.swap();
Your program would now be in a state where the Event Dispatch Queue and the game thread can access the same image. Meaning funny things could be drawn to the panel if you were drawing to the image whilst the EDT was drawing the image to screen.
For the purposes of reading and writing to the frontImage field on its own then you're thread safe as all access is done inside synchronized blocks. However, the paintComponent method could be better written to reduce the amount of time spent in a synchronized block. That is:
#Override
public void paintComponent (Graphics g)
{
BufferedImage localFrontImage;
synchronized (this)
{
localFrontImage = mFrontImage;
}
if (localFrontImage == null)
super.paintComponent (g);
else
g.drawImage (localFrontImage, 0, 0, null);
}

Swing is pure single threaded and all event must be done on EDT, then I can't see reason for synchronized (this)
use Icon in the JLabel for dispaying Images in the Swing GUI
for any of swap's types, you have look at CardLayout

Related

Why is my code in Swing just returning a black screen? [duplicate]

I'm creating a graphical front-end for a JBox2D simulation. The simulation runs incrementally, and in between the updates, the contents of the simulation are supposed to be drawn. Similar to a game except without input.
I only need geometric primitives to draw a JBox2D simulation. This API seemed like the simplest choice, but its design is a bit confusing.
Currently I have one class called Window extending JFrame, that contains as a member another class called Renderer. The Window class only initializes itself and provides an updateDisplay() method (that is called by the main loop), that calls updateDisplay(objects) method on the Renderer. I made these two methods myself and their only purpose is to call repaint() on the Renderer.
Is the JPanel supposed to be used that way? Or am I supposed to use some more sophisticated method for animation (such that involves events and/or time intervals in some back-end thread)?
If you are wanting to schedule the updates at a set interval, javax.swing.Timer provides a Swing-integrated service for it. Timer runs its task on the EDT periodically, without having an explicit loop. (An explicit loop would block the EDT from processing events, which would freeze the UI. I explained this more in-depth here.)
Ultimately doing any kind of painting in Swing you'll still be doing two things:
Overriding paintComponent to do your drawing.
Calling repaint as-needed to request that your drawing be made visible. (Swing normally only repaints when it's needed, for example when some other program's window passes over top of a Swing component.)
If you're doing those two things you're probably doing it right. Swing doesn't really have a high-level API for animation. It's designed primarily with drawing GUI components in mind. It can certainly do some good stuff, but you will have to write a component mostly from scratch, like you're doing.
Painting in AWT and Swing covers some of the 'behind the scenes' stuff if you do not have it bookmarked.
You might look in to JavaFX. I don't know that much about it personally, but it's supposed to be more geared towards animation.
As somewhat of an optimization, one thing that can be done is to paint on a separate image and then paint the image on to the panel in paintComponent. This is especially useful if the painting is long: repaints can be scheduled by the system so this keeps when it happens more under control.
If you aren't drawing to an image, then you'd need to build a model with objects, and paint all of them every time inside paintComponent.
Here's an example of drawing to an image:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
If the routine is long-running and repaints could happen concurrently, double buffering can also be used. Drawing is done to an image which is separate from the one being shown. Then, when the drawing routine is done, the image references are swapped so the update is seamless.
You should typically use double buffering for a game, for example. Double buffering prevents the image from being shown in a partial state. This could happen if, for example, you were using a background thread for the game loop (instead of a Timer) and a repaint happened the game was doing the painting. Without double buffering, this kind of situation would result in flickering or tearing.
Swing components are double buffered by default, so if all of your drawing is happening on the EDT you don't need to write double buffering logic yourself. Swing already does it.
Here is a somewhat more complicated example which shows a long-running task and a buffer swap:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
/**
* Left-click to spawn a new background
* painting task.
*/
class DoubleBuffer {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DoubleBuffer();
}
});
}
final int width = 640;
final int height = 480;
BufferedImage createCompatibleImage() {
GraphicsConfiguration gc =
GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// createCompatibleImage creates an image that is
// optimized for the display device.
// See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
// The front image is the one which is
// displayed in the panel.
BufferedImage front = createCompatibleImage();
// The back image is the one that gets
// painted to.
BufferedImage back = createCompatibleImage();
boolean isPainting = false;
final JFrame frame = new JFrame("Double Buffer");
final JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Scaling the image to fit the panel.
Dimension actualSize = getSize();
int w = actualSize.width;
int h = actualSize.height;
g.drawImage(front, 0, 0, w, h, null);
}
};
final MouseAdapter onClick = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (!isPainting) {
isPainting = true;
new PaintTask(e.getPoint()).execute();
}
}
};
DoubleBuffer() {
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(Color.WHITE);
panel.addMouseListener(onClick);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void swap() {
BufferedImage temp = front;
front = back;
back = temp;
}
class PaintTask extends SwingWorker<Void, Void> {
final Point pt;
PaintTask(Point pt) {
this.pt = pt;
}
#Override
public Void doInBackground() {
Random rand = new Random();
synchronized(DoubleBuffer.this) {
Graphics2D g2 = back.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setBackground(new Color(0, true));
g2.clearRect(0, 0, width, height);
// (This computes pow(2, rand.nextInt(3) + 7).)
int depth = 1 << ( rand.nextInt(3) + 7 );
float hue = rand.nextInt(depth);
int radius = 1;
int c;
// This loop just draws concentric circles,
// starting from the inside and extending
// outwards until it hits the outside of
// the image.
do {
int rgb = Color.HSBtoRGB(hue / depth, 1, 1);
g2.setColor(new Color(rgb));
int x = pt.x - radius;
int y = pt.y - radius;
int d = radius * 2;
g2.drawOval(x, y, d, d);
++radius;
++hue;
c = (int) (radius * Math.cos(Math.PI / 4));
} while (
(0 <= pt.x - c) || (pt.x + c < width)
|| (0 <= pt.y - c) || (pt.y + c < height)
);
g2.dispose();
back.flush();
return (Void) null;
}
}
#Override
public void done() {
// done() is completed on the EDT,
// so for this small program, this
// is the only place where synchronization
// is necessary.
// paintComponent will see the swap
// happen the next time it is called.
synchronized(DoubleBuffer.this) {
swap();
}
isPainting = false;
panel.repaint();
}
}
}
The painting routine is just intended draw garbage which takes a long time:
For a tightly coupled simulation, javax.swing.Timer is a good choice. Let the timer's listener invoke your implementation of paintComponent(), as shown here and in the example cited here.
For a loosely coupled simulation, let the model evolve in the background thread of a SwingWorker, as shown here. Invoke publish() when apropos to you simulation.
The choice is dictated in part by the nature of the simulation and the duty cycle of the model.
Why not just use stuff from the testbed? It already does everything. Just take the JPanel, controller, and debug draw. It uses Java 2D drawing.
See here for the JPanel that does the buffered rendering:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/TestPanelJ2D.java
and here for the debug draw:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/DebugDrawJ2D.java
See the TestbedMain.java file to see how the normal testbed is launched, and rip out what you don't need :)
Edits:
Disclaimer: I maintain jbox2d
Here is the package for the testbed framework: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework
TestbedMain.java is in the j2d folder, here:
https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d

Can Java have text in the background of a graphics file?

I have this code and its supposed to have the text "Simple Animation" scroll across the screen in swirling colors. Right now, it does that, but even after the text moves along, the color still stays. I was wondering if there was a way to have text in the background. For example, I was thinking I could just print out the exact same "Simple Animation" but in the same color as the background and about 10 pixels behind the actual text. However, when I tried this, the white text (that's the background color) just covered the swirling colors. I tried googling if I could have background text, but from I read, the only thing that a background can do is set the color. So, is there a way to have text in the background in a Java Graphics file?
Here is my Code:
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import javax.swing.*;
public class Scrolling_Sign extends JApplet implements Runnable {
String mesag = "Simple Animation";
Font f = new Font("Bauhaus 93",Font.BOLD,72);
Color colors[] = new Color[100000];
Thread runThread;
int Xposition = 600;
public void init() {
setBackground(Color.white);
}
public void start() {
if (runThread == null) {
runThread = new Thread(this);
runThread.start();
}
}
public void stop() {
if (runThread != null) {
runThread.stop();
runThread = null;
}
}
public void run() {
float c = 0;
for (int i = 0; i < colors.length; i++) {
colors[i] = Color.getHSBColor(c, (float)1.0,(float)1.0);
c += .02;
}
int i = 0;
while (true) {
setForeground(colors[i]);
repaint();
i++;
try { Thread.sleep(100); }
catch (InterruptedException e) { }
if (i == colors.length ) i = 0;
}
}
public void paint(Graphics g) {
g.setFont(f);
g.drawString(mesag,Xposition,100);
Xposition--;
if (Xposition < -290) {
Xposition = 600;
}
}
}
Thank you!
Suggestions:
Never draw directly within a JApplet or other top-level window.
Instead draw in the paintComponent of a JPanel that is displayed within the applet. The Swing tutorials will show you how.
Be sure to call the super.paintComponent(g) method within your override, and again read the Swing tutorials to see why. For more tutorials see: Swing Info
This is Swing -- use a Swing Timer to drive your animation, not threads.
If you ever do use Threads, never call Thread#stop() or use any other deprecated methods. Please read Why is Thread.stop deprecated?.
Please look at this answer for an example of Swing animation using a Swing Timer.
Unless this is for a class assignment, don't create JApplets as this is a dead technology, something even Oracle will tell you.
To display text in the background use the java.awt.Graphics method for writing text: drawString(...). Either that or place a JLabel over your background image.

Java animation stutters when not moving mouse cursor

I have a quite simple animation, a text in a big font moving continuously (pixel by pixel) to the left. The text is first converted to an image, then a timer task is started which repeatedly (every 10-20 ms) decrements the x coordinate of the image by 1, and does a repaint().
This program shows a strange behavior on some systems. On my PC with a nVidia card it runs smoothly. On my Vaio notebook, on a BeagleBoneBlack and on a friend's Mac it stutters heavily. It appears to pause for a while, then jump to the left about 10 pixels, pause again and so on.
What stumps me is the fact that on these systems the animation only stutters if you don't touch the mouse. As long as you move the mouse cursor within the window, no matter how slowly, or drag the window itself around, the animation runs perfectly smooth!
Can anybody explain this? Here is the program:
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
class Textimg extends JComponent
{
String str;
Font font;
int x = 0;
final int ytext = 136;
Image img;
public Textimg(String s)
{
str = s;
font = new Font("Noserif", Font.PLAIN, 96);
setLayout(null);
}
protected void paintComponent(Graphics g)
{
if (img == null)
{
img = createImage(4800, 272);
Graphics gr = img.getGraphics();
gr.setFont(font);
gr.setColor(Color.BLACK);
gr.fillRect(0, 0, 4800, 272);
gr.setColor(new Color(135, 175, 0));
gr.drawString(str, 0, ytext);
gr.dispose();
}
g.drawImage(img, x, 0, this);
}
public void addX(int dif)
{
if (isVisible())
{
x = x + dif;
Graphics g = getGraphics();
if (g != null) paintComponent(g);
}
}
}
public class Banner extends JFrame
{
StringBuffer buf;
int sleeptime = 10;
Banner(String path) throws IOException
{
setSize(new Dimension(480, 272));
setTitle("Java Test");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), "UTF-8"));
buf = new StringBuffer();
while (true)
{
String line = reader.readLine();
if (line == null) break;
buf.append(line);
}
final Textimg textimg = new Textimg(buf.toString());
add(textimg);
textimg.setBounds(0, 0, 480, 272);
final javax.swing.Timer timer = new javax.swing.Timer(200, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
textimg.addX(-1);
}
});
timer.setDelay(sleeptime);
timer.start();
}
//----------------------------------------------------------------------
public static void main(String[] args) throws Exception
{
new Banner(args[0]).setVisible(true);
}
}
Try calling this method when you are done drawing:
Toolkit.getDefaultToolkit().sync();
This flushs the graphics buffer which some systems like Linux use. See the Javadoc: http://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync()
Don't, EVER, use getGraphics and you should NEVER call paintComponent yourself, this not how custom painting is done in Swing. Instead, update the state and call repaint.
Don't rely on magic numbers, use the information you have at hand (getWidth and getHeight)
Swing components are doubled buffered, so it's unlikely you would need to create you own buffered strategy. This simple act could be slowing down your painting
You must call super.paintComponent. This is even more important with JComponent, as it is not opaque and failing to do so could result in some nasty paint artefacts.
You should override JComponent#getPreferredSize so it can work with layout managers for efficiently.
You may find a shorter delay produces a better illusion, say 40 milliseconds (roughly 25fps) for example
Take a look at Swing animation running extremely slow, which through some object management and optimisation, was able to increase from 500 animated objects up to 4500.
Also take a look at Performing Custom Painting and Painting in AWT and Swing in particular
Profiling shows that you are saturating the shared thread used by javax.swing.Timer. One mitigation strategy is to use a longer period and/or a larger increment/decrement, as shown here.
Addendum: In addition, you are laboriously re-rendering the entire image in each call to paintComponent(). Instead, render it once using TextLayout, seen here, and draw() only the newly visible portion each time.
Problem solved!
To answer my own question: After realizing that any continuous input (mouse or keyboard) makes the animation run smoothly, I remembered that inputs can be generated by the program itself, using an object of the class java.awt.Robot. That lead to a simple workaround:
Create a Robot and let it press a key or a mouse move in each animation cycle.
final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// update your image...
robot.keyPress(62);
}
});
This is a kludge, but works perfectly.

How to use double Buffered AWT Animations in Java

I have recently begun working with double buffering in Java. I have read tutorials that have shown me how to display images and move them using mouse and keyboard events. However this is where I become confused. My program is simple, there is a rectangle at the bottom of my window that is movable by LEFT and RIGHT key events. However I can not figure out for the life of my how to draw another shape onto the screen by an event and continue buffering it.
I would like to be able to push a key to draw a "missile" (which in my case would be a small Oval) at the X and Y position of the rectangle I have drawn already, and have it fire upwards. Much like any classic space shooter.
This however is not as much of a specific, concrete problem of mine, but a concept I do not understand. I learned how to do many of the similar things in Lua, however when it came to drawing new images after initialization or an image upon key events, I was stumped.
My question is this: In what order of Java's init(), stop(), destroy(), start(), run(), paint(), and update() cycle do I use to buffer a new shape/image onto the screen from a key event?
I have searched many tutorials with example code, but with no avail. I have been learning Java for nearly 8 months now, but no matter how basic or simple I try to understand something, it's as if even the most primordial tutorial requires prerequisite knowledge.
My code is as follows.
import java.applet.*;
import java.awt.*;
public class SquareApplet extends Applet implements Runnable
{
int x_pos = 10;
int y_pos = 400;
int rectX = 50;
int rectY = 20;
int x_speed = 5;
private Image dbImage;
private Graphics dbg;
public void init( ) { }
//public void start() { }
public void stop( ) { }
public void destroy( ) { }
//public void run ( ) { }
//public void paint (Graphics g) { }
public void start()
{
// define a new thread
Thread th = new Thread (this);
// start this thread
th.start ();
}
public void run ()
{
// lower ThreadPriority
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
// run a long while (true) this means in our case "always"
while (true) //Runtime
{
if (x_pos > this.getSize().width - rectX) {x_pos = this.getSize().width - rectX;}
if (x_pos < 0) {x_pos = 0 ;}
// repaint the applet
repaint();
try
{
// Stop thread for 20 milliseconds
Thread.sleep (20);
}
catch (InterruptedException beepis)
{
// do nothing
}
// set ThreadPriority to maximum value
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
}
}
public void paint (Graphics g)
{
// set background color
g.setColor(Color.black);
g.fillRect(0,0,this.getSize().width,this.getSize().height);
// set player color
g.setColor (Color.white);
// paint a filled colored rectangle
g.fillRect(x_pos, y_pos, rectX,rectY );
}
public void update (Graphics g)
{
// initialize buffer
if (dbImage == null)
{
dbImage = createImage (this.getSize().width, this.getSize().height);
dbg = dbImage.getGraphics ();
}
// clear screen in background
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// draw elements in background
dbg.setColor (getForeground());
paint (dbg);
// draw image on the screen
g.drawImage (dbImage, 0, 0, this);
}
//KEY EVENTS
public boolean keyDown(Event e, int key)
{
//Up Down Left Right
if (key == Event.LEFT)
{
x_pos -= x_speed;
}
if (key == Event.RIGHT)
{
x_pos += x_speed;
}
return true;
}
}

Custom JPanel Class, paintComponent is Being Spammed

I am having trouble with a custom JPanel class I am using. I have a networked camera which I am receiving Images from using an HttpURLConnection and a JPEGDecoder. These images are then displayed using Graphic.drawImage. The camera is set to run at 1 fps for debugging purposes.
This JPanel class is include inside one JFrame, I also have another JFrame which contains a NASA WorldWind. When displaying the pictures from the Camera, my WorldWind map is unresponsive and will not repaint when resized. I believe it is because my paintComponent in the custom JPanel is being spammed.
I do not understand what is calling my JPanel's paintComponent so much, and preventing my WorldWind Frame to update.
A blurb of the custom JPanel class follows:
public class ActiCamera extends JPanel implements Runnable
{
private String mjpgURL;
private DataInputStream dis;
public ActiCamera(String ip)
{
mjpgURL = "http://" + ip + "/cgi-bin/cmd/encoder?GET_STREAM";
}
public void connect()
{
URL u = new URL(mjpgURL);
...
dis = new DataInputStream(from buffered input stream from HttpURLConnection);
}
public void start()
{
appletThread = new Thread(this);
appletThread.start();
}
public void run()
{
connect();
GetImages();
}
public void GetImages()
{
while(true)
{
//This blocks, executes at 1Hz
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(dis);
image = decoder.decodeAsBufferedImage();
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(image != null)
g.drawImage(image.getScaledInstance(getWidth(), getHeight(), Image.SCALE_DEFAULT), 0, 0, this);
}
public static void main(String [] args)
{
JFrame jframe = new JFrame();
ActiCamera my_panel = new ActiCamera("1.1.1.1");
my_panel.start();
jframe.getContentPane().add(my_panel);
jframe.setVisible(true);
}
}
Note, I do not call repaint() or force a paint anywhere. However, if I put a print out in my paint component class, it gets spammed at a much greater speed than 1 Hz. I am completely lost as to whats going on.
P.S. - I do realize I need a mutex between the paintComponent and the GetImages, they're being called from different threads, but I do not imagine that would cause the problem?
I found my answer, I had to change my paint component
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(image != null)
g.drawImage(image, 0, 0, this);
}
The paintComponent in my earlier code snippet seems to have an implicit paintComponent call in it somewhere... perhaps in (getWidth() and getHeight() or getScaledInstance())

Categories