NIHVIVO-817 Use Java Advanced Imaging library to crop and scale images instead of javax.imageio
This commit is contained in:
parent
5203cd0182
commit
85f3fa25fa
3 changed files with 127 additions and 55 deletions
BIN
webapp/lib/jai_codec.jar
Normal file
BIN
webapp/lib/jai_codec.jar
Normal file
Binary file not shown.
BIN
webapp/lib/jai_core.jar
Normal file
BIN
webapp/lib/jai_core.jar
Normal file
Binary file not shown.
|
@ -9,9 +9,7 @@ import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUpload
|
||||||
import static edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest.FILE_ITEM_MAP;
|
import static edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest.FILE_ITEM_MAP;
|
||||||
import static edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest.FILE_UPLOAD_EXCEPTION;
|
import static edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest.FILE_UPLOAD_EXCEPTION;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.image.renderable.ParameterBlock;
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
@ -22,7 +20,10 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.media.jai.Interpolation;
|
||||||
|
import javax.media.jai.JAI;
|
||||||
|
import javax.media.jai.RenderedOp;
|
||||||
|
import javax.media.jai.util.ImagingListener;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.apache.commons.fileupload.FileItem;
|
import org.apache.commons.fileupload.FileItem;
|
||||||
|
@ -30,6 +31,8 @@ import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import com.sun.media.jai.codec.MemoryCacheSeekableStream;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.CropRectangle;
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.CropRectangle;
|
||||||
|
@ -74,6 +77,15 @@ public class ImageUploadHelper {
|
||||||
return Collections.unmodifiableMap(map);
|
return Collections.unmodifiableMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevent Java Advanced Imaging from complaining about the lack of
|
||||||
|
* accelerator classes.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
JAI.getDefaultInstance().setImagingListener(
|
||||||
|
new NonNoisyImagingListener());
|
||||||
|
}
|
||||||
|
|
||||||
private final WebappDaoFactory webAppDaoFactory;
|
private final WebappDaoFactory webAppDaoFactory;
|
||||||
private final FileModelHelper fileModelHelper;
|
private final FileModelHelper fileModelHelper;
|
||||||
private final FileStorage fileStorage;
|
private final FileStorage fileStorage;
|
||||||
|
@ -182,14 +194,18 @@ public class ImageUploadHelper {
|
||||||
* if the image is smaller than a thumbnail.
|
* if the image is smaller than a thumbnail.
|
||||||
*/
|
*/
|
||||||
Dimensions getNewImageSize(FileInfo fileInfo) throws UserMistakeException {
|
Dimensions getNewImageSize(FileInfo fileInfo) throws UserMistakeException {
|
||||||
|
InputStream source = null;
|
||||||
|
try {
|
||||||
String uri = fileInfo.getBytestreamUri();
|
String uri = fileInfo.getBytestreamUri();
|
||||||
String filename = fileInfo.getFilename();
|
String filename = fileInfo.getFilename();
|
||||||
|
|
||||||
InputStream stream = null;
|
source = fileStorage.getInputStream(uri, filename);
|
||||||
try {
|
MemoryCacheSeekableStream stream = new MemoryCacheSeekableStream(
|
||||||
stream = fileStorage.getInputStream(uri, filename);
|
source);
|
||||||
BufferedImage i = ImageIO.read(stream);
|
RenderedOp image = JAI.create("stream", stream);
|
||||||
Dimensions size = new Dimensions(i.getWidth(), i.getHeight());
|
|
||||||
|
Dimensions size = new Dimensions(image.getWidth(), image
|
||||||
|
.getHeight());
|
||||||
log.debug("new image size is " + size);
|
log.debug("new image size is " + size);
|
||||||
|
|
||||||
if ((size.height < THUMBNAIL_HEIGHT)
|
if ((size.height < THUMBNAIL_HEIGHT)
|
||||||
|
@ -207,9 +223,9 @@ public class ImageUploadHelper {
|
||||||
throw new IllegalStateException("Can't read image file: "
|
throw new IllegalStateException("Can't read image file: "
|
||||||
+ fileInfo, e);
|
+ fileInfo, e);
|
||||||
} finally {
|
} finally {
|
||||||
if (stream != null) {
|
if (source != null) {
|
||||||
try {
|
try {
|
||||||
stream.close();
|
source.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -405,61 +421,117 @@ public class ImageUploadHelper {
|
||||||
*/
|
*/
|
||||||
private InputStream scaleImageForThumbnail(InputStream source,
|
private InputStream scaleImageForThumbnail(InputStream source,
|
||||||
CropRectangle crop) throws IOException {
|
CropRectangle crop) throws IOException {
|
||||||
BufferedImage bsrc = ImageIO.read(source);
|
try {
|
||||||
|
// Read the main image.
|
||||||
|
MemoryCacheSeekableStream stream = new MemoryCacheSeekableStream(
|
||||||
|
source);
|
||||||
|
RenderedOp mainImage = JAI.create("stream", stream);
|
||||||
|
int imageWidth = mainImage.getWidth();
|
||||||
|
int imageHeight = mainImage.getHeight();
|
||||||
|
|
||||||
// If the image was displayed in reduced form, adjust the crop info.
|
// Adjust the crop rectangle, if needed, to compensate for scaling
|
||||||
crop = adjustForScaledImageDisplay(bsrc.getWidth(), crop);
|
// and to limit to the image size.
|
||||||
|
crop = adjustCropRectangle(crop, imageWidth, imageHeight);
|
||||||
|
|
||||||
// Insure that x and y fall within the image dimensions.
|
// Crop the image.
|
||||||
int x = Math.max(0, Math.min(bsrc.getWidth(), Math.abs(crop.x)));
|
ParameterBlock cropParams = new ParameterBlock();
|
||||||
int y = Math.max(0, Math.min(bsrc.getHeight(), Math.abs(crop.y)));
|
cropParams.addSource(mainImage);
|
||||||
|
cropParams.add((float) crop.x);
|
||||||
// Insure that width and height are reasonable.
|
cropParams.add((float) crop.y);
|
||||||
int w = Math.max(5, Math.min(bsrc.getWidth() - x, crop.width));
|
cropParams.add((float) crop.width);
|
||||||
int h = Math.max(5, Math.min(bsrc.getHeight() - y, crop.height));
|
cropParams.add((float) crop.height);
|
||||||
|
RenderedOp croppedImage = JAI.create("crop", cropParams);
|
||||||
|
|
||||||
// Figure the scales.
|
// Figure the scales.
|
||||||
double scaleWidth = ((double) THUMBNAIL_WIDTH) / ((double) w);
|
float scaleWidth = ((float) THUMBNAIL_WIDTH) / ((float) crop.width);
|
||||||
double scaleHeight = ((double) THUMBNAIL_HEIGHT) / ((double) h);
|
float scaleHeight = ((float) THUMBNAIL_HEIGHT)
|
||||||
|
/ ((float) crop.height);
|
||||||
log.debug("Generating a thumbnail, initial crop info: " + crop);
|
|
||||||
log.debug("Generating a thumbnail, bounded crop info: " + crop);
|
|
||||||
log.debug("Generating a thumbnail, scales: " + scaleWidth + ", "
|
log.debug("Generating a thumbnail, scales: " + scaleWidth + ", "
|
||||||
+ scaleHeight);
|
+ scaleHeight);
|
||||||
|
|
||||||
// Create the transform.
|
// Create the parameters for the scaling operation.
|
||||||
AffineTransform at = new AffineTransform();
|
Interpolation interpolation = Interpolation
|
||||||
at.scale(scaleWidth, scaleHeight);
|
.getInstance(Interpolation.INTERP_BILINEAR);
|
||||||
at.translate(-x, -y);
|
ParameterBlock scaleParams = new ParameterBlock();
|
||||||
|
scaleParams.addSource(croppedImage);
|
||||||
|
scaleParams.add(scaleWidth); // x scale factor
|
||||||
|
scaleParams.add(scaleHeight); // y scale factor
|
||||||
|
scaleParams.add(0.0F); // x translate
|
||||||
|
scaleParams.add(0.0F); // y translate
|
||||||
|
scaleParams.add(interpolation);
|
||||||
|
RenderedOp image2 = JAI.create("scale", scaleParams);
|
||||||
|
|
||||||
// Apply the transform.
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
BufferedImage bdest = new BufferedImage(THUMBNAIL_WIDTH,
|
JAI.create("encode", image2, bytes, "JPEG", null);
|
||||||
THUMBNAIL_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
bytes.close();
|
||||||
Graphics2D g = bdest.createGraphics();
|
return new ByteArrayInputStream(bytes.toByteArray());
|
||||||
g.drawRenderedImage(bsrc, at);
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to scale the image", e);
|
||||||
// Get an input stream.
|
}
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(bdest, "JPG", buffer);
|
|
||||||
return new ByteArrayInputStream(buffer.toByteArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the source image was too big to fit in the page, then it was displayed
|
* If the source image was too big to fit in the page, then it was displayed
|
||||||
* at a reduced scale. The crop values must expand to apply to the
|
* at a reduced scale, and needs to be unscaled.
|
||||||
* full-sized image.
|
*
|
||||||
|
* The bounds should be limited to the bounds of the image.
|
||||||
*/
|
*/
|
||||||
private CropRectangle adjustForScaledImageDisplay(int imageWidth,
|
private CropRectangle adjustCropRectangle(CropRectangle crop,
|
||||||
CropRectangle crop) {
|
int imageWidth, int imageHeight) {
|
||||||
if (imageWidth <= MAXIMUM_IMAGE_DISPLAY_WIDTH) {
|
log.debug("Generating a thumbnail, initial crop info: " + crop);
|
||||||
return crop;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CropRectangle adjusted;
|
||||||
|
if (imageWidth <= MAXIMUM_IMAGE_DISPLAY_WIDTH) {
|
||||||
|
adjusted = crop;
|
||||||
|
} else {
|
||||||
float displayScale = ((float) MAXIMUM_IMAGE_DISPLAY_WIDTH)
|
float displayScale = ((float) MAXIMUM_IMAGE_DISPLAY_WIDTH)
|
||||||
/ ((float) imageWidth);
|
/ ((float) imageWidth);
|
||||||
log.debug("Generating a thumbnail, unscaled crop info: " + crop
|
adjusted = crop.unscale(displayScale);
|
||||||
|
log.debug("Generating a thumbnail, unscaled crop info: " + adjusted
|
||||||
+ ", displayScale=" + displayScale);
|
+ ", displayScale=" + displayScale);
|
||||||
return crop.unscale(displayScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insure that x and y fall within the image dimensions.
|
||||||
|
int x = Math.max(0, Math.min(imageWidth, Math.abs(adjusted.x)));
|
||||||
|
int y = Math.max(0, Math.min(imageHeight, Math.abs(adjusted.y)));
|
||||||
|
|
||||||
|
// Insure that width and height are reasonable.
|
||||||
|
int w = Math.max(5, Math.min(imageWidth - x, adjusted.width));
|
||||||
|
int h = Math.max(5, Math.min(imageHeight - y, adjusted.height));
|
||||||
|
|
||||||
|
CropRectangle bounded = new CropRectangle(x, y, h, w);
|
||||||
|
log.debug("Generating a thumbnail, bounded crop info: " + bounded);
|
||||||
|
return bounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* This {@link ImagingListener} means that Java Advanced Imaging won't dump
|
||||||
|
* an exception log to {@link System#out}. It writes to the log, instead.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Further, since the lack of native accelerator classes isn't an error, it
|
||||||
|
* is written as a simple log message.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private static class NonNoisyImagingListener implements ImagingListener {
|
||||||
|
@Override
|
||||||
|
public boolean errorOccurred(String message, Throwable thrown,
|
||||||
|
Object where, boolean isRetryable) throws RuntimeException {
|
||||||
|
if (thrown instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) thrown;
|
||||||
|
}
|
||||||
|
if ((thrown instanceof NoClassDefFoundError)
|
||||||
|
&& (thrown.getMessage()
|
||||||
|
.contains("com/sun/medialib/mlib/Image"))) {
|
||||||
|
log.info("Java Advanced Imaging: Could not find mediaLib "
|
||||||
|
+ "accelerator wrapper classes. "
|
||||||
|
+ "Continuing in pure Java mode.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.error(thrown, thrown);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue