I am currently working on a game and for more diversity of graphics I want to call a rotate(BufferedImage imgOld, int deg) method a few hundred times to rotate some graphics (eg. tree stumps).
With unrotated graphics I never had any issues with memory. But as soon as I started to use the rotator the error occurred if I didn't reduce the amount of rotated images drastically (like 95% drastically).
The error always occurred inside of the rotate(BufferedImage imgOld, int deg) method and only since I started to rotate massive amounts of images so I suppose there must be some leftovers inside the memory from the rotate(BufferedImage imgOld, int deg) method I don't know how to deal with.
Here you see the code of the class Rotator:
package movement;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
public class Rotator {
public static BufferedImage rotate(BufferedImage imgOld, int deg){ //Parameters for the method are the image to rotate and the rate of rotation in degrees
AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(deg), (int)(imgOld.getWidth()/2), (int)(imgOld.getHeight()/2)); //setting up the transform
BufferedImage imgNew = new BufferedImage(imgOld.getWidth(), imgOld.getHeight(), imgOld.getType()); //creating a new image with the same properties of the old one
Graphics2D g = (Graphics2D) imgNew.getGraphics(); //create my graphics
g.setTransform(at); //applying the transform
g.drawImage(imgOld, 0, 0, null); //painting rotated image
return imgNew; //return rotated image
}
}
I hope somebody has an idea and i didn't make any mistakes (its my first question posted here).
Your biggest problem is simple- you're duplicating every image you're every time you rotate. You can try to draw the rotated image back onto itself, removing the need to churn through a massive amount of objects for the garbage collector.
Also, if you are dead-set on making new objects, make sure you flush (BufferedImage#flush) the old images and dipose (Graphics2D#dispose) the graphics objects.
You are using the wrong tool for the job. If you want to transform entire images, esp. BufferedImages, you should have a look at BufferedImageOps and in your case AffineTransformOp.
Then the entire operation may be implemented as
public static BufferedImage rotate(BufferedImage imgOld, int deg){
AffineTransform at = AffineTransform.getRotateInstance(
Math.toRadians(deg), imgOld.getWidth()/2, imgOld.getHeight()/2);
AffineTransformOp op=new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
BufferedImage imgNew = new BufferedImage(
imgOld.getWidth(), imgOld.getHeight(), imgOld.getType());
return op.filter(imgOld, imgNew);
}
This doesn’t deal with Graphics and such alike. The way your original approach works, is to rotate the original image and draw it onto the destination image by combining the destination’s original content (initially empty) with the rotated data of the source, depending on the Graphics blending mode. In contrast, the code above will directly render the transformed picture into the destination, not attempting to preserve existing content.
Note further, that you can pass in an existing image for reuse. BufferedImageOp.filter supports a null destination image argument in order to create a new compatible image but the AffineTransformOp would create an image bigger than the source for angles like 45° which seems not what you want, given the code in your question.
Related
I'm developing a little graphic engine using Canvas in JavaFX. In some point I had to render an off screen image, and then print it on my primary canvas using its GraphicContext.
I'm using this code right now:
private Canvas offScreenCanvas;
private GraphicsContext offScreenGraphic;
private SnapshotParameters parameters;
private WritableImage offScreenImage;
[...]
offScreenCanvas = new Canvas(WIDTH, HEIGHT);
offScreenGraphic = offScreenCanvas.getGraphicsContext2D();
parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
[...]
offScreenImage = offScreenCanvas.snapshot(parameters, offScreenImage);
graphic.setGlobalBlendMode(BlendMode.HARD_LIGHT);
graphic.drawImage(offScreenImage, 0, 0);
graphic.setGlobalBlendMode(BlendMode.SRC_OVER);
My problem is the method snaphot() takes too much time, ~14ms, in each execution. I need to update the canvas at least at 60fps, so this consumes practically all the time I have to draw.
Is there another way to get an Image or WritableImage from a canvas? Maybe another different process?
This is another method to obtain a visual equivalent result, without reduce performance.
I have used java.awt clases, instead of JavaFX clases. The creation of a java.awt.image.BufferedImage offers the possibility to get a java.awt.Graphics2D where you can draw using other methods. The main problem here is that draw big images consumes a lot of time using this libraries, but you can create a scaled image. In my case, I have created a quarter-size BufferedImage, and I have drawn all the objects using that scale factor.
At the end of the draw process, just convert the BufferedImage, to a javafx.scene.image.Image, using:
SwingFXUtils.WritableImage toFXImage(BufferedImage bimg, WritableImage wimg);
Then print it on the main canvas using:
graphic.drawImage(Image image, 0, 0, WIDTH, HEIGHT);
To fill all the canvas with the image.
Finally, the scale factor is configurable, so if you need a detailed image, just use a higher value. For me, a 25-percent-size image is enough because I am drawing gradients. Now, it takes 1-3ms to draw the image, this is much better than before.
Hope it helps someone.
I have a quadrilateral drawn in Path2D, and I would like for there to be an image on it. More specifically, I am trying to draw an image of my choice to 4 different points on a quadrilateral. In my case, it is a parallelogram. I do not want the image to go over the paralellogram. A better way to see what I am trying to say is to see the screenshot below.
I would like the image to be transformed to fit the green area. Not clipped.
I want the image to be pinned over the green paralellogram. However. I do not want the image to go over into the blue paralellogram, or the white space foe that matter.
So far I have tried
Researching for a way to place images directly onto Path2D.Double() objects. No answer
Rotating the image to fit the paralellogram. Didnt work.
Using AffineTransform in java. Dont get it ;-;
Thanks. I am new to java so do try to be lenient?
One way is to:
create a separate BufferedImage.
Apply a transform to the new image.
Draw your image to that new image.
Use the Shape object for the green area as a clip on the main drawing area
Draw the transformed image onto the main drawing area.
It's been a while since I have done transformations. You may have to set the transformation first and then draw the image after. Transformation has to come first.
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.transform(AffineTransform.getShearInstance(1.0, 0));
g2.drawImage(image, 0, 0, this);
}
Here is a simple example of how transforms work. You will have to spend some time on figuring out what values you need to make it work or if you might need to manually create a transformation matrix yourself.
I have a rather pressing question in regards to Swing, which I haven't touched for many years.
I have a code that allows the user to draw different rectangles on the pdf document (contained within JPanel). I draw them, move them, resize them, select them, and even write text on them. What I can't do is to keep them consistent when I zoom the document. As the document gets bigger, the rectangles I've drawn stay at the same position and the same size.
I was wondering if there's a relatively easy logic to track the zooming level and, most importantly, update the rectangles accordingly. I can retrieve zoom factor, it's a float, but, unfortunately, I'm using Rectangle object, which uses int for x, y, height, and width. It will be a hassle to convert it to Rectangle.Float, and I wanted to save it for a last resort.
I've tried to use AffineTransform, but I'm not quite familiar with it, for some reason I'm getting the wrong coordinates for y. Can anyone explain to me:
What's the best way to control the Rectangle object, as the pdf document gets zoomed in and out?
If AffineTransform is the best way, how should I handle it (maybe there's a link to a good explanation, if so - I couldn't find it)?
This is the only issue I've been struggling with and it's getting a bit frustrating now.
To scale using an AffineTransform:
Get the transform T of the Graphics object G
Create an AffineTransform object A
Set the scale of A
Set the transform of the G to A
Draw the shapes
Set the transform of G back to T
Translated into code - assuming scale is the value to scale by:
#Override
protected void paintComponent(Graphics gr){
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
AffineTransform prevTransform = g.getTransform();
AffineTransform at = new AffineTransform(prevTransform);
at.scale(scale, scale);
g.setTransform(at);
g.drawRect(...);
g.setTransform(prevTransform);
}
I am making a grid-based game that resizes its grid as the window size changes. I also may apply color filters for lighting effects in the future. I am concerned about the performance of this code, which draws to the screen an image in one of the grid squares.
public void drawSquares(Graphics g){
ListIterator<Viewport> iterator = vp.listIterator();
while(iterator.hasNext()){
Viewport v = (Viewport)iterator.next();
BufferedImage img = v.getSqView().getImage();
Rectangle b = v.getPixRect();
g.drawImage(img, b.x, b.y, b.width, b.height, v.getSqView().getBackground(), null);
}
return;
}
What this code does is get the image (stored in img) and get the pixel rectangle it needs to fit in (stored in b), then draw it in the space alloted via drawImage.
drawImage says that it scales images on the fly - which means that all images are being rescaled every frame. But the window is only resized rarely, so this must waste lots of processor time doing the same thing over and over again.
Now I saw this and decided that I would just update all the images upon resizing once, then store the result and be able to draw normally.
Like this:
public void resizeImage(int width, int height){
BufferedImage resized = new BufferedImage(width, height, img.getType());
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(img, 0, 0, width, height, 0, 0, img.getWidth(), img.getHeight(), null);
g.dispose();
img = resized;
}
This doesn't work - I think it has something to do with img = resized. I just lose all the images with this code.
I have a few questions then.
What is the performance cost of repeatedly scaling with drawImage? Is it any different even if the window has not been resized in between frames?
How should I get the second code snippet to work? What is going wrong?
If I apply a lighting filter to a tile, will that eat up tons of processor time as well if I run it each frame? (Think 225 or so small images on a 800x800 or so display)
What is best practice for applying lighting filters? I am planning on overlaying on the whole map a pitch black filter, then exposing the areas around light sources.
Thanks for any help with this!
Resize the frame of this Grid to get a subjective feel for the latency. Use the approach shown here to measure the latency. Verify your findings in a profiler.
There's no reason you shouldn't be able to resize the elements of a List<Image> as you propose, but add() the resized instances to a new list as they are created.
What is the performance cost of repeatedly scaling with drawImage? Is
it any different even if the window has not been resized in between
frames?
You should always measure, but there is definitely a performance cost here, even if the window is not resized, because as the Javadoc says, there is no caching behind this drawImage method. The cost also depends on the frame rate.
How should I get the second code snippet to work? What is going wrong?
The second code snippet should be OK, I think the problem is somewhere else. Try reproducing the problem in a "small but complete" program, and post another question if you still see the problem.
If I apply a lighting filter to a tile, will that eat up tons of processor time as well if I run it each frame? (Think 225 or so small images on a 800x800 or so display)
You should always measure :)
What is best practice for applying lighting filters? I am planning on overlaying on the whole map a pitch black filter, then exposing the areas around light sources.
You can use an AlphaComposite for this.
I've create a SWING component JImageEditor which simply displays a picture. The idea is to add more functionality to the component further down the road.
One functionality which I've already implemented is zooming. Right now, this part is taken care of in the paintComponent() method. However, somehow I suspect that this might be a bad idea as this means the image will be scaled from the original size to the current "zoom size" each and every time paintComponent() is invoked. The paintComponent code goes as follows :
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
int w = getWidth();
int h = getHeight();
double imageWidth = scale * image.getWidth();
double imageHeight = scale * image.getHeight();
double x = (w - imageWidth) / 2;
double y = (h - imageHeight) / 2;
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
at.scale(scale, scale);
g2.drawRenderedImage(image, at);
}
Now, what I though of as an alternative was to keep two instances of BufferedImage where one is the original and one is the current "view". That way I could handle the actual zooming/scaling whenever the setScale() method is invoked, instead of scaling in paintComponent(). However, the drawback is that i need to keep two instances of BufferedImage which will lead to higher memory consumption depending on the image size. It is, of course, impossible to predict how large images any given user will open using the component.
What I'm looking for is either a thumbs up if I am on the right track with my work, or thumbs down If it's bad design and some other solution should be considered. I appreciate all input, and will reward all answers which enlightens me :-)
I'd say put a timing section in your paintComponent to measure how long it takes. Get your base measure from what you have now. Then implement the optimized method with the extra BufferedImage. Compare the measurements and pick the one that is smaller. I have a feeling your intuition is correct that doing the affine transform every paint cycle is slow, and by creating a double buffer for the scaled image and the source will be faster. Although I can't find any thing out there that confirms or denies this, and it could be affected by hardware acceleration.
If you extracted that section of code into a ZoomableBufferedImage class you could easily turn on or off the optimized/unoptimized versions. The ZoomableBufferedImage would hold a reference to the source image, and contain a extra buffered image that it can keep the scaled version in. As you zoom in/out the ZoomableBufferedImage draws to the buffer or not based on its settings, then in it's paint method it can draw either from the buffer or by applying the AffineTransform to the source and drawing that based on it's settings.