Here's my problem:
When rendering an object which has speed (ie it's moving), the last frame of the object is also drawn, meaning that instead of a nice moving animation, I end up with the object drawn multiple times across its movement path.
Here is my render method (in the game loop):
private void render() {
//if the surface is NOT valid, exit rendering
if (!surfaceHolder.getSurface().isValid()){
return;
}
//lock the canvas
canvas = surfaceHolder.lockCanvas();
//draw all game objects to canvas (only 1 object atm)
wizard.render(canvas);
//unlock and post the canvas
surfaceHolder.unlockCanvasAndPost(canvas);
}
and the wizard render:
public void render(Canvas canvas) {
canvas.drawBitmap(spriteSheet, position.x, position.y, null);
}
As far as I know, between each unlock and lock of the surface, its contents are not preserved, meaning that it is completely redrawn each time the render function is called:
"The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written." ~ taken from the documentation
So why isn't this happening? Why is the canvas not being re-drawn and instead is preserving all of the wizard's frames?
Thanks in advance! ^_^
Try adding:
public void render(Canvas canvas) {
canvas.drawBitmap(spriteSheet, position.x, position.y, null);
invalidate();
}
and see what happens.
Related
When ran, the program displays a 3D sphere rendered in a P3D environment in the PGraphics object 'g', which is shown by taking the rendered PGraphics object and displaying it through the image() method in the main graphics context, which happens to be P2D.
The purpose of the program is to show how window size doesn't always correlate with render size. If you play an old Widows98 game in full screen, the game most likely will be rendered at 480p no matter what, so taking it into full screen just decreases the pixels per inch, plus making the image appear blurry. Which is fine, since fullscreen at 480p is preferred over windowed mode ( esp. if you're on 4K X_X )
the mouse's y position in the window changes the 3d camera's field of view, and the x position changes the rendering resolution of the P3D context used to display the sphere. Additionally, the P3D context is drawn in the main (P2D) context through the image() method, and is 'forcefully'. displayed at the size of the window. So if the P3D render resolution is smaller than the window, then it will start to look blurry and more pixelated, and if the render resolution is larger, you get a strange sharpening effect.
Now, my program works fine as it is, but. Another purpose of the program is shadowed by this issue, it's how the 'crispness' of the sphere fades as the render resolution decreases. You might say that it's clearly shown, but what I'm looking for is an image where there is no "anti-alias" effect going on. I want the image to preserve the pixels as the resolution gets smaller, so you can see the actual shape of the sphere at say, 50 x 50 pixels.
The noSmooth() method doesn't seem to work, and before you tell me to just do
g.loadPixels();
and then do a double for loop to draw the raw pixels to the 2d context. No, it's sloppy. I know that there must be some reason why this blurring is going on. I'm hoping that it's the image() method and that I should be using a different method or I should add another method before it to remove image blurring.
PGraphics g;
void setup(){
size(1000,1000,P2D);
frameRate(1000);
noSmooth();
}
void draw(){
background(200);
float res = map(mouseX,0,width,0.75,128);
if (res==0) {
res=1;
}
g = createGraphics((int)(width/res),(int)(height/res),P3D);
g.noSmooth(); // is this thing working?????
float cameraZ = ((height/2.0) / tan(PI*60.0/360.0));
g.beginDraw();
g.perspective(radians(map(mouseY,0,height,0.1,160)), width/height, cameraZ/10.0, cameraZ*10.0);
g.camera(g.width/2.0, g.height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), g.width/2.0, g.height/2.0, 0, 0, 1, 0);
g.background(200);
g.translate(g.width/2 ,g.height/2);
g.sphere(100);
g.endDraw();
image(g, 0, 0, width, height); // this is where it all comes together
text("rendering resolution: "+g.width+" x "+g.height,0,14);
text("fps: "+frameRate,0,14*2);
}
Replace g.noSmooth() with ((PGraphicsOpenGL)g).textureSampling(2);
Credits go to Vallentin as I oddly enough had the same question with the P3D renderer
(Edit: This solution fixes the problem in the default renderer, but the OP is using the P2D renderer. The solution should be similar, so if somebody knows how to change the image interpolation mode in opengl, that's the answer.)
This is not really caused by anti-aliasing. It's caused by image scaling.
Also, it's much easier to help if you provide a MCVE, like this one:
PGraphics buffer;
void setup() {
size(1000, 1000);
buffer = createGraphics(100, 100);
buffer.noSmooth();
buffer.beginDraw();
buffer.background(255);
buffer.line(0, 0, width, height);
buffer.endDraw();
}
void draw() {
background(0);
image(buffer, 0, 0, mouseX, mouseY);
}
This code exhibits the same problem, but it's much easier to understand and work with.
Anyway, tracing through Processing's code, we can see that the image() function eventually calls the imageImpl() function in the PGraphics class here.
This function then draws your image using this code:
beginShape(QUADS);
texture(img);
vertex(x1, y1, u1, v1);
vertex(x1, y2, u1, v2);
vertex(x2, y2, u2, v2);
vertex(x2, y1, u2, v1);
endShape();
The endShape() function is then implemented in the renderer, specifically the PGraphicsJava2D class, which calls the drawShape() function here:
protected void drawShape(Shape s) {
if (fillGradient) {
g2.setPaint(fillGradientObject);
g2.fill(s);
} else if (fill) {
g2.setColor(fillColorObject);
g2.fill(s);
}
if (strokeGradient) {
g2.setPaint(strokeGradientObject);
g2.draw(s);
} else if (stroke) {
g2.setColor(strokeColorObject);
g2.draw(s);
}
}
Finally, that shows us that the Graphics2D.fill() function is being called, which is what actually draws your function.
The "problem" is that Graphics2D.fill() is scaling your image using an algorithm that causes some blurriness. We can consult the Java API and Google to figure out how to fix that though.
Specifically, this tutorial shows you how to set various rendering hints to change the scaling algorithm. We can use that in Processing like this:
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import processing.awt.PGraphicsJava2D;
PGraphics buffer;
void setup() {
size(1000, 1000);
buffer = createGraphics(100, 100);
buffer.noSmooth();
buffer.beginDraw();
buffer.background(255);
buffer.line(0, 0, width, height);
buffer.endDraw();
}
void draw() {
if (mousePressed) {
Graphics2D g2d = ((PGraphicsJava2D)g).g2;
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
background(0);
image(buffer, 0, 0, mouseX, mouseY);
}
First, we import the classes we're going to need. Then we get to the Graphics2D instance in the renderer, and finally we call its setRenderingHint() function. I wrapped it in an if(mousePressed) so you could easily see the difference. When you click the mouse, interpolation is set to nearest neighbor, and you no longer see the blurriness.
Also notice that my code uses the g variable that's inherited from the PApplet superclass, so you would have to change your g variable so it's no longer hiding it.
I am thinking of some different ways of rendering the game into the screen.
I've currently working on a indie game and usually I just paint the textures on the screen right away:
public void paintComponent(Graphics g){
for(int i=0;i<textures.size;i++){
g.drawImage(texture.get(i).getTexture(),texture.get(i).getX(), texture.get(i).getY(), null)
}
}
But this is just an example. The problem with this is that if you have too many textures, you might start noticing some flickering or the process of drawing because when the g.drawImage is called, it paints the texture into the screen right after.
So I've though of a solution however I am not sure if it's a good way of doing it.
What I've done is instead of painting the textures into the screen, I simply draw them into the BufferedImage. After that, I simply draw the BufferedImage into the screen.
Here is an example:
public void paintComponent(Graphics g){
g.drawImage(bufferRender(), 0, 0, null);
}
public BufferedImage bufferRender(){
BufferedImage render = new BufferedImage(Main.window.getWidth(), Main.window.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = render.getGraphics();
for(int i=0;i<textures.size;i++){
g.drawImage(texture.get(i).getTexture(),texture.get(i).getX(), texture.get(i).getY(), null)
}
return render;
}
Now the question is... Is it a fine way of doing this?
Bitmap[] planeFrames = new Bitmap[4];
protected void onDraw(Canvas canvas) {
for(int i = 0 ; i < planeFrames.length;i++)
canvas.drawBitmap(planeFrames[i], plane.getCenterX(), 0, null); // planeFrames is an array of Bitmaps
}
Im trying to animate a plane by just swapping images but its not working I don't know if my method is to simple to work with
You can not animate this way. onDraw is called per draw iteration meaning that whatever you draw on the Canvas at the return is what's going to be drawn on the screen. Basically what you're doing here is drawing all the Bitmaps on the Canvas at the same spot and they will all be plastered on top of each other.
What you need to do is call the invalidate() method of the View. To make things more efficient, you can use a Rect or define the areas around the Bitmaps.
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(planeFrames[i], plane.getCenterX(), 0, null);
if(++i >= planeFrames.length) {
i = 0;
}
invalidate(bmpRect);
}
This will consistently draw through the bitmaps while repeatedly drawing the new bitmaps. This will last forever as long as the View is on screen. bmpRect is a Rect that has the coordinates of the Bitmap.
I have a JPanel subclass with custom paintComponent() implementation. It is being refreshed at 50fps. It is typically in the range of 500x300 pixels in size. I'm seeing some flickering (not too bad but noticeable) and I've inserted some debug code that indicates that Swing/EDT is skipping (presumably) redundant painting. I am guessing that's because the EDT is not giving enough time for paintComponent() to always finish or it is taking too much time on the EDT.
My thinking is that I need to take the code currently implementing paintComponent() (which is not very complex but not completely trivial either) and refactor it so it is executed on its own Thread (or at least not the EDT) and draws to an ImageBuffer. I then implement paintComponent on my custom JPanel and draw (render) from the ImageBuffer to the screen (actually to the buffer behind Swing components as my research into the solution led me some information about Swing being (by default) double-buffered, though I'm not completely clear on that). If it is true that the rendering from the ImageBuffer to the JPanel is faster than my implementation that constructs the ImageBuffer then I will be going in the right direction.
Is this the proper design direction for me to take?
UPDATE
I modified my implementation as discussed in reponses below:
1) Create a BufferedImage
BufferedImage myBufferedImage = new BufferedImage(mySize.width,mySize.height,BufferedImage.TYPE_INT_ARGB)
2) Create a Thread dedicated to peforming the processing to determine what is to be drawn.
3) Move the code previously in paintComponent() to another method that is executed by the dedicated Thread. At the end of this method, call repaint();
4) Create a new paintComponent() that simply calls g.drawImage(myBufferedImage,0,0,null);
5) Where I previously would call repaint(), trigger myThread to perform the drawing to myBufferedImage.
This was a disaster, as predicted. Much worse flickering and sluggishness, partial paints, etc. I believe this was due to contention reading/writing myBufferedImage (as mentioned below). So I then created a lock and lock myBufferedImage when I am writing to it (in the dedicated drawing Thread) and wait to get that lock in paintComponent() before calling Graphics2D.drawImage(); The flicker and partial paints go away - but performance is no better (maybe even worse) than when I was doing all the calculations for the drawing in paintComponent (and therefore in the EDT).
This has me stumped at this point.
If you're not updating the entire component (ie only small areas are changing), you could use JComponent#repaint(Rectangle r) indicating the areas that have changed. This will result in a repaint cycle that updates (potentially) a much smaller area.
I generated a "animated sequence" library some time ago to take a series of images and layer them ontop of each, given a "speed" of each layer, it would transpose them from right to left.
The whole sequence would cycle for 10 seconds, where a speed of 1 would take take 10 seconds to complete. Each layer is moving at difference speeds.
The original images where 1024x256, and the sequence was devised of 5 animated layers and 2 static layers...
I only wish I could show you how smooth this plays on my PC and Mac.
The only signification issue I had to over come was making sure that the images where compatible with the screen devices color model.
UPDATED
These are some utility classes I use when loading or creating BufferedImages, especially for animation. The make sure that the colour models are the same as those used by the screens, which will make them faster to update/repaint
public static BufferedImage loadCompatibleImage(URL resource) {
BufferedImage image = null;
try {
image = ImageIO.read(resource);
} catch (IOException ex) {
}
return image == null ? null : toCompatibleImage(image);
}
public static BufferedImage toCompatibleImage(BufferedImage image) {
if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {
return image;
}
BufferedImage compatibleImage =
getGraphicsConfiguration().createCompatibleImage(
image.getWidth(), image.getHeight(),
image.getTransparency());
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
// Check out java.awt.Transparency for valid values
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
image.coerceData(true);
return image;
}
I think this is what you're looking for on information about double buffering:
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
You could turn off double buffering with setDoubleBuffered(false) if you can't get access to the underlying buffer which I'm not entirely sure you can.
I don't think you can safely draw on an image from another thread because you'll get into the thread writing to the image while the EDT is reading that same image as it redraws. If you share an image between them you're going to have multi-threading issues that you'll have to synchronize. If you synchronize then you're performance isn't going to be very good. If you instantiate a new image every frame you're memory is going to skyrocket and GC will get you. You may be able to instantiate 10 frames and keep the writing away from the reading or something like that, but either way this is going to very tricky to make it performant and correct.
My suggestion is to do all drawing from EDT, and figure out a way to do the calculations (rendering) on another thread that doesn't involve ImageBuffer sharing.
Update While it is used for fullscreen. The suggestions in there apply to windowed mode as well: "Separate your drawing code from your rendering loop, so that you can operate fully under both full-screen exclusive and windowed modes." See this http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html
i've has similar problems trying to paint smoothly.
try running this and see how smooth it is (its smooth for me).
profiler says most of the time is in paint component. interestingly draw image is not mentioned.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class P extends JPanel {
void init(Dimension d) {
GraphicsConfiguration gc=getGraphicsConfiguration();
bi=gc.createCompatibleImage(d.width,d.height);
}
#Override public void paintComponent(Graphics g) {
//super.paintComponent(g);
if(bi!=null)
g.drawImage(bi,0,0,null);
}
BufferedImage bi;
}
public class So13424311 {
So13424311() {
p=new P();
}
void createAndShowGUI() {
Frame f=new JFrame("so13424311");
// f.setUndecorated(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(p);
p.init(d);
p.setSize(d);
p.setPreferredSize(d);
f.pack();
// if(moveToSecondaryDisplay)
// moveToSecondaryDisplay(f);
f.setVisible(true);
}
void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
Timer t=new Timer(20,new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
Graphics g=p.bi.getGraphics();
Color old=g.getColor();
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.red);
g.fillRect(n%(d.width/2),n%(d.height/2),20,20);
g.setColor(Color.green);
g.fillRect(n%(d.width/2)+20,n%(d.height/2),20,20);
g.setColor(Color.blue);
g.fillRect(n%(d.width/2),n%(d.height/2)+20,20,20);
g.setColor(Color.yellow);
g.fillRect(n%(d.width/2)+20,n%(d.height/2)+20,20,20);
g.setColor(old);
g.dispose();
p.repaint();
n++;
}
int n;
});
t.start();
}
public static void main(String[] args) {
new So13424311().run();
}
final P p;
Dimension d=new Dimension(500,300);
}
I have a function
#Override
public void run() {
while(running && (!eof)){
if(surfaceHolder.getSurface().isValid()){
Canvas canvas = surfaceHolder.lockCanvas();
paint(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
thread = null;
}
where paint(canvas) calls a bunch of other functions that draw a graph and text, for example
canvas.drawText("Time="+myRecord.getMyTime(), 100, 100, paint);
The problem I'm having is that the graph and the text, both of which should be constantly changing, don't get erased but instead keep drawing over themselves. Shouldn't my entire canvas get redrawn every time because that's how double buffering works with the lock() and unlock()? Am I not understanding this correctly? How am I supposed to do this?
You need to clear the Canvas yourself after lockCanvas() using Canvas.drawColor().
This might be relevant too:
The content of the Surface is never preserved between unlockCanvas()
and lockCanvas(), for this reason, every pixel within the Surface area
must be written. The only exception to this rule is when a dirty
rectangle is specified, in which case, non-dirty pixels will be
preserved.
Source