I have a field which extends BitmapField (called AnimatedGIFField) and an AnimatorThread (extending Thread) which does the work of looping through the GIF frames, incrementing the current frame and invalidating the field, which calls the paint() method to draw the next frame (resulting in animation). The animation code works fine, but my issue is in the paint() method of the AnimatedGIFField class. I'm calling 'graphics.drawImage()' and I'm having trouble getting proper positions for x and y (the first two args to drawImage()). Positioning the AnimatedGIFField is working and is accomplished by overriding 'getPreferredWidth()' and 'getPreferredHeight()'. The relevant code is here:
public class AnimatedGIFField extends BitmapField {
/**
The current frame in the animation sequence; the AnimatorThread
increments this so paint() knows which frame to draw.
*/
private int currentFrame;
public AnimatedGIFField(GIFEncodedImage image, long style) {
super(image.getBitmap(), style);
this.image = image;
this.preferredWidth = this.image.getWidth();
this.preferredHeight = -(this.image.getHeight() * 4);
}
protected void paint(Graphics graphics) {
// Calling super draws the first background frame.
super.paint(graphics);
// Don't redraw if this is the first frame.
if (this.currentFrame != 0) {
// Draw the animation frame.
/* getFrameLeft() and getFrameTop() both return the top left-most position (0, 0). */
/* graphics.drawImage(this.image.getFrameLeft(this.currentFrame), */
/* this.image.getFrameTop(this.currentFrame), */
/* this.image.getFrameWidth(this.currentFrame), */
/* this.image.getFrameHeight(this.currentFrame), */
/* this.image, this.currentFrame, 0, 0); */
/*
Currently trying some hackish nonsense like this to position the frame, although
it probably won't scale properly across different devices/screen sizes.
*/
int x = (this.getManager().getWidth() / 2) - 45;
int y = (this.getManager().getHeight() / 2) + 83;
graphics.drawImage(x, y,
this.image.getFrameWidth(this.currentFrame),
this.image.getFrameHeight(this.currentFrame),
this.image, this.currentFrame, 0, 0);
}
}
}
What about something like this?
graphics.drawImage(this.image.getFrameLeft(this.currentFrame,
this.image.getFrameTop(this.currentFrame),
this.image.getFrameWidth(this.currentFrame),
this.image.getFrameHeight(this.currentFrame),
this.image, this.currentFrame, 0, 0);
Fixed the problem by stripping out all getPreferredWidth/Height stuff, removed the sublayout() method on my custom field and just overwrote layout() inside of my custom manager to position all fields (just the one) properly. That caused image.getFrameLeft() and image.getFrameTop() return proper values, which is where I had my positioning hacks before.
Thanks for the responses. I was making it way more complicated than it needed to be.
Related
I'm working on a drawing application and am pretty close to release but I'm having issues with the eraser part of the app. I have 2 main screens (fragments) one is just a blank white canvas that the user can draw on with some options and so on. The other is a note taking fragment. This note taking fragment looks like notebook paper. So for erasing on the drawing fragment, I can simply use the background of the canvas and the user wont know the difference. On the note fragment though I cannot do this beacuse I need to keep the background in tact. I have looked into PorterDuffer modes and have tried the clear version and tried to draw onto a separate bitmap but nothing has worked. If there was a way to control what gets draw ontop of what then that would be useful. I'm open to any suggestions, I can't seem to get anything to work.
Ive also played with enabling a drawing cache before erasing and that doesn't work. In addition I tried hardware enabling and that made my custom view behave oddly. Below is the relavent code. My on draw methods goes through a lot of paths because I am querying them in order to allow for some other functionallity.
#Override
protected void onDraw(Canvas canvas) {
//draw the backgroumd type
if(mDrawBackground) {
//draw the background
//if the bitmap is not null draw it as the background, otherwise we are in a note view
if(mBackgroundBitmap != null) {
canvas.drawBitmap(mBackgroundBitmap, 0, 0, backPaint);
} else {
drawBackgroundType(mBackgroundType, canvas);
}
}
for (int i = 0; i < paths.size(); i++ ) {
//Log.i("DRAW", "On draw: " + i);
//draw each previous path.
mDrawPaint.setStrokeWidth(strokeSizes.get(i));
mDrawPaint.setColor(colors.get(i));
canvas.drawPath(paths.get(i), mDrawPaint);
}
//set paint attributes to the current value
mDrawPaint.setStrokeWidth(strokeSize);
mDrawPaint.setColor(mDrawColor);
canvas.drawPath(mPath, mDrawPaint);
}
and my draw background method
/**
* Method that actually draws the notebook paper background
* #param canvas the {#code Canvas} to draw on.
*/
private void drawNoteBookPaperBackground(Canvas canvas) {
//create bitmap for the background and a temporary canvas.
mBackgroundBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBackgroundBitmap);
//set the color to white.
mBackgroundBitmap.eraseColor(Color.WHITE);
//get the height and width of the view minus padding.
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int width = getWidth() - getPaddingLeft() - getPaddingRight();
//figure out how many lines we can draw given a certain line width.
int lineWidth = 50;
int numOfLines = Math.round(height / lineWidth);
Log.i("DRAWVIEW", "" + numOfLines);
//iterate through the number of lines and draw them.
for(int i = 0; i < numOfLines * lineWidth; i+=lineWidth) {
mCanvas.drawLine(0+getPaddingLeft(), i+getPaddingTop(), width, i+getPaddingTop(), mNoteBookPaperLinePaint);
}
//now we need to draw the vertical lines on the left side of the view.
float startPoint = 30;
//set the color to be red.
mNoteBookPaperLinePaint.setColor(getResources().getColor(R.color.notebook_paper_vertical_line_color));
//draw first line
mCanvas.drawLine(startPoint, 0, startPoint, getHeight(), mNoteBookPaperLinePaint);
//space the second line next to the first one.
startPoint+=20;
//draw the second line
mCanvas.drawLine(startPoint, 0, startPoint, getHeight(), mNoteBookPaperLinePaint);
//reset the paint color.
mNoteBookPaperLinePaint.setColor(getResources().getColor(R.color.notebook_paper_horizontal_line_color));
canvas.drawBitmap(mBackgroundBitmap, 0, 0, backPaint);
}
To all who see this question I thought I would add how I solved the problem. What I'm doing is creating a background bitmap in my custom view and then passing that to my hosting fragment. The fragment then sets that bitmap as its background for the containing view group so that when I use the PorterDuff.CLEAR Xfermode, the drawn paths are cleared but the background in the fragment parent remains untouched.
This is a program I wrote for creating a pacman. I now want the Pacman to move in a straight line from a random start point to a random goal point.
Could you please suggest how to do it.
import javax.swing.JFrame;
/**
* Main class for pacman example. All it does is create a frame and put
* the pacman panel in it.
*/
public class PacmanGUI extends JFrame{
private Pacman pc;
public PacmanGUI(){
super("Pacman");
pc = new Pacman();
this.getContentPane().add(pc);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
}
public static void main(String[] args) {
new PacmanGUI();
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* Pacman class that extends JPanel and paints a pacman animation.
* It uses Timers and Actionlistener to do the Animation.
*
*/
public class Pacman extends JPanel implements ActionListener{
private int figureSize = 50;
private static final int DELAY = 200;
private double mouthOpenPercentages[] = {.1,.5};
private Timer animationTimer;
private int mouthState = 0;
private Point pacManPosition;
/**
* No args constructor that starts the animation.
*/
public Pacman(){
startAnimation();
}
/**
* Overriden paintComponent method that paints pacman.
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
pacManPosition = new Point(this.getWidth()/2 - figureSize/2,
this.getHeight()/2 - figureSize/2);
g.fillRect(0,0,this.getWidth(), this.getHeight());
drawPacMan(g);
mouthState = (++mouthState) % mouthOpenPercentages.length;
}
/**
* Stops the animation by stopping the animation timer.
*/
public void stopAnimation(){ animationTimer.stop(); }
/**
* Method do deal with actionevents that are triggered. In this
* case we only have actionevents being triggered from our timer
* and by the more usual case of a button click.
*/
public void actionPerformed(ActionEvent e){ repaint(); }
/**
* Gets the size that this component would like to be.
*/
public Dimension getPreferredSize(){ return new Dimension(400,400); }
/**
* Gets the minimum size for this component.
*/
public Dimension getMinimumSize(){ return new Dimension(200,200); }
/**
* Starts the animation by setting a timer. When this timer goes
* off the actionPerformed method will be triggered, which in
* turn triggers the painting.
*/
private void startAnimation(){
if (animationTimer == null){
mouthState = 0;
animationTimer = new Timer(DELAY, this);
animationTimer.start();
} else { //continue animating..
if (!animationTimer.isRunning())
animationTimer.restart();
}
}
/**
* Draws our little pacman on the given graphics canvas.
* #param g
*/
private void drawPacMan(Graphics g){
Color c = g.getColor();
g.setColor(Color.yellow);
g.fillOval(pacManPosition.x, pacManPosition.y, figureSize, figureSize);
//Change color back to original and draw pacman's mouth
g.setColor(c);
//calculate mouth offsets
int yOffset = (int)((figureSize/2)*mouthOpenPercentages[mouthState]);
//draw the mouth cutout.
int x[] = {pacManPosition.x + figureSize/2, pacManPosition.x + figureSize, pacManPosition.x + figureSize};
int y[] = {pacManPosition.y + figureSize/2,
pacManPosition.y + figureSize/2 + yOffset,
pacManPosition.y + figureSize/2 - yOffset};
g.fillPolygon(x, y, x.length);
}
}
Inside the Pacman class you would need to create 2 more values to store the start and end points. You already have private Point pacManPosition; declared so I would also declare these as Points. You'll want to set pacManPosition initially to the start point.
Point start = // random start point
Point end = // random end point
Point pacManPoint = new Point(start);
Now you'll want to determine the speed you want your Pacman to move at, let's say 2 pixels per frame.
int speed = 2;
To determine how much to move the Pacman each frame, we'll need to do some calculations. First, get the distance of the line -
double distance = Math.sqrt(Math.pow(end.x - start.x, 2) +
Math.pow(end.y - start.y, 2));
Then we calculate how many frames it will take to go that distance at the speed we want.
int totalFrames= (int)Math.round(distance / speed);
And add a frame counter -
int frame = 0;
Now, look inside your paintComponent method. Right now you're setting pacManPosition to the same point (the center of the panel) each time it paints. What you want to do here instead is to update pacManPosition each frame until it gets to the end position. You're doing something similar lower in paintComponent where you're updating mouthState each time to get the mouth to animate. For animating position it will look like -
if (frame < totalFrames) {
pacManPosition.x = start.x + frame * (end.x - start.x) / totalFrames;
pacManPosition.y = start.y + frame * (end.y - start.y) / totalFrames;
frame++;
}
This is only one way to do movement animation, and it assumes several things - constant speed, no need to avoid obstacles, no player control. The calculation in totalFrames isn't exact - it moves pacMan close to the end point, but there's no guarantee it will end exactly there. It is also tied to the frame rate, which has drawbacks. There are many, many other ways to do this depending on the situation.
Problem
You have to manage two animations at the same time.
The first, which you've already coded, opens and closes the Pacman's mouth.
The second animation is responsible for moving the Pacman from one location to another.
Solution - Sprite class
I suggest you create a Sprite class. The Sprite class would be responsible for holding the current position of the sprite, the next position of the sprite, and the speed at which the sprite moves.
You would extend Sprite to get one Pacman class, and a Chaser class with 4 instances.
Pacman class
The Pacman class would be responsible for the mouth animation.
Chaser class
The Chaser class would be responsible for determining whether to chase the Pacman, or run away from the Pacman.
Swing Tips
You should not extend Java Swing components, unless you are overriding one or more of the component classes. You should use Swing components.
You should always start your Swing GUI on the Event Dispatch Thread (EDT). You do this by executing the invokeLater method of SwingUtilities.
You should have a GUI model, separate from your GUI components. The three classes I defined would be part of your GUI model. You also need to lay out a maze.
I've been experimenting with different ways of moving a image over a grid of tiles for a game, but I've been unable to get a working implementation.
First I tried using a grid layout to hold a bunch of Tiles that extended Canvas and drew themselves. This drew the tiles nicely, however it seems that I am unable to draw my Player object on top of them. Originally, the Player also extended Canvas and I intended to have the widget on top of the tiles. It seems like this is impossible.
I then tried to have the Tile simply extend nothing, and just hold the image. I then hold each Tile in a 2D array and draw each Tile by a nested for loop, using the int from the for loop, multiplied by the image size, to draw Tile's Image. I put this code in a PaintListener inside of my constructor for my Map class that extended Canvas and dropped my Map onto my Shell in a Fill layout, but the PaintListener never gets called (I tested with a print statement).
What implementation could I use to draw the Tiles at the start of the game, then allow me to control the movement of my Player image?
I did something similar.
Using a PaintListener I get the calls when the Widget needs to be repainted. In my paint function, I loop over a tile array (wrapped in a World class) and draw all tiles. Afterwards I use the same technique with a worldObjects array/class:
public class WorldWidget extends Canvas {
WorldWidget() {
addPaintListener(new PaintListener() {
#Override
public void paintControl(PaintEvent e) {
WorldWidget.this.paintControl(e);
}
});
}
protected void paintControl(PaintEvent e) {
GC gc = e.gc;
for (short y = 0; y < world.getHeight(); y++) {
for (short x = 0; x < world.getWidth(); x++) {
final ITile tile = world.getTile(x, y);
final Image image = ImageCache.getImage(tile);
gc.drawImage(image, x * tileSize, y * tileSize);
}
}
// Here is used a similar loop, to draw world objects
}
}
This is obviously a condensed code example, as the class is part of an editor and reacts on mouse clicks and movement amongst other things.
When I did a tile based simulation while ago I did it this way:
I had 2 layers of the tile map - one for the terrain and second for the units.
The map itself was represented by a JPanel.
So roughly you got this for the JPanel:
public void paintComponent(Graphics graphics) {
// create an offscreen buffer to render the map
if (buffer == null) {
buffer = new BufferedImage(SimulationMap.MAP_WIDTH, SimulationMap.MAP_HEIGHT, BufferedImage.TYPE_INT_ARGB);
}
Graphics g = buffer.getGraphics();
g.clearRect(0, 0, SimulationMap.MAP_WIDTH, SimulationMap.MAP_HEIGHT);
// cycle through the tiles in the map drawing the appropriate
// image for the terrain and units where appropriate
for (int x = 0; x < map.getWidthInTiles(); x++) {
for (int y = 0; y < map.getHeightInTiles(); y++) {
if (map.getTerrain(x, y) != null) {
g.drawImage(tiles[map.getTerrain(x, y).getType()], x * map.getTILE_WIDTH(), y * map.getTILE_HEIGHT(), null);
}
}
}
if (map.getSimulationUnits() != null) {
for (Unit unit : map.getSimulationUnits()) {
g.drawImage(tiles[unit.getUnitType()], (int) Math.round(unit.getActualXCor() * map.getTILE_WIDTH()), (int) Math.round(unit.getActualYCor() * map.getTILE_HEIGHT()),
null);
}
}
// ...
// draw the buffer
graphics.drawImage(buffer, 0, 0, null);
}
Logic:
private Terrain[][] terrain = new Terrain[WIDTH][HEIGHT];
/** The unit in each tile of the map */
private Unit[][] units = new Unit[WIDTH][HEIGHT];
Then you have your game loop where you update the position of the units and other things, basically render() and update() the game. Check the links I've provided below.
NOTE
Since you are making a simple game this post about making game loops will be definitely useful for you. This hopefully also answer your question about moving the object on the map.
This site will be also very helpful since you will probably need to detect collision at some point too.
I'm writing a method for the Java Helper Library to show a "windowless" swing component (image) which can be used as a way to show a progress wheel or something. I asked how to do this a while ago and received a good answer which I'm using. It works great except the animated gif is not animated. (I'm not using the image itself because seeing it the whole time you're reading this might make you sick...) It's not animated as in it's not moving. It's seemingly paused or something. The answer on the other question said animated gifs would work fine. Is the answer-er wrong or am I implementing this wrong?:
public static void main(String[] args) throws IOException, InterruptedException {
Image image = SwingHelper.resizeImageFromResourceBy(TestClass.class, progressImageLocation, 32, true); //This just gets the image in the right size I want it.
JWindow window = SwingHelper.getProgressWheelWindow(new ImageIcon(image), .9f, 600, 400);
window.setVisible(true);
Thread.sleep(3000); //Just so the image only shows up for a short period of time.
window.setVisible(false);
SwingHelper.centerAndPack(window); //A method to center and pack the window
}
/**
* Returns a window with a partially opaque progress Icon
*
* #param icon the icon to set in the progress window
* #param opacity a float value from 0-1
* #param x the x location of the window
* #param y the y location of the window
* #return a jWindow of the progress wheel
*/
public static JWindow getProgressWheelWindow(final Icon icon, final Float opacity, final int x, final int y) {
JWindow jWindow = new JWindow() {
{
setOpacity(opacity);
setLocation(x, y);
setSize(icon.getIconWidth(), icon.getIconHeight());
add(new JLabel(icon));
pack();
}
};
return jWindow;
}
SwingHelper.resizeImageFromResourceBy(
TestClass.class, progressImageLocation, 32, true);
//This just gets the image in the right size I want it.
My best guess short of an SSCCE, is that the helper method returns a static (resized) version of the original image.
Note that by using HTML in the label, an animated GIF can be resized 'on-the-fly', however the effect is less than optimal. It is better to design the image the right size.
I'm attempting to sort of "highlight" a tile object within a game I'm making (Mahjong Solitaire). To do this, I'm drawing a Rectangle2D object in the same position as the tile and trying to have it display when the mouse is clicked.
I'm able to get the mouse click event to work and recognize when tiles are selected, but for some reason, the rectangle is not drawn when I'm within the mousePressed function. I can't seem to figure out why...
Here is what I consider the relevant code - I can expand it if necessary!
/* Above this, the positions of tiles are set */
if (content[i][y][x].isVisible()) {
/* Draws the image to screen at the appropriate location */
graphics.drawImage(image, x*TILEW+TILEW/2+i*TILESKEW, (y+1)*TILEH/2-i*TILESKEW,null);
}
/* Represents the area around a tile, so that you can determine
* whether appropriate area pressed within a tile */
final Rectangle2D rect = new Rectangle2D.Double(x*TILEW+TILEW/2+i*TILESKEW,(y+1)*TILEH/2-i*TILESKEW, image.getWidth(null), image.getHeight(null));
/* Set colour of border rectangle */
graphics.setColor(Color.red);
/* Store positions and sizes of tile objects */
final int xPos = x*TILEW+TILEW/2+i*TILESKEW;
final int yPos = (y+1)*TILEH/2-i*TILESKEW;
final int height = image.getHeight(null)+2;
/* This works - outside of the mouse event */
//graphics.drawRoundRect(xPos, yPos, width, height, 7, 7);
/* Mouse event */
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
/* Draw the rectangle to the screen -- Doesn't display! */
graphics.drawRoundRect(xPos, yPos, width, height, 7, 7);
}
The "graphics" Graphic object is passed to the function:
public void paintComponent(final Graphics graphics) { ... }
Any suggestions would be greatly appreciated!
Thank you in advance for your help!
Your program structure sounds off in that you usually shouldn't have the MouseListener directly manipulating the Graphics object that is passed into paintComponent. The reason for this is that the Graphics object obtained this way won't persist. Usually you'll have the MouseAdapter (both MouseListener and MouseMotionListener) alter class fields and then call repaint() on the component. Then paintComponent uses the fields set by the mouse adapter to draw the rectangle.
Edit 1
For example, please see my sample program here: drawing-a-rectangle-over-an-existing-graphics-page