Faulty Mouse Point Detection? - java

Hey :) So I'm making buttons for a game I'm making. The graphics work, at least to the extent that I don't have to fix them yet. However, click detection is a bit iffy. For example pressing where the black line is in the first picture below, triggers a response. Now obviously that point is not on the button. I have tested the buttons bounding box by drawing a rectangle around it, using it's getBounds() method (which is also used for click detection) and it draws a perfect rectangle around it. So then I tested the mouse click points and it turns out that even though the button is placed at y = 100, at the black line, the mouse point is also equal to 100... Now I have no idea why that is happening, especially because, if I place the button in the top left corner, the mouse detection correctly detects the top pixels and there is no offset...

This is rather interesting, and during my times in have had similar problems. This all really depends on why the Mouse Listener is attached to. Many people attach the listener to the frame, but draw on a panel. This can have the effects you are describing so it is usually better to either draw directly onto the frame, or attach the listener to the panel. In 99.99% of cases, I would always choose the latter. Really, no one should ever choose the latter UNLESS it's something very small.
Panels are exactly that; they're boxes which hold things, hence 'panel'. In my experiences it has always been more effective to use a panel. Frames are just the container to hold multiple panels.
Hope I could help, report your findings in a comment and/or post update.
Jarod.

Got bored so I whipped up an example of what I think is going on.
In essence, I do full rendering to a buffer (BufferedImage here). And then draw the render to the canvas. This may or may not be what you do, but I did it merely for example.
Seeing as you did say that it works fine in the top-left corner, I came to the hypothesis that scaling is the issue, since the x,y-values near the top left approach 0, and 0 * scale = 0, even a scaling of 1000 won't have any offset. The issue is when those components are not at the top-left corner, which you demonstrated for us.
Hopefully this answers your question. As for solving it, you can either accommodate for scaling, or use a letterboxing technique. Beyond those two, there are certainly many other ways to deal with this (such as fixing the screen size).
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author Obicere
*/
public class GraphicScale {
public GraphicScale(){
final JFrame frame = new JFrame("Graphic Scale Example");
final MyPanel panel = new MyPanel();
final Timer repaintTimer = new Timer(50, e -> frame.repaint());
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
repaintTimer.start();
}
public static void main(final String[] args){
SwingUtilities.invokeLater(GraphicScale::new);
}
public class MyPanel extends JPanel {
private final Rectangle box = new Rectangle(100, 100, 100, 50);
private final Dimension size = new Dimension(500, 500);
private final BufferedImage render = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
render.flush();
render();
g.drawImage(render, 0, 0, getWidth(), getHeight(), this); // Trick is that this gets rescaled!
}
public void render(){
final Graphics2D g = (Graphics2D) render.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, render.getWidth(), render.getHeight());
final Point mouse = getMousePosition();
if(mouse != null){
if(box.contains(mouse)) {
g.setColor(Color.GREEN);
g.fill(box);
}
g.setColor(Color.DARK_GRAY);
g.drawOval(mouse.x - 3, mouse.y - 3, 6, 6);
}
g.setColor(Color.BLACK);
g.draw(box);
}
#Override
public Dimension getPreferredSize(){
return size;
}
}
}

Ok, so it turns out that there was some scaling going on with the frame, however I have no idea where it came from. I prepped the game to be scalable so I did all the painting to the optimal size BufferedImage and then I scale that image to the frame. However, even when I removed that the mouse location was still offset. In the end I overcame it by finishing the scaling optimization which required finding the scale of the frame by dividing the current width and height by the optimal width and height. And then dividing the mouse location by that value.I just figured this out. Setting the size of a component and packing the frame after adding the component, results in the actual frame being that size (counting the border), yet when you retrieve the size of the frame, it disregards the border... Why does this happen?
Solved
When I did the game screen scaling, I used the actual frame's height and width to scale the screen, instead of the canvas's height and width. I changed that and now it works perfectly!

Related

How do I avoid "anti-aliasing effect" when creating a PGraphics object that's smaller than the window, then scale it up to the windows size?

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.

Java Project changing behaviour

