Finding best fit from a list of images - java

I'm using a library called Image4j to load an ico file, choose one of the images from the list, scale it (if necessary) and put it in a JLabel as an ImageIcon.
The library has a method read(File icoFile) which returns a List<BufferedImage> , a list of all the images contained within the ico file.
What I want to be able to do is quickly choose the image from the list that is the closest fit for the size of the label. Both the labels and the ico images will be square.
I've come up with the most naive way, but I suspect there's a faster one. I'd settle for this one, but my program will be doing this routine a lot so I want this part to be as efficient as possible.
public class IconUtil() {
private final List<BufferedImage> list;
public IconUtil(List<BufferedImage> list) {
this.list = list;
}
public BufferedImage getBestFit(int sizeToFit) {
int indexOfBestFit = 0; // assume it's the first image
int bestMargin = Math.abs(list.get(0).getWidth() - sizeToFit);
int margin;
for(int i = 1; i < list.size(); i++) {
margin = Math.abs(list.get(i).getWidth() - sizeToFit);
if(margin < bestMargin) {
bestMargin = margin;
indexOfBestFit = i;
}
}
return list[indexOfBestFit];
}
}
The choice to compare the images by their width was arbitrary because they're all square.
Also, as the main reason for picking the best fit is to try and maintain the quality of the images, should this method discard any images which are smaller than the target size? If so, how would that change the algorithm?
I have little experience with re-scaling images in Java. Is it even necessary to find the closest match in size? Maybe there are ways to re-scale without losing much quality even if the original is much bigger?

Related

Matching images from file with Sikuli

I just found about Sikuli when I was looking for a library to find matches of a given image within a larger image (both loaded from files).
By default, Sikuli only supports loading the searched image from file, but relies on a proprietary class Screen to take screenshots to use as base for the search... And I'd like to have the ability to use a image file instead.
Looking for a solution has led me to this question, but the answer is a bit vague when you consider that I have no prior experience with Sikuli and the available documentation is not particularly helpful for my needs.
Does anyone have any examples on how to make a customized implementation of Screen, ScreenRegion, ImageScreen and ImageScreenLocation? Even a link to a more detailed documentation on these classes would be a big help.
All I want is to obtain the coordinates of an image match within another image file, so if there's another library that could help with this task I'd more than happy to learn about it!
You can implement it by yourself with something like this:
class MyImage{
private BufferedImage img;
private int imgWidth;
private int imgHeight;
public MyImage(String imagePath){
try{
img = ImageIO.read(getClass().getResource(imagePath));
}catch(IOException ioe){System.out.println("Unable to open file");}
init();
}
public MyImage(BufferedImage img){
this.img = img;
init();
}
private void init(){
imgWidth = img.getWidth;
imgHeight = img.getHeight();
}
public boolean equals(BufferedImage img){
//Your algorithm for image comparison (See below desc for your choices)
}
public boolean contains(BufferedImage subImage){
int subWidth = subImage.getWidth();
int subHeight = subImage.getHeight();
if(subWidth > imgWidth || subHeight > imgHeight)
throw new IllegalArgumentException("SubImage is larger than main image");
for(int x=0; x<(imgHeight-subHeight); x++)
for(int y=0; y<(imgWidth-subWidth); y++){
BufferedImage cmpImage = img.getSumbimage(x, y, subWidth, subHeight);
if(subImage.equals(cmpImage))
return true;
}
return false;
}
}
The contains method will grab a subimage from the main image and compare with the given subimage. If it is not the same, it will move on to the next pixel until it went through the entire image. There might be other more efficient ways than moving pixel by pixel, but this should work.
To compare 2 images for similarity
You have at least 2 options:
Scan pixel by pixel using a pair of nested loop to compare the RGB value of each pixel. (Just like how you compare two int 2D array for similarity)
It should be possible to generate a hash for the 2 images and just compare the hash value.
Aah... Sikuli has an answer for this too... You just didnt look close enough. :)
Answer : The FINDER Class
Pattern searchImage = new Pattern("abc.png").similar((float)0.9);
String ScreenImage = "xyz.png"; //In this case, the image you want to search
Finder objFinder = null;
Match objMatch = null;
objFinder = new Finder(ScreenImage);
objFinder.find(searchImage); //searchImage is the image you want to search within ScreenImage
int counter = 0;
while(objFinder.hasNext())
{
objMatch = objFinder.next(); //objMatch gives you the matching region.
counter++;
}
if(counter!=0)
System.out.println("Match Found!");
In the end I gave up on Sikuli and used pure OpenCV in my Android project: The Imgproc.matchTemplate() method did the trick, giving me a matrix of all pixels with "scores" for the likehood of that being the starting point of my subimage.
With Sikuli, you can check for the presence of an image inside another one.
In this example code, the pictures are loaded from files.
This code tell us if the second picture is a part of the first picture.
public static void main(String[] argv){
String img1Path = "/test/img1.png";
String img2Path = "/test/img2.png";
if ( findPictureRegion(img1Path, img2Path) == null )
System.out.println("Picture 2 was not found in picture 1");
else
System.out.println("Picture 2 is in picture 1");
}
public static ScreenRegion findPictureRegion(String refPictureName, String targetPictureName2){
Target target = new ImageTarget(new File(targetPictureName2));
target.setMinScore(0.5); // Precision of recognization from 0 to 1.
BufferedImage refPicture = loadPicture(refPictureName);
ScreenRegion screenRegion = new StaticImageScreenRegion(refPicture);
return screenRegion.find(target);
}
public static BufferedImage loadPicture(String pictureFullPath){
try {
return ImageIO.read(new File(pictureFullPath));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
To use Sikuli package, I added this dependency with Maven :
<!-- SIKULI libraries -->
<dependency>
<groupId>org.sikuli</groupId>
<artifactId>sikuli-api</artifactId>
<version>1.1.0</version>
</dependency>

LibGDX - find out if a given screen resolution is supported on the current device

So I'm writing some tools for my program to deal with basic configurations, reading settings from a data file, and then making those settings the active configuration. I'm also building in an error checking mechanism to make sure that settings are formatted correctly and have valid values.
I want to check to see what the current devices supported resolutions are, and I want to compare that to the resolution specified in the data file. Is there an easy way to do this in libGDX that I'm missing? I know that LWJGL has a function that will give you an array of the supported resolutions, but I don't want to reinvent the wheel.
I'm looking for something like:
boolean isValidResolutionForDevice(int width, int height)
Am I going to have to write this myself? Or does this exist already? It seems to me to be such a useful function that it must have been written into the libraries somewhere.
There actually is a way to do this. The main com.badlogic.gdx.Application interface has a getGraphics() method, and the com.badlogic.gdx.Graphics class returned from that has getDisplayModes() method, which returns DisplayMode[]. DisplayMode basically looks like this (comments and constructor removed):
public class DisplayMode {
/** the width in physical pixels **/
public final int width;
/** the height in physical pixles **/
public final int height;
/** the refresh rate in Hertz **/
public final int refreshRate;
/** the number of bits per pixel, may exclude alpha **/
public final int bitsPerPixel;
}
And then you can scan through these to see if the resolution in question is supported or not.
The only bit of annoying news is because getGraphics() is on the Application object, it doesn't seem like you can query for the available graphics modes until after the Application object (e.g. LwjglApplication) has been created. So perhaps you pick an arbitrary resolution (e.g. 1024x768), start the game, then immediately switch to a supported resolution if the original wasn't actually supported (and optionally let the user pick in a settings menu).
I came across the method I believe you're referring to, org.lwjgl.opengl.Display.getAvailableDisplayModes(), and...honestly I'd just use this one for your use case. You'd pretty much just be iterating over it with a simple conditional inside. Not exactly reinventing anything.
Unfortunately, there is no ready solution in libgdx, I solved it like this:
private Map<Integer, Integer> supportedReolutions;
private String graphicFolder;
supportedReolutions = new HashMap<Integer, Integer>();
supportedReolutions.put(1024, 768);
supportedReolutions.put(1080, 1920);
supportedReolutions.put(1200, 1920);
supportedReolutions.put(2048, 1536);
supportedReolutions.put(480, 800);
supportedReolutions.put(640, 1136);
graphicFolder = "1080x1920";
/**
* Chose the folder with best graphic for current device
*/
private void setCurrentResolutionFolder() {
int width = Gdx.graphics.getWidth();
int height = Gdx.graphics.getHeight();
if (supportedReolutions.containsKey(width)) {
if (supportedReolutions.get(width) == height) {
graphicFolder = String.valueOf(width) + "x"
+ String.valueOf(height);
}
}
}

How should I do image animation?

I plan to have an animated character (the character's image changing multiple times to make it appear to be moving), and I would like to know the best way to do it. I am currently planning to do something like this:
String fileLocation = "./images/picture";
BufferedImage img;
int numImages = 10;
for(int i = 0; i < numImages; i++){
img = ImageIO.read(new File(fileLocation + i + ".png"));
Thread.sleep(100);
g.drawImage(img, 0, 0, null);
}
This is an incredibly simplified version, missing a few things, but I'm sure you get what I mean. Are there any problems doing it this way? (Note: the for loop would repeat again straight after finishing, and there would be files called "picture0.png", "picture1.png", etc. in the "images" folder)
If the images are not huge and don't require a lot of memory for storing them, I would rather read the images first and cache them. When they need to be displayed, I would read them from memory rather than from disk.

Figure out width of a String in a certain Font

Is there any way to figure out how many pixels wide a certain String in a certain Font is?
In my Activity, there are dynamic Strings put on a Button. Sometimes, the String is too long and it's divided on two lines, what makes the Button look ugly. However, as I don't use a sort of a console Font, the single char-widths may vary. So it's not a help writing something like
String test = "someString";
if(someString.length()>/*someValue*/){
// decrement Font size
}
because an "mmmmmmmm" is wider than "iiiiiiii".
Alternatively, is there a way in Android to fit a certain String on a single line, so the system "scales" the Font size automatically?
EDIT:
since the answer from wsanville was really nice, here's my code setting the font size dynamically:
private void setupButton(){
Button button = new Button();
button.setText(getButtonText()); // getButtonText() is a custom method which returns me a certain String
Paint paint = button.getPaint();
float t = 0;
if(paint.measureText(button.getText().toString())>323.0){ //323.0 is the max width fitting in the button
t = getAppropriateTextSize(button);
button.setTextSize(t);
}
}
private float getAppropriateTextSize(Button button){
float textSize = 0;
Paint paint = button.getPaint();
textSize = paint.getTextSize();
while(paint.measureText(button.getText().toString())>323.0){
textSize -= 0.25;
button.setTextSize(textSize);
}
return textSize;
}
You should be able to use Paint.setTypeface() and then Paint.measureText(). You'll find other methods on the Paint class like setTextSize() to help too.
Your followup question about scaling text was addressed in this question.

Improving performance when processing images in Java

I am writing a program which among other things takes a folder of images (Typically around 2000 jpeg images) resizes them, and adds them to a timeline of images. The result of this being as follows:
This works fine, however the way I have done this seems very inefficient. The code which processes these images is shown below:
public void setTimeline(Vector<String> imagePaths){
int numberOfImages = imagePaths.size();
JLabel [] TotalImages = new JLabel[numberOfImages];
setGridPanel.setLayout(new GridLayout(1, numberOfImages, 10, 0));
Dimension image = new Dimension(96, 72);
if (imagePaths != null){
for(int i = 0; i <numberOfImages; i++){
TotalImages[i] = new JLabel("");
TotalImages[i].setPreferredSize(image);
ImageIcon tempicon = new ImageIcon(imagePaths.elementAt(i));
Image tempimage = tempicon.getImage();
Image newimg = tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);
ImageIcon newIcon = new ImageIcon(newimg);
TotalImages[i].setIcon(newIcon);
setGridPanel.add(TotalImages[i]);
}
}
}
As can be seen, this code loops through each image path, adds it to a label and adds it to the panel - performing exactly as it should with the correct output.
However, the time taken to do this is substantial. Typically around 5 minutes for 2000 images (depending on the machine). I wondered if there is any way I could improve this performance by using different techniques?
Any help is greatly appreciated.
Save your scaled instances and load them direct. Hard drive space is cheap. This won't get around the initial cost of generating the thumbs, but any subsequent appearances will be lightning-fast.
takes a folder of images
with processes by using tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);
use JTable, with reduced funcionality you can use JList too
Typically around 5 minutes for 2000 images
Image.getScaledInstance is simple asynchonous, witouth guarantee an fast and performance, then you have to redirect loading of images to the Background task
advantage first part of images are available immediatelly
dis_advantage required dispalying statuses of loading for user, very good knowledge about Swing and Event Dispatch Thread
I'd suggest to look at Runnable#Thread, and output put to the DefaultTableModel, notice this output must be wrapped into invokeLater
another and most complex way is use SwingWorker, but required very good knowledge about Java and Swing too
To add to mKorbel's excellent answer, I would definitely use a background thread such as a SwingWorker. This may not make the program any faster, but it will seem a lot faster, and that can make all the difference. Something like:
// use List<String> not Vector<String> so you can use Vector now, or change your
// mind and use ArrayList later if desired
// pass dimensions and components in as parameters to be cleaner
public void setTimeLine2(List<String> imagePaths, Dimension imgSize,
JComponent imgDisplayer) {
if (imagePaths != null && imgSize != null && imgDisplayer != null) {
// are you sure you want to set the layout in here?
imgDisplayer.setLayout(new GridLayout(1, 0, 10, 0));
// create your SwingWorker, passing in parameters that it will need
ImageWorker imgWorker = new ImageWorker(imagePaths, imgSize,
imgDisplayer);
imgWorker.execute(); // then ask it to run doInBackground on a background thread
} else {
// TODO: throw exception
}
}
private class ImageWorker extends SwingWorker<Void, ImageIcon> {
private List<String> imagePaths;
private JComponent imgDisplayer;
private int imgWidth;
private int imgHeight;
public ImageWorker(List<String> imagePaths, Dimension imgSize,
JComponent imgDisplayer) {
this.imagePaths = imagePaths;
this.imgDisplayer = imgDisplayer;
imgWidth = imgSize.width;
imgHeight = imgSize.height;
}
// do image creation in a background thread so as not to lock the Swing event thread
#Override
protected Void doInBackground() throws Exception {
for (String imagePath : imagePaths) {
BufferedImage bImg = ImageIO.read(new File(imagePath));
Image scaledImg = bImg.getScaledInstance(imgWidth, imgHeight,
Image.SCALE_SMOOTH);
ImageIcon icon = new ImageIcon(scaledImg);
publish(icon);
}
return null;
}
// but do all Swing manipulation on the event thread
#Override
protected void process(List<ImageIcon> chunks) {
for (ImageIcon icon : chunks) {
JLabel label = new JLabel(icon);
imgDisplayer.add(label);
}
}
}
Use tiles. Which means than rather than operating on images which are not shown in the screen, you only operated when the image has to be shown on the screen.
You need to maintain the logical position of the timeline, as well as displayed images.
When the user move the cursor to a previously hidden position, you compute which image(s) need to be shown next. If the images are not already processed, you process them. That's the same technique web-browsers use for performance.
A first thing you could do would be to add the images asynchronously, instead of trying to add all of them at once. Loop over them as you do, add them to the panel and render it every few images or so the user doesn't need to wait for a long initialization time.
Reuse image objects. A flyweight pattern would come to mind, and possibly limit the screen redraws to only the portions where you add a new image in your asynchronous loading.
If you are likely to have the same images redrawn (or to reload the same folders) in the future, you might want to consider caching some of the image objects, and maybe to save to file the resized thumbnails (many photo viewers do this and will store thumbnails versions - and some useful metadata - in hidden files or folders, so they can reload them faster the next time around.
what you could do to make it faster is by making 4 threads, and have them computing simultaneously the images. i dont know if the vm will spread them over multiple cpu cores though. something to look into because that would boost perfotrmace on a multicore pc

Categories