I need to make images I downloaded from flickr to fit into a 500x500 shape. If the aspect ratio is not 1:1 than black bars should be added to top / bottom or left / right to fill empty space. Transparent background could also work. Important is 500x500 and resizing without cropping. how can I do it in java?
Kris,
You can give imgscalr a try; it implements the most optimized method for scaling images in Java and also (by default) honors the orientation and proportions of the original image when resizing... it also provides a very handy pad(...) operation that will give you the border you want.
The only thing it won't do for you is auto-pad the difference between the scaled picture and a perfectly square 500x500 size but you can scale the image to something like 498x498 -- it will give you a proportional result fitting the primary dimension (horz or port depending on orientation) and then you can pad(2) to give it a nice border with any color you want including a transparent one.
For example, the code would look something like this (using static imports for readability):
import org.imgscalr.Scalr.*;
import java.awt.Color;
public static BufferedImage createThumbnail(BufferedImage img) {
// Target width of 500x500 is used
img = resize(img, 500);
return pad(img, 2, Color.BLACK);
}
The resize() method takes any number of additional arguments for adjusting fitting behavior, image quality, speed-of-operation, etc.
You can also apply any of the pre-defined OPs on the resulting image before returning it (here) by passing it as the last arg to resize or pad (or any of the other operations).
Additionally if you are trying to do this in a server process and want to run these ops asynchronously, you can look at the AsyncScalr class which offers all the same functions, but queues the operations up against a configurable number of scaling threads to avoid saturating the host machine.
imgscalr has been deployed in a number of server and client scenarios in production over the last few years. I'd love to hear your feedback if you get a chance to try out the library.
Related
I want to have a pixel-styled look for my test game, but when I scale them to the size I want them to be displayed at (50x50; the actual size of the tiles is 16x16), it gets that ugly smoothed out look I dont want to have.
img = new Image("test.png").getScaledCopy(50,50);
Size of test image is 16x16 and I want it to be displayed at a size of 50x50, but like 1:1 scaled, not the ugly smoothed out way.
A bit like in the sandbox game "Minecraft", the block tiles and such are much smaller than the displayed ones, yet they are not "smoothed".
Thank you a lot for any help :)
In your case, the anti aliasing filters look crummy because you have a very small image (16x16) and are upscaling it only a small bit (to 50x50). In your case, you likely want a sharper image, so you'd likely want to go with nearest neighbor interpolation, rather than the built-in default (either bi-linear or bi-cubic, not entirely sure which is the default).
Image original = …;
original.setFilter(Image.FILTER_NEAREST);
Image scaled = original.getScaledCopy();
So in your case, it would be something like:
Image original;
Image scaled;
original = new Image("test.png");
original.setFilter(Image.FILTER_NEAREST);
scaled = original.getScaledCopy(50, 50);
Please note that this won't be a true "1:1" scaling, since the scaling factor is not an integer ratio (ie: 50/16 isn't a whole non-decimal number). If you were to upscale to 64x64, you could have a "1:1" rescaling, since 16*k=64, where k is an integer.
I have a very large hi-res map which I want to use in an application (imagesize is around 80 mb).
I would like to know the following:
How can I load this image the best way possible? I know it will take some seconds to load the image (which is ok) but I would like to notify the user of the progress. I would like to use a determined mode and show this in some sort of JProgressBar to the user. This should reflect the number of bytes that have been loaded or something like that. Is there any Image loading method that can provide this functionality (like ImageIO.read())?
Because the map is of very high resolution I would like to offer the user to scroll to zoom in and out. How can I do this the best way? I know for a fact that rescaling a BufferedImage the standard way would take a VERY long time for such a big file. Is there any efficient way of doing this?
Thank you for your input!
kind regards,
Héctor van den Boorn
p.s. The image will be drawn on the canvas of a JPanel.
Hi Andrew, Thank you so much for your help; everything worked out perfectly and is loading quick.
Without your expertise and explanation I would have still been working on this so you've earned the bounty fair and square.
What I did was the following; using the imagemagick I created multiple images of different resolution and at the start of execution I load only the smallest res. image. The rest are loaded in seperate threads so execution is not stalled. Using the information you provided me I then use the appropriate images when zooming in or out. I'm a bit sceptical of using the tiles because I need to draw my own images on top of the map and I couldn't find the paint function in the external jar you told me to use, so I ended up using something simple; when zooming or panning the rescale mode is set to fast and when you're not zooming or panning the rescale is set to smooth for pixel-perfect images (just like you suggested), but this turns out to be fast enough and I don't need tiles (altough I do see that with even larger images this would be necesarry and I understand the information you've given me).
So thanks again and everything is working perfectly :)
There are two approaches you should (simultaneously) take:
Downscaling your image into various sizes. You should downscale your image at a series of lower resolutions (1/2, 1/4, 1/8, etc until the image is about the largest likely screen resolution). When the user first opens the image, you show the lower resolution image. This will load fast and allow the user to pan. When the user zooms in, you use a higher resolution image. You can use ImageMagick for this: http://www.imagemagick.org/Usage/resize/
Tile your larger images. This breaks down the single, large image into a large number of small images in a grid pattern. When a user zooms in on an area, you compute which tiles the user is looking at, and you render only them, not the other areas of the image. You can use ImageMagick to do split an image into tile, eg ImageMagick. What is the correct way to dice an image into sub-tiles. The documentation is http://www.imagemagick.org/Usage/crop/#crop_tile
(Providing a cache of appropriately sized and tiles images is what allows GoogleEarth and countless other mapping applications, to render so fast, yet zoom into the map at incredibly high resolution)
Once you have your tiles, you can use one of several engines in Java:
https://wiki.openstreetmap.org/wiki/Tirex
http://www.slick2d.org/wiki/index.php/Tiled
There may be others as well.
You can implement arbitrary zooming (suitable for pinch-to-zoom or similar) within this framework. Within the zoom limits you allow, your algorithm would be something like:
For the zoom level chosen by the user, choose the closest higher resolution cache. For example, if you have 100%, 50%, 25% and 12.5% tiles, and the user chooses 33% zoom, select the 50% tiles
Set the layout for the tiles so the tile squares have the correct size for the chosen zoom (this might be a single tile at lowest zoom levels). For example, at 33% zoom using 50% tiles, with the tiles being 100 pixels square, the grid will be 67 pixel squares
Individually load and scale the tile images to fit the screen (this can be multi-threaded which works well on modern CPU architectures)
There are a couple of points to note:
The scaling algorithm changes when you reach the greatest resolution you have tiles for.
Up to 100% zooming for the image, use bilinear or bicubic scaling. This provides excellent appearance for photographs with little jaggedness
Above 100%, you probably want to show the pixels, so nearest-neighbour might be a good choice
For higher fidelity, use a higher scale tile and downscale > 50%. For example, suppose you have tiles prepared at 100%, 50%, 25% and 12.5%. To show 40% zoom, don't scale down the 50% tiles; instead use the 100% tiles and scale them down to 40%. This is useful:
If your images are textual or diagrams (i.e. the raster images containing many straight lines). Scaling these type of images will often produce nasty artefacts if you don't oversample
If you need very high fidelity on photographic-style images
If you need to render a preview of the zoom (eg while the user is still pinching-and-zooming), grab a screenshot at the start of the gesture and zoom that. It matters much more that the animation is smooth than the zoom preview is pixel-perfect.
Selection of the right size of tile is important. Very large tiles (<1 per screen) is slow to render. Too small tiles creates other overheads and often produces nasty rendering artefacts where you see the screen filling up randomly. A good compromise between performance and complexity is to make the tiles about a quarter of the full-screen size.
When using these techniques, the images should load very much faster and so the progress bar is not so important. If it is, then you need to register a IIOReadProgressListener on the ImageReader:
ImageReader.addIIOReadProgressListener()
From the JavaDoc:
An interface used by ImageReader implementations to notify callers of their image and thumbnail reading methods of progress.
This interface receives general indications of decoding progress (via the imageProgress and thumbnailProgress methods), and events indicating when an entire image has been updated (via the imageStarted, imageComplete, thumbnailStarted and thumbnailComplete methods). Applications that wish to be informed of pixel updates as they happen (for example, during progressive decoding), should provide an IIOReadUpdateListener.
This isn't directly a programing problem but I feel it still can fall under the catagory, I am sorry if this is the wrong place. I am making a game in flash using box2d and I decided to draw the levels in flash as the level design would look better, The levels are very large ( this level is 10,000 pixels long) and the canvas in flash just won't display anything.
The preview in the library seems to be able to display the drawing longer than the one on the stage. How do I go about making the canvas longer? Should I try upgrading to a newer version of flash, does that version allow this?
You just don't put everything at once over your canvas, instead draw only those level primitives or parts that are visible right now. Or, if your level is basically a pretty simple shape, you can just change its X and Y so that the relevant part of the level is displayed on stage.
Don't use giant bitmaps - they use a lot of memory, and even if not all of the content is visible, they will degrade performance considerably. For this reason, Flash imposes a size limit of 4095x4095 pixels (or an equal amount of pixels in rectangular formats).
The way to deal with this is to tile your graphics into parts of equal size, preferably smaller than the stage (1/2 or 1/3 side length is a good measure). You then place them all as a grid into a larger Sprite or MovieClip and set visible=false; on each tile. Then, at runtime, your game loop must check for each frame, which of the tiles should actually appear on the stage - and only those should then be set to visible=true;. That way, you reduce the amount of pixels drawn to what is absolutely necessary, and keep screen memory usage to a minimum.
I have a lot of images that taken by my Digital camera with very high resolution 3000 * 4000 and it takes a lot of Hard disk space, I used Photoshop to open each Image and re-size it o be with small resolution, but it needs a lot of time and effort
I think that I can write simple program that open the folder of images and read each file and get it's width and height and if it's very high change it and overwrite the image with the small one.
Here some code I use in a Java-EE project (should work in normal application to:
int rw = the width I needed;
BufferedImage image = ImageIO.read(new File(filename));
ResampleOp resampleOp = new ResampleOp(rw,(rw * image.getHeight()) / image.getWidth() );
resampleOp.setFilter(ResampleFilters.getLanczos3Filter());
image = resampleOp.filter(image, null);
File tmpFile = new File(tmpName);
ImageIO.write(image, "jpg", tmpFile);
The resample filter comes from java-image-scaling library. It also contains BSpline and Bicubic filters among others if you don't like the Lanczos3. If the images are not in sRGB color space Java silently converts the color space to sRGB (which accidentally was what I needed).
Also Java loses all EXIF data, thought it does provide some (very hard to use) methods to retrieve it. For color correct rendering you may wish to at least add a sRGB flag to the file. For that see here.
+1 to what some of the other folks said about not specifically needing Java for this, but I imagine you must have known this and were maybe asking because you either wanted to write such a utility or thought it would be fun?
Either way, getting the image file listing from a dir is straight forward, resizing them correctly can take a bit more leg work as you'll notice from Googling for best-practices and seeing about 9 different ways to actually resize the files.
I wrote imgscalr to address this exact issue; it's a dead-simple API (single class, bunch of static methods) and has some good adoption in webapps and other tools utilizing it.
Steps to resize would look like this (roughly):
Get file list
BufferedImage image = ImageIO.read(files[i]);
image = Scalr.resize(image, width);
ImageIO.write(image);
There are a multitude of "resize" methods to call on the Scalr class, and all of them honor the image's original proportions. So if you scale only using a targetWidth (say 1024 pixels) the height will be calculated for you to make sure the image still looks exactly right.
If you scale with width and height, but they would violate the proportions of the image and make it look "Stretched", then based on the orientation of the image (portrait or landscape) one dimension will be used as the anchor and the other incorrect dimension will be recalculated for you transparently.
There are also a multitude of different Quality settings and FIT-TO scaling modes you can use, but the library was designed to "do the right thing" always, so using it is very easy.
You can dig through the source, it is all Apache 2 licensed. You can see that it implements the Java2D team's best-practices for scaling images in Java and pedantically cleans up after itself so no memory gets leaked.
Hope that helps.
You do not need Java to do this. It's a waste of time and resources. If you have photoshop you can do it with recording actions: batch resize using actions
AffineTransformOp offers the additional flexibility of choosing the interpolation type, as shown here.
You can individually or batch resize with our desktop image resizing application called Sizester. There's a full functioning 15-day free trial on our site (www.sizester.com).
I would like to resize a Java BufferedImage, making it smaller vertically but without using any type of averaging, so that if a pixel-row is "blank" (white) in the source image, there will be a white pixel-row in the corresponding position of the destination image: the "min" operation. The default algorithms (specified in getScaledInstance) do not allow me a fine-grained enough control. I would like to implement the following logic:
for each pixel row in the w-pixels wide destination image, d = pixel[w]
find the corresponding j pixel rows of the source image, s[][] = pixel[j][w]
write the new line of pixels, so that d[i] = min(s[j][i]) over all j, i
I have been reading on RescaleOp, but have not figured out how to implement this functionality -- it is admittedly a weird type of scaling. Can anyone provide me pointers on how to do this? In the worse case, I figure I can just reserve the destination ImageBuffer and copy the pixels following the pseudocode, but I was wondering if there is better way.
The RescaleOp methods include a parameter called RenderingHints. There is a hint called KEY_INTERPOLATION that decides the color to use when scaling an image.
If you use the value VALUE_INTERPOLATION_NEAREST_NEIGHBOR for the KEY_INTERPOLATION, Java will use the original colors, rather than using some type of algorithm to recalculate the new colors.
So, instead of white lines turning to gray or some mix of color, you'll get either white lines, or you won't get any lines at all. It all depends on the scaling factor, and if it's an even or odd row. For example, if you are scaling by half, then each 1 pixel horizontal line has at least a 50% change of appearing in the new image. However, if the white lines were two pixels in height, you'd have a 100% chance of the white line appearing.
This is probably the closest you're going to get besides writing your own scaling method. Unfortunately, I don't see any other hints that might help further.
To implement your own scaling method, you could create a new class that implements the BufferedImageOp interface, and implement the filter() method. Use getRGB() and setRGB() on the BufferedImage object to get the pixels from the original image and set the pixels on the new image.