How do I implement zooming for a Jpanel? - java

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);
}

Related

Issues with coordinates

I'm working on something that involves clicking specific points on a buffered image in a JPanel. I had issues with this earlier in the project (affine transform translation not working properly), but nothing I found fixed it so I decided I would come back to it later.
I'm not entirely sure how to trouble shoot it since I'm a novice, but I think it's reading my y coordinates too low. I made a mouse input listener that tracks the number of times the user has clicked and gets the mouse pointer's location for functions I haven't made yet. For testing I have it output the coordinates and number of clicks then make a circle centered where the mouse clicks.
#Override
public void mouseClicked(MouseEvent e) {
Point mouseCursor = MouseInfo.getPointerInfo().getLocation();
panel.drawCenteredCircle(mouseCursor.getX(), mouseCursor.getY(), 100);
System.out.println(String.valueOf(mouseCursor));
System.out.println(String.valueOf(clickCount));
clickCount++;
}
Here is drawCenteredCircle in my custom panel class:
public void drawCenteredCircle(double x, double y, int r) {
imgG2 = image.createGraphics();
imgG2.setPaint(Color.RED);
x = (x-r/2.0);
y = (y-r/2.0);
imgG2.fillOval((int)Math.round(x), (int)Math.round(y), r, r);
this.repaint();
imgG2.dispose();
}
I tried taking a screenshot to show what happens, but the circle properly centers on the x coordinate, but not the y coordinate. Instead it draws the circle with the pointer at the top center edge.
I overrided the paintComponent of my JPanel to implement a zoom feature:
#Override
protected void paintComponent(Graphics g) {
//Implimenting zoom
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
/*Supposed to counter the movement from the scale, not working properly
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double x = (w - scale * imageWidth)/2;
double y = (h - scale * imageHeight)/2;*/
AffineTransform at = new AffineTransform()/*.getTranslateInstance(x, y) */;
at.scale(scale, scale);
g2.drawRenderedImage(image, at);
//g2.dispose(); I was told to put this, but I'm not sure if it's necessary or what it does entirely
}
My confused notes are because I got this code from an example someone made and, as I said earlier, the affine translation wasn't working (I took the actual translation out). They're irrelevant to the question.
The reason I put this is because I initially had code that was meant to fit the image to the screen/frame depending if it was fullscreen or not:
int x = image.getWidth();
int y = image.getHeight();
double frameW = frame.getBounds().getWidth();
double frameH = frame.getBounds().getHeight();
//Rectangle winSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
double screenW = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
double screenH = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
if (!isFullScreen) {
if (x/y > frameW/frameH) {
scale = frameW/x;
} else {
scale = frameH/y;
}
} else {
if (x/y > screenW/screenH) {
scale = screenW/x;
} else {
scale = screenH/y;
}
}
It uses my zoom function which scales the image with the double "scale." I noticed that when I zoomed in or out, it would change where the dots would appear relative to the pointer. It wasn't until I removed the code for the image to start fitted to the window and had it start at 100% that I received the result of the pointer being at the top center of the circle.
I also tried removing the part that's supposed to center the circle and the result was the pointer being on the left side and having a gap between it and the top of the circle.
Sorry if this is too much stuff. I'm pretty novice and learned just as much about java (the only coding language I know) working on this project as I knew when I first started it. I'm not sure what information I have that could be helpful in this, so I just threw in everything I thought could help. I appreciate any help, even irrelevant to my question.

Make an object render half way through a reapint

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();
}
});

How to save a specific part of a JPanel?

