Graphics2D Scaling twice in PaintComponent() - java

Why does this code output two lines that are the same size?
import java.awt.*;
import javax.swing.*;
public class G2Scale extends JPanel{
public static void main(String args[]) {
G2Scale g = new G2Scale();
g.setPreferredSize(new Dimension(200, 200));
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(g);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.scale(0.5, 1.0);
g2.drawLine(5, 50, 100, 50);
g2.setColor(Color.GREEN);
g2.scale(1.0, 1.0);
g2.drawLine(5, 100, 100, 100);
}
}
I would expect these lines to be different sizes because they are scaled differently. From what I am seeing I am thinking that the scale is based off of the previous scale. Am I right about this?
If this is true, how would I get the second line to be scaled to what I thought it should be?
Thanks

All methods you call on a Graphics object that don't output something but instead change a property of it (like setColor, setFont, and so on), are stored in the context of the graphics object. Actually, you should think of a Graphics instance as a graphics context that contains and abstract all the information you need to draw into the screen.
So basically, yes, your second scale is based on the first, since the first one changes the graphics context and the second one acts on top of it.
There're two ways to change this behavior:
Reset the state your Graphics instance by aplying the opposite of your first change (in this case, the inverse scalling).
Make a copy of the Graphics object before applying any context change.
I'm more inclined to the second option, but here're some examples of both:
Graphics2D g2 = (Graphics2D) g;
// resetting the context state
g2.scale(0.5, 1.0);
g2.drawLine(5, 50, 100, 50);
g2.scale(2, 1.0);
// using a copy of the context
// note: casting is mandatory since 'create' returns a Graphics object
Graphics2D g2copy = (Graphics2D)g2.create();
g2copy.scale(1.0, 1.0);
g2copy.drawLine(5, 100, 100, 100);
// this one doesn't have any scale applied
g2.drawLine(5, 150, 100, 150);

Related

Java AffineTransform working differently on different systems

I have started using AffineTransform to rotate text I'm drawing with Graphics2D and I noticed it would sometimes work fine and other times it wouldn't when I just realized that it works always as expected on my Windows 7 PC but never on my Windows 10 laptop.
I use Java 15.0.1 on both systems.
Here is a small test case to show you my point:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class AffineTransformTest extends JPanel {
private static final int SIZE = 40;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
int centerX = getWidth()/2;
int centerY = getHeight()/2;
g2.setColor(Color.BLACK);
g2.drawRect(centerX - SIZE/2, centerY - SIZE/2, SIZE, SIZE);
AffineTransform at = g2.getTransform();
at.setToRotation(Math.toRadians(45),centerX, centerY);
g2.setTransform(at);
g2.setColor(Color.RED);
g2.drawRect(centerX - SIZE/2, centerY - SIZE/2, SIZE, SIZE);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AffineTransformTest test = new AffineTransformTest();
frame.setContentPane(test);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
frame.setVisible(true);
}
}
The black rectangle is a regular one centered in the middle of the JPanel.
The red rectangle is drawn after a 45° rotation and is supposed to share the same center as the black one (as shown on the first picture).
My laptop however produces the result shown on the second picture.
Expected result - Windows 7 |
Incorrect result - Windows 10
How can this be?
This is likely because you are violating the contract of the paintComponent() method, where the Javadoc states
If you override this in a subclass you should not make permanent
changes to the passed in Graphics. For example, you should not alter
the clip Rectangle or modify the transform. If you need to do these
operations you may find it easier to create a new Graphics from the
passed in Graphics and manipulate it.
So maybe subtle differences between the Windows 7 and Windows 10 native implementations of the UI is why one breaks and the other doesn't.
To see if this is indeed the cause, try changing the line Graphics2D g2 = (Graphics2D)g; to Graphics2D g2 = (Graphics2D)g.create(); which will make a clone of the Graphics object and leave the original unchanged.
Edit to clarify the correct answer:
The issue was caused by the at.setToRotation call clearing the default scaling transform on the graphics context after drawing the black square and before drawing the rotated red square. The recommended solution is to call the rotate(theta,x,y) form of the method directly on the Graphics2D object which preserves the existing scale transform.

No Graphics2D.draw method in java?

