/*
Copyright (C) 2016 maik.jablonski@jease.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package jfix.util;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import org.imgscalr.Scalr;
/**
* Utilities to handle image-operations.
*/
public class Images {
private static class ScaledImage {
private File file;
private int size;
ScaledImage(File file, int size) {
this.file = file;
this.size = size;
}
File getFile() {
return file;
}
int getSize() {
return size;
}
}
private static Map<File, ScaledImage> limitedImageCache = new ConcurrentHashMap<File, ScaledImage>();
private static Map<File, ScaledImage> scaledImageCache = new ConcurrentHashMap<File, ScaledImage>();
/**
* Limits given image file to given size (in pixels) and caches the result.
*/
public static File limit(File image, int size) throws IOException {
ScaledImage limitedImage = limitedImageCache.get(image);
if (limitedImage == null
|| limitedImage.getFile().lastModified() < image.lastModified()
|| limitedImage.getSize() != size) {
Dimension source = getSize(image);
if (source.width > size || source.height > size) {
Dimension target = scaleDimension(source, new Dimension(size,
size));
limitedImageCache.put(image,
new ScaledImage(scale(image, target), size));
} else {
limitedImageCache.put(image,
new ScaledImage(scale(image, source), size));
}
}
return limitedImageCache.get(image).getFile();
}
/**
* Scales given image file with given size (in pixels) and caches the
* result.
*/
public static File scale(File image, int size) throws IOException {
ScaledImage scaledImage = scaledImageCache.get(image);
if (scaledImage == null
|| scaledImage.getFile().lastModified() < image.lastModified()
|| scaledImage.getSize() != size) {
Dimension source = getSize(image);
Dimension target = scaleDimension(source, new Dimension(size, size));
scaledImageCache.put(image, new ScaledImage(scale(image, target),
size));
}
return scaledImageCache.get(image).getFile();
}
/**
* Scales given image to given dimension.
*
* If you want to leave the aspect ratio intact, use
* {@link #scaleDimension(sourceDimension, targetDimension)} to calculate an
* appropriate dimension.
*/
public static File scale(File image, Dimension targetDimension)
throws IOException {
OutputStream output = null;
try {
File targetFile = File.createTempFile("img-", ".tmp");
output = new FileOutputStream(targetFile);
ImageIO.write(
getScaledInstance(ImageIO.read(image),
(int) targetDimension.getWidth(),
(int) targetDimension.getHeight()),
getFormat(image), output);
return targetFile;
} finally {
if (output != null) {
output.close();
}
}
}
/**
* Returns a down-scaled dimension based on given source dimension which is
* scaled by given target dimension while keeping the aspect ratio intact.
*/
public static Dimension scaleDimension(Dimension source, Dimension target) {
double width = 0;
double height = 0;
double ratio = source.getWidth() / source.getHeight();
if (ratio > 1) {
width = target.getWidth();
height = target.getWidth() / ratio;
if (height > target.getHeight()) {
width = target.getHeight() * ratio;
height = width / ratio;
}
} else {
width = target.getHeight() * ratio;
height = target.getHeight();
if (width > target.getWidth()) {
height = target.getWidth() / ratio;
width = height * ratio;
}
}
return new Dimension((int) Math.ceil(width), (int) Math.ceil(height));
}
public static File rotate(File image) throws IOException {
OutputStream output = null;
try {
File targetFile = File.createTempFile("img-", ".tmp");
output = new FileOutputStream(targetFile);
ImageIO.write(getRotatedInstance(ImageIO.read(image)),
getFormat(image), output);
return targetFile;
} finally {
if (output != null) {
output.close();
}
}
}
/**
* Returns format name for given image.
*/
public static String getFormat(File image) throws IOException {
ImageInputStream stream = ImageIO
.createImageInputStream(new FileInputStream(image));
Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
if (!iter.hasNext()) {
return null;
}
ImageReader reader = (ImageReader) iter.next();
stream.close();
return reader.getFormatName();
}
/**
* Returns array with width/height for given image.
*/
public static Dimension getSize(File image) throws IOException {
BufferedImage bufferedImage = ImageIO.read(image);
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
return new Dimension(width, height);
}
/**
* Returns scaled version of given image for given width and height.
*/
public static BufferedImage getScaledInstance(BufferedImage image,
int width, int height) {
return Scalr.resize(image, Scalr.Method.ULTRA_QUALITY, width, height);
}
/**
* Returns clockwise rotated version of given image.
*/
public static BufferedImage getRotatedInstance(BufferedImage image) {
return Scalr.rotate(image, Scalr.Rotation.CW_90);
}
/**
* Returns true if given content type can be displayed as image by major
* broswers.
*/
public static boolean isBrowserCompatible(String contentType) {
return "image/jpeg".equals(contentType)
|| "image/gif".equals(contentType)
|| "image/png".equals(contentType);
}
}