I am working on a chess game and I would like to let the player choose the board's colors. Therefore I will use this method:
static void createBoard(Graphics g) {
Color bright = new Color(255, 225, 181); //player chooses color
Color dark = new Color(188, 141, 105); //player chooses color
boolean darkTile = false;
for (int y = spaceY; y < (spaceY + BOARDHEIGHT); y += TILESIZE) {
for (int x = spaceX; x < (spaceX + BOARDWIDTH); x += TILESIZE) {
if (darkTile) {
g.setColor(dark);
} else {
g.setColor(bright);
}
g.fillRect(x, y, TILESIZE, TILESIZE);
darkTile = !darkTile;
}
darkTile = !darkTile;
}
BufferedImage overlay;
try {
overlay = ImageIO.read(new File("overlay.png"));
JLabel label = new JLabel(new ImageIcon(overlay));
g.drawImage(overlay, spaceX, spaceY, BOARDWIDTH, BOARDHEIGHT, null);
} catch (IOException e) {}
}
This I would like to save as a BufferedImage, so I don't have to run this method all the time.
So how can I save just this part of my JPanel, without the stuff outside of the chess board? (there will be more painted)
This I would like to save as a BufferedImage,
Don't know that your need to save the BufferedImage to a file. You can just create a BufferedImage to be used by the application when the application starts. You can then recreate the BufferedImage if any of the user colors change.
You can paint directly to a BufferedImage:
BufferedImage image = new BufferedImage(boardSize, boardSize, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
// draw the squares onto board
g2d.dispose();
Now your createBoard() method should probably return the BufferedImage so it can be used by your application.
You put in certain efforts to put up your question, so lets honor that with some thoughts to get you going.
First of all: you have an empty catch block {}. That is bad practice. This simply eats up any error messages you get. That is not helpful. Either allow that exception to bubble up and stop your application; or at least print its contents - so that you understand what happens.
And given your comment: you never now if there will be errors. Especially when doing IO, all sorts of things can go wrong. Please believe me: empty catch blocks are bad practice; and you should not train yourself to accept them.
Second thought: don't go for that yet. As convenient as it might sound; but saving a background picture doesn't add much value at this point.
You don't need to worry about this code; it is executed once when your application comes up.
So, the real answer here: focus on the features you want to implement; and don't get distracted with pre-mature optimizations.

Java: Am I responsible for only painting the visible region of (JScrollPane) Viewport View?

I'm using the Java Tutorials example of how to use a JScrollPane (with row/column headers). The example is using a subclass of JLabel to display an image in the Viewport View. I used the sample code for displaying the row/column headers (Rule.java example code) and was perplexed at the bizarre results. I finally removed the call to getClipBounds() (apparently used to determine what region of the row/column header is visible to paint only that region) and painted the entire header, and the problem was resolved. That means that I'm now drawing the entire area (in both the row/column headers and the main Viewport). That strikes me as non-optimal.
Can anyone explain why the Java Tutorials example works properly (other than the source is not the same as that being executed in the example)?
Is it correct for me to be painting the entire pane even though it is only partially visible?
How can I determine what region of the overall object is visible in the Viewport (for row/column headers and the main Viewport) so I can just paint that region?
UPDATE:
I still don't know why the example works, but I've found that if I use JComponent.getVisibleRect() instead of Graphics.getClipBounds() things seem to work as expected. Not sure if this is the correct use of this method.
Look at this code below. I was just painting visible part.
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Rectangle view = new Rectangle();
if (getParent() instanceof JViewport) {
JViewport vp = (JViewport) getParent();
view = vp.getViewRect();
} else {
view = new Rectangle(0, 0, getWidth(), getHeight());
}
g2d.setColor(getBackground());
g2d.fillRect((int) view.getX(), (int) view.getY(), (int) view.getWidth(), (int) view.getHeight());
g2d.setColor(Color.YELLOW);
double x = view.getX();
double y = view.getY();
double w = view.getWidth();
double h = view.getHeight();
// draw Strings
for (StringShape ss : stringList) {
Rectangle sb = ss.getRectangle(g2d.getFontMetrics(ss.getFont()));
if (containShape(view, sb)) {
g2d.setFont(ss.getFont());
g2d.setColor(ss.getColor());
g2d.drawString(ss.getString(), (int) sb.getX(), (int) sb.getY());
}
}
}
JComponent.getVisibleRect() was the trick. Clearly I misunderstand the meaning/use of getClipBounds().

Components don't show in custom JPanel/JComponent

I've created a custom swing component. I can see it (the grid from the paint method is drawn), but the buttons that are added (verified by println) aren't shown. What am I doing wrong?
Background information: I'm trying to build a tree of visible objects like the Flash/AS3 display list.
public class MapPanel extends JComponent { // or extends JPanel, same effect
private static final long serialVersionUID = 4844990579260312742L;
public MapPanel(ShapeMap map) {
setBackground(Color.LIGHT_GRAY);
setPreferredSize(new Dimension(1000,1000));
setLayout(null);
for (Layer l : map.getLayers()) {
// LayerView layerView = new LayerView(l);
// add(layerView);
System.out.println(l);
JButton test = new JButton(l.getName());
add(test);
validate();
}
}
#Override
protected void paintComponent(Graphics g) {
// necessary?
super.paintComponent(g);
// background
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
// grid
g.setColor(Color.GRAY);
for (double x = 0; x < getWidth(); x += 10) {
g.drawLine((int)x, 0, (int)x, getHeight());
}
for (double y = 0; y < getHeight(); y += 10) {
g.drawLine(0, (int)y, getWidth(), (int)y);
}
}
}
Setting null as the layout manager and then adding buttons will not have any effect. A layout manager is responsible for computing the bounds of the children components, and setting layout manager to null effectively leaves all your buttons with bounds = (0,0,0,0).
Try calling test.setBounds(10, 10, 50, 20) as a quick test to see if the buttons appear. If they do, they will be shown at exactly the same spot. From there you can either install a custom layout manager that gives each button the required bounds, or use one of the core / third party layout managers.
It would be easier for us to diagnose your problem if you gave us a SSCCE. As it stands, we may not have enough information to fix your problem.
I can see it (the grid from the paint
method is drawn),
I don't know what that means, there is no paint() method in the posted code. (But I suppose it is easy enough to assume that you meant paintComponent(g))
However, it looks like the problem is that you are uisng a "null layout". The children will not paint unless you manually set the size and location of the children.
You should probably read a quick tutorial on LayoutManagers. It may make things easier for you when drawing components.

Categories

Resources