I use NetBeans and the last version of JDK. For some reason there is no Graphics.draw() method, though java.awt.geom.Line2D and java.awt.Graphics2D are imported. How do I draw a Line2D.Double element?
Shape pLine;
private void playerDraw(){
Graphics g2 = getGraphics();
pLine = new Line2D.Double(px, py, Math.cos(angle)*10+px,Math.sin(angle)*10+py);
g2.drawRect(px-5, py-5, 10, 10);
g2.draw(pLine); //this doesn't compile(cannot find symbol)
}
Your main problem is that you're using a Graphics object as if it were a Graphics2D object, but it's not and as the Graphics class Java API entry will show you, the Graphics class does not have a draw method, while Graphics2D does. I think that you're missing a key line, something like:
Graphics g = getGraphics();
Graphics2D g2 = (Graphics2D) g; // the missing line
But having said that, you are using Graphics incorrectly as you should avoid getting it by calling getGraphics() on a Swing component since this gives you an unstable short-lived Graphics object whose use risks causing short-lived images or NullPointerExceptions, but rather you should do your drawing within the JComponent's paintComponent(Graphics g) method.
You missed to declare pLine variable in your Class:
Eg.
public class Example
{
public Line2D.Double pLine;
private void playerDraw(){
Graphics g2 = getGraphics();
pLine = new Line2D.Double(px, py,Math.cos(angle)*10+px,Math.sin(angle)*10+py);
g2.drawRect(px-5, py-5, 10, 10);
g2.draw(pLine); //this doesn't compile(cannot find symbol)
}
}

Issues with drawing multiple circles (drawOval / Java)

My issues is the following: My actual project (of which the code below is a simplified version of) involves many concentric circles (each with a different colour) and animation utilising a Timer. The circles are drawn using the drawOval method.
My problem is that when these concentric circles are drawn, there appears to be loads of gaps in the outline of these circles, which I'm guessing is something to do with the fact that a circle is composed of pixels and lines as is any shape so the appearance of roundness is an illusion. I say this because when I swap the drawOval method for drawRect the painting looks as you would expect.
When messing around with other people's codes I saw that using RenderingHints somehow solved this problem however slowed down the animation beyond a point that I felt was acceptable.
Below is a screenshot of what is painted. Rather than seeing a solid opaque circle (as all of the circles drawn have the same colour in this example) we see this:
Here is my simplified code:
Test10
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class Test10 extends JPanel {
Circle[] circles;
public static void main(String[] args) {
new Test10().go();
}
void go() {
JFrame frame = new JFrame("Circle Test");
frame.getContentPane().add(this);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
circles = new Circle[200];
for (int i = 0; i < 200; i++) {
circles[i] = new Circle(i, ((2 * ( 200 - i) + 1)));
}
repaint();
frame.setPreferredSize(new Dimension(500,500));
frame.pack();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
for (Circle circle : circles ) {
circle.draw(g);
}
}
}
Circle
import java.awt.Graphics;
public class Circle {
int topLeft;
int diameter;
public Circle(int topLeft, int diameter) {
this.topLeft = topLeft;
this.diameter = diameter;
}
void draw(Graphics g) {
g.drawOval(topLeft, topLeft, diameter, diameter);
}
}
Could anyone explain to me a) Why this is happening and b) How to overcome this problem.
UPDATE
Having tried various methods including starting with the outermost circle and using fillOval instead of drawOval, and using a higher stroke value, I still find I have a problem with certain artefacts appearing similar to the screenshot Pavel posted. Here is a screenshot from my full application running the animation, if you look carefully you can see inconsistencies in the colour of mostly any given circle, resulting in these strange results. Their distribution actually follows the same pattern as the screenshot posted above so clearly something fundamental isn't being addressed by these options. Here is my screen shot:
It is impossible to draw perfect circle.
Try using the following method
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
int i = 0;
for (Circle circle : circles ) {
Shape circle2 = new Ellipse2D.Double(i++, i, circle.diameter, circle.diameter);
g2d.draw(circle2);
}
}
You said you tried with RenderingHints, and it slowed your animation, but you haven't give us any code with animation, so maybe try my code (it would be good to see animation implementation). It looked better, but still not what you wanted. Setting stroke to another value will solve this (set to at least 2). Another one is to use .fill() instead of .draw(). I know that it is not perfect, but you may try it.
ANOTHER IDEA
I thought, that maybe you could add some blur to your image, so those artifacts are not visible?
I haven't done it before, but I found this (found HERE):
private class BlurGlass extends JComponent {
private JFrame f;
public BlurGlass(JFrame f) {
this.f = f;
setOpaque(false);
setFocusable(false);
}
public void paintComponent(Graphics g) {
int w = f.getWidth();
int h = f.getHeight();
setLocation(0, 0);
setSize(w, h);
g.setColor(new Color(0, 0, 0, 0.3f));
g.fillRect(0, 0, w, h);
}
}
now somwhere in go() method:
frame.setGlassPane(new BlurGlass(frame));
frame.getGlassPane().setVisible(true);
It looks a lot better for me. Play a bit with this GlassPane color (try changing .3f to some other value).
You might want to make the Stroke bigger. I've had luck with this in situations similar to yours
You can try by adding this line in your Circle class inside draw function:
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//and draw the Oval on g2
Also another solution might be to fill the circles:
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2.fill(circle);
That happens because a computer cannot draw a perfect circle.
A computer uses square pixels to approximate a real circle but its just not possible to achieve perfection and that results in some pixels not being shown
Drawing a filled circle will help you
a detailed explanation
Can you please try fillOval method instead of drawOval.
g.fillOval(topLeft, topLeft, diameter, diameter);
Reverse your idea. Start with the outermost circle, then draw the inner circle and so on, finishing with the smallest circle. You should use fillOval in the process.
The other rendering hint that is often useful for circles/ovals is
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
See my other answer with more details and example.

Java 2D, Translation with the same amount on X and Y axis leads to different results

I'm just playing around with Java 2D for a weekend-project. What I try to accomplish is simply to load and draw an image at position 50,50 away from the origin. Now when I try to do exactly that, with
g2d.drawImage(imageLeft, 50, 50, 200, 200, null);
then I get the following:
Frankly, this confuses me a little bit. Why does the x-part of the translation get scaled differently than the y-part? What is going on here? Must be an obvious beginner's mistake, but I cannot figure it out the reason for this behavior for the life of me ;-)
The complete code I use to draw the image is:
final IsogenWindow window = new IsogenWindow();
final BufferedImage imageLeft = loadImage(new File(getClass().getResource("/texture/tile1.png").toURI()));
final Graphics2D g2d = Graphics2D)window.getGraphics();
window.setSize(1000, 1000);
window.setVisible(true);
g2d.drawImage(imageLeft, 50, 50, 200, 200, null);
EDIT 1:
To try out DontRelaX' suggestion, I changed my code so a panel is added to the JFrame and its Graphic2D object is used for drawing:
getContentPane().setLayout(null);
this.renderPanel = new JPanel();
getContentPane().add(renderPanel);
final Graphics2D g2d = (Graphics2D)renderPanel.getGraphics();
...
g2d.drawImage(imageLeft, 50, 50, 200, 200, null);
Unfortunately, the result is still the same. But I think DontRelaX is on the right track. Any further suggestions?
EDIT 2: Basically DontRelaX' answer was correct. I indeed used not the renderPanel's Graphics2D but instead still the JFrame's Graphics2D object.
Cheers,
nanoquack
Pixels count from left top corner of window, not gray place. Add panel to your window and draw on it.

How to call a method using ActionListener in GUI using Java2D

I am writing a graphical user interface program in Java that implements ActionListener. The program is a guitar chord visualizer using 2D graphics. I have a toolbar where the user selects what chord to be displayed. So in the actionPerformed(ActionEvent e) method, when the user selects a certain chord, that choice calls a method where the chord is displayed. However, when I test the program I get tons of errors. Here are the error messages
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at fretboard.displayAMajor(fretboard.java:493)
at fretboard.actionPerformed(fretboard.java:74)
at java.awt.MenuItem.processActionEvent(MenuItem.java:650)
at java.awt.MenuItem.processEvent(MenuItem.java:609)
at java.awt.MenuComponent.dispatchEventImpl(MenuComponent.java:343)
at java.awt.MenuComponent.dispatchEvent(MenuComponent.java:331)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:707)
at java.awt.EventQueue.access$400(EventQueue.java:82)
at java.awt.EventQueue$2.run(EventQueue.java:663)
at java.awt.EventQueue$2.run(EventQueue.java:661)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:98)
at java.awt.EventQueue$3.run(EventQueue.java:677)
at java.awt.EventQueue$3.run(EventQueue.java:675)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:674)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Also, I am not using JPanel or JFrame. I am simply using Frame.
What is wrong with my program? Thanks!
Here is some of my code (The program is over 500 lines as of now).
public class fretboard extends Frame implements ActionListener{
public static void main(String[] args) {
Frame frame = new fretboard();
frame.setSize(1280, 960);
frame.setVisible(true);
}
/**
* Create the menu bar and set title
*/
public fretboard() {
// Change the title of the window
setTitle("Fretboard");
// Create a menu bar where user will be given choice of chords
MenuBar mb = new MenuBar();
setMenuBar(mb);
Menu menu = new Menu("Chords");
mb.add(menu);
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if("A Major".equals(command)) {
displayAMajor();
}
This method contains the bulk of my program. Here are just a few lines.
public void paint(Graphics g) {
// Declare local variables
int h = 40, w = 26, x = 695, y = 230;
Graphics2D g2 = (Graphics2D) g;
Font font = new Font("SansSerif", Font.BOLD, 28);
Font font1 = new Font("SansSerif", Font.BOLD, 18);
// Declare the note variables
// First string
Ellipse2D E1 = new Ellipse2D.Double(x, y-110, w, h);
// Open the image
File fretBoardFile = new File("/Users/macbook/desktop/Gibson_Fretboard.jpg");
BufferedImage bi = null;
try {
bi = ImageIO.read(fretBoardFile);
g.drawImage(bi, 25, 25, null);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Draw notes
// Draw the E note on the open 1st string
// Change color to blue
g2.setColor(Color.blue);
g2.draw(E1);
g2.fill(E1);
g2.setColor(Color.white);
g2.setFont(font);
g2.drawString("E", x+5, y-80);
// Change color back to blue
g2.setColor(Color.blue);
public void displayAMajor() {
// Declare local variables
int h = 40, w = 26, x = 695, y = 230;
Graphics g = null;
Graphics2D g2 = (Graphics2D) g;
//Graphics2D g2 = new Graphics();
Font font = new Font("SansSerif", Font.BOLD, 28);
// Declare notes
Ellipse2D E1 = new Ellipse2D.Double(x, y-110, w, h);
// Display notes for the A Major chord
// Draw the E note on the open 1st string
// Change color to red
g2.setColor(Color.red);
g2.draw(E1);
g2.fill(E1);
g2.setColor(Color.white);
g2.setFont(font);
g2.drawString("E", x+5, y-80);
// Change color back to blue
g2.setColor(Color.blue);
repaint();
}
Aha, casting variables, null, graphics, I'm going to take a guess -- you're trying to save the Graphics object as a class field, you're casting it to Graphics2D, and it's null. If so, you'll want to read up on how to do drawing with Swing because that's not how it's done. You must use the Graphics object provided by the JVM and passed into a JComponent's (such as a JPanel's) paintComponent(Graphics g) method. This is a decent link to start learning about Performing Custom Painting in Swing.
And again, you'll want to refactor your code because your class sounds way too big. Also you'll want to learn and use Java naming conventions. Class names should start with upper case letters for instance.
Yikes, it's even worse:
Graphics g = null; // ******* this is null!!!!
Graphics2D g2 = (Graphics2D) g; // ***** it's *STILL* null!!
//Graphics2D g2 = new Graphics();
Font font = new Font("SansSerif", Font.BOLD, 28);
Ellipse2D E1 = new Ellipse2D.Double(x, y-110, w, h);
g2.setColor(Color.red); // ***** it's *STILL* null!!
g2.draw(E1); // ***** it's *STILL* null!!
g2.fill(E1); // **** etc...
You're using a variable that is null, so it should be no surprise that it throws a NPE. Please read the tutorial that I've linked to.
You'll want to do your drawing either in the paintComponent(...) method override of a JPanel or other JComponent-derived class, either that or in a BufferedImage by extracting its Graphics object and drawing with that. If you're creating static images such as chord tabs, then BufferedImages will likely be the way to go, and then display them either in a ImageIcon held in a JLabel or in a paintComponent method.
Just what did you expect from:
Graphics g = null;
Graphics2D g2 = (Graphics2D) g;
Also, your paint method breaks one of the most important rules of custom painting, it fails to call super.paint this is going to create more problems then it is worth listing.
The preferred method to override when performing custom painting is paintComponent, but, because you're overriding Frame (??) it doesn't have a paintComponent method.
Here's some ideas...
Use Swing components over AWT. They're easier to use.
Extend from something like JPanel and perform your custom painting on it instead. This provides you with more flexible design choices and cause less issues.
Always call super.paintXxx, if you don't, expect things to blow up in your face.
All painting MUST be done from the a paint method. You do not control over the paint process, just accept it and move on. You can encourage a repaint, but you must only paint from within the context of a paint method.
The Graphics context is controlled by the system. If you want to paint on it, you must wait for one to become available, see previous point. This context can change, so you should never maintain a reference to it. The Graphics context is shared, failing to honor the paint chain will result in paint artifacts appearing
You might like to make the time to have a read through
Performing Custom Painting
Painting in AWT and Swing
2D Graphics

Categories

Resources