I have been working on a basic Java Swing application, that paints some objects into a opaque JPanel. I have been coding this app on a University MAC-PC. Yesterday I tested the program on my macbook air(after exporting the project), the behaviour of the application totally change.
The logic of the application is as follows, a Jframe that contains a JLayeredPaneL with Jpanel, in each Jpanel I paint some objects.The application is working correctly on the University laptop.
1)The JPanel is not longer transparent
//Creating Layered Panel
JLayeredPane lpane = new JLayeredPane();
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
this.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
//creating Jpanel
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
this.setBackground(new Color(0, 255, 0, 0));//makes JFrame invisible
this.setVisible(true);
this.setResizable(false);
2) The JPanel does not remember what is previously drawn(it actually creates two Jpanel one what is being drawn at the moment and what was previously drawn)
I paint lines, whenever a new line is added to an array I call paintAgain(), here is the code of the paintComponent
public void paintLines(Point p)
{
arrayLines.add(p);
repaint();
//Only the point is displayed the other points are not visible,
//the other points are in another JPANEL?
}
public void delete()
{
delete = true;
arrayLines.clear();
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(delete)
{
g.clearRect(0,0,screenSize.width,screenSize.height);
delete =false;
}
else
{
g2d = (Graphics2D) g.create();
g2d.setStroke(new BasicStroke(10));
g2d.setColor(pickedColor);
//It just paints the new lines, it does not iterate through all the points
g2d.draw(new Line2D.Float(arrayLines.get(arraySize-1).getx1(), arrayLines.get(arraySize-1).gety1(),arrayLines.get(arraySize-1).getx2(),arrayLines.get(arraySize-1).gety2()));
}
}
I do not know why the behaviour of the program changes. Maybe the JRE version that i'm using?I have literally no idea since, it has never happened before
Thank you in advance
In your paintComponent method, your code as written just draws one line, and so one line is all that you see. Instead you need to use a for loop to iterate through your Point collection, arrayLines, drawing a line between points.
// note that i *must* start at 1, not at 0
for (int i = 1; i < arrayLines.size(); i++) {
int x1 = arrayLines.get(i - 1).x;
int y1 = arrayLines.get(i - 1).y;
int x2 = arrayLines.get(i).x;
int y2 = arrayLines.get(i).y;
g2.drawLine(x1, y1, x2, y2);
}
Also, where do you set delete to true ever?
Edit
Regarding your comments:
...The paint method is an example, I call paintLines() several times.
This won't matter if paintComponent draws one and only one line.
Do I have to repaint everything , everytime i make a modification in the array?
At this point, probably, yes. Later consider doing your static background drawing to a BufferedImage and then displaying that in the paintComponent almost first thing, right after the super.paintComponent(g) call, and then drawing your non-static, your moving sprites directly in paintComponent. If you know for a fact that you've only altered a portion of a component, you can call one of the repaint(...) overload methods that suggest repainting a rectangular area of the component.
The problem is this code is working in another computer and in mine it misbehaves.
The issue for me is that I have no idea why the code would have a prayer of working on another system since it is broken code.
One of the other issues you are having is the fact that you are using an alpha based color as the background to an opaque component...
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
Swing only knows how to paint opaque or transparent components and makes these decisions based on the opaque state of the components.
When transparent, the API knows that it must first prepare the graphics context properly and secondly, paint all components that might be beneath this one.
When using an alpha based background color, the component is unable to clear the Graphics context for painting (as filling with a transparent color doesn't do anything), this tends to mean that the Graphics context still contains what ever was painted to it previously (as the Graphics context is a shared resource).
Instead, remove...
myGlassPane.setBackground(new Color(0, 255, 0, 0));
and use
myGlassPane.setOpaque(false);
which will give you the same effect.
It will also mean you won't need
g.clearRect(0,0,screenSize.width,screenSize.height);
and can simply remove the all the elements from the arrayLines instead, which will give you the same effect...just longer lasting...

Java Upside Down Text - Bug or Feature?

While playing around with the Java font class and Swing, I set the font size to a negative value.
I discovered that this makes the text be drawn upside down. Is this a bug or a feature? Can anyone explain why this behavior happens?
Try it out:
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class UpsideDown extends JFrame{
public UpsideDown(){
setSize(500,500);
setContentPane(new Panel());
setVisible(true);
}
public class Panel extends JPanel{
public void paintComponent(Graphics g){
Font f = new Font("Sans-Serif", Font.PLAIN, -50);
g.setFont(f);
g.drawString("Upside Down", 400, 100);
}
}
public static void main(String args[]){
new UpsideDown();
}
}
Seems like this is happening:
Swing draws your font's height downwards, because it multiplies the font size with the glyph height of the font. -50 * glyph_height is negative -> drawing downwards instead of upwards.
It also draws the glyph's (the letter's) width to the left, again because it multiplies your font size with the glyph width specified by the font.
It is a feature.
In Swing there are very few, if any, absolute quantities. Virtually all quantities are algebraic, meaning that they can be negative, and they can participate in algebraic calculations which may change their sign, as the case is when you multiply by -1. For example, a rectangle can have a negative width, and that's perfectly fine. A font is not an exception to this rule.
By all means, do try this at home:
Take any piece of code that you might have lying around which draws 2D graphics in a Graphics2D graphics context. (That would be a component that overrides paintComponent( Graphics g ) and begins with Graphics2D g2 = (Graphics2D)g;) For example, you might have a component that draws a graph, like so:
Before the drawing operations, insert the following two lines:
g2.scale( -1.0, -1.0 );
g2.translate( -getWidth(), -getHeight() );
Now check the result. It will all be perfectly up-side down, like this:
Needless to say, if you double the x scale without doubling the y scale, everything will be elongated, including the characters of the text, like so:
This demonstrates how everything, all the way down to the individual coordinates of character glyphs, is an algebraic quantity in Swing.
This gives a great degree of freedom. If you want to invert the graph but keep the text upright, one way to achieve it is to make the font size negative.

