0,0 → 1,799 |
package ak.photoalbum.images; |
|
import java.util.Map; |
import java.util.HashMap; |
import java.util.Comparator; |
import java.util.Arrays; |
import java.io.File; |
import java.io.FileFilter; |
import java.io.IOException; |
import java.io.FileInputStream; |
import java.io.FileOutputStream; |
import java.io.OutputStream; |
import java.awt.Image; |
import java.awt.Toolkit; |
import java.awt.Graphics; |
import java.awt.image.BufferedImage; |
import java.awt.image.ImageObserver; |
import java.awt.image.PixelGrabber; |
import javax.imageio.ImageIO; |
import org.apache.log4j.Logger; |
import marcoschmidt.image.ImageInfo; |
import ak.photoalbum.util.FileUtils; |
import ak.photoalbum.util.FileNameComparator; |
|
public class Thumbnailer |
{ |
protected static final String DEFAULT_FORMAT = "jpg"; |
protected static final String SMALL_SUFFIX = ".small"; |
protected static final String MEDIUM_SUFFIX = ".medium"; |
protected static final String DIR_SUFFIX = ".dir"; |
protected static final int DEFAULT_SMALL_WIDTH = 120; |
protected static final int DEFAULT_SMALL_HEIGHT = 120; |
protected static final int DEFAULT_MEDIUM_WIDTH = 800; |
protected static final int DEFAULT_MEDIUM_HEIGHT = 800; |
|
protected Logger logger; |
protected ImageResizer resizer; |
protected int smallWidth = DEFAULT_SMALL_WIDTH; |
protected int smallHeight = DEFAULT_SMALL_HEIGHT; |
protected int mediumWidth = DEFAULT_MEDIUM_WIDTH; |
protected int mediumHeight = DEFAULT_MEDIUM_HEIGHT; |
protected File cacheDir; |
protected String format = DEFAULT_FORMAT; |
protected Map smallCache = new HashMap(); |
protected Map mediumCache = new HashMap(); |
protected Map dirCache = new HashMap(); |
protected File imagesRoot; |
protected FileFilter imagesFilter = null; |
protected Comparator fileNameComparator = new FileNameComparator(true); |
protected Comparator fileNameComparatorRev = new FileNameComparator(false); |
|
protected File dirTemplate; |
protected ThumbnailPosition[] dirThumbnailPositions; |
protected int[] dirTemplateSize; |
protected long dirTemplateTimestamp; |
|
public Thumbnailer() |
{ |
this.logger = Logger.getLogger(this.getClass()); |
} |
|
public String getMime() |
{ |
return FileUtils.getMime(format); |
} |
|
public int[] getDirSize(File origin) |
throws IOException |
{ |
if(dirTemplateSize == null |
|| dirTemplateTimestamp != dirTemplate.lastModified()) |
{ |
dirTemplateSize = getOriginSize(dirTemplate); |
dirTemplateTimestamp = dirTemplate.lastModified(); |
} |
|
return dirTemplateSize; |
} |
|
public int[] getSmallSize(File origin) |
throws IOException |
{ |
CachedFile cached = getCached(smallCache, origin); |
|
if(cached == null) { |
int[] originSize = getOriginSize(origin); |
|
return calcSizes(originSize[0], originSize[1], smallWidth, smallHeight); |
} |
else { |
int[] originSize = new int[2]; |
|
originSize[0] = cached.getWidth(); |
originSize[1] = cached.getHeight(); |
|
return originSize; |
} |
} |
|
public int[] getMediumSize(File origin) |
throws IOException |
{ |
CachedFile cached = getCached(mediumCache, origin); |
|
if(cached == null) { |
int[] originSize = getOriginSize(origin); |
|
return calcSizes(originSize[0], originSize[1], mediumWidth, mediumHeight); |
} |
else { |
int[] originSize = new int[2]; |
|
originSize[0] = cached.getWidth(); |
originSize[1] = cached.getHeight(); |
|
return originSize; |
} |
} |
|
protected int[] getOriginSize(File origin) |
throws IOException |
{ |
if(logger.isDebugEnabled()) |
logger.debug("get size of " + origin.getCanonicalPath()); |
|
ImageInfo ii = new ImageInfo(); |
FileInputStream in = null; |
int[] res = new int[2]; |
|
try { |
in = new FileInputStream(origin); |
ii.setInput(in); |
|
if(!ii.check()) { |
logger.warn("not supported format of " + origin.getCanonicalPath()); |
res[0] = 0; |
res[1] = 0; |
} |
else{ |
res[0] = ii.getWidth(); |
res[1] = ii.getHeight(); |
} |
} |
finally { |
if(in != null) in.close(); |
} |
|
return res; |
} |
|
public void rebuildCache() |
throws IOException |
{ |
logger.info("rebuild cache"); |
|
deleteCache(); |
buildCache(); |
} |
|
public void deleteCache() |
throws IOException |
{ |
logger.info("delete cache"); |
|
deleteCache(cacheDir); |
} |
|
public void buildCache() |
throws IOException |
{ |
logger.info("build cache"); |
|
buildCache(imagesRoot); |
} |
|
protected void deleteCache(File dir) |
throws IOException |
{ |
File[] children = dir.listFiles(); |
|
if(children == null) return; // the dir does not exists |
|
Arrays.sort(children, fileNameComparator); |
|
for(int i = 0; i < children.length; i++) { |
if(children[i].isDirectory()) |
deleteCache(children[i]); |
else |
children[i].delete(); |
} |
dir.delete(); |
} |
|
protected void buildCache(File dir) |
throws IOException |
{ |
File[] children; |
|
if(imagesFilter == null) |
children = dir.listFiles(); |
else |
children = dir.listFiles(imagesFilter); |
|
if(children == null) return; // the dir does not exists |
|
Arrays.sort(children, fileNameComparator); |
|
for(int i = 0; i < children.length; i++) { |
if(children[i].isDirectory()) { |
writeDir(children[i], null); |
buildCache(children[i]); |
} |
else { |
writeSmall(children[i], null); |
writeMedium(children[i], null); |
} |
} |
} |
|
protected CachedFile getCached(Map cache, File imageFile) |
throws IOException |
{ |
CachedFile cached = (CachedFile)cache.get(imageFile.getCanonicalPath()); |
|
if(cached == null || !cached.getFile().exists()) { |
logger.debug("not found in cache"); |
return null; |
} |
|
if(cached.getOriginTimestamp() != imageFile.lastModified()) { |
cached.getFile().delete(); |
cache.remove(imageFile.getCanonicalPath()); |
logger.debug("timestamps dont match"); |
return null; |
} |
|
return cached; |
} |
|
protected boolean writeCached(Map cache, File imageFile, OutputStream out) |
throws IOException |
{ |
CachedFile cached = getCached(cache, imageFile); |
|
if(cached == null) return false; |
|
if(logger.isDebugEnabled()) |
logger.debug("write cached " + imageFile.getCanonicalPath()); |
|
if(out != null) { |
FileInputStream in = null; |
|
try { |
in = new FileInputStream(cached.getFile()); |
FileUtils.copyStreams(in, out); |
} |
finally { |
if(in != null) in.close(); |
} |
} |
|
return true; |
} |
|
protected void cacheThumbnail(Map cache, File imageFile, |
BufferedImage thumbnail, String suffix, int width, int height) |
throws IOException |
{ |
logger.debug("cache thumbnail " + suffix + " " |
+ imageFile.getCanonicalPath()); |
|
File dir = getCacheFileDir(imageFile); |
File cacheFile = new File(dir, |
imageFile.getName() + suffix + "." + format); |
|
dir.mkdirs(); |
ImageIO.write(thumbnail, format, cacheFile); |
|
cache.put(imageFile.getCanonicalPath(), |
new CachedFile(cacheFile, imageFile, |
cacheFile.lastModified(), imageFile.lastModified(), width, height)); |
} |
|
public void startup() |
throws IOException |
{ |
logger.info("startup"); |
loadCaches(cacheDir); |
logger.info("started"); |
} |
|
protected void loadCaches(File dir) |
throws IOException |
{ |
if(logger.isDebugEnabled()) |
logger.debug("load caches in " + dir.getCanonicalPath()); |
|
File[] children = dir.listFiles(); |
String dirEnd = DIR_SUFFIX + "." + format; |
String smallEnd = SMALL_SUFFIX + "." + format; |
String mediumEnd = MEDIUM_SUFFIX + "." + format; |
|
if(children == null) return; // the dir does not exists |
|
Arrays.sort(children, fileNameComparator); |
|
for(int i = 0; i < children.length; i++) { |
if(children[i].isDirectory()) |
loadCaches(children[i]); |
else { |
File origin; |
Map cache; |
|
if(children[i].getName().endsWith(smallEnd)) { |
origin = getOriginFile(children[i], SMALL_SUFFIX); |
cache = smallCache; |
|
if(logger.isDebugEnabled()) |
logger.debug("load cached small " + children[i].getCanonicalPath() |
+ " for " + origin.getCanonicalPath()); |
} |
else if(children[i].getName().endsWith(mediumEnd)) { |
origin = getOriginFile(children[i], MEDIUM_SUFFIX); |
cache = mediumCache; |
|
if(logger.isDebugEnabled()) |
logger.debug("load cached medium " + children[i].getCanonicalPath() |
+ " for " + origin.getCanonicalPath()); |
} |
else if(children[i].getName().endsWith(dirEnd)) { |
origin = getOriginFile(children[i], DIR_SUFFIX); |
cache = dirCache; |
|
if(logger.isDebugEnabled()) |
logger.debug("load cached dir " + children[i].getCanonicalPath() |
+ " for " + origin.getCanonicalPath()); |
} |
else { |
if(logger.isInfoEnabled()) |
logger.warn( |
"unknown type of cached " + children[i].getCanonicalPath()); |
|
continue; |
} |
|
long originTimestamp = origin.lastModified(); |
long cachedTimestamp = children[i].lastModified(); |
int[] sizes = getOriginSize(children[i]); |
|
if(origin.exists() && cachedTimestamp >= originTimestamp) { |
cache.put(origin.getCanonicalPath(), |
new CachedFile(children[i], origin, cachedTimestamp, |
originTimestamp, sizes[0], sizes[1])); |
|
logger.debug("added"); |
} |
else { |
children[i].delete(); |
|
if(logger.isDebugEnabled()) |
logger.debug("deleted: " + origin.exists() |
+ " " + cachedTimestamp + " " + originTimestamp); |
} |
} |
} |
} |
|
protected File getOriginFile(File cached, String suffix) |
throws IOException |
{ |
String fileEnd = suffix + "." + format; |
String fileName = cached.getName(); |
String cachedPath = cached.getParentFile().getCanonicalPath(); |
String cacheRoot = cacheDir.getCanonicalPath(); |
|
fileName = fileName.substring(0, fileName.length() - fileEnd.length()); |
|
if(!cacheRoot.equals(cachedPath)) |
if(!cacheRoot.endsWith(File.separator)) cacheRoot += File.separator; |
|
return new File(imagesRoot, cachedPath.substring(cacheRoot.length()) |
+ File.separator + fileName); |
} |
|
protected File getCacheFileDir(File imageFile) |
throws IOException |
{ |
String imagePath = imageFile.getParentFile().getCanonicalPath(); |
String rootPath = imagesRoot.getCanonicalPath(); |
|
if(imagePath.equals(rootPath)) return cacheDir; |
if(!rootPath.endsWith(File.separator)) rootPath += File.separator; |
|
if(!imagePath.startsWith(rootPath)) |
throw new RuntimeException("Image " + imageFile.getCanonicalPath() |
+ " is not under images root " + imagesRoot.getCanonicalPath()); |
|
return new File(cacheDir, imagePath.substring(rootPath.length())); |
} |
|
public void writeSmall(File imageFile, OutputStream out) |
throws IOException |
{ |
if(logger.isInfoEnabled()) |
logger.info("write small " + imageFile.getCanonicalPath()); |
|
if(writeCached(smallCache, imageFile, out)) return; |
|
BufferedImage small = createThumbnail(imageFile, smallWidth, smallHeight); |
|
if(small != null) { |
int sizes[] = calcSizes( |
small.getWidth(null), small.getHeight(null), smallWidth, smallHeight); |
cacheThumbnail( |
smallCache, imageFile, small, SMALL_SUFFIX, sizes[0], sizes[1]); |
|
if(out != null) ImageIO.write(small, format, out); |
} |
} |
|
public void writeMedium(File imageFile, OutputStream out) |
throws IOException |
{ |
if(logger.isInfoEnabled()) |
logger.info("write medium " + imageFile.getCanonicalPath()); |
|
if(writeCached(mediumCache, imageFile, out)) return; |
|
BufferedImage medium |
= createThumbnail(imageFile, mediumWidth, mediumHeight); |
|
if(medium != null) { |
int sizes[] = calcSizes(medium.getWidth(null), medium.getHeight(null), |
mediumWidth, mediumHeight); |
cacheThumbnail( |
mediumCache, imageFile, medium, MEDIUM_SUFFIX, sizes[0], sizes[1]); |
|
if(out != null) ImageIO.write(medium, format, out); |
} |
} |
|
public void writeDir(File dir, OutputStream out) |
throws IOException |
{ |
if(logger.isInfoEnabled()) |
logger.info("write dir " + dir.getCanonicalPath()); |
|
if(writeCached(dirCache, dir, out)) return; |
|
BufferedImage thumbnail = createDirThumbnail(dir); |
|
if(thumbnail != null) { |
if(dirTemplateSize == null |
|| dirTemplateTimestamp != dirTemplate.lastModified()) |
{ |
dirTemplateSize = getOriginSize(dirTemplate); |
dirTemplateTimestamp = dirTemplate.lastModified(); |
} |
|
cacheThumbnail(dirCache, dir, thumbnail, DIR_SUFFIX, |
dirTemplateSize[0], dirTemplateSize[1]); |
|
if(out != null) ImageIO.write(thumbnail, format, out); |
} |
} |
|
synchronized protected BufferedImage createThumbnail(File imageFile, |
int width, int height) |
throws IOException |
{ |
if(logger.isDebugEnabled()) |
logger.debug("create thumbnail " + imageFile.getCanonicalPath()); |
|
Image image = loadImage(imageFile.getCanonicalPath()); |
// = ImageIO.read(imageFile); |
int[] sizes; |
|
if(image == null) { // not supported format |
logger.warn("unsupported format for origin or operation interrupted"); |
|
return null; |
} |
else { |
sizes = calcSizes(image.getWidth(null), image.getHeight(null), |
width, height); |
logger.debug("resize to " + sizes[0] + "x" + sizes[1]); |
|
return resizer.resize(image, sizes[0], sizes[1]); |
} |
} |
|
synchronized protected BufferedImage createDirThumbnail(File dir) |
throws IOException |
{ |
if(logger.isDebugEnabled()) |
logger.debug("create dir thumbnail " + dir.getCanonicalPath()); |
|
Image template = loadImage(dirTemplate.getCanonicalPath()); |
BufferedImage dirThumbnail; |
Graphics graphics; |
int count; |
File[] firstFiles; |
|
if(template == null) { // not supported format |
logger.warn("unsupported format for template or operation interrupted"); |
|
return null; |
} |
|
dirThumbnail = createBufferedImage(template); |
|
graphics = dirThumbnail.getGraphics(); |
count = dirThumbnailPositions.length; |
firstFiles = new File[count]; |
count = getFirstFiles(dir, count, firstFiles, 0); |
|
for(int i = 0; i < count; i++) { |
Image image = loadImage(firstFiles[i].getCanonicalPath()); |
|
if(image == null) { // not supported format |
logger.warn("unsupported format for origin or operation interrupted"); |
|
return null; |
} |
else { |
BufferedImage thumbnail; |
int[] sizes; |
|
sizes = calcSizes(image.getWidth(null), image.getHeight(null), |
dirThumbnailPositions[i].getWidth(), |
dirThumbnailPositions[i].getHeight()); |
|
thumbnail = resizer.resize(image, sizes[0], sizes[1]); |
graphics.drawImage(thumbnail, |
getXPosition(dirThumbnailPositions[i].getX(), |
dirThumbnailPositions[i].getWidth(), sizes[0], |
dirThumbnailPositions[i].getHorAlign()), |
getYPosition(dirThumbnailPositions[i].getY(), |
dirThumbnailPositions[i].getHeight(), sizes[1], |
dirThumbnailPositions[i].getVertAlign()), |
null); |
} |
} |
|
return dirThumbnail; |
} |
|
protected Image loadImage(String fileName) |
{ |
// FIXME: probably toolbox reads an image not by every request but |
// caches it |
Toolkit toolkit = Toolkit.getDefaultToolkit(); |
Image image = toolkit.getImage(fileName); |
|
toolkit.prepareImage(image, -1, -1, null); |
|
while(true) { |
int status = toolkit.checkImage(image, -1, -1, null); |
|
if((status & ImageObserver.ALLBITS) != 0) break; |
if((status & ImageObserver.ERROR) != 0) return null; |
|
try { |
Thread.sleep(100); |
} |
catch(Exception ex) { |
return null; |
} |
} |
|
return image; |
} |
|
protected int[] calcSizes(int width, int height, int maxWidth, int maxHeight) |
{ |
int[] result = new int[2]; |
double xRate; |
double yRate; |
|
if(width == 0 || height == 0) { |
result[0] = 0; |
result[1] = 0; |
return result; |
} |
|
xRate = (double)maxWidth / (double)width; |
yRate = (double)maxHeight / (double)height; |
if(xRate >= 1.0 || yRate >= 1.0) { |
result[0] = width; |
result[1] = height; |
} |
else if(xRate > yRate) { |
result[0] = maxHeight * width / height; |
result[1] = maxHeight; |
} |
else { |
result[0] = maxWidth; |
result[1] = maxWidth * height / width; |
} |
|
return result; |
} |
|
protected int getXPosition(int left, int maxWidth, int width, int align) |
{ |
if(align == ThumbnailPosition.ALIGN_HOR_LEFT) |
return left; |
else if(align == ThumbnailPosition.ALIGN_HOR_RIGHT) |
return left + (maxWidth - width); |
else if(align == ThumbnailPosition.ALIGN_HOR_CENTER) |
return left + (maxWidth - width) / 2; |
else |
throw new RuntimeException("Unknown align type: " + align); |
} |
|
protected int getYPosition(int top, int maxHeight, int height, int align) |
{ |
if(align == ThumbnailPosition.ALIGN_VERT_TOP) |
return top; |
else if(align == ThumbnailPosition.ALIGN_VERT_BOTTOM) |
return top + (maxHeight - height); |
else if(align == ThumbnailPosition.ALIGN_VERT_CENTER) |
return top + (maxHeight - height) / 2; |
else |
throw new RuntimeException("Unknown align type: " + align); |
} |
|
protected int getFirstFiles(File dir, int count, File[] files, int pos) |
{ |
File[] children; |
|
if(imagesFilter == null) |
children = dir.listFiles(); |
else |
children = dir.listFiles(imagesFilter); |
|
if(children == null) return 0; // the dir does not exists |
|
Arrays.sort(children, fileNameComparatorRev); |
|
for(int i = 0; i < children.length; i++) { |
if(children[i].isDirectory()) |
; //pos = getFirstFiles(children[i], count, files, pos); |
else { |
files[pos++] = children[i]; |
} |
|
if(pos >= count) break; |
} |
|
return pos; |
} |
|
protected BufferedImage createBufferedImage(Image image) |
{ |
if(image == null) return null; |
|
int width = image.getWidth(null); |
int height = image.getHeight(null); |
|
if(width < 1 || height < 1) return null; |
|
// get pixels |
int[] pixels = new int[width * height]; |
PixelGrabber pg = new PixelGrabber(image, |
0, 0, width, height, pixels, 0, width); |
|
try { |
pg.grabPixels(); |
} |
catch(InterruptedException e) { |
return null; |
} |
|
if((pg.getStatus() & ImageObserver.ABORT) != 0) return null; |
|
// create buffered image |
BufferedImage buffered = new BufferedImage(width, height, |
BufferedImage.TYPE_INT_RGB); |
|
for(int y = 0; y < height; y++) { |
for(int x = 0; x < width; x++) |
buffered.setRGB(x, y, pixels[y * width + x]); |
} |
|
return buffered; |
} |
|
public ImageResizer getResizer() |
{ |
return resizer; |
} |
|
public void setResizer(ImageResizer resizer) |
{ |
this.resizer = resizer; |
} |
|
public String getFormat() |
{ |
return format; |
} |
|
public void setFormat(String format) |
{ |
this.format = format; |
} |
|
public File getCacheDir() |
{ |
return cacheDir; |
} |
|
public void setCacheDir(File dir) |
{ |
this.cacheDir = dir; |
} |
|
public File getImagesRoot() |
{ |
return imagesRoot; |
} |
|
public void setImagesRoot(File dir) |
{ |
this.imagesRoot = dir; |
} |
|
public int getSmallWidth() |
{ |
return smallWidth; |
} |
|
public void setSmallWidth(int width) |
{ |
this.smallWidth = width; |
} |
|
public int getSmallHeight() |
{ |
return smallHeight; |
} |
|
public void setSmallHeight(int height) |
{ |
this.smallHeight = height; |
} |
|
public int getMediumWidth() |
{ |
return mediumWidth; |
} |
|
public void setMediumWidth(int width) |
{ |
this.mediumWidth = width; |
} |
|
public int getMediumHeight() |
{ |
return mediumHeight; |
} |
|
public void setMediumHeight(int height) |
{ |
this.mediumHeight = height; |
} |
|
public FileFilter getImagesFilter() |
{ |
return imagesFilter; |
} |
|
public void setImagesFilter(FileFilter filter) |
{ |
this.imagesFilter = filter; |
} |
|
public File getDirTemplate() |
{ |
return dirTemplate; |
} |
|
public void setDirTemplate(File dirTemplate) |
{ |
this.dirTemplate = dirTemplate; |
} |
|
public ThumbnailPosition[] getDirThumbnailPositions() |
{ |
return dirThumbnailPositions; |
} |
|
public void setDirThumbnailPositions( |
ThumbnailPosition[] dirThumbnailPositions) |
{ |
this.dirThumbnailPositions = dirThumbnailPositions; |
} |
} |