Merge pull request #10533 from 4Science/task/dspace-8_x/DURACOM-243

[dspace-8_x] Handles Rotation EXIF Metadata inside JPEGFilter
This commit is contained in:
Tim Donohue
2025-10-02 14:36:24 -05:00
committed by GitHub
6 changed files with 495 additions and 74 deletions

View File

@@ -7,9 +7,7 @@
*/ */
package org.dspace.app.mediafilter; package org.dspace.app.mediafilter;
import java.awt.image.BufferedImage;
import java.io.InputStream; import java.io.InputStream;
import javax.imageio.ImageIO;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
@@ -63,27 +61,20 @@ public class BrandedPreviewJPEGFilter extends MediaFilter {
@Override @Override
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
throws Exception { throws Exception {
// read in bitstream's image
BufferedImage buf = ImageIO.read(source);
// get config params // get config params
ConfigurationService configurationService ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService(); = DSpaceServicesFactory.getInstance().getConfigurationService();
float xmax = (float) configurationService int xmax = configurationService.getIntProperty("webui.preview.maxwidth");
.getIntProperty("webui.preview.maxwidth"); int ymax = configurationService.getIntProperty("webui.preview.maxheight");
float ymax = (float) configurationService boolean blurring = configurationService.getBooleanProperty("webui.preview.blurring");
.getIntProperty("webui.preview.maxheight"); boolean hqscaling = configurationService.getBooleanProperty("webui.preview.hqscaling");
boolean blurring = (boolean) configurationService
.getBooleanProperty("webui.preview.blurring");
boolean hqscaling = (boolean) configurationService
.getBooleanProperty("webui.preview.hqscaling");
int brandHeight = configurationService.getIntProperty("webui.preview.brand.height"); int brandHeight = configurationService.getIntProperty("webui.preview.brand.height");
String brandFont = configurationService.getProperty("webui.preview.brand.font"); String brandFont = configurationService.getProperty("webui.preview.brand.font");
int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint"); int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint");
JPEGFilter jpegFilter = new JPEGFilter(); JPEGFilter jpegFilter = new JPEGFilter();
return jpegFilter return jpegFilter.getThumb(
.getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, currentItem, source, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, brandFont
brandFont); );
} }
} }

View File

