I am currently working on an animation to compare two stock exchange algorithms. I am running the algorithms within the paint component extending JComponent. (not the best, but I don't care) I need to have the screen refresh half way through the paint component. I do not want it to have to get all the way through before it up dates the screen. The reason being is I have one algorithm with a nested while loop and the other without. How would I go about doing this?
public void paintComponent(Graphics g) {
//calls in the super class and calls the calibrate the graphics method
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
calibrateFrame( getHeight(), getWidth() );
//Clears the rectangle to avoid overlaying, makes it black
g2D.clearRect(0, 0, getWidth(), getHeight());
g2D.setColor(Color.BLACK);
g2D.fillRect(0, 0, getWidth(), getHeight());
//Draws the rectangles without the algorithm started
redraw(g2D, -1);
/**
*algorithms
*/
fastSpans[0] = 1;
slowSpans[0] = 1;
//Makes a new stack pushes 0 on the stack
Stack myStack = new Stack();
myStack.push(0);
//If it has not been sorted or paused, continue the algorithm
if (!(pause) && !(sorted)){
//The slower algorithm needs to start out at zero (j)
int j = indexValue-1;
g2D.setColor(Color.BLUE);
//Calculates the values for the X and Y coordinates for the
//new rectangle, along with the height
int slowY = calSlowY(j);
int slowX = calSlowX(j);
int curHeightSlow = (int) ((stocks[j]/maxStockValue)*maxHeight);
//Here is the actual algorithm
int k = 1;
boolean span_end = false;
//Nested While Loop
while (((j-k)>0) && !span_end){
if (stocks[j-k] <= stocks[j]){
k = k + 1;
// Draw the current component
// **********************
// DO REFRESH MID PAINT COMPONENT
}
else{ span_end = true; }
}
slowSpans[j] = k;
g2D.setColor(Color.WHITE);
for(int i = 0; i < numberOfStock ; i++){
}
if (!(indexValue >= numberOfStock)){
while (!( myStack.empty()) && (stocks[(int)myStack.peek()]) <= stocks[indexValue]){
myStack.pop();
}
if (myStack.empty()){
fastSpans[indexValue] = indexValue + 1;
}
else {
fastSpans[indexValue]= indexValue - (int) myStack.peek();
//System.out.println("Im in the else");
}
myStack.push(indexValue);
}
}
drawStrings(g2D);
}
I am running the algorithms within the paint component extending JComponent. (not the best, but I don't care)
But you should care, since this impacts on your problem and possible solution.
I need to have the screen refresh half way through the paint component. I do not want it to have to get all the way through before it up dates the screen. The reason being is I have one algorithm with a nested while loop and the other without. How would I go about doing this?
Then don't run the algorithm through paintComponent. Instead use a SwingWorker<Void, Image>, update a BufferedImage and pass that image to the GUI through the worker's publish/process method pair, calling repaint() at the same time. For more on how to use a SwingWorker, please have a look at: Lesson: Concurrency in Swing
Don't understand what half way means.
If it means half of the component, then the simple way is to using two JComponent.
If u means in same component one line updated but another line not updated.
What I understand the repaint is calling when it packs(), updateUI(), or caused by invalidate().
So in my view, the repaint() should only care about paint these lines/2D, and in another thread to execute these loops/generate these data. Once the data collection is finished just call updateUI()/paintImmediately.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
Related
I'm doing a project for school in witch I have to simulate an ant colony and also show it in a graphics user interface.
The whole project is almost complete but I neet to implement a zoom feature for my jPanel.
I've found a thread on this site with basically what I need.Here's the link:
Zooming in and zooming out within a panel
What Thanasis made in that thread is what I basically need but I have no Idea how to implement it inside my code with the other classes.
I am a newbie in Graphic User Interface and we are basically learning and understanding it by doing this project so forgive me if the answer is Super easy and I'm asking for the answer.
I can provide code for the Pannel and Window classes.
I've allready tried launching it without anything thinking that it will work directly on my jpanel but it didn't of course.also tried to call it in my main but that didn't work either. Here's my paintComponent from my panel . I basically do this for everything that shows(ants, colony, food).
public void paintComponent(Graphics g){
int tailletab = this.gri.getTab().length;
//On récupère le tableau de la Grille
int[][] gril = this.gri.getTab();
int taillecarre;
int xcol = this.colo.getPos().getX();
int ycol = this.colo.getPos().getY();
int xsou = this.source.getPos().getX();
int ysou = this.source.getPos().getY();
if(tailletab<=50){
taillecarre = tailletab/4+2;
}else{
if(tailletab<60){
taillecarre = tailletab/5+1;
}else{
if(tailletab<70){
taillecarre = tailletab/7+1;
}else{
if(tailletab<80){
taillecarre = tailletab/8;
}else{
if(tailletab<90){
taillecarre = tailletab/10;
}else{
taillecarre = tailletab/13;
}
}
}
}
}
for(int i=0; i<tailletab; i++){
for(int j=0; j<tailletab; j++){
if(gril[j][i]==0){
if(j==xcol && i==ycol){
g.setColor(new Color(102, 51, 0));
g.fillRect(xcol*taillecarre, ycol*taillecarre,taillecarre,taillecarre);
g.setColor(Color.BLACK);
g.drawRect(xcol*taillecarre, ycol*taillecarre,taillecarre,taillecarre);
}else{
if(j==xsou && i==ysou){
g.setColor(Color.RED);
g.fillRect(xsou*taillecarre, ysou*taillecarre,taillecarre,taillecarre);
g.setColor(Color.BLACK);
g.drawRect(xsou*taillecarre, ysou*taillecarre,taillecarre,taillecarre);
}else{
g.setColor(Color.BLACK);
g.drawRect(j*taillecarre, i*taillecarre, taillecarre, taillecarre);
}
}
}else{
g.setColor(Color.BLACK);
g.drawRect(j*taillecarre, i*taillecarre, taillecarre, taillecarre);
g.fillRect(j*taillecarre, i*taillecarre, taillecarre, taillecarre);
}
}
}
}
The answer of Andreas Holstenson in the link you provided is better than Thanasis' because you shouldn't override paint but paintComponent as you correctly did, and Thanasis doesn't overwrite the transformation but tries to be dumb-clever about not progressively updating the transform. If you lost me, just forget that answer altogether.
Otherwise, the choice of whether to use AffineTransform or not does not matter as the result is the same. Arguably, AffineTransform is easier to use.
To answer your question, put the additional scale/translate code at the top of that method and all graphics drawn after that should be zoomed (even if if you use g instead of g2).
#Override
protected void paintComponent(Graphics g) { // No need to widen it to public
Graphics2D g2 = (Graphics2D)g;
AffineTransform oldTransform = g2.getTransform();
try {
float zoom = 0.5f; // shrink
// Note that transforms are in reverse order
// because of how matrix multiplications work.
AffineTransform newTransform = new AffineTransform();
// 2nd transform: re-center
newTransform.translate(getWidth() * (1 - zoom) / 2,
getHeight() * (1 - zoom) / 2);
// 1st transform: zoom (relative to upper-left corner)
newTransform.scale(zoom, zoom);
g2.transform(newTransform);
// Draw here
} finally {
// Try-finally is probably not required, but ensures that the transform
// gets restored even if an exception is thrown during drawing.
g2.setTransform(oldTransform);
}
Hello once again stackoverflow, so I am working on my latest project, an oil-tycoon game, after being inspired by a movie. It involves an overworld map which you can zoom in, and for that I have a function which scales the images to a certain size depending on how much you scrolled. I am used to C++ where you manually free up memory so please go easy on me as I don't fully understand the garbage collector and what not, English also isn't my native language.
The function is this (partly from stackoverflow, I love you guys):
public void scale(int dWidth, int dHeight) {
Image newImg = this.baseImg.getScaledInstance(dWidth, dHeight, Image.SCALE_DEFAULT);
// Create a buffered image with transparency
BufferedImage bimage = new BufferedImage(newImg.getWidth(null), newImg.getHeight(null), BufferedImage.TYPE_INT_ARGB);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(newImg, 0, 0, null);
bGr.dispose();
this.img.flush();
this.img = bimage;
bimage.flush();
newImg.flush();
}
This function is part of a class, Bitmap, which has a BufferedImage member variable, img. This member variable is used to actually draw the images on screen. However upon testing my new scaling functionality, I noticed memory usage skyrocketed as the function was called. Moreover, the memory wasn't actually freed up. I tested it, and without the calls to the scale function, memory usage is stable at around ~200MB, but once I start calling this function (for one image only, called every time a MouseWheelMoved event fires) it skyrockets once you scroll. Every scroll increases the memory usage by a lot, and it never goes back down. I already added the flush calls, but it doesn't seem to have any effect. Neither does setting the local variables to null. I suspect there is some kind of memory leak in this function.
So my question to you guys: can anyone confirm this? Does anybody see a memory leak in this function? If not, what else could be causing this ridiculous increase in memory usage upon calling this function? I just want to stress again that I'm 100% sure this function is the cause of it.
I can post more code if needed (for instance the actual calls to the function), but I omitted it for simplicity for the time being.
Thanks in advance!
EDIT
Added extra code, as requested.
Lets dive down this rabbit hole, from top to bottom.
First off, I have a class, GameWindow, which extends JFrame. It has a MouseWheelListener like this:
this.addMouseWheelListener(new MouseWheelListener(){
#Override
public void mouseWheelMoved(MouseWheelEvent arg0) {
double scrollAmount = arg0.getPreciseWheelRotation();
curMapScale -= ((double)scrollAmount * 0.1);
// Reset zooming
if(curMapScale <= 0)
{
curMapScale = 1.0;
}
}
});
My game loop is as such (noobish, I know):
private void gameLoop()
{
boolean abort = false;
long curTime = 0;
long prevTime = System.currentTimeMillis();
while(!abort)
{
curTime = System.currentTimeMillis();
// 500ms for debugging the scaling issue
if(curTime - prevTime > (500))
{
prevTime = curTime;
wGame.render();
}
}
}
wGame is the GameWindow object, and it's render function is like this:
public void render()
{
mainPanel.updateGame();
mainPanel.render();
}
mainPanel is an object of GraphicsPanel, a class that extends JPanel.
It's updateGame() function:
public void updateGame()
{
em.update();
}
and it's render function:
public void render()
{
this.repaint();
}
And finally it's scaleEntities() function
public void scaleEntities()
{
// Deleted scaling of all entities except the map in order to debug the scaling issue
em.scaleMap(getWidth() * GameWindow.curMapScale, getHeight() * GameWindow.curMapScale );
}
em is an object of the class EntityManager, a class which contains ArrayLists with every entity in the game world. It's update function goes like this:
public void update()
{
// Loop through all generic entities en update them (entityList is empty
// for the moment
for(int i = 0; i < entityList.size(); i++)
{
entityList.get(i).update();
}
// Loop through all pointsOfInterests, which exist only on the overworld
// also empty for the moment
for(int i = 0; i < pointsOfInterest.size(); i++)
{
pointsOfInterest.get(i).update();
}
// Update the current map (note the update() function isn't actually used yet, it will be used to update an entities position if it has a velocity
mapList.get(curMap).update();
// Stuff pertaining to Window Resizing, which is irrelevant right now
GameWindow.resetOffset();
GameWindow.hasMouseDragged = false;
}
and it's scaleMap() function goes like this:
public void scaleMap(double width, double height)
{
mapList.get(curMap).scale((int)Math.ceil(width), (int)Math.ceil(height));
}
the call to scale in this function is passed down directly to the Bitmap object of the map. The scale function we started out with is thus called.
Last but not least, the paintComponent() function of the GraphicsPanel class, where the objects actually get drawn:
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, getWidth(), getHeight());
em.render(g2d);
}
The EntityManagers render function:
public void render(Graphics2D g2d)
{
// Only draw the map for debugging purposes
mapList.get(curMap).draw(g2d);
// for(int i = 0; i < entityList.size(); i++)
// {
// entityList.get(i).draw(g2d);
// }
// for(int i = 0; i < pointsOfInterest.size(); i++)
// {
// pointsOfInterest.get(i).draw(g2d);
// }
}
And the maps draw function:
#Override
public void draw(Graphics2D g2d) {
BufferedImage temp = bitm.getSource();
g2d.drawImage(temp, (int)this.loc.getX(), (int)this.loc.getY(), null);
temp.flush();
}
bitm is obviously the Bitmap member of the map, and it's getSource() function goes like this:
public BufferedImage getSource()
{
return this.img;
}
This should be all relevant code. Does anybody see where the memory leak might come from? I have a feeling the newImg and bimage variables in the Bitmaps scale function aren't properly deleted.
Thanks in advance, once again!
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.
Is there a common reason why the paint() method may be called twice without being intended. I have the following code:
public void paint(Graphics g)
{
//Graphics2D gg;
//gg=(Graphics2D) g;
drawMatrix(g);
}
private void drawMatrix(Graphics g) {
int side = 40;
hex hexagon=new hex();
for(int i = 0; i<9; i++)
for(int k = 0; k<9; k++){
g.setColor(Color.lightGray);
g.fill3DRect(i*side,k*side, side, side, true);
if (matrix[i][k]!=null){System.out.println("i is "+i+" k is "+k);
g.setColor(Color.black);hexagon.DrawHexfromMatrix(g, i, k, Color.black);}
}
}
hex is a class that extends polygon (to model a hexagon figure), and the DrawHexfromMatrix is a function that draws a hexagon from the index of the matrix that is drawn(put the hexagon in the slot of a matrix). I can provide the whole code if you think it helps, but for now i don't understand why the system.out.println is executed twice.( for example if[1][2] and [2][3] are not null it will print:
i is 1 k is 2
i is 2 k is 3
i is 1 k is 2
i is 2 k is 3
I think this also affects my drawing because sometimes although an element exists at [i][k] is isn't drawn.(matrix is a matrix of hex).
Later edit: Is it possible somehow that g.fill3DRect(i*side,k*side, side, side, true); to overpaint the hexagons i'm trying to paint with hexagon.DrawHexfromMatrix(g, i, k, Color.black);???
First of all, you should not paint directly to a JApplet.
You should define a JPanel that is added to the JApplet. You paint to the JPanel.
Second, you should use the paintComponent() method, and call the super class behavior, like this.
protected void paintComponent(Graphics g) {
// Paint the default look.
super.paintComponent(g);
// Your custom painting here.
g.drawImage(foregroundImage, x, y, this);
}
Third, you have no control over when Swing fires the paintComponent() method. You should do the calculations in some other method, and limit the code in paintComponent() to actual drawing methods.
I'm new to graphics programming. I'm trying to create a program that allows you to draw directed graphs. For a start I have managed to draw a set of rectangles (representing the nodes) and have made pan and zoom capabilities by overriding the paint method in Java.
This all seems to work reasonably well while there aren't too many nodes. My problem is when it comes to trying to draw a dot grid. I used a simple bit of test code at first that overlayed a dot grid using two nested for loops:
int iPanX = (int) panX;
int iPanY = (int) panY;
int a = this.figure.getWidth() - iPanX;
int b = this.figure.getHeight() - (int) iPanY;
for (int i = -iPanX; i < a; i += 10) {
for (int j = -iPanY; j < b; j += 10) {
g.drawLine(i, j, i, j);
}
}
This allows me to pan the grid but not zoom. However, the performance when panning is terrible! I've done a lot of searching but I feel that I must be missing something obvious because I can't find anything on the subject.
Any help or pointers would be greatly appreciated.
--Stephen
Use a BufferedImage for the dot grid. Initialize it once and later only paint the image instead of drawing the grid over and over.
private init(){
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// then draw your grid into g
}
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
// then draw the graphs
}
And zooming is easily achieved using this:
g.drawImage(image, 0, 0, null); // so you paint the grid at a 1:1 resolution
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
// then draw the rest into g2 instead of g
Drawing into the zoomed Graphics will lead to proportionally larger line width, etc.
I think re-drawing all your dots every time the mouse moves is going to give you performance problems. Perhaps you should look into taking a snapshot of the view as a bitmap and panning that around, redrawing the view 'properly' when the user releases the mouse button?