Currently I have a JPanel with its paintComponent overridden with lots of image processing based on various states. When an event occurs (infrequent) many of the states change and the images that are drawn change. It doesn't seem the best way to keep doing all the processing every time the paintComponent is it possible to do something like when an event occurs draw everything to a Graphics2D instance and then merge this with the paintComponent one? Is this also the best way to do it?
As MadProgrammer suggested, storing rendered output can help you.
When the event that might change the image occurs, you can draw stuff to a BufferedImage like following.
private BufferedImage renderedImage;
public void triggerEvent(int width, int height) {
this.renderedImage = new BufferedImage(width, height, TYPE_INT_ARGB);
Graphics2D g = this.renderedImage.createGraphics();
// Paint things on g.
}
#Override
public void paintComponent(Graphics g) {
g.drawImage(this.renderedImage, 0, 0, this);
}
Hope this helps.
Related
I am coding a Swing Application, it uses Apache PDFBox to draw a PDF page to the Graphics2D object of a JPanel in the paintComponent method. The drawing takes a while, so when my application needs to display many PDF pages simultaneously, it get's slow and laggy. I know, since the JPanel I draw the PDF page to is part of the GUI, it needs to be drawn in the Event Dispatch Thread. But is there absolutely no possibility to draw each JPanel in an own thread? Like using SwingWorker or so?
Example code (simplified):
public class PDFPanel extends JPanel {
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g2 = (Graphics2D) graphics;
int scale = 1; // (simplified this line)
g2.setColor(getBackground());
g2.fillRect(0, 0, getWidth(), getHeight());
try {
pdfRenderer.renderPageToGraphics(pageNumber, g2, (float) scale, (float) scale);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Use a BufferedImage image field. It has a method createGraphics() where you can draw to. Afterward call Graphics.dispose() to clean up resources.
Then in paintComponent check the availability of the image, for displaying it.
The rendering can be done in a Future, SwingWorker or whatever. You are right that heavy operations should never be done in paintComponent especially as it may be called repeatedly.
Better launch the rendering in the constructor, or in your controller.
BufferedImage image = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
try {
pdfRenderer.renderPageToGraphics(pageNumber, g2, (float) scale, (float) scale);
} finally {
g2d.dispose();
}
Initially width and height are not filled, so better use width and height from the PDF. Also not that a Graphics2D allows scaling; you could easily add zooming.
How to atomically handle passing the rendered image is probably clear.
I am thinking of some different ways of rendering the game into the screen.
I've currently working on a indie game and usually I just paint the textures on the screen right away:
public void paintComponent(Graphics g){
for(int i=0;i<textures.size;i++){
g.drawImage(texture.get(i).getTexture(),texture.get(i).getX(), texture.get(i).getY(), null)
}
}
But this is just an example. The problem with this is that if you have too many textures, you might start noticing some flickering or the process of drawing because when the g.drawImage is called, it paints the texture into the screen right after.
So I've though of a solution however I am not sure if it's a good way of doing it.
What I've done is instead of painting the textures into the screen, I simply draw them into the BufferedImage. After that, I simply draw the BufferedImage into the screen.
Here is an example:
public void paintComponent(Graphics g){
g.drawImage(bufferRender(), 0, 0, null);
}
public BufferedImage bufferRender(){
BufferedImage render = new BufferedImage(Main.window.getWidth(), Main.window.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = render.getGraphics();
for(int i=0;i<textures.size;i++){
g.drawImage(texture.get(i).getTexture(),texture.get(i).getX(), texture.get(i).getY(), null)
}
return render;
}
Now the question is... Is it a fine way of doing this?
I wrote codes that loads images into JEditorPane using HTMLEditorKit. I know how to resize the image using HTML. But the problem is the loaded image losses quality. I am trying to find ways to resize without losing quality.
As Andrew Thompson suggested ,
extend the HTMLEditorKit and override public View create(Element element)
of HTMLFactory .
extend from ImageView and override the public void paint(Graphics g, Shape a) method. Get the image and resize it.
getScaledInstance(WIDTH, HEIGHT, Image.SCALE_AREA_AVERAGING)
with your favourite scaling HINT and finally draw.
I'm unclear on the exact use-case the OP is after, but I've used the following simpler alternative for an HTML help viewer – override the paint() method of the JEditorPane to set a rendering hint:
JEditorPane editor = new JEditorPane() {
#Override public void paint(Graphics g) {
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
super.paint(g2);
g2.dispose();
} else {
// Print and other non-Graphics2D contexts.
super.paint(g);
}
}
};
This provides nice and easy improvement with non-integral scaling factor hidpi screens, for example. The quality would still be suboptimal if an image is scaled more than half down of the original. For that use-case a custom HTMLEditorKit / HTMLVactory producing a custom ImageView would be necessary, as others have pointed out.
in my application I have a cross road picture in the background and I want to draw traffic lights on the top of it (black rectangle with 3 circles)
The problem is, I cannot see the rectangle at all, as if it was under the image or something. And if I switch the order in which the items are painted, I get all black image.
Do you have any idea how this can be solved?I am new to graphics and searched similar questions, but none helped me.
Thank you.
public MainFrame() throws HeadlessException {
super("semafor");
crossroad = new ImageIcon("cross.png");
initFrame();
initComponents();
sem1 = new Semafor(true, 100, 100);
add(sem1);
repaint();
setVisible(true);
}
//here I paint the image
#Override
public void paint(Graphics g) {
super.paint(g);
g.drawImage(crossroad.getImage(), 0, 45, this);
}
//and in class Semafor i paint the actual traffic lights
#Override
public void paint(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.darkGray);
//and then the circles
}
The first thing I'm noticing is that you are calling <unknown>.getWidth() and <unknown>.getHeight() for the rectangle size. If it's covering the entire image, this suggests that it is getting that width and height from the panel it is being drawn on.
A simple stack trace,
(new Exception).printStackTrace();
or
Thread.dumpStack();
will tell you as much. You could also query the width and height with a System.out call to verify that you're getting the values you're expecting, or, if this really gets out of control, learn to use JUnit and the assert statement. Honestly, though, it looks like you're just accidentally calling the wrong method.
Sorry asking so many questions but believe me.. I tried Google first. :)
When you use the g.drawImage in paint() on an Applet... is there a way you can remove it? What I mean is remove the image that was drawn.
There's not really a direct way to clear the image, unless you are using an off screen buffer and painting that. I'm assuming you are drawing directly to the screen. To clear the image, you add a new flag to your applet, which you check in your paint() method. The flag indicates if the image should be drawn or not. E.g.
boolean shouldDrawImage = true;
void paint(Graphics g) {
if (shouldDrawImage) {
g.drawImage(...);
}
}
To clear the image, you then set the flag to false and invoke the repaint() method.
g.setColor( getBackground() );
g.fillRect(0, 0, getWidth(), getHeight());
public void removeImage(Image img, int id, width w, height h);
This function removes the image specified by name, id, height and width.