Images.java
package jasper.component;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.exif.ExifIFD0Directory;
import io.micrometer.core.annotation.Timed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
public class Images {
private static final Logger logger = LoggerFactory.getLogger(Images.class);
private static final int THUMBNAIL_SIZE = 192;
@Timed(value = "jasper.images")
public byte[] thumbnail(InputStream image) {
try (image) {
return thumbnail(image.readAllBytes());
} catch (IOException e) {
return null;
}
}
@Timed(value = "jasper.images")
public byte[] thumbnail(byte[] imageData) {
try {
// Read orientation from metadata
var orientation = 1;
try {
var metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
var directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
}
} catch (Exception e) {
logger.debug("Error reading EXIF data", e);
}
// Read and process the image
var bi = ImageIO.read(new ByteArrayInputStream(imageData));
if (bi == null) {
logger.debug("Could not read image");
return null;
}
// Apply orientation if needed
bi = rotateImageIfNeeded(bi, orientation);
var bo = new ByteArrayOutputStream();
var width = bi.getWidth();
var height = bi.getHeight();
if (width > THUMBNAIL_SIZE || height > THUMBNAIL_SIZE) {
var ar = (double) width / height;
if (width > height) {
width = THUMBNAIL_SIZE;
height = (int) Math.floor(THUMBNAIL_SIZE / ar);
} else {
height = THUMBNAIL_SIZE;
width = (int) Math.floor(THUMBNAIL_SIZE * ar);
}
var scaled = bi.getScaledInstance(width, height, Image.SCALE_SMOOTH);
ImageIO.write(buffer(scaled, width, height), "png", bo);
} else {
return null;
}
return bo.toByteArray();
} catch (Exception e) {
logger.debug("Error resizing thumbnail", e);
return null;
}
}
private BufferedImage rotateImageIfNeeded(BufferedImage image, int orientation) {
return switch (orientation) {
case 1 -> image; // Normal
case 3 -> // 180 rotate
rotate(image, Math.PI);
case 6 -> // 90 CW
rotate(image, Math.PI / 2);
case 8 -> // 270 CW
rotate(image, -Math.PI / 2);
default -> image;
};
}
private BufferedImage rotate(BufferedImage image, double angle) {
var w = image.getWidth();
var h = image.getHeight();
var rotated = new BufferedImage(w, h, image.getType());
var g = rotated.createGraphics();
g.rotate(angle, w/2, h/2);
g.drawImage(image, 0, 0, null);
g.dispose();
return rotated;
}
public static BufferedImage buffer(Image image, int width, int height) {
if (image instanceof BufferedImage) return (BufferedImage) image;
var bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
var g = bi.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return bi;
}
}