Java Images - "1 to 1" image scaling - java

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.

Related

How To Check If Two BufferedImages Are Equal Ignoring Color White?

I have one BufferedImage image1 and BufferedImage image2, and I want to know if they are equal.
image1 is made before-hand and stored into an image file, where I convert using ImageIO. However, image2 is made on the spot, so it is pretty much guaranteed that they have different sizes. What I do know is that image2 will equal one of 9 different image1's.
So, what I want to do is check if they are the same image's, but ignoring all the white pixels on the edge because they are different size, so if I compare all the pixels they would be different no matter what. If you're wondering why there is the color white on the edge, the images are numbers so the remaining space will be white.
If you want to make it simpler, the color of the real image will always be black, but I would like it better if you make it a generic solution (meaning taking in account all colors) so I could use the concepts later.
private boolean equals(BufferedImage image1, BufferedImage image2) {
// This is what I want to fill out.
}
What I first tried to do was to find the first non-white pixel of image1, and the first non-whiten pixel of image2, and then check the rows after that to see if everthing is equal. However, the images are pretty big, and this approach takes more than O(n ^ 2). I need a faster way.
What I first tried to do was to find the first non-white pixel of image1, and the first non-whiten pixel of image2, and then check the rows after that to see if everthing is equal. However, the images are pretty big, and this approach takes more than O(n ^ 2). I need a faster way.
Most probably there is no very faster way using this approach. You can use edge detection, but the algorithms for that aren't really faster too.
I would try to work with bounding boxes for each image (number).
If it is possible to save image1 the size the number is, this were the way to go. Just shrink the image to the real size of the number and save that image to disk. You then can shrink image2 to its bounding box too and the comparison is quite simple and fast.
If shrinking is no option, calculation of the bounding box is an option. Go through the image array and detect the top most and the left most pixel in both images. You then get at least the bounding edges for the top and left side, which is all you need to compare the images. (If images can differ in size, you need the whole bounding box)
By the way, you don't need to run in O(n^2). If you detect the top most or left most pixel in both images, you can set an offset to work from. You only need to find a difference to state that these numbers are different. You can work with logic to determine, which number it must be based on simple tests. For example take numbers one (1) and zero (0). Whereas zero has white pixels in the middle part, the one must have black pixels there and vice versa. So detecting areas where the numbers definitely are black or white can help you estimate the number in the image by testing up to 9 areas.

Approximating a fitting image size

The solution I am aiming for does select the best fitting image size from a given number of sizes.
Given a number of rather random resolutions, I would like to find an image sized as close as possible to my preferred size.
Suppose I would like to use an image sized width x height (preferredImageSize).
Example: 320x200
Suppose I have the following image sizes at my disposal (availableImageSize) width1 x height1, width2 x height2, ... (maybe up to 10 different sizes).
Examples: 474x272, 474x310, 264x150, 226x128, 640x365, 474x410, 480x276, 256x144, 160x90, 320x182, 640x365, 192x108, 240x137, 480x276
For developing some generic approach to make the preferredImageSize variable I am trying to find a good solution that computes rather quick but also results into something that does look good on the screen.
I define looks good on the screen as an image that is:
hardly upscaled
as close to the given aspect-ratio (preferredImageSize.width / preferredImageSize.height) as possible
may be heavily downscaled
may be cropped/stretched in very small amounts
My initial (rather trivial) approach:
Run through the available image sizes once and find the smallest width delta (abs(preferredImageSize.width - availableImageSize.width)). The image with that smallest delta is then chosen (bestFitWidth).
That is certainly a way to solve the issue but definitely does not comply with my looks good on the screen hopes.
Any hints, no matter if text, source or links would be awesome. Ow, and if you think that my requirements (aka hopes) are already leading into the wrong direction, go ahead, let me know...
Edit: added cropping and stretching as options - which, I am afraid will make the issue even harder to solve. So if needed leave it out of the equation.
Simple "if/then" approach:
I would do two things:
Since you would rather not upscale, but are OK with downscaling (which I find a good choice), NEVER use a source image that is smaller than your target, unless none is available.
Since "heavy" downscaling is OK, I would try to find an image that matches the aspect ratio as closely as possible, starting with the smallest acceptable image and going to progressively larger images.
To put it together, first throw out all images from the list that are smaller than your target. Then, start with the smallest image left and check its aspect ratio against your target. If the mismatch is acceptable (which you need to quantify), use the image, otherwise go to the next bigger one. If you don't find any acceptable ones, use the one with the best match.
If you've already thrown out all images as smaller than your target, you will likely end up with a bad-looking image either way, but you should then try out whether it is worse the use an image that requires more upscaling, or whether it is worse to use an image that is a worse aspect ratio match.
One other thing you need to think about is whether you want to stretch or crop the images to match your target aspect ratio.
More complex quantitative approach:
The most flexible approach, though, would be to define yourself a "penalty" function that depends on the size mismatch and the aspect ratio mismatch and then find the source image that gives you the lowest "penalty". This is what you have currently done and you've defined your penalty function as abs(preferredImageSize.width - availableImageSize.width). You could go with something a little more complex, like for example:
width_diff = preferredImageSize.width - availableImageSize.width
height_diff = preferredImageSize.height - availableImageSize.height
if (width_diff > 0) width_penalty = upscale_penalty * width_diff
else width_penalty = downscale_penalty * width_diff
if (height_diff > 0) height_penalty = upscale_penalty * height_diff
else height_penalty = downscale_penalty * height_diff
aspect_penalty = ((preferredImageSize.width / preferredImageSize.height) -
(availableImageSize.width / availableImageSize.height)) * stretch_penalty;
total_penalty = width_penalty + height_penalty + aspect_penalty;
Now you can play with the 3 numbers upscale_penalty, downscale_penalty, and stretch_penalty to give these three quality reducing operations different importance. Just try a couple of combinations and see which works best.

resize image to fixed size, add border if needed

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.

How to specify behavior of Java BufferedImage resize: need min for pixel rows instead of averaging

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.

Shape at "Actual Size"

What's the easy way to render a shape in Java to its "actual size". For example, I have a tube with a diameter of 1" and I want to depict it on screen as the outline of a 1" circle. The Graphics2D method drawOval(int x, int y, int width, int height) takes a height and width in pixels. What are the steps to translate a pixel size into the size rendered on screen?
Thanks in advance.
The getNormalizingTransform() method of the class GraphicsConfiguration looks like it has some potential
http://java.sun.com/javase/6/docs/api/java/awt/GraphicsConfiguration.html#getNormalizingTransform()
The java.awt.Toolkit will tell you the size of a pixel, and the pixel dimensions of the screen. This is based on information from the underlying system, however, which may sometimes be misconfigured.
So, to draw a 1" circle, you'd use a diameter of 1.0 * tk.getScreenResolution(), a 2.5" circle is 2.5 * tk.getScreenResolution(), etc.
You should be aware that, even though you might be able to find out about the screen size and resolution, you still can't be sure of the actual size of the displayed picture. If the user has a CRT screen, the screen is likely to be a bit smaller than the actual screen size.
Therefore, if you really need accurate results, the only way is to let the user adjust a ruler displayed on the screen interactively and compare it with an actual ruler.
In theory you can do it this way. The java.awt.Toolkit will tell you the size of a pixel, and the pixel dimensions of the screen. So, to draw a 1" circle, you'd use a diameter of 1.0 * tk.getScreenResolution(), a 2.5" circle is 2.5 * tk.getScreenResolution(), etc. Or you can use the GraphicsConfiguration.getNormalizingTransform() method which adjusts the resolution to a 'fixed' size.
Unfortunately both of these methods rely on the underlying system knowing (and telling you) the actual resolution of your screen. In practice this very rarely occurs. All sorts of things can affect the actual size of a pixel. The actual size and make of monitor is one, and some monitors even allow you to adjust the size of the image on the screen.
This article http://www.developer.com/java/other/print.php/626071 discusses this.
Printers are generally better at telling you their real resolution. If you absolutely must have a picture which is the correct size, send it there.
Acknowledgements to the various answers from which I synthesized this one.
The problem you're going to have is Pixels are not always the same size. For example a 100 x 100 pixel square is going to be different sizes on a 17" 1280 x 1024 monitor and a 19" 1280 x 1024 monitor.
I don't believe there is an API which tells you the physical size of a display.
Asking the user the size of their monitor might not help as a lot of people simply won't know.
You could display a number of lines on screen and get the user to click which one is closest to 1 inch and scale all your rendering to that, but it's a bit clumsy.
Well, you would need the size of the monitor and resolution. Let's say the monitor is 17" with a 1280:1024 aspect ratio.
The screen size is the hypotenuse, so you would need to find the number of pixels on the hypotenuse. Simple geometry later, you can get the pixels/inch calculation.
1280px^2 + 1024px^2 = c^2, c ~= 1639.2px. So it's 1639.2px/17inch = 96.4px/inch for a 17 inch monitor with 1280x1024 resolution. This would have to be entered by the user.
I was under the impression that simply getting the screen resolution from Toolkit was not enough. I think you need to do something more along the lines of
float scalingFactor = Toolkit.getDefaultToolkit().getScreenResolution() / 72f;
Making a 1" square
int width = 1.0 * scalingFactor;
and a 2.5" square
int width = 2.5 * scalingFactor;
All of this being that Java2D assumes a 72 dpi screen resolution, and if the system is set differently you need to scale up to correct for this.
This is a curious question, one I haven't thought of. Off the top of my head, you would probably need to know a combination of screen size (17", 21", etc.), screen resolution ("800x600, 1280x1024, etc.) and DPI of the screen (72, 96, 120, etc.).
Through various api's, you can determine the screen resolution, and maybe the dpi... but good luck with the screen size. And even with all that, you're still not guaranteed to produce the correct size on screen.

Categories