Performance-minded way to draw a scaled image in a JPanel - java

I have a Java Application that includes functionality to load in photos to show a user that they choose, but I want the width of each photo to be a max of 200 pixels.
My current solution is to use a class that subclasses JPanel, and in its constructor, I load the image and calculate the correct size.
I override paintComponent(Graphics g) and draw the image there using:
super.paintComponent(g);
g.drawImage(image.getScaledInstance(width, height, Image.SCALE_FAST), 0, 0, this);
However, this resizes the image once every frame, since paintComponent is called every frame, and I’d prefer to only have to resize the image once and draw that.
Is there a way to achieve better performance, doing what I need it to do?

Related

Stop JScrollPane from redraw JPanel

I have a JPanel with a JScrollPane sorounding it, the problem is that when i use the JScrollPane the JPanels redraw methode get called. I want to disable that because my JPanel redraw by its self at the right time.
I want it so it just updateds the getClipBounds() for the paint methode but withoud calling the paint methode.
You can't do that - since the viewport displays different parts of the contained JPanel, depending on the position of the scrollbar, the areas that have to be repainted might in fact be newly revealed and might not have been painted before.
Since JScrollPane doesn't know how the contained Component is implemented and whether it repaints its entire area or only the area that needs repainting, it forces the contained Component to redraw itself upon scrolling.
However, you can instead render the content you want to show to a bitmap, and then paint the bitmap in the paintComponent(Graphics) method. Thus, you effectively buffer your painted content and can initiate an update to the buffered bitmap whenever it suits you.
In order to paint onto a bitmap, you can do this:
BufferedImage buffer; // this is an instance variable
private void updateBuffer(){
// Assuming this happens in a subclass of JPanel, where you can access
// getWidth() and getHeight()
buffer=new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g=buffer.getGraphics();
// Draw into the graphic context g...
g.dispose();
}
Then, in your JPanel, you override the paintComponent method:
public void paintComponent(Graphics g){
g.drawImage(buffer, 0, 0, this);
}

Image only paints within original bounds

I want to animate several jpgs in a JFrame, I'll show you some extracts:
My class constructor that extends JFrame
super(title);
setLayout(null);
setResizable(false);
setSize(Settings.windowWidth, Settings.windowHeight);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
My class constructor that extends JPanel
i = new ImageIcon(image).getImage();
setSize(i.getWidth(this),i.getHeight(this));
setBounds(x, y, i.getWidth(this), i.getHeight(this));
The overwritten method
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(i, getX(), getY(), null);
}
Yes, I know null layout isn't preferrable, but unless you have a better idea for absolute positioning I'll stick with it for now.
Above code does paint the image, starting at (x,y), but not completely.
For 50 and 100 it shows this:
Which is pretty much: It only paints the image within a 256x256 box (image dimensions) from 0,0, no matter where it has been relocated to.
Any advice, help, solutions, suggestions?
If you need more code, ask me, just don't feel like putting everything around it in here, too ;)
There is no need for custom painting:
Add the ImageIcon to a JLabel and add the JLabel to a JPanel
Change the location of the label on the panel when you want to animate it.
Or, if you do custom painting then there is no need for a null layout.
You override the getPreferredSize() method of the JPanel and add your panel to the frame.
Then in the paintComponent() method you can paint the image where every you want withing the bounds of the preferred size that you set.
My gut feeling is you don't understand how component painting actually works...
First, you do this...
i = new ImageIcon(image).getImage();
setSize(i.getWidth(this),i.getHeight(this));
setBounds(x, y, i.getWidth(this), i.getHeight(this));
Then you do this...
g.drawImage(i, getX(), getY(), null);
which seems to be painting the image at a offset position from the components origin, but since the component is sized to match the size of the image, the image is cropped at the component boundaries.
When a component is painted, the Graphics context's origin is set to the components location, meaning that 0x0 is now the components top/left corner.
You can test this by using setBorder(new LineBorder(Color.RED)), which will show you the physical bounds of the component
In your case, something like
g.drawImage(i, 0, 0, this);
In your case, you should be moving the component not the image.
Personally, I'd add the JPanel to the JFrame using a BorderLayout, then you can simply move the image anywhere within the context of the component itself. Remember to override getPreferredSize to return an appropriate size for your purposes so the frame can be packed around it more effectively.
It's tricky to do animation with components (not impossible, there's just a lot to take into account), generally it's just easier to paint directly to a canvas like a JPanel, but that's me
See Painting in AWT and Swing and Performing Custom Painting for more details about how painting works

does java bufferedImage really give a black canvas?

I was trying to achieve the same thing with two different approaches. I want to draw a simple rectangle.
In the first approach, I simple get g object and draw directly on it.
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(100, 100, 50, 50);
}
In the second approach, i get a image canvas draw on it and then draw the image on the graphics object of jpanel.
public void paintComponent(Graphics g){
super.paintComponent(g);
BufferedImage img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
Graphics a = img.getGraphics();
a.drawRect(100, 100, 50, 50);
g.drawImage(img, 0, 0, 500, 500, null);
}
I was hoping both would result in similar output but the one below where i am drawing an image gives white rectangle on black background while the one on the top gives black rectangle on a white background. Why is this so?
When you do custom painting on a component. The background is painted first. It appears you are extending JComponent which will simply paint a white background. Other Swing components will use the color specified by the setBackground() method, for example a JPanel defaults to the grey color. The graphics object is then set to be the foreground color of the component so you can do your custom painting. The default foreground color is black.
When you create a BufferedImage it looks like it is filled in with a black background by default and then the color of the Graphics object is set to WHITE so you can do custom painting and it will show up on the black background. You can always fill the background of the buffered image to be any color and then change the graphics object to use any color for the foreground.
You don't call setColor(), so the rectangle is drawn with the default colour. The default colours are different in the two examples, which is why you should always set the colour explicitly (and fill the background with your background colour).
You are using TYPE_INT_RGB. You can explore the other types at this link. TYPE_INT_ARGB supports a transparent background, which is what you may be looking for.
But it is not as simple as setting the type. The background color is also affected by the component type when you embed it, or by the file format when you save it as a file. PNG, for example, is a file format that supports transparency. You will have to experiment with different component types to see whether they affect transparency or not.

How to rescale painted contents of JPanel

I have a window which contains upper JPanel where figures are painted and the lower JPanel where figures are listed in the JList the list looks like:
[minature][figure.toString()]
The minature is JPanel of a size 10x10. I would like it to contain minature of drawn figure in the upper Panel. By it I mean.
class minaturePanel{
Figure f;....
public void paint(Graphics g){
g.drawFullSizeFigure(f);
g.rescaleWholeInsidesOfThePanel();
}
}
This example paints into a BufferedImage and uses AffineTransformOp to do the scaling.
You may also want to look at JScrollNavigator, examined here. It lets you navigate on a thumbnail image of your entire component, seen at full size in an adjacent scroll pane.
It seems you want to draw your full figure to an image, return the image, and then rescale the image then draw the image on the panel which will be the right size as the panel now. Actually you can rescale the image while drawing it.
If the Figure has no capacity to scale it self. You can use a AffineTransformation to scale the Graphics context your self. Just don't forget to restore the previous transformation...
Check out Transforming Shapes, Text, and Images for more details.

Make an image follow the frame size

I'm using Java Swing to make a GUI.
I need to present to the user some information printed on images (which are generated at run time as BufferedImage objects).
What I am doing: I put a JPanel on my JFrame, and when the system calls the paint(Graphics g) method I draw the image on g -> (g.drawImage(buffImg,0,0,null)).
The thing I do not like is: When I resize the frame, the image remains the same, I only expand the field of view. I'd like instead to make the image "stretch" with the frame when I resize it.
Is there an efficient way of doing it? (I thought I could create a new resized image, the size of the frame, each time I refresh the graphics, but I'm updating the image several times per second, so it would be a really heavy task..)
Change:
g.drawImage(BuffImg,0,0,null):
To:
g.drawImage(BuffImg,0,0,getWidth(),getHeight(),this):

Categories

Resources