How to Improve the Performance of G.Drawimage() Method for Resizing Images

How to improve the performance of g.drawImage() method for resizing images

You can use ImageMagick to create thumbnails.

convert -define jpeg:size=500x180  hatching_orig.jpg  -auto-orient \
-thumbnail 250x90 -unsharp 0x.5 thumbnail.gif

To use it from Java you can try JMagick which provides a Java (JNI) interface to ImageMagick. Or you can simply invoke the ImageMagick commands directly using Runtime.exec or ProcessBuilder.

Image not scaling with Graphics.drawImage()

I found an answer to my own question that works.

int imageWidth  = source.getWidth();
int imageHeight = source.getHeight();

double scaleX = (double)width/imageWidth;
double scaleY = (double)height/imageHeight;
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);

source = bilinearScaleOp.filter(
source,
new BufferedImage(width, height, source.getType()));

I found it on another question from a few years ago.

How to improve the performance of g.drawImage() method for resizing images

Graphics.drawImage() consumes a lot of memory drawing an int[] image

There are several issues with the code. Some refer to performance, others to style or best practices, and others (at least potentially) refer to memory consumption.

  • Performance: The getScaledInstance method is distressingly slow. See https://stackoverflow.com/a/32278737/3182664 and others for better alternatives
  • Style: It's imageWidth, not image_width
  • Best practices: For a JComponent, you usually, you override paintComponent and not paint
  • Memory consumption: That's the main point...:

As MadProgrammer already pointed out: Do things as rarely as possible. The role and purpose of this updateCounter is not entirely clear. I think that the responsibility for updating the image less frequently should be in the class that uses your component - particularly, in the class that calls updateImage (which should simply be done less often). Maintaining this in the paint method is not very reliable.

In your current code, it seems like the currentDisplayedImage is (despite its name) neither displayed nor used in any other way. It may, however, be a good idea to keep it: It will be needed to be filled with the int[] data, and as a source for the scaled image that might have to be created.

One possible implementation of your class might look as follows:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;

public class ImageViewComponent extends JComponent {

private int updateInterval, updateCounter;
private BufferedImage fullImage;
private BufferedImage displayedImage;

/**
* @param width The width of this component
* @param height The height of this component
* @param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.fullImage = null;
this.displayedImage =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}

public void setImage(int[] image, int width, int height) {

// Note: The updateInvervall/updateCounter stuff COULD
// probably also be done here...
if (fullImage == null ||
fullImage.getWidth() != width ||
fullImage.getHeight() != height)
{
fullImage = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
}
fullImage.setRGB(0, 0, width, height, image, 0, width);
scaleImage(fullImage, displayedImage);
repaint();
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(displayedImage, 0, 0, this);
}

private static BufferedImage scaleImage(
BufferedImage input, BufferedImage output)
{
double scaleX = (double) output.getWidth() / input.getWidth();
double scaleY = (double) output.getHeight() / input.getHeight();
AffineTransform affineTransform =
AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp affineTransformOp =
new AffineTransformOp(affineTransform, null);
return affineTransformOp.filter(input, output);
}

}

but note that this does not do this "updateInterval" handling, for the reason mentioned above.

And a side note: Maybe you don't even have to scale the image. If your intention is to have the image always being displayed at the size of the component, then you can simply do

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

// Draw the FULL image, which, regardless of its size (!)
// is here painted to just fill this component:
g.drawImage(fullImage, 0, 0, getWidth(), getHeight(), null);
}

Usually, drawing a scaled image like this is pretty fast. But depending on many factors, separating the step of scaling and painting the image, like you did, may also be a reasonable option.

Resize image, maintain aspect ratio

Here we go:

Dimension imgSize = new Dimension(500, 100);
Dimension boundary = new Dimension(200, 200);

Function to return the new size depending on the boundary:

public static Dimension getScaledDimension(Dimension imgSize, Dimension boundary) {

int original_width = imgSize.width;
int original_height = imgSize.height;
int bound_width = boundary.width;
int bound_height = boundary.height;
int new_width = original_width;
int new_height = original_height;

// first check if we need to scale width
if (original_width > bound_width) {
//scale width to fit
new_width = bound_width;
//scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}

// then check if we need to scale even with the new height
if (new_height > bound_height) {
//scale height to fit instead
new_height = bound_height;
//scale width to maintain aspect ratio
new_width = (new_height * original_width) / original_height;
}

return new Dimension(new_width, new_height);
}

In case anyone also needs the image resizing code, here is a decent solution.

If you're unsure about the above solution, there are different ways to achieve the same result.

how to resize Image in java?

I use affine transformation to achieve this task, here is my code, hope it helps

/**
* scale image
*
* @param sbi image to scale
* @param imageType type of image
* @param dWidth width of destination image
* @param dHeight height of destination image
* @param fWidth x-factor for transformation / scaling
* @param fHeight y-factor for transformation / scaling
* @return scaled image
*/
public static BufferedImage scale(BufferedImage sbi, int imageType, int dWidth, int dHeight, double fWidth, double fHeight) {
BufferedImage dbi = null;
if(sbi != null) {
dbi = new BufferedImage(dWidth, dHeight, imageType);
Graphics2D g = dbi.createGraphics();
AffineTransform at = AffineTransform.getScaleInstance(fWidth, fHeight);
g.drawRenderedImage(sbi, at);
}
return dbi;
}

Is Graphics.DrawImage too slow for bigger images?

Yes, it is too slow.

I ran into this problem several years ago while developing Paint.NET (right from the start, actually, and it was rather frustrating!). Rendering performance was abysmal, as it was always proportional to the size of the bitmap and not the size of the area that it was told to redraw. That is, framerate went down as the size of the bitmap went up, and framerate never went up as the size of the invalid/redraw area went down when implementing OnPaint() and calling Graphics.DrawImage(). A small bitmap, say 800x600, always worked fine, but larger images (e.g. 2400x1800) were very slow. (You can assume, for the preceding paragraph anyway, that nothing extra was going on, such as scaling with some expensive Bicubic filter, which would have adversely affected performance.)

It is possible to force WinForms into using GDI instead of GDI+ and avoid even the creation of a Graphics object behind the scenes, at which point you can layer another rendering toolkit on top of that (e.g. Direct2D). However, it's not simple. I do this in Paint.NET, and you can see what's required by using something like Reflector on the class called GdiPaintControl in the SystemLayer DLL, but for what you're doing I'd consider it a last resort.

However, the bitmap size you're using (800x1200) should still work OK enough in GDI+ without having to resort to advanced interop, unless you're targeting something as low as a 300MHz Pentium II. Here are some tips that might help out:

  • If you are using an opaque bitmap (no alpha/transparency) in the call to Graphics.DrawImage(), and especially if it's a 32-bit bitmap with an alpha channel (but you know it's opaque, or you don't care), then set Graphics.CompositingMode to CompositingMode.SourceCopy before calling DrawImage() (be sure to set it back to the original value after, otherwise regular drawing primitives will look very ugly). This skips a lot of extra blending math per-pixel.
  • Make sure Graphics.InterpolationMode isn't set to something like InterpolationMode.HighQualityBicubic. Using NearestNeighbor will be the fastest, although if there's any stretching it may not look very good (unless it's stretching by exactly 2x, 3x, 4x, etc.) Bilinear is usually a good compromise. You should never use anything but NearestNeighbor if the bitmap size matches the area you're drawing to, in pixels.
  • Always draw into the Graphics object given to you in OnPaint().
  • Always do your drawing in OnPaint. If you need to redraw an area, call Invalidate(). If you need the drawing to happen right now, call Update() after Invalidate(). This is a reasonable approach since WM_PAINT messages (which results in a call to OnPaint()) are "low priority" messages. Any other processing by the window manager will be done first, and thus you could end up with lots of frame skipping and hitching otherwise.
  • Using a System.Windows.Forms.Timer as a framerate/tick timer won't work very well. These are implemented using Win32's SetTimer and result in WM_TIMER messages which then result in the Timer.Tick event being raised, and WM_TIMER is another low priority message which is sent only when the message queue is empty. You're better off using System.Threading.Timer and then using Control.Invoke() (to make sure you're on the right thread!) and calling Control.Update().
  • In general, do not use Control.CreateGraphics(). (corollary to 'always draw in OnPaint()' and 'always use the Graphics given to you by OnPaint()')
  • I recommend not using the Paint event handler. Instead, implement OnPaint() in the class you're writing which should be derived from Control. Deriving from another class, e.g. PictureBox or UserControl, will either not add any value for you or will add additional overhead. (BTW PictureBox is often misunderstood. You will probably almost never want to use it.)

Hope that helps.

Performance of rescaling and filtering images repeatedly with drawImage(...) and solutions

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.



Related Topics



Leave a reply



Submit