@@ -8,19 +8,32 @@
package org.dspace.app.mediafilter; package org.dspace.app.mediafilter;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.Transparency; import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp; import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp; import java.awt.image.ConvolveOp;
import java.awt.image.Kernel; import java.awt.image.Kernel;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; 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 java.io.InputStream;
import javax.imageio.ImageIO; 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.content.Item;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.factory.DSpaceServicesFactory;
@@ -33,6 +46,8 @@ import org.dspace.services.factory.DSpaceServicesFactory;
* @author Jason Sherman jsherman@usao.edu * @author Jason Sherman jsherman@usao.edu
*/ */
public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats { public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats {
private static final Logger log = LogManager.getLogger(JPEGFilter.class);
@Override @Override
public String getFilteredName(String oldFilename) { public String getFilteredName(String oldFilename) {
return oldFilename + ".jpg"; return oldFilename + ".jpg";
@@ -62,6 +77,115 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
return "Generated Thumbnail"; 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;
}
}
/**
* 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 currentItem item
* @param source source input stream * @param source source input stream
@@ -72,10 +196,65 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
@Override @Override
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
throws Exception { throws Exception {
// read in bitstream's image return getThumb(currentItem, source, verbose);
BufferedImage buf = ImageIO.read(source); }
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 = 0;
try (FileInputStream fis = new FileInputStream(tempFile)) {
rotation = getImageRotationUsingImageReader(fis);
}
try (FileInputStream fis = new FileInputStream(tempFile)) {
// read in bitstream's image
BufferedImage buf = ImageIO.read(fis);
return getThumbDim(
currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation,
brandFont
);
}
} }
public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose) public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose)
@@ -83,25 +262,28 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
// get config params // get config params
final ConfigurationService configurationService final ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService(); = DSpaceServicesFactory.getInstance().getConfigurationService();
float xmax = (float) configurationService int xmax = configurationService
.getIntProperty("thumbnail.maxwidth"); .getIntProperty("thumbnail.maxwidth");
float ymax = (float) configurationService int ymax = configurationService
.getIntProperty("thumbnail.maxheight"); .getIntProperty("thumbnail.maxheight");
boolean blurring = (boolean) configurationService boolean blurring = (boolean) configurationService
.getBooleanProperty("thumbnail.blurring"); .getBooleanProperty("thumbnail.blurring");
boolean hqscaling = (boolean) configurationService boolean hqscaling = (boolean) configurationService
.getBooleanProperty("thumbnail.hqscaling"); .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, boolean blurring, boolean hqscaling, int brandHeight, int brandFontPoint,
String brandFont) int rotation, String brandFont)
throws Exception { throws Exception {
// now get the image dimensions
float xsize = (float) buf.getWidth(null); // Rotate the image if needed
float ysize = (float) buf.getHeight(null); BufferedImage correctedImage = rotateImage(buf, rotation);
int xsize = correctedImage.getWidth();
int ysize = correctedImage.getHeight();
// if verbose flag is set, print out dimensions // if verbose flag is set, print out dimensions
// to STDOUT // to STDOUT
@@ -109,86 +291,63 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
System.out.println("original size: " + xsize + "," + ysize); System.out.println("original size: " + xsize + "," + ysize);
} }
// scale by x first if needed // Calculate new dimensions while maintaining aspect ratio
if (xsize > xmax) { Dimension newDimension = getScaledDimension(
// calculate scaling factor so that xsize * scale = new size (max) new Dimension(xsize, ysize),
float scale_factor = xmax / xsize; 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 flag is set, print details to STDOUT
if (verbose) { if (verbose) {
System.out.println("size after fitting to maximum height: " + xsize + ", " System.out.println("size after fitting to maximum height: " + newDimension.width + ", "
+ ysize); + newDimension.height);
} }
xsize = newDimension.width;
ysize = newDimension.height;
// create an image buffer for the thumbnail with the new xsize, ysize // create an image buffer for the thumbnail with the new xsize, ysize
BufferedImage thumbnail = new BufferedImage((int) xsize, (int) ysize, BufferedImage thumbnail = new BufferedImage(xsize, ysize, BufferedImage.TYPE_INT_RGB);
BufferedImage.TYPE_INT_RGB);
// Use blurring if selected in config. // Use blurring if selected in config.
// a little blur before scaling does wonders for keeping moire in check. // a little blur before scaling does wonders for keeping moire in check.
if (blurring) { if (blurring) {
// send the buffered image off to get blurred. // send the buffered image off to get blurred.
buf = getBlurredInstance((BufferedImage) buf); correctedImage = getBlurredInstance(correctedImage);
} }
// Use high quality scaling method if selected in config. // Use high quality scaling method if selected in config.
// this has a definite performance penalty. // this has a definite performance penalty.
if (hqscaling) { if (hqscaling) {
// send the buffered image off to get an HQ downscale. // send the buffered image off to get an HQ downscale.
buf = getScaledInstance((BufferedImage) buf, (int) xsize, (int) ysize, correctedImage = getScaledInstance(correctedImage, xsize, ysize,
(Object) RenderingHints.VALUE_INTERPOLATION_BICUBIC, (boolean) true); RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
} }
// now render the image into the thumbnail buffer // now render the image into the thumbnail buffer
Graphics2D g2d = thumbnail.createGraphics(); 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) { if (brandHeight != 0) {
ConfigurationService configurationService ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService(); = 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"), BufferedImage brandImage = brand.create(configurationService.getProperty("webui.preview.brand"),
configurationService.getProperty("webui.preview.brand.abbrev"), configurationService.getProperty("webui.preview.brand.abbrev"),
currentItem == null ? "" : "hdl:" + currentItem.getHandle()); 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);
} }
ByteArrayInputStream bais;
// now create an input stream for the thumbnail buffer and return it // now create an input stream for the thumbnail buffer and return it
ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(thumbnail, "jpeg", baos); ImageIO.write(thumbnail, "jpeg", baos);
// now get the array // now get the array
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); bais = new ByteArrayInputStream(baos.toByteArray());
}
return bais; // hope this gets written out before its garbage collected! return bais; // hope this gets written out before its garbage collected!
} }

View File

@@ -81,6 +81,7 @@ public class PDFBoxThumbnail extends MediaFilter {
// Generate thumbnail derivative and return as IO stream. // Generate thumbnail derivative and return as IO stream.
JPEGFilter jpegFilter = new JPEGFilter(); JPEGFilter jpegFilter = new JPEGFilter();
return jpegFilter.getThumb(currentItem, buf, verbose); return jpegFilter.getThumb(currentItem, buf, verbose);
} }
} }

View File

@@ -0,0 +1,270 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.mediafilter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.dspace.AbstractUnitTest;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.junit.Test;
import org.mockito.Mock;
public class JPEGFilterTest extends AbstractUnitTest {
@Mock
private ConfigurationService mockConfigurationService;
@Mock
private DSpaceServicesFactory mockDSpaceServicesFactory;
@Mock
private InputStream mockInputStream;
@Mock
private Item mockItem;
/**
* Tests that the convertRotationToDegrees method returns 0 for an input value
* that doesn't match any of the defined rotation cases.
*/
@Test
public void testConvertRotationToDegrees_UnknownValue_ReturnsZero() {
int result = JPEGFilter.convertRotationToDegrees(5);
assertEquals(0, result);
}
/**
* Test getNormalizedInstance method with a null input.
* This tests the edge case of passing a null BufferedImage to the method.
* The method should throw a NullPointerException when given a null input.
*/
@Test(expected = NullPointerException.class)
public void testGetNormalizedInstanceWithNullInput() {
JPEGFilter filter = new JPEGFilter();
filter.getNormalizedInstance(null);
}
/**
* Test getThumbDim method with a null BufferedImage input.
* This tests the edge case where the input image is null, which should result in an exception.
*/
@Test(expected = NullPointerException.class)
public void testGetThumbDimWithNullBufferedImage() throws Exception {
JPEGFilter filter = new JPEGFilter();
Item currentItem = null;
BufferedImage buf = null;
boolean verbose = false;
int xmax = 100;
int ymax = 100;
boolean blurring = false;
boolean hqscaling = false;
int brandHeight = 0;
int brandFontPoint = 0;
int rotation = 0;
String brandFont = null;
filter.getThumbDim(
currentItem, buf, verbose, xmax, ymax, blurring, hqscaling,
brandHeight, brandFontPoint, rotation, brandFont
);
}
/**
* Tests that the rotateImage method returns the original image when the rotation angle is 0.
* This is an edge case explicitly handled in the method implementation.
*/
@Test
public void testRotateImageWithZeroAngle() {
BufferedImage originalImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
BufferedImage rotatedImage = JPEGFilter.rotateImage(originalImage, 0);
assertSame(
"When rotation angle is 0, the original image should be returned",
originalImage, rotatedImage
);
}
/**
* Test case for convertRotationToDegrees method when input is 6.
* Expected to return 90 degrees for the rotation value of 6.
*/
@Test
public void test_convertRotationToDegrees_whenInputIs6_returns90() {
int input = 6;
int expected = 90;
int result = JPEGFilter.convertRotationToDegrees(input);
assertEquals(expected, result);
}
/**
* Tests that getBlurredInstance method applies a blur effect to the input image.
* It verifies that the returned image is not null, has the same dimensions as the input,
* and is different from the original image (indicating that blurring has occurred).
*/
@Test
public void test_getBlurredInstance_appliesBlurEffect() {
JPEGFilter filter = new JPEGFilter();
BufferedImage original = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
BufferedImage blurred = filter.getBlurredInstance(original);
assertNotNull("Blurred image should not be null", blurred);
assertEquals("Width should be the same", original.getWidth(), blurred.getWidth());
assertEquals("Height should be the same", original.getHeight(), blurred.getHeight());
assertNotEquals("Blurred image should be different from original", original, blurred);
}
/**
* Test case for getBundleName method of JPEGFilter class.
* This test verifies that the getBundleName method returns the expected string "THUMBNAIL".
*/
@Test
public void test_getBundleName_returnsExpectedString() {
JPEGFilter filter = new JPEGFilter();
String result = filter.getBundleName();
assertEquals("THUMBNAIL", result);
}
/**
* Tests that the getDescription method returns the expected string "Generated Thumbnail".
* This verifies that the method correctly provides the description for the JPEG filter.
*/
@Test
public void test_getDescription_1() {
JPEGFilter filter = new JPEGFilter();
String description = filter.getDescription();
assertEquals("Generated Thumbnail", description);
}
/**
* Tests that getFilteredName method appends ".jpg" to the input filename.
*/
@Test
public void test_getFilteredName_appendsJpgExtension() {
JPEGFilter filter = new JPEGFilter();
String oldFilename = "testimage";
String expectedResult = "testimage.jpg";
String actualResult = filter.getFilteredName(oldFilename);
assertEquals(expectedResult, actualResult);
}
/**
* Test case for getFormatString method of JPEGFilter class.
* Verifies that the method returns the expected string "JPEG".
*/
@Test
public void test_getFormatString_returnsJPEG() {
JPEGFilter filter = new JPEGFilter();
String result = filter.getFormatString();
assertEquals("JPEG", result);
}
/**
* Tests the behavior of getImageRotationUsingImageReader when an ImageProcessingException occurs.
* This test verifies that the method handles an ImageProcessingException by logging the error
* and returning 0 degrees rotation.
*/
@Test
public void test_getImageRotationUsingImageReader_imageProcessingException() {
InputStream errorStream = new InputStream() {
@Override
public int read() throws IOException {
throw new IOException("Simulated image processing error");
}
};
int result = JPEGFilter.getImageRotationUsingImageReader(errorStream);
assertEquals(0, result);
}
/**
* Testcase for getImageRotationUsingImageReader when the image doesn't contain orientation metadata.
* This test verifies that the method returns 0 when there's no ExifIFD0Directory
* or when it doesn't contain the TAG_ORIENTATION.
*/
@Test
public void test_getImageRotationUsingImageReader_noOrientationMetadata() throws IOException {
URL resource = this.getClass().getResource("cat.jpg");
int rotationAngle = -1;
try (InputStream inputStream = new FileInputStream(resource.getFile())) {
// Call the method under test
rotationAngle = JPEGFilter.getImageRotationUsingImageReader(inputStream);
}
assertEquals(0, rotationAngle);
}
/**
* Tests the getImageRotationUsingImageReader method when the image contains
* valid EXIF orientation metadata.
*
* This test verifies that the method correctly reads the orientation tag
* from the EXIF metadata and returns the appropriate rotation angle in degrees.
*/
@Test
public void test_getImageRotationUsingImageReader_withValidExifOrientation() throws Exception {
// Create a mock InputStream with EXIF metadata containing orientation information
URL resource = this.getClass().getResource("cat-rotated-90.jpg");
int rotationAngle = -1;
try (InputStream inputStream = new FileInputStream(resource.getFile())) {
// Call the method under test
rotationAngle = JPEGFilter.getImageRotationUsingImageReader(inputStream);
}
// Assert the expected rotation angle
// Note: The expected value should be adjusted based on the mock data
assertEquals(90, rotationAngle);
}
/**
* Tests the getScaledInstance method of JPEGFilter class with higher quality scaling.
* This test verifies that the method correctly scales down an image in multiple passes
* when higherQuality is true and the image dimensions are larger than the target dimensions.
*/
@Test
public void test_getScaledInstance() {
JPEGFilter filter = new JPEGFilter();
BufferedImage originalImage = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
int targetWidth = 100;
int targetHeight = 75;
Object hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
boolean higherQuality = true;
BufferedImage result = filter.getScaledInstance(originalImage, targetWidth, targetHeight, hint, higherQuality);
assertNotNull(result);
assertEquals(targetWidth, result.getWidth());
assertEquals(targetHeight, result.getHeight());
}
/**
* Tests the rotateImage method with a non-zero angle.
* This test verifies that the image is rotated correctly when given a non-zero angle.
*/
@Test
public void test_rotateImage_nonZeroAngle() {
BufferedImage originalImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB);
int angle = 90;
BufferedImage rotatedImage = JPEGFilter.rotateImage(originalImage, angle);
assertNotNull(rotatedImage);
assertEquals(50, rotatedImage.getWidth());
assertEquals(100, rotatedImage.getHeight());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB