I'm overriding the paintComponent method for a background in a JComponent and all is going well.
However, I want to start painting from the lower left corner instead of the upper left.
Do I need to transform something, or what?
Yes, you can use an AffineTransform to draw from the lower left corner:
Code:
public static void main(String[] args) {
JFrame frame = new JFrame("Test");
frame.add(new JComponent() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// save the "old" transform
AffineTransform old = g2d.getTransform();
// update graphics object with the inverted y-transform
g2d.translate(0, getHeight() - 1);
g2d.scale(1, -1);
// draw what you want
g2d.drawLine(0, 0, 300, 200);
// restore the old transform
g2d.setTransform(old);
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setVisible(true);
}
Related
I filled two rectangles in this way:
graphics.setColor(Color.RED);
graphics.translate(0, 150);
graphics.fillRect(0,0,65,65); // First rect
graphics.dispose(); // If remove this line, nothing will change
graphics.translate(0, 150);
graphics.fillRect(0,150,100,65); // Second rect
For some reason, only one rectangle is rendered :(
First, translates are additive. So your panel may not be tall enough to show the second rectangle. See the following code and comments.
graphics.setColor(Color.RED);
graphics.translate(0, 150); // 0,0 is now at 0,150
graphics.fillRect(0,0,65,65);
graphics.translate(0, 150); // 0,0, is now at 0,300
graphics.fillRect(0,150,100,65); // 0,150 is drawing at
// 0,450 with a size of 100,65
Since you are new to painting I will provide more information that may be of use.
don't subclass JFrame. It is a top level class meant to hold other components. Subclass JPanel and add that to it. Or create another class that subclasses JPanel.
override paintComponent(Graphics g) for doing your painting.
call super.paintComponent(g) first to process parent method bookkeeping
As a general rule, I find it helps to fill the object first then do the outline/border. It will simply overwrite the original figure with the outline.
it is considered standard practice to override getPreferredSize so other areas may not arbitrarily change the size.
call frame.pack() to size the frame and layout the components.
public class RectanglesExample extends JPanel {
JFrame f = new JFrame("Rectangles");
public static void main(String[] args) {
SwingUtilities.invokeLater((()->
new RectanglesExample()));
}
public RectanglesExample() {
setBackground(Color.WHITE);
f.add(this);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 700);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D graphics = (Graphics2D) g.create();
graphics.setColor(Color.RED);
graphics.translate(0, 150);
graphics.fillRect(0,0,65,65); // First rect at 0,150
graphics.translate(0, 150);
graphics.fillRect(0,150,100,65); // Second rect at 0,450
graphics.dispose();
}
I'm trying to achieve camera shake for my game by randomly setting the location of the JPanel which everything in the game is drawn on. After a bit of experimentation, I am certain that JPanel.setLocation(Point p) triggers a repaint, which I don't want to happen.
So the way I create screen shake is by specifying the intensity and the frames it should last. However, the effect always wore off far too quickly, so I did some experimentation. I found that the paintComponent(Graphics g) method of the JPanel was triggered multiple times within one frame, but only while there was screen shake (how really does not add much to the point).
This is how the effect is generated:
public void display(){
framesAlive++; //<-- used to track when the effect has worn off
int intensityX = (int) (Math.random() * vals[0] - vals[0] / 2);
int intensityY = (int) (Math.random() * vals[0] - vals[0] / 2);
pane.setLocation(new Point(intensityX, intensityY));
}
And this is the simplified version of the paintComponent method:
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (int i = 0; i < stockEffects.size(); i++) {
stockEffects.get(i).display(g);
}
}
Again, my guess is that setLocation() causes a repaint, which basically results in an infinite loop in which the paintComponent() method triggers the display() function, which triggers setLocation(), which triggers a repaint that starts the whole cycle again. This results in the framesAlive variable being incremented multiple times per frame, which throws the whole timing system off. Is there an elegant way to solve this?
you can use AffineTransform. this don't have to change objects real location.
it just change how to draw.
you can shake, rotate, flip, scale etc....
public static void main (String[] arg) {
MainFrame mainFrame = new MainFrame();
mainFrame.setVisible(true);
}
public static class MainFrame extends JFrame{
public MainFrame() {
this.setSize(600,600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
MainPanel mainPanel = new MainPanel();
this.add(mainPanel);
}
}
public static class MainPanel extends JPanel{
public void paint(Graphics g) {
super.paint(g);
// Panel Size = 400 X 400
g.drawLine(200, 0, 200, 400); // Y Axis
g.drawLine(0, 200, 400, 200); // X Axis
// Create Transform
AffineTransform at = new AffineTransform();
at.translate(200, 200); // Move Center Form (0, 0) To JPanel Center (200, 200)
// Change Transform
at.translate(-200, 0); // Move Center
// Set Transform To Graphics2D
Graphics2D g2d = (Graphics2D) g;
g2d.setTransform(at);
// Draw Rectangle By Graphics2D
g2d.fillRect(100, 100, 100, 100);
}
}
Im writing a River-Raid-Like game, and all images are drew with g.drawImage(...).
My question is: How to keep the content aspect ratio, and scale the content to fit the new window size ,when someone will resize the JFrame?
Is there any option like that? Can i do this without using JLabels and Layouts? If not, how to do this other way?
My code to draw things on the JPanel
private void doDrawing(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
drawStrings(g2);
mapa.drawMap(g2);
ArrayList ms = craft.getMissiles();
for (Object m1 : ms) {
Missile m = (Missile) m1;
g2.drawImage(m.getImage(), m.getX(), m.getY(), this);
}
g2.drawImage(craft.getImage(), craft.getX(), craft.getY(), this);
for (EnemyJet enemy : enemies) {
g2.drawImage(enemy.getImage(), enemy.getX(), enemy.getY(), this);
}
for (Fuel fuel : fuels) {
g2.drawImage(fuel.getImage(), fuel.getX(), fuel.getY(), fuel.getHeight(), fuel.getHeight(), this);
}
for (Obstacle o : obst) {
g2.drawImage(o.getImage(), o.getX(), o.getY(), this);
}
drawStrings(g2);
}
Also Jpanel constructor:
private void initBoard() {
addKeyListener(new TAdapter());
setFocusable(true);
setBackground(Color.WHITE);
setLayout(new GridBagLayout());
craft = new Craft(ICRAFT_X, ICRAFT_Y);
mapa = new Mapa();
setMinimumSize(new Dimension(WIDTH, HEIGHT));
initEnemiesAndAddThem();
czas = new Timer(delay, this);
czas.start();
}
JFrame constructor:
private void initGame()
{
add(new Plansza());
setTitle("Reeevah Raaid");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(WIDTH, HEIGHT);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLocationRelativeTo(null);
pack();
setVisible(true);
//setExtendedState(JFrame.MAXIMIZED_BOTH);
//setResizable(false);
}
You can scale the entire thing:
private void doDrawing(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
AffineTransform originalTransform = g2.getTransform();
g2.scale(getWidth() / (double) WIDTH, getHeight() / (double) HEIGHT);
// Do all drawing here
g2.setTransform(originalTransform);
}
Some notes:
The g2.setTransform(originalTransform) at the end is important. The Graphics object does not belong to you—it may be used to draw other components, so you need to leave it in the same state it had when it was given to you.
WIDTH and HEIGHT need to be cast to double, because in Java, if any operand of a division operation is a double, the result is double, but if they are both int, integer division is performed, which is definitely not what you want. (For instance, 3 / (double) 2 is 1.5, but 3 / 2 is exactly 1.)
Any image needs to be drawn as follows:
double fct=1.0*image.getWidth()/getWidth();
double fct2=1.0*image.getHeight()/getHeight();
if(fct2>fct) fct=fct2;
g2.drawImage(image, x, y, (int)(1.0*image.getWidth()/fct), (int)(1.0*image.getHeight()/fct), this);
This will leave a lot of empty area - but that is if you want to keep the aspect ratio.
--
To center the image you do the following:
int newWidth=(int)(1.0*image.getWidth()/fct);
int newHeight=(int)(1.0*image.getHeight()/fct);
g2.drawImage(image, getWidth()/2-newWidth()/2, getHeight()/2-newHeight()/2, newWidth, newHeight, this);
my goal is to draw some bufferedimage onto another. then all this stuff draw onto some other bufferedimage and so on. And finally draw this on top of a panel.
For now i'm trying to draw bufferedimage onto panel and nothing works. My bufferedimage looks completely white:
public class Main2 {
public static void main(String[] args) {
JFrame frame = new JFrame("asdf");
final JPanel panel = (JPanel) frame.getContentPane();
frame.setSize(500,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
panel.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
somepaint(panel);
}
});
}
private static void somepaint(JPanel panel) {
BufferedImage image = new BufferedImage(200,200,BufferedImage.TYPE_INT_ARGB);
image.getGraphics().setColor(Color.red);
image.getGraphics().fillRect(0, 0, 200, 200);
Graphics2D graphics = (Graphics2D) panel.getGraphics();
graphics.setColor(Color.magenta);
graphics.fillRect(0, 0, 500, 500);
graphics.drawImage(image, null, 0, 0); // draws white square instead of red one
}
}
thanks
Re:
private static void somepaint(JPanel panel) {
BufferedImage image = new BufferedImage(200,200,BufferedImage.TYPE_INT_ARGB);
image.getGraphics().setColor(Color.red);
image.getGraphics().fillRect(0, 0, 200, 200);
Graphics2D graphics = (Graphics2D) panel.getGraphics();
This is not how you draw inside of a JPanel or JComponent.
Don't call getGraphics() on a component as the Graphics object returned will be short-lived, and anything drawn with it will not persist. Instead do your JPanel's drawing inside of its paintComponent(Graphics G) method override. You will need to create a class that extends JPanel in order to override paintComponent(...).
Most importantly, to see how to do Swing graphics correctly, don't guess. You'll want to read the Swing Graphics Tutorials first as it will require you to toss out some incorrect assumptions (I know that this is what I had to do to get it right).
You need to rectify your parameters in the drawImage() call. Change this:
graphics.drawImage(image, null, 0, 0);
to
graphics.drawImage(image, 0, 0,null);
Check the Java docs for more details.
Can JPanels background be set to transparent?
My frame is has two JPanels:
Image Panel and
Feature Panel.
Feature Panel is overlapping Image Panel.
The Image Panel is working as a background and it loads image from a remote URL.
On Feature Panel I want to draw shapes. Now Image Panel cannot be seen due to Feature Panel's background color.
I need to make Feature Panel background transparent while still drawing its shapes and I want Image Panel to be visible (since it is doing tiling and cache function of images).
I'm using two JPanel's, because I need to seperate the image and shape drawing .
Is there a way the overlapping Jpanel have a transparent background?
Calling setOpaque(false) on the upper JPanel should work.
From your comment, it sounds like Swing painting may be broken somewhere -
First - you probably wanted to override paintComponent() rather than paint() in whatever component you have paint() overridden in.
Second - when you do override paintComponent(), you'll first want to call super.paintComponent() first to do all the default Swing painting stuff (of which honoring setOpaque() is one).
Example -
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TwoPanels {
public static void main(String[] args) {
JPanel p = new JPanel();
// setting layout to null so we can make panels overlap
p.setLayout(null);
CirclePanel topPanel = new CirclePanel();
// drawing should be in blue
topPanel.setForeground(Color.blue);
// background should be black, except it's not opaque, so
// background will not be drawn
topPanel.setBackground(Color.black);
// set opaque to false - background not drawn
topPanel.setOpaque(false);
topPanel.setBounds(50, 50, 100, 100);
// add topPanel - components paint in order added,
// so add topPanel first
p.add(topPanel);
CirclePanel bottomPanel = new CirclePanel();
// drawing in green
bottomPanel.setForeground(Color.green);
// background in cyan
bottomPanel.setBackground(Color.cyan);
// and it will show this time, because opaque is true
bottomPanel.setOpaque(true);
bottomPanel.setBounds(30, 30, 100, 100);
// add bottomPanel last...
p.add(bottomPanel);
// frame handling code...
JFrame f = new JFrame("Two Panels");
f.setContentPane(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
// Panel with a circle drawn on it.
private static class CirclePanel extends JPanel {
// This is Swing, so override paint*Component* - not paint
protected void paintComponent(Graphics g) {
// call super.paintComponent to get default Swing
// painting behavior (opaque honored, etc.)
super.paintComponent(g);
int x = 10;
int y = 10;
int width = getWidth() - 20;
int height = getHeight() - 20;
g.drawArc(x, y, width, height, 0, 360);
}
}
}
Alternatively, consider The Glass Pane, discussed in the article How to Use Root Panes. You could draw your "Feature" content in the glass pane's paintComponent() method.
Addendum: Working with the GlassPaneDemo, I added an image:
//Set up the content pane, where the "main GUI" lives.
frame.add(changeButton, BorderLayout.SOUTH);
frame.add(new JLabel(new ImageIcon("img.jpg")), BorderLayout.CENTER);
and altered the glass pane's paintComponent() method:
protected void paintComponent(Graphics g) {
if (point != null) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.3f));
g2d.setColor(Color.yellow);
g2d.fillOval(point.x, point.y, 120, 60);
}
}
As noted here, Swing components must honor the opaque property; in this variation, the ImageIcon completely fills the BorderLayout.CENTER of the frame's default layout.
In my particular case it was easier to do this:
panel.setOpaque(true);
panel.setBackground(new Color(0,0,0,0,)): // any color with alpha 0 (in this case the color is black
(Feature Panel).setOpaque(false);
Hope this helps.
To set transparent you can set opaque of panel to false like
JPanel panel = new JPanel();
panel.setOpaque(false);
But to make it transculent use alpha property of color attribute like
JPanel panel = new JPanel();
panel.setBackground(new Color(0,0,0,125));
where last parameter of Color is for alpha and alpha value ranges between 0 and 255 where 0 is full transparent and 255 is fully opaque
public void paintComponent (Graphics g)
{
((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.0f)); // draw transparent background
super.paintComponent(g);
((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,1.0f)); // turn on opacity
g.setColor(Color.RED);
g.fillRect(20, 20, 500, 300);
}
I have tried to do it this way, but it is very flickery
As Thrasgod correctly showed in his answer, the best way is to use the paintComponent, but also if the case is to have a semi transparent JPanel (or any other component, really) and have something not transparent inside. You have to also override the paintChildren method and set the alfa value to 1.
In my case I extended the JPanel like that:
public class TransparentJPanel extends JPanel {
private float panelAlfa;
private float childrenAlfa;
public TransparentJPanel(float panelAlfa, float childrenAlfa) {
this.panelAlfa = panelAlfa;
this.childrenAlfa = childrenAlfa;
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(getBackground());
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, panelAlfa));
super.paintComponent(g2d);
}
#Override
protected void paintChildren(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(getBackground());
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_ATOP, childrenAlfa));
super.paintChildren(g);
}
//getter and setter
}
And in my project I only need to instantiate Jpanel jp = new TransparentJPanel(0.3f, 1.0f);, if I want only the Jpanel transparent.
You could, also, mess with the JPanel shape using g2d.fillRoundRect and g2d.drawRoundRect, but it's not in the scope of this question.