How to treat a shape painted with an algorithm like an object?

Ok dear folks, i've got this question and i don't really know a certain way to solve it.
I'm doing like a "Paint application" in java, i know everything is ready, but I need to paint the shapes with Computer Graphics Algorithms.
So, the thing is, once the shape is painted in the container how could I convert it like sort of an "Object" to be able to select the shape and move it around (I have to move it with another algorithm) I just want to know how could I know that some random point clicked in the screen belongs to an object, knowing that, I would be able to fill it(with algorithm).
I was thinking that having a Point class, and a shape class, if i click on the screen, get the coordinates and look within all the shapes and their points, but this may not be very efficient.
Any ideas guys ?
Thanks for the help.
Here is some of my code:
public class Windows extends JFrame{
private JPanel panel;
private JLabel etiqueta,etiqueta2;
public Windows() {
initcomp();
}
public void initcomp()
{
panel = new JPanel();
panel.setBounds(50, 50, 300, 300);
etiqueta = new JLabel("Circulo Trigonometrico");
etiqueta.setBounds(20, 40, 200, 30);
etiqueta2 = new JLabel("Circulo Bresenham");
etiqueta2.setBounds(150, 110, 200, 30);
panel.setLayout(null);
panel.add(etiqueta);
panel.add(etiqueta2);
panel.setBackground(Color.gray);
this.add(panel);
this.setLayout(null);
this.setVisible(true);
this.setSize(400,400);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g){
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.red);
g2d.setStroke(new BasicStroke(2));
dibujarCirculo_bresenham(g2d, 50, 260, 260);
dibujarCirculo_trigonometrico(g2d, 50, 130, 200);
}
/*This functions paints a Circle*/
public void dibujarCirculo_trigonometrico(Graphics g,int R,int xc,int yc)
{
int x,y;
for (int i = 0; i < 180; i++) {
double angulo = Math.toRadians(i);
x = (int) (Math.cos(angulo)*R);
y = (int) (Math.sin(angulo)*R);
g.drawLine(x+xc, y+yc, x+xc, y+yc);
g.drawLine((-x+xc), (-y+yc), (-x+xc), (-y+yc));
}
}
I assume that any image is a valid (isn't constrained to a particular set of shapes). To get an contiguous area with similar properties, try using a flood fill.
To colour in or move a particular shape around, you can use flood fill to determine the set of pixels and manipulate the set accordingly. You can set a tolerance for similar hue, etc so that it's not as rigid as in Paint, and becomes more like the magic selection tool in Photoshop.
There are a couple of approaches to take here depending on what precisely you want.
1) is to have objects, one for each drawn thing on screen, with classes like Circle and Rectangle and Polygon so on. They would define methods like paint (how to draw them on screen), isCLickInsideOf (is a click at this point on screen contained by this shape, given size/position/etc?) and so on. Then, to redraw the screen draw each object, and to test if an object is being clicked on ask each object what it thinks.
2) is, if objects have the property of being uniform in colour, you can grab all pixels that make up a shape when the user clicks on one of the pixels by using a floodfill algorithm. Then you can load these into some kind of data structure, move them around as the user moves the mouse around, etc. Also, if every object is guaranteed to have a unique colour, you can test which object is being clicked on by just looking at colour. (Libraries like OpenGL use a trick like this sometimes to determine what object you have clicked on - drawing each object as a flat colour on a hidden frame and testing what pixel colour under the mouse pointer is)

java swing: Incorrect dimensions on rotated JComponent after resizing

Is there any way to correct the dimensions for a rotated component?
In my swing application I’d like to be able to rotate a single panel, and have it respond correctly to resize events. The rotation is straight-forward, but on resizing, the height and width dimensions are reversed. It could almost would work if a call to setSize was called from an overridden paint() call to swap the dimensions, but that doesn’t work, because setSize causes another call to paint to occur, and recursion ensues... I’ve also tried adding a ComponentAdapter to handle the swapping on resize events, but got the same result.
So, here’s a simplified example of what I’m working with. The components here are buttons, but the logic applies to a JComponent like a JPanel too. Button c is rotated with a JXTransformer, but this doesn’t resize (it’s commented out in the code, but you can add the JXTransformer class to the classpath if you wish). If you compile the sample, try resizing the window and see how the rotated button behaves. Screenshot:
(It said I can't post screenshots, but these links appear to be live..)
http://i.stack.imgur.com/S3qmb.png
If I add in a scale transformation, the resizing is correct, but the component is distorted beyond usability. Screenshot:
http://i.stack.imgur.com/K4l9e.png
I’ve seen lots of questions on here that discuss the rotating part, but nothing about the resizing issue. For instance, A rotated square panel in Java GUI
Thanks!
Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class RotatingTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel(new BorderLayout());
// The button rotates, but the height/width dimensions are incorrect
RotatedButton a = new RotatedButton("ROTATED CONTENTS!");
JButton b = new JButton("Normal Contents");
JButton c = new JButton("Transformer Contents");
// JXTransformer t = new JXTransformer(c);
// t.rotate(Math.toRadians(90));
panel.add(a, BorderLayout.CENTER);
panel.add(b, BorderLayout.WEST);
// panel.add(t, BorderLayout.EAST);
panel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class RotatedButton extends JButton{
private static final long serialVersionUID = -3355833063753337573L;
RotatedButton(String string){
super(string);
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
AffineTransform txOrig = graphics.getTransform();
AffineTransform transformer = new AffineTransform(txOrig);
transformer.translate(width/2, height/2);
transformer.rotate(Math.toRadians(90));
transformer.translate(-height/2, -width/2);
// this scaling fits the button to the window, but distorts the contents
// double coef1 = (double)width / (double)height;
// double coef2 = (double)height / (double)width;
// transformer.scale(coef2, coef1);
// this line sets the rotation, comment out to disable
graphics.setTransform(transformer);
super.paintComponent(graphics);
graphics.setTransform(txOrig);
graphics.dispose();
System.out.println("Parent size: "+getRootPane().getParent().getSize());
System.out.println("this size: "+getSize());
}
}
So, the problem here is that JComponents do not care what is actually on them when setting their size. When you modify something in paintComponent() it only affects superficial portions of the component, and not anything that will actually be returned with from the component. What you are going to want to do, is remodel your paintComponent() method, or change how you are resizing the component.
Think of it like when you place an image on a button that is too large. The button will simply display what it can, and the rest does not matter as far as the button is concerned. Rotating the graphics on a JComponent is the exact same way. You can create the graphics ahead of time, and set your size to that (this is the complicated way), or if you are only rotating 90 degrees, simply change what you put int for setSize(). I have always found the second to be very simple, as you can constantly change the size of it with no extra code, you just have to remember to switch all your dimensions.
If, however, you wish to put it at an angle that is not a multiple of 90 degrees, you will have to make a larger square for the component to sit in. (using the Pythagorean theory) This may seem silly, but think about how all coordinates are made, from the top left corner. that corner defines that object's location, but if it is not at an absolute (the highest leftmost point) then anything working with that component would have to manually look at every part of the component instead of just calculating it.
Hope this helps, late as it may be.

Categories

Resources