mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00
[DURACOM-243] Adds rotation handling inside JPEGFilter
Conflicts:
(cherry picked from commit 08e330c1c0
)
This commit is contained in:
@@ -7,9 +7,7 @@
|
||||
*/
|
||||
package org.dspace.app.mediafilter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
@@ -63,27 +61,20 @@ public class BrandedPreviewJPEGFilter extends MediaFilter {
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
// read in bitstream's image
|
||||
BufferedImage buf = ImageIO.read(source);
|
||||
|
||||
// get config params
|
||||
ConfigurationService configurationService
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
float xmax = (float) configurationService
|
||||
.getIntProperty("webui.preview.maxwidth");
|
||||
float ymax = (float) configurationService
|
||||
.getIntProperty("webui.preview.maxheight");
|
||||
boolean blurring = (boolean) configurationService
|
||||
.getBooleanProperty("webui.preview.blurring");
|
||||
boolean hqscaling = (boolean) configurationService
|
||||
.getBooleanProperty("webui.preview.hqscaling");
|
||||
int xmax = configurationService.getIntProperty("webui.preview.maxwidth");
|
||||
int ymax = configurationService.getIntProperty("webui.preview.maxheight");
|
||||
boolean blurring = configurationService.getBooleanProperty("webui.preview.blurring");
|
||||
boolean hqscaling = configurationService.getBooleanProperty("webui.preview.hqscaling");
|
||||
int brandHeight = configurationService.getIntProperty("webui.preview.brand.height");
|
||||
String brandFont = configurationService.getProperty("webui.preview.brand.font");
|
||||
int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint");
|
||||
|
||||
JPEGFilter jpegFilter = new JPEGFilter();
|
||||
return jpegFilter
|
||||
.getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint,
|
||||
brandFont);
|
||||
return jpegFilter.getThumb(
|
||||
currentItem, source, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, brandFont
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -8,22 +8,36 @@
|
||||
package org.dspace.app.mediafilter;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.BufferedImageOp;
|
||||
import java.awt.image.ConvolveOp;
|
||||
import java.awt.image.Kernel;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.drew.imaging.ImageMetadataReader;
|
||||
import com.drew.imaging.ImageProcessingException;
|
||||
import com.drew.metadata.Metadata;
|
||||
import com.drew.metadata.MetadataException;
|
||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* Filter image bitstreams, scaling the image to be within the bounds of
|
||||
@@ -33,6 +47,8 @@ import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
* @author Jason Sherman jsherman@usao.edu
|
||||
*/
|
||||
public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats {
|
||||
private static final Logger log = LogManager.getLogger(JPEGFilter.class);
|
||||
|
||||
@Override
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".jpg";
|
||||
@@ -62,6 +78,134 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
|
||||
return "Generated Thumbnail";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation angle from image's metadata using ImageReader.
|
||||
* This method consumes the InputStream, so you need to be careful to don't reuse the same InputStream after
|
||||
* computing the rotation angle.
|
||||
*
|
||||
* @param buf InputStream of the image file
|
||||
* @return Rotation angle in degrees (0, 90, 180, or 270)
|
||||
*/
|
||||
public static int getImageRotationUsingImageReader(InputStream buf) {
|
||||
try {
|
||||
Metadata metadata = ImageMetadataReader.readMetadata(buf);
|
||||
ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||
if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
|
||||
return convertRotationToDegrees(directory.getInt(ExifIFD0Directory.TAG_ORIENTATION));
|
||||
}
|
||||
} catch (MetadataException | ImageProcessingException | IOException e) {
|
||||
log.error("Error reading image metadata", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int convertRotationToDegrees(int valueNode) {
|
||||
// Common orientation values:
|
||||
// 1 = Normal (0°)
|
||||
// 6 = Rotated 90° CW
|
||||
// 3 = Rotated 180°
|
||||
// 8 = Rotated 270° CW
|
||||
switch (valueNode) {
|
||||
case 6:
|
||||
return 90;
|
||||
case 3:
|
||||
return 180;
|
||||
case 8:
|
||||
return 270;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to find a node with given name in metadata tree
|
||||
*/
|
||||
private static Node findNode(Node node, String name) {
|
||||
if (node.getNodeName().equalsIgnoreCase(name)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Node child = node.getFirstChild();
|
||||
while (child != null) {
|
||||
Node found = findNode(child, name);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates an image by the specified angle
|
||||
*
|
||||
* @param image The original image
|
||||
* @param angle The rotation angle in degrees
|
||||
* @return Rotated image
|
||||
*/
|
||||
public static BufferedImage rotateImage(BufferedImage image, int angle) {
|
||||
if (angle == 0) {
|
||||
return image;
|
||||
}
|
||||
|
||||
double radians = Math.toRadians(angle);
|
||||
double sin = Math.abs(Math.sin(radians));
|
||||
double cos = Math.abs(Math.cos(radians));
|
||||
|
||||
int newWidth = (int) Math.round(image.getWidth() * cos + image.getHeight() * sin);
|
||||
int newHeight = (int) Math.round(image.getWidth() * sin + image.getHeight() * cos);
|
||||
|
||||
BufferedImage rotated = new BufferedImage(newWidth, newHeight, image.getType());
|
||||
Graphics2D g2d = rotated.createGraphics();
|
||||
AffineTransform at = new AffineTransform();
|
||||
|
||||
at.translate(newWidth / 2, newHeight / 2);
|
||||
at.rotate(radians);
|
||||
at.translate(-image.getWidth() / 2, -image.getHeight() / 2);
|
||||
|
||||
g2d.setTransform(at);
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
g2d.dispose();
|
||||
|
||||
return rotated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates scaled dimension while maintaining aspect ratio
|
||||
*
|
||||
* @param imgSize Original image dimensions
|
||||
* @param boundary Maximum allowed dimensions
|
||||
* @return New dimensions that fit within boundary while preserving aspect ratio
|
||||
*/
|
||||
private Dimension getScaledDimension(Dimension imgSize, Dimension boundary) {
|
||||
|
||||
int originalWidth = imgSize.width;
|
||||
int originalHeight = imgSize.height;
|
||||
int boundWidth = boundary.width;
|
||||
int boundHeight = boundary.height;
|
||||
int newWidth = originalWidth;
|
||||
int newHeight = originalHeight;
|
||||
|
||||
|
||||
// First check if we need to scale width
|
||||
if (originalWidth > boundWidth) {
|
||||
// Scale width to fit
|
||||
newWidth = boundWidth;
|
||||
// Scale height to maintain aspect ratio
|
||||
newHeight = (newWidth * originalHeight) / originalWidth;
|
||||
}
|
||||
|
||||
// Then check if we need to scale even with the new height
|
||||
if (newHeight > boundHeight) {
|
||||
// Scale height to fit instead
|
||||
newHeight = boundHeight;
|
||||
newWidth = (newHeight * originalWidth) / originalHeight;
|
||||
}
|
||||
|
||||
return new Dimension(newWidth, newHeight);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param currentItem item
|
||||
* @param source source input stream
|
||||
@@ -72,10 +216,59 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
// read in bitstream's image
|
||||
BufferedImage buf = ImageIO.read(source);
|
||||
return getThumb(currentItem, source, verbose);
|
||||
}
|
||||
|
||||
return getThumb(currentItem, buf, verbose);
|
||||
public InputStream getThumb(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
// get config params
|
||||
final ConfigurationService configurationService
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
int xmax = configurationService
|
||||
.getIntProperty("thumbnail.maxwidth");
|
||||
int ymax = configurationService
|
||||
.getIntProperty("thumbnail.maxheight");
|
||||
boolean blurring = (boolean) configurationService
|
||||
.getBooleanProperty("thumbnail.blurring");
|
||||
boolean hqscaling = (boolean) configurationService
|
||||
.getBooleanProperty("thumbnail.hqscaling");
|
||||
|
||||
return getThumb(currentItem, source, verbose, xmax, ymax, blurring, hqscaling, 0, 0, null);
|
||||
}
|
||||
|
||||
protected InputStream getThumb(
|
||||
Item currentItem,
|
||||
InputStream source,
|
||||
boolean verbose,
|
||||
int xmax,
|
||||
int ymax,
|
||||
boolean blurring,
|
||||
boolean hqscaling,
|
||||
int brandHeight,
|
||||
int brandFontPoint,
|
||||
String brandFont
|
||||
) throws Exception {
|
||||
|
||||
File tempFile = File.createTempFile("temp", ".tmp");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
// Write to temp file
|
||||
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = source.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
int rotation = getImageRotationUsingImageReader(new FileInputStream(tempFile));
|
||||
// read in bitstream's image
|
||||
BufferedImage buf = ImageIO.read(new FileInputStream(tempFile));
|
||||
|
||||
return getThumbDim(
|
||||
currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation,
|
||||
brandFont
|
||||
);
|
||||
}
|
||||
|
||||
public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose)
|
||||
@@ -83,25 +276,28 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
|
||||
// get config params
|
||||
final ConfigurationService configurationService
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
float xmax = (float) configurationService
|
||||
int xmax = configurationService
|
||||
.getIntProperty("thumbnail.maxwidth");
|
||||
float ymax = (float) configurationService
|
||||
int ymax = configurationService
|
||||
.getIntProperty("thumbnail.maxheight");
|
||||
boolean blurring = (boolean) configurationService
|
||||
.getBooleanProperty("thumbnail.blurring");
|
||||
boolean hqscaling = (boolean) configurationService
|
||||
.getBooleanProperty("thumbnail.hqscaling");
|
||||
|
||||
return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, 0, 0, null);
|
||||
return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, 0, 0, 0, null);
|
||||
}
|
||||
|
||||
public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verbose, float xmax, float ymax,
|
||||
public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verbose, int xmax, int ymax,
|
||||
boolean blurring, boolean hqscaling, int brandHeight, int brandFontPoint,
|
||||
String brandFont)
|
||||
int rotation, String brandFont)
|
||||
throws Exception {
|
||||
// now get the image dimensions
|
||||
float xsize = (float) buf.getWidth(null);
|
||||
float ysize = (float) buf.getHeight(null);
|
||||
|
||||
// Rotate the image if needed
|
||||
BufferedImage correctedImage = rotateImage(buf, rotation);
|
||||
|
||||
int xsize = correctedImage.getWidth();
|
||||
int ysize = correctedImage.getHeight();
|
||||
|
||||
// if verbose flag is set, print out dimensions
|
||||
// to STDOUT
|
||||
@@ -109,77 +305,53 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
|
||||
System.out.println("original size: " + xsize + "," + ysize);
|
||||
}
|
||||
|
||||
// scale by x first if needed
|
||||
if (xsize > xmax) {
|
||||
// calculate scaling factor so that xsize * scale = new size (max)
|
||||
float scale_factor = xmax / xsize;
|
||||
// Calculate new dimensions while maintaining aspect ratio
|
||||
Dimension newDimension = getScaledDimension(
|
||||
new Dimension(xsize, ysize),
|
||||
new Dimension(xmax, ymax)
|
||||
);
|
||||
|
||||
// if verbose flag is set, print out extracted text
|
||||
// to STDOUT
|
||||
if (verbose) {
|
||||
System.out.println("x scale factor: " + scale_factor);
|
||||
}
|
||||
|
||||
// now reduce x size
|
||||
// and y size
|
||||
xsize = xsize * scale_factor;
|
||||
ysize = ysize * scale_factor;
|
||||
|
||||
// if verbose flag is set, print out extracted text
|
||||
// to STDOUT
|
||||
if (verbose) {
|
||||
System.out.println("size after fitting to maximum width: " + xsize + "," + ysize);
|
||||
}
|
||||
}
|
||||
|
||||
// scale by y if needed
|
||||
if (ysize > ymax) {
|
||||
float scale_factor = ymax / ysize;
|
||||
|
||||
// now reduce x size
|
||||
// and y size
|
||||
xsize = xsize * scale_factor;
|
||||
ysize = ysize * scale_factor;
|
||||
}
|
||||
|
||||
// if verbose flag is set, print details to STDOUT
|
||||
if (verbose) {
|
||||
System.out.println("size after fitting to maximum height: " + xsize + ", "
|
||||
+ ysize);
|
||||
System.out.println("size after fitting to maximum height: " + newDimension.width + ", "
|
||||
+ newDimension.height);
|
||||
}
|
||||
|
||||
xsize = newDimension.width;
|
||||
ysize = newDimension.height;
|
||||
|
||||
// create an image buffer for the thumbnail with the new xsize, ysize
|
||||
BufferedImage thumbnail = new BufferedImage((int) xsize, (int) ysize,
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage thumbnail = new BufferedImage(xsize, ysize, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
// Use blurring if selected in config.
|
||||
// a little blur before scaling does wonders for keeping moire in check.
|
||||
if (blurring) {
|
||||
// send the buffered image off to get blurred.
|
||||
buf = getBlurredInstance((BufferedImage) buf);
|
||||
correctedImage = getBlurredInstance(correctedImage);
|
||||
}
|
||||
|
||||
// Use high quality scaling method if selected in config.
|
||||
// this has a definite performance penalty.
|
||||
if (hqscaling) {
|
||||
// send the buffered image off to get an HQ downscale.
|
||||
buf = getScaledInstance((BufferedImage) buf, (int) xsize, (int) ysize,
|
||||
(Object) RenderingHints.VALUE_INTERPOLATION_BICUBIC, (boolean) true);
|
||||
correctedImage = getScaledInstance(correctedImage, xsize, ysize,
|
||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
|
||||
}
|
||||
|
||||
// now render the image into the thumbnail buffer
|
||||
Graphics2D g2d = thumbnail.createGraphics();
|
||||
g2d.drawImage(buf, 0, 0, (int) xsize, (int) ysize, null);
|
||||
g2d.drawImage(correctedImage, 0, 0, xsize, ysize, null);
|
||||
|
||||
if (brandHeight != 0) {
|
||||
ConfigurationService configurationService
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
Brand brand = new Brand((int) xsize, brandHeight, new Font(brandFont, Font.PLAIN, brandFontPoint), 5);
|
||||
Brand brand = new Brand(xsize, brandHeight, new Font(brandFont, Font.PLAIN, brandFontPoint), 5);
|
||||
BufferedImage brandImage = brand.create(configurationService.getProperty("webui.preview.brand"),
|
||||
configurationService.getProperty("webui.preview.brand.abbrev"),
|
||||
currentItem == null ? "" : "hdl:" + currentItem.getHandle());
|
||||
|
||||
g2d.drawImage(brandImage, (int) 0, (int) ysize, (int) xsize, (int) 20, null);
|
||||
g2d.drawImage(brandImage, 0, ysize, xsize, 20, null);
|
||||
}
|
||||
|
||||
// now create an input stream for the thumbnail buffer and return it
|
||||
|
@@ -81,6 +81,7 @@ public class PDFBoxThumbnail extends MediaFilter {
|
||||
|
||||
// Generate thumbnail derivative and return as IO stream.
|
||||
JPEGFilter jpegFilter = new JPEGFilter();
|
||||
|
||||
return jpegFilter.getThumb(currentItem, buf, verbose);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user