Created separate thread for image scaling operations.
This commit is contained in:
parent
f78d06bc7b
commit
7f046cfdaa
6 changed files with 266 additions and 119 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="RunJettyRunWebAppClassPathProvider"/>
|
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="RunJettyRunWebAppClassPathProvider"/>
|
||||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="runjettyrun.Bootstrap"/>
|
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="runjettyrun.Bootstrap"/>
|
||||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="album"/>
|
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="album"/>
|
||||||
|
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1024m"/>
|
||||||
<stringAttribute key="run_jetty_run.CONTEXT_ATTR" value="/"/>
|
<stringAttribute key="run_jetty_run.CONTEXT_ATTR" value="/"/>
|
||||||
<stringAttribute key="run_jetty_run.PORT_ATTR" value="5080"/>
|
<stringAttribute key="run_jetty_run.PORT_ATTR" value="5080"/>
|
||||||
<stringAttribute key="run_jetty_run.WEBAPPDIR_ATTR" value="webapp"/>
|
<stringAttribute key="run_jetty_run.WEBAPPDIR_ATTR" value="webapp"/>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<cache
|
<cache
|
||||||
name="imageCache"
|
name="imageCache"
|
||||||
maxElementsInMemory="120"
|
maxElementsInMemory="1"
|
||||||
maxElementsOnDisk="5000"
|
maxElementsOnDisk="5000"
|
||||||
eternal="true"
|
eternal="true"
|
||||||
overflowToDisk="true"
|
overflowToDisk="true"
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,9 @@
|
||||||
package org.forkalsrud.album.web;
|
package org.forkalsrud.album.web;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.ImageReadParam;
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.ImageWriter;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
|
|
@ -28,7 +16,6 @@ import net.sf.ehcache.Element;
|
||||||
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.log4j.PropertyConfigurator;
|
import org.apache.log4j.PropertyConfigurator;
|
||||||
import org.forkalsrud.album.exif.Dimension;
|
|
||||||
import org.forkalsrud.album.exif.DirectoryEntry;
|
import org.forkalsrud.album.exif.DirectoryEntry;
|
||||||
import org.forkalsrud.album.exif.Entry;
|
import org.forkalsrud.album.exif.Entry;
|
||||||
import org.forkalsrud.album.exif.FileEntry;
|
import org.forkalsrud.album.exif.FileEntry;
|
||||||
|
|
@ -41,6 +28,7 @@ public class AlbumServlet
|
||||||
String basePrefix;
|
String basePrefix;
|
||||||
Cache imageCache;
|
Cache imageCache;
|
||||||
CacheManager cacheManager;
|
CacheManager cacheManager;
|
||||||
|
PictureScaler pictureScaler;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init()
|
public void init()
|
||||||
|
|
@ -54,6 +42,7 @@ public class AlbumServlet
|
||||||
LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger");
|
LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger");
|
||||||
cacheManager = CacheManager.create();
|
cacheManager = CacheManager.create();
|
||||||
imageCache = cacheManager.getCache("imageCache");
|
imageCache = cacheManager.getCache("imageCache");
|
||||||
|
pictureScaler = new PictureScaler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -101,7 +90,7 @@ public class AlbumServlet
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileEntry e = (FileEntry)DirectoryEntry.getEntry(file);
|
FileEntry e = (FileEntry)DirectoryEntry.getEntry(file);
|
||||||
scaleImage(req, res, file, e.getThumbnail(), size);
|
procesScaledImageRequest(req, res, file, e.getThumbnail(), size);
|
||||||
return;
|
return;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("sadness", e);
|
throw new RuntimeException("sadness", e);
|
||||||
|
|
@ -154,7 +143,7 @@ public class AlbumServlet
|
||||||
return reqDate > 0 && fDate > 0 && fDate <= reqDate;
|
return reqDate > 0 && fDate > 0 && fDate <= reqDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
|
void procesScaledImageRequest(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
|
||||||
|
|
||||||
if (notModified(req, file)) {
|
if (notModified(req, file)) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
|
|
@ -167,118 +156,40 @@ public class AlbumServlet
|
||||||
System.out.println(file.getName() + " not modified (based on etag)");
|
System.out.println(file.getName() + " not modified (based on etag)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.setDateHeader("Last-Modified", file.lastModified());
|
|
||||||
res.setHeader("ETag", fileEtag);
|
|
||||||
|
|
||||||
|
|
||||||
String key = file.getPath() + ":" + size;
|
String key = file.getPath() + ":" + size;
|
||||||
|
|
||||||
|
CachedImage cimg = null;
|
||||||
Element element = imageCache.get(key);
|
Element element = imageCache.get(key);
|
||||||
if (element != null) {
|
if (element != null) {
|
||||||
CachedImage cimg = (CachedImage) element.getObjectValue();
|
cimg = (CachedImage) element.getObjectValue();
|
||||||
if (cimg.lastModified == file.lastModified()) {
|
if (cimg.lastModified == file.lastModified()) {
|
||||||
System.out.println("cache hit on " + key);
|
System.out.println("cache hit on " + key);
|
||||||
//res.setContentType(cimg.mimeType);
|
|
||||||
res.setContentLength(cimg.bits.length);
|
|
||||||
res.getOutputStream().write(cimg.bits);
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println(" " + key + " has changed so cache entry wil be refreshed");
|
System.out.println(" " + key + " has changed so cache entry wil be refreshed");
|
||||||
imageCache.remove(key);
|
imageCache.remove(key);
|
||||||
|
cimg = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cimg == null) {
|
||||||
synchronized (this) {
|
try {
|
||||||
Dimension orig = thumbnail.getSize();
|
cimg = pictureScaler.scalePicture(file, thumbnail, size);
|
||||||
Dimension outd;
|
|
||||||
if (size.endsWith("h")) {
|
|
||||||
int height = Integer.parseInt(size.substring(0, size.length() - 1));
|
|
||||||
outd = orig.scaleHeight(height);
|
|
||||||
} else if (size.endsWith("w")) {
|
|
||||||
int width = Integer.parseInt(size.substring(0, size.length() - 1));
|
|
||||||
outd = orig.scaleWidth(width);
|
|
||||||
} else {
|
|
||||||
int worh = Integer.parseInt(size);
|
|
||||||
outd = orig.scaled(worh);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* In order to make the quality as good as possible we follow the advice from
|
|
||||||
* http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
|
|
||||||
* and scale the image by a factor of 2 in intermediate steps, thereby getting a smoother
|
|
||||||
* end result. The first scale will get to a size which is a multiple of the result size.
|
|
||||||
* the first scaling operation will also take care of any rotation that needs to happen.
|
|
||||||
*/
|
|
||||||
Dimension intermediate = new Dimension(outd);
|
|
||||||
int targetWidth = orig.getWidth() / 2;
|
|
||||||
while (intermediate.getWidth() < targetWidth) {
|
|
||||||
intermediate = intermediate.doubled();
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
|
|
||||||
ImageReader reader = readers.next();
|
|
||||||
ImageInputStream iis = ImageIO.createImageInputStream(file);
|
|
||||||
reader.setInput(iis, true);
|
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
|
||||||
BufferedImage img = reader.read(0, param);
|
|
||||||
|
|
||||||
// Recalculate scale after sub-sampling was applied
|
|
||||||
double scale;
|
|
||||||
AffineTransform xform;
|
|
||||||
if (thumbnail.getOrientation() == 6) {
|
|
||||||
xform = AffineTransform.getTranslateInstance(intermediate.getWidth() / 2d, intermediate.getHeight() / 2d);
|
|
||||||
xform.rotate(Math.PI / 2);
|
|
||||||
scale = (double)intermediate.getHeight() / img.getWidth();
|
|
||||||
xform.scale(scale, scale);
|
|
||||||
xform.translate(-img.getWidth() / 2d, -img.getHeight() / 2d);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
scale = (double)intermediate.getWidth() / img.getWidth();
|
|
||||||
xform = AffineTransform.getScaleInstance(scale, scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedImage buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), img.getType());
|
|
||||||
Graphics2D g2 = buf2.createGraphics();
|
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
|
||||||
|
|
||||||
g2.transform(xform);
|
|
||||||
g2.drawImage(img, 0, 0, null);
|
|
||||||
|
|
||||||
while (intermediate.getWidth() > outd.getWidth()) {
|
|
||||||
|
|
||||||
BufferedImage buf3 = buf2;
|
|
||||||
intermediate = intermediate.halved();
|
|
||||||
buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), img.getType());
|
|
||||||
Graphics2D g3 = buf2.createGraphics();
|
|
||||||
g3.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
|
||||||
g3.drawImage(buf3, 0, 0, intermediate.getWidth(), intermediate.getHeight(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// g2.drawImage(img, operation, 0, 0);
|
|
||||||
|
|
||||||
|
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
|
||||||
ImageWriter writer = writers.next();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bits = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
ImageOutputStream ios = ImageIO.createImageOutputStream(bits);
|
|
||||||
writer.setOutput(ios);
|
|
||||||
writer.write(buf2);
|
|
||||||
ios.flush();
|
|
||||||
|
|
||||||
CachedImage cimg = new CachedImage();
|
|
||||||
cimg.lastModified = file.lastModified();
|
|
||||||
cimg.mimeType = "image/jpeg";
|
|
||||||
cimg.bits = bits.toByteArray();
|
|
||||||
imageCache.put(new Element(key, cimg));
|
imageCache.put(new Element(key, cimg));
|
||||||
|
|
||||||
System.out.println(" " + key + " added to the cache with size " + cimg.bits.length + " -- now " + imageCache.getSize() + " entries");
|
System.out.println(" " + key + " added to the cache with size " + cimg.bits.length + " -- now " + imageCache.getSize() + " entries");
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
res.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
res.setStatus(HttpServletResponse.SC_OK);
|
res.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
res.setDateHeader("Last-Modified", file.lastModified());
|
||||||
|
res.setHeader("ETag", fileEtag);
|
||||||
res.setContentType(cimg.mimeType);
|
res.setContentType(cimg.mimeType);
|
||||||
res.setContentLength(cimg.bits.length);
|
res.setContentLength(cimg.bits.length);
|
||||||
res.getOutputStream().write(cimg.bits);
|
res.getOutputStream().write(cimg.bits);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Entry resolveEntry(String pathInfo) {
|
Entry resolveEntry(String pathInfo) {
|
||||||
|
|
@ -316,6 +227,9 @@ public class AlbumServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder appendFile(StringBuilder buf, File file) {
|
StringBuilder appendFile(StringBuilder buf, File file) {
|
||||||
|
if (file == null) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
if (base.equals(file.getAbsoluteFile())) {
|
if (base.equals(file.getAbsoluteFile())) {
|
||||||
return buf.append("/").append(base.getName());
|
return buf.append("/").append(base.getName());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -323,12 +237,6 @@ public class AlbumServlet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class CachedImage implements Serializable {
|
|
||||||
long lastModified;
|
|
||||||
String mimeType;
|
|
||||||
byte[] bits;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eof
|
// eof
|
||||||
|
|
|
||||||
12
src/org/forkalsrud/album/web/CachedImage.java
Normal file
12
src/org/forkalsrud/album/web/CachedImage.java
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.forkalsrud.album.web;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
class CachedImage implements Serializable {
|
||||||
|
long lastModified;
|
||||||
|
String mimeType;
|
||||||
|
byte[] bits;
|
||||||
|
}
|
||||||
226
src/org/forkalsrud/album/web/PictureScaler.java
Normal file
226
src/org/forkalsrud/album/web/PictureScaler.java
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
package org.forkalsrud.album.web;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
|
import org.forkalsrud.album.exif.Dimension;
|
||||||
|
import org.forkalsrud.album.exif.Thumbnail;
|
||||||
|
|
||||||
|
public class PictureScaler {
|
||||||
|
|
||||||
|
ExecutorService executor;
|
||||||
|
BlockingQueue queue;
|
||||||
|
HashMap<String, PictureRequest> outstandingRequests;
|
||||||
|
|
||||||
|
|
||||||
|
public PictureScaler() {
|
||||||
|
queue = new PriorityBlockingQueue<PictureRequest>(20, createPriorityComparator());
|
||||||
|
executor = Executors.newSingleThreadExecutor();
|
||||||
|
outstandingRequests = new HashMap<String, PictureRequest>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
synchronized PictureRequest getPictureRequest(File file, Thumbnail thumbnail, String size) {
|
||||||
|
String key = file.getPath() + ":" + size;
|
||||||
|
PictureRequest req = outstandingRequests.get(key);
|
||||||
|
if (req == null) {
|
||||||
|
req = new PictureRequest(key, file, thumbnail, size);
|
||||||
|
outstandingRequests.put(key, req);
|
||||||
|
Future<CachedImage> future = executor.submit(req);
|
||||||
|
req.setFuture(future);
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void completePictureRequest(PictureRequest req) {
|
||||||
|
|
||||||
|
outstandingRequests.remove(req.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PictureRequest implements Callable<CachedImage> {
|
||||||
|
|
||||||
|
long priority;
|
||||||
|
String key;
|
||||||
|
File file;
|
||||||
|
Thumbnail thumbnail;
|
||||||
|
String size;
|
||||||
|
Future<CachedImage> future;
|
||||||
|
|
||||||
|
public PictureRequest(String key, File file, Thumbnail thumbnail, String size) {
|
||||||
|
this.key = key;
|
||||||
|
this.file = file;
|
||||||
|
this.thumbnail = thumbnail;
|
||||||
|
this.size = size;
|
||||||
|
this.priority = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CachedImage call() throws Exception {
|
||||||
|
return scalePictureReally(file, thumbnail, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFuture(Future<CachedImage> future) {
|
||||||
|
this.future = future;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedImage waitForIt(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
|
||||||
|
return future.get(/*timeout, unit*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
future.cancel(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Comparator<PictureRequest> createPriorityComparator() {
|
||||||
|
|
||||||
|
return new Comparator<PictureRequest>() {
|
||||||
|
|
||||||
|
public int compare(PictureRequest o1, PictureRequest o2) {
|
||||||
|
return Long.signum(o1.priority - o2.priority);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CachedImage scalePicture(File file, Thumbnail thumbnail, String size) throws TimeoutException {
|
||||||
|
|
||||||
|
PictureRequest req = getPictureRequest(file, thumbnail, size);
|
||||||
|
try {
|
||||||
|
return req.waitForIt(30, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException toe) {
|
||||||
|
req.cancel();
|
||||||
|
toe.printStackTrace();
|
||||||
|
throw new TimeoutException("timed out");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
completePictureRequest(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file
|
||||||
|
* @param thumbnail
|
||||||
|
* @param size
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
CachedImage scalePictureReally(File file, Thumbnail thumbnail, String size) throws IOException {
|
||||||
|
|
||||||
|
Dimension orig = thumbnail.getSize();
|
||||||
|
Dimension outd;
|
||||||
|
if (size.endsWith("h")) {
|
||||||
|
int height = Integer.parseInt(size.substring(0, size.length() - 1));
|
||||||
|
outd = orig.scaleHeight(height);
|
||||||
|
} else if (size.endsWith("w")) {
|
||||||
|
int width = Integer.parseInt(size.substring(0, size.length() - 1));
|
||||||
|
outd = orig.scaleWidth(width);
|
||||||
|
} else {
|
||||||
|
int worh = Integer.parseInt(size);
|
||||||
|
outd = orig.scaled(worh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In order to make the quality as good as possible we follow the advice from
|
||||||
|
* http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
|
||||||
|
* and scale the image by a factor of 2 in intermediate steps, thereby getting a smoother
|
||||||
|
* end result. The first scale will get to a size which is a multiple of the result size.
|
||||||
|
* the first scaling operation will also take care of any rotation that needs to happen.
|
||||||
|
*/
|
||||||
|
Dimension intermediate = new Dimension(outd);
|
||||||
|
int targetWidth = orig.getWidth() / 2;
|
||||||
|
while (intermediate.getWidth() < targetWidth) {
|
||||||
|
intermediate = intermediate.doubled();
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
|
||||||
|
ImageReader reader = readers.next();
|
||||||
|
ImageInputStream iis = ImageIO.createImageInputStream(file);
|
||||||
|
reader.setInput(iis, true);
|
||||||
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
BufferedImage img = reader.read(0, param);
|
||||||
|
|
||||||
|
// Recalculate scale after sub-sampling was applied
|
||||||
|
double scale;
|
||||||
|
AffineTransform xform;
|
||||||
|
if (thumbnail.getOrientation() == 6) {
|
||||||
|
xform = AffineTransform.getTranslateInstance(intermediate.getWidth() / 2d, intermediate.getHeight() / 2d);
|
||||||
|
xform.rotate(Math.PI / 2);
|
||||||
|
scale = (double)intermediate.getHeight() / img.getWidth();
|
||||||
|
xform.scale(scale, scale);
|
||||||
|
xform.translate(-img.getWidth() / 2d, -img.getHeight() / 2d);
|
||||||
|
} else {
|
||||||
|
scale = (double)intermediate.getWidth() / img.getWidth();
|
||||||
|
xform = AffineTransform.getScaleInstance(scale, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
int imgType = img.getType();
|
||||||
|
BufferedImage buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), imgType);
|
||||||
|
Graphics2D g2 = buf2.createGraphics();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
|
||||||
|
g2.transform(xform);
|
||||||
|
g2.drawImage(img, 0, 0, null);
|
||||||
|
img = null;
|
||||||
|
|
||||||
|
while (intermediate.getWidth() > outd.getWidth()) {
|
||||||
|
|
||||||
|
BufferedImage buf3 = buf2;
|
||||||
|
intermediate = intermediate.halved();
|
||||||
|
buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), imgType);
|
||||||
|
g2 = buf2.createGraphics();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2.drawImage(buf3, 0, 0, intermediate.getWidth(), intermediate.getHeight(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
||||||
|
ImageWriter writer = writers.next();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bits = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
ImageOutputStream ios = ImageIO.createImageOutputStream(bits);
|
||||||
|
writer.setOutput(ios);
|
||||||
|
writer.write(buf2);
|
||||||
|
ios.flush();
|
||||||
|
|
||||||
|
CachedImage cimg = new CachedImage();
|
||||||
|
cimg.lastModified = file.lastModified();
|
||||||
|
cimg.mimeType = "image/jpeg";
|
||||||
|
cimg.bits = bits.toByteArray();
|
||||||
|
return cimg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
#set($thpath = $mapper.map(${entry.thumbnail.getPath()}))
|
#set($thpath = $mapper.map(${entry.thumbnail.getPath()}))
|
||||||
#set($enpath = $mapper.map(${entry.getPath()}))
|
#set($enpath = $mapper.map(${entry.getPath()}))
|
||||||
<div class="photo">
|
<div class="photo">
|
||||||
<a href="${base}${enpath}"><img src="${base}${thpath}?size=$thmb" border="0" width="$dim.width" height="$dim.height"/></a><br/>
|
<img src="${base}${thpath}?size=$thmb" border="0" width="$dim.width" height="$dim.height"/><br/>
|
||||||
<span class="caption">$!entry.caption</span>
|
<span class="caption">$!entry.caption</span>
|
||||||
</div>
|
</div>
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue