- No max dimensions specified : render raw image data when source and

target image format are the same.
- Corrected scaling condition.
pull/26/head
luc 9 years ago
parent 4c36b7bd14
commit e40ae0943b

@ -26,13 +26,17 @@ import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.MediaTracker; import java.awt.MediaTracker;
import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.Raster; import java.awt.image.Raster;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import net.yacy.cora.document.id.DigestURL; import net.yacy.cora.document.id.DigestURL;
@ -232,10 +236,11 @@ public class ViewImage {
ImageInputStream imageInStream) throws IOException { ImageInputStream imageInStream) throws IOException {
EncodedImage encodedImage = null; EncodedImage encodedImage = null;
BufferedImage image = ImageIO.read(imageInStream); // BufferedImage image = ImageIO.read(imageInStream);
if (image == null) { Iterator<ImageReader> readers = ImageIO.getImageReaders(imageInStream);
if (!readers.hasNext()) {
try { try {
/* When a null image is returned, we have to close the stream */ /* When no reader can be found, we have to close the stream */
imageInStream.close(); imageInStream.close();
} catch (IOException ignoredException) { } catch (IOException ignoredException) {
} }
@ -245,16 +250,20 @@ public class ViewImage {
*/ */
throw new IOException("Image format is not supported."); throw new IOException("Image format is not supported.");
} }
ImageReader reader = readers.next();
reader.setInput(imageInStream, true, true);
int maxwidth = post.getInt("maxwidth", 0); int maxwidth = post.getInt("maxwidth", 0);
int maxheight = post.getInt("maxheight", 0); int maxheight = post.getInt("maxheight", 0);
final boolean quadratic = post.containsKey("quadratic"); final boolean quadratic = post.containsKey("quadratic");
boolean isStatic = post.getBoolean("isStatic"); boolean isStatic = post.getBoolean("isStatic");
BufferedImage image = null;
boolean returnRaw = true;
if (!auth || maxwidth != 0 || maxheight != 0) { if (!auth || maxwidth != 0 || maxheight != 0) {
// find original size // find original size
final int originWidth = image.getWidth(null); final int originWidth = reader.getWidth(0);
final int originHeigth = image.getHeight(null); final int originHeigth = reader.getHeight(0);
// in case of not-authorized access shrink the image to // in case of not-authorized access shrink the image to
// prevent // prevent
@ -267,35 +276,115 @@ public class ViewImage {
int w = originWidth; int w = originWidth;
int h = originHeigth; int h = originHeigth;
if (quadratic && originWidth != originHeigth) { if (quadratic && originWidth != originHeigth) {
image = makeSquare(image, originHeigth, originWidth); Rectangle square = getMaxSquare(originHeigth, originWidth);
h = image.getHeight(null); h = square.height;
w = image.getWidth(null); w = square.width;
} }
Dimension finalDimensions = calculateDimensions(w, h, maxDimensions); Dimension finalDimensions = calculateDimensions(w, h, maxDimensions);
if (w != finalDimensions.width && h != finalDimensions.height) { if (originWidth != finalDimensions.width || originHeigth != finalDimensions.height) {
returnRaw = false;
image = readImage(reader);
if (quadratic && originWidth != originHeigth) {
image = makeSquare(image);
}
image = scale(finalDimensions.width, finalDimensions.height, image); image = scale(finalDimensions.width, finalDimensions.height, image);
} }
if (finalDimensions.width == 16 && finalDimensions.height == 16) { if (finalDimensions.width == 16 && finalDimensions.height == 16) {
// this might be a favicon, store image to cache for // this might be a favicon, store image to cache for
// faster // faster
// re-load later on // re-load later on
if (image == null) {
returnRaw = false;
image = readImage(reader);
}
iconcache.put(urlString, image); iconcache.put(urlString, image);
} }
} }
/* /* Image do not need to be scaled or cropped */
* An error can still occur when transcoding from buffered image to if (returnRaw) {
* target ext : in that case return null if (!reader.getFormatName().equalsIgnoreCase(ext) || imageInStream.getFlushedPosition() != 0) {
*/ /*
encodedImage = new EncodedImage(image, ext, isStatic); * image parsing and reencoding is only needed when source image
if (encodedImage.getImage().length() == 0) { * and target formats differ, or when first bytes have been discarded
throw new IOException("Image could not be encoded to format : " + ext); */
returnRaw = false;
image = readImage(reader);
}
} }
if (returnRaw) {
byte[] imageData = readRawImage(imageInStream);
encodedImage = new EncodedImage(imageData, ext, isStatic);
} else {
/*
* An error can still occur when transcoding from buffered image to
* target ext : in that case return null
*/
encodedImage = new EncodedImage(image, ext, isStatic);
if (encodedImage.getImage().length() == 0) {
throw new IOException("Image could not be encoded to format : " + ext);
}
}
return encodedImage; return encodedImage;
} }
/**
* Read image using specified reader and close ImageInputStream source.
* Input must have bean set before using
* {@link ImageReader#setInput(Object)}
*
* @param reader
* image reader. Must not be null.
* @return buffered image
* @throws IOException
* when an error occured
*/
private static BufferedImage readImage(ImageReader reader) throws IOException {
BufferedImage image;
try {
image = reader.read(0);
} finally {
reader.dispose();
Object input = reader.getInput();
if (input instanceof ImageInputStream) {
try {
((ImageInputStream) input).close();
} catch (IOException ignoredException) {
}
}
}
return image;
}
/**
* Read image data without parsing.
*
* @param inStream
* image source. Must not be null. First bytes must not have been marked discarded ({@link ImageInputStream#getFlushedPosition()} must be zero)
* @return image data as bytes
* @throws IOException
* when a read/write error occured.
*/
private static byte[] readRawImage(ImageInputStream inStream) throws IOException {
byte[] buffer = new byte[4096];
int l = 0;
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
inStream.seek(0);
try {
while ((l = inStream.read(buffer)) >= 0) {
outStream.write(buffer, 0, l);
}
return outStream.toByteArray();
} finally {
try {
inStream.close();
} catch (IOException ignored) {
}
}
}
/** /**
* Calculate image dimensions from image original dimensions, max * Calculate image dimensions from image original dimensions, max
* dimensions, and target dimensions. * dimensions, and target dimensions.
@ -413,29 +502,55 @@ public class ViewImage {
return result; return result;
} }
/**
*
* @param h
* image height
* @param w
* image width
* @return max square area fitting inside dimensions
*/
protected static Rectangle getMaxSquare(final int h, final int w) {
Rectangle square;
if (w > h) {
final int offset = (w - h) / 2;
square = new Rectangle(offset, 0, h, h);
} else {
final int offset = (h - w) / 2;
square = new Rectangle(0, offset, w, w);
}
return square;
}
/** /**
* Crop image to make a square * Crop image to make a square
* *
* @param image * @param image
* image to crop * image to crop
* @param h
* @param w
* @return * @return
*/ */
protected static BufferedImage makeSquare(BufferedImage image, final int h, final int w) { protected static BufferedImage makeSquare(BufferedImage image) {
final int w = image.getWidth();
final int h = image.getHeight();
if (w > h) { if (w > h) {
final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_ARGB); final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = dst.createGraphics(); Graphics2D g = dst.createGraphics();
final int offset = (w - h) / 2; final int offset = (w - h) / 2;
g.drawImage(image, 0, 0, h - 1, h - 1, offset, 0, h + offset, h - 1, null); try {
g.dispose(); g.drawImage(image, 0, 0, h - 1, h - 1, offset, 0, h + offset, h - 1, null);
} finally {
g.dispose();
}
image = dst; image = dst;
} else { } else {
final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB); final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = dst.createGraphics(); Graphics2D g = dst.createGraphics();
final int offset = (h - w) / 2; final int offset = (h - w) / 2;
g.drawImage(image, 0, 0, w - 1, w - 1, 0, offset, w - 1, w + offset, null); try {
g.dispose(); g.drawImage(image, 0, 0, w - 1, w - 1, 0, offset, w - 1, w + offset, null);
} finally {
g.dispose();
}
image = dst; image = dst;
} }
return image; return image;

@ -34,6 +34,26 @@ public class EncodedImage {
private ByteBuffer image; private ByteBuffer image;
private String extension; private String extension;
private boolean isStatic; private boolean isStatic;
/**
* Instanciates an encoded image with raw image data.
* Image ByteBuffer will be empty when encoding format is not supported.
* @param imageData the image data encode in format specified. Must not be null.
* @param format the image format of imageData. Must not be null.
* @param isStatic shall be true if the image will never change, false if not
* @throws IllegalArgumentException when imageData or format parameter is null
*/
public EncodedImage(final byte[] imageData, final String format, final boolean isStatic) {
if(imageData == null) {
throw new IllegalArgumentException("imageData parameter is null");
}
if(format == null) {
throw new IllegalArgumentException("format parameter is null");
}
this.image = new ByteBuffer(imageData);
this.extension = format;
this.isStatic = isStatic;
}
/** /**
* set an encoded image; prefer this over methods with Image-source objects because png generation is faster when done from RasterPlotter sources. * set an encoded image; prefer this over methods with Image-source objects because png generation is faster when done from RasterPlotter sources.

Loading…
Cancel
Save