Android SurfaceHolder.unlockCanvasAndPost() does not cause redraw - java

I'm implementing a fairly standard app with the Android sdk that involves drawing using the SurfaceView, SurfaceHolder, Callback setup.
In my main thread (UI thread) I have no drawing or handling of the SurfaceHolder (or the canvas you retrieve with it).
In a separate thread I have the following:
Log.i("GAME.DrawThread", "run()");
Log.i("GAME.DrawThread", Thread.currentThread().getName());
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
synchronized(holder) {
Log.i("GAME", "draw():synchronized");
Paint paint = new Paint();
paint.setColor(R.color.draw_color);
canvas.drawColor(R.color.draw_color);
canvas.drawLine(0, 0, 500, 500, paint);
}
} catch (SurfaceHolder.BadSurfaceTypeException e) {
Log.e("GAME", "onDraw(): BadSurfaceTypeException");
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
This code is being executed, throws no exceptions, and has no negative side effects that I can find; however, the unlockCanvasAndPost() call never causes onDraw() to be called.
In other words, unlockCanvasAndPost() does not cause a redraw of the SurfaceView.
Any ideas what could cause this symptom? I have plenty of java experience, a fair amount of android experience, and a lot of debugging experience and cannot track this one down.
Thanks in advance.

So it turns out that when using SurfaceView you draw to a Surface that is underneath a Window. I was setting the background color of the View in xml; it turns out that sets the background color of the Window, not the Surface. In effect, I made the Window opaque so that you couldn't see the Surface underneath.
Lesson Learned.

That's not how SurfaceView works. Calling unlockCanvasAndPost() does not invoke onDraw(), that's the whole point of using a SurfaceView. A SurfaceView's surface lives in a different window.

This is old, but I have a feeling the problem is there is no surfaceholder callback. He was trying to draw on the surface before the surface was created

For set SurfaceView to top Z-View index, add to youre CustomerSurfaceView a code below:
init {
setZOrderOnTop(true)
}

Related

Java Paint Method Being Called By Graphics2D

I was working on creating a fade-in animation and I ran into a problem where whenever I try to draw a BufferedImage with:
g2d.drawImage(unknown.getScaledInstance(100, 200, BufferedImage.SCALE_SMOOTH),
10,button.getLocation().y - 200, this);
after using
.setCompositeAlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)
My JPanel's paint method was constantly being repeatedly and continuously being called. I tried commenting out the drawImage bit and everything worked fine again. I also noticed that when I called the paint method from another class using:
answerPanel.paint(answerPanel.getGraphics());
The paint method stopped being continuously called.
So could anyone shed some light on why the drawImage line is causing the paintMethod to be constantly called until I call it from another class? Thanks!
Agree with #user2864740, you should never call paint(...) directly, but I don't think that is the root of the problem here.
The last parameter to drawImage(...) (e.g. this) is an ImageObserver. It receives callbacks whenever an Image is updated (changed), and will cause a repaint() (which will eventually call paint()).
When you invoke Image.getScaledInstance(...) this will immediately return a new Image instance, but the actual scaling (or, resampling, really) will happen later. The image observer will handle "automagic" repaints of your component, as soon as (parts of) the scaled image is done. You component will be requested to repaint one or more times.
You should be able to observe the effects of this, by passing null as the last parameter, instead of this (probably, nothing will be painted, but you'll avoid the repaint loop).
The reason your component will go into a repaint loop, is because each time the component is asked to repaint itself, a new scaled instance is returned, causing a restart of the scaling and the automatic repainting by the image observer, as described above.
To fix the problem, don't use getScaledInstance() and instead, try:
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.drawImage(unknown, 10, button.getLocation().y - 200, 100, 200, this);
Another way, would be to make sure you create the rescaled image only once, and continue using it for each repaint:
Image scaled;
public void paint(Graphics g) {
...
if (scaled == null) {
scaled = unknown.getScaledInstacnce(100, 200);
}
...
g2d.drawImage(scaled, 10, button.getLocation().y - 200, this);
}

How can I load images in the background in a Swing application?

I've got a simple Swing/AWT application that runs in full screen mode on Windows. I have a couple of different PNG files that it loads as its own background image depending on context.
It loads them like this:
BufferedImage bufferedImage;
bufferedImage = ImageIO.read(getClass().getResource("/bg1.png"));
Image bgImage1 = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
bufferedImage = ImageIO.read(getClass().getResource("/bg2.png"));
Image bgImage2 = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
bufferedImage = ImageIO.read(getClass().getResource("/bg3.png"));
Image bgImage3 = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
And later draws them like this:
window.repaint();
graphics.drawImage(bgImage1, 0, 0, null);
// draw some other stuff too, like text
And just to be thorough, window is a JWindow variable, and graphics is a Graphics2D variable.
The problem I'm running into happens when I switch out one of the background images for another. The first time I do the switch, calling something like this:
window.repaint();
graphics.drawImage(bgImage2, 0, 0, null);
// draw some other stuff too, like text
...the entire screen goes white for about a second. And then it does successfully show the image, but that flicker is really annoying. My guess is that because the images are relatively large and high resolution (2560x1440), it needs about a second to load them and scale them to the appropriate size.
How can I get it to load those images silently? As in... how do I avoid drawing a blank white screen for a second, that first time it displays a new background image? All subsequent times are already instantaneous, probably because it's truly grabbed them into memory at that point. But simply calling getScaledInstance apparently isn't enough to put things into memory, because it doesn't actually flicker until I call drawImage down the line.
ImageIcon will load in the background as a feature.
You can also accomplish this fairly easily with a background thread e.g.:
final String path = "/example.png";
new SwingWorker<BufferedImage, Void>() {
#Override
public BufferedImage doInBackground() throws IOException {
return ImageIO.read(ClassName.class.getResource(path));
}
#Override
public void done() {
try {
BufferedImage img = get();
// put img somewhere
} catch(InterruptedException ignored) {
} catch(ExecutionException ex) {
ex.printStackTrace(System.err);
}
}
}.execute();
Also,
window.repaint();
graphics.drawImage(bgImage2, 0, 0, null);
This worries me a little bit. In general we do not need to ask for a repaint of an entire top-level container. You should also not be using getGraphics() to paint.
Non-top-level Swing components are double-buffered but if you are painting outside of the paint structure you do not get this. It will result in flickering.
Two good sources for correct custom painting are:
Lesson: Performing Custom Painting
Painting in AWT and Swing
Painting should be done passively by overriding paintComponent on a JComponent. A JLabel can also display an ImageIcon as a feature so you do not necessarily have to do custom painting if you just want to display an image.
I notice that the scaling takes a lot of time, not the loading of the images.
Because of this, I usually store a scaled image so I can use it later on so I only have to scale once.

Drawing to canvas

so i am dabbling in making a game and i am a fairly messy worker and then i go back and tidy it up. So please ignore any stupid naming conventions or methods or anything. As I go back later and tidy up.
So basically I am trying to make a main menu, i just want to draw something to the screen and have it stay there. I am writing the graphics to a image ATM and displaying the image as i tried just drawing to the canvas and it just flickered up onto the screen and then it went blank. I changed it to a runnable so i could put in sleep() to try and error hunt. I've removed all my error hunting code now. It turns out when i have a sleep() before or after the image is put on the screen for longer than 50ms the rectangle stays on screen and doesn't flicker away like 1ms after it was drawn.
I'm not sure what's going on. I am probably not that clear and I apologies.
import java.awt.Canvas;
import java.awt.Graphics;
import javax.swing.JFrame;
public class test extends Thread{
public static void main(String[] args){
test t = new test();
t.start();
}
#Override
public void run() {
JFrame f = new JFrame("Test");
Canvas c = new Canvas();
Graphics g;
c.setSize(400, 400);
f.add(c);
f.pack();
f.setVisible(true);
c.setVisible(true);
try {
sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
g = c.getGraphics();
g.drawRect(10, 10, 40, 68);
}
}
Basically, this is not how painting in AWT works. Painting should be done within the context of a component's paint method. In AWT Canvas seems to be the preferred base component to use.
You should never use getGraphics, apart from been able to return null, it will only represent the Graphics context from the last paint cycle, anything added to it could be wiped about by newer paint events...which is probably happening to you.
Take a look at Painting in AWT and Swing for details about how painting works.
Having said all that, I would discourage you from using AWT based API as it was replaced by Swing some 15 years ago.
Take a look at Performing Custom Painting for more details about painting in Swing
You're kind of getting the idea, but there are a few standard design patterns of creating a game, for starters check out this code.
Basically, you'll need something called a game loop which will update and render to the display a certain amount of times every second (framerate or fps). When it renders, it will clear and redraw the image in a new location at a certain interval, if your fps was 60 you would be rendering (clearing, and redrawing) 60 times a second. We can also introduce more advanced concepts such as buffer strategies (shown in the example above) to reduce tearing and flickering.
To sum it up, you're kind of there, you just need to constantly do that g = c.getGraphics() so...
while (true) {
g = c.getGraphics();
// set color
// draw a rectangle
g.dispose();
}
Note how I added the dispose, this will just free up any unused memory. Just to clarify, this is by no means good code, but it will give you a place to start :)
The flickering is due to only having 1 screen, to stop this flickering you need a buffer strategy. Basically having two buffers is like having two screens, whilst you render on one 'screen' you can clear and draw the next step on the second screen and switch between the two buffers; this reduces the flickering.

How to show two identical camera previews with the Android camera?

I need to be able to display two identical previews from a running camera instance on screen, is this possible?
So far the only way I could think of was to clone the preview surfaceview. So using the raw video data provided to onPreviewFrame,
I am creating a new bitmap for the current frame and then drawing that to the surfaceholder canvas.
This displayed perfectly when run on the ui thread, but obviously it blocked the UI every couple of seconds whilst creating the bitmaps. I have now moved the bitmap creation into a new thread which resolves the app locking up, but now my surfaceview flickers/tears slightly!
Would this ever work? or am I going the wrong way about it? How can I stop the flicker?
Cut down sample code;
public void onPreviewFrame(byte[] data, Camera camera) {
-- new thread
// create bitmap from YUV (videoFrame)
-- UI thread
Canvas canvas = null;
try {
canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
canvas.drawBitmap(videoFrame, null, new Rect(0, 0, 200, 200), null);
}
} catch (Exception e) {
mLogger.error(e.getMessage());
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
I believe this is something to do with the surfaceview double buffer, I have quite a few solutions but cant stop the flicker!
If you don't need to run on Android releases before 3.0 (Honeycomb), you can use the SurfaceTexture output option, and OpenGL ES for rendering multiple previews.
Setting up a working OpenGL view is a bit involved, so you'll want to find a guide for that.
Once you have that working, create a SurfaceTexture object with a OpenGL texture ID you want to use for the preview texture, pass that into the camera with setPreviewTexture instead of using setPreviewDisplay, and then in your OpenGL thread, call updateTexImage to update the texture to show the latest frame from the camera (best to do so in response to a onFrameAvailable callback for best performance).
Then you need OpenGL code to render two rectangles for your two copies of preview, and a shader to draw the preview texture on both.
This is a decent bit of setup, but in the end, you'll have a very flexible system - then you can animate the preview in various fun ways, etc.

Loading bufferedImage disables JPanel's paintCompontent method

I have a slight problem with BufferedImage and JPanel. I am creating a game with some 2d animation.
Basically i have an animationHandler that will loop through the pictures and depending on the rotation will also display it correctly. But the problem is - when I load in the pictures my Jpanel wont draw anything. It doesnt event matter if I comment out the custom paint methods - the paintComponent method wont draw anything and it seems like it skips the paintCompontent method. Even though the game doesnt crash and the timer still is running - it wont use the paintComponent method in an extended JPanel.
The class that contains the timer - calls the JPanel throught JPanel.repaint();
Here is the loadImg method
/**
* Test method to check animationHandler and bufferedImgs
*/
private void loadImages() {
BufferedImage b_1;
BufferedImage b_2;
BufferedImage b_3;
BufferedImage b_4;
BufferedImage b_5;
BufferedImage[] imgs = new BufferedImage[5];
try {
b_1 = ImageIO.read(new File("warlock1.png"));
b_2 = ImageIO.read(new File("warlock2.png"));
b_3 = ImageIO.read(new File("warlock3.png"));
b_4 = ImageIO.read(new File("warlock4.png"));
b_5 = ImageIO.read(new File("warlock5.png"));
imgs[0] = b_1;
imgs[1] = b_2;
imgs[2] = b_3;
imgs[3] = b_4;
imgs[4] = b_5;
animationHandler.addAnimation(imgs);
} catch (Exception e) {
e.printStackTrace();
}
}
Cheers!
You're could possibly be loading images on the Swing event thread or EDT (Event Dispatch Thread), and since the EDT is responsible for all Swing graphics and user interactions, this will freeze your Swing application until the loading is complete. The solution: load the images on a background thread such as can be obtained from a SwingWorker object. Please check out the Concurrency in Swing tutorial for more on this subject.
Also, if possible, and if the images aren't too large, it is usually best to load the images once and then hold a reference to them or to an ImageIcon.
And finally, whatever you do, don't load images inside of the paintComponent(...) method. This method must be lean and mean -- as fast as possible, and it should concern itself only with painting and nothing else. Else your program's responsiveness could become pitifully slow.
Also, regarding:
it wont use the paintComponent method in an extended JPanel.
You might want to show us this code.

Categories

Resources