From 08e330c1c0a62ecfb83c2e7617472bd5a50f8c5a Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 26 Mar 2025 18:06:08 +0100 Subject: [PATCH 1/2] [DURACOM-243] Adds rotation handling inside JPEGFilter Conflicts: --- .../mediafilter/BrandedPreviewJPEGFilter.java | 23 +- .../dspace/app/mediafilter/JPEGFilter.java | 276 ++++++++++++++---- .../app/mediafilter/PDFBoxThumbnail.java | 1 + 3 files changed, 232 insertions(+), 68 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java index 7b082c6c21..483e4f5f6e 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java @@ -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 + ); } } diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java index 502f71eb5c..181e3bcc4b 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java @@ -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 diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java index 3acb6900db..577f1dec4a 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java @@ -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); } } From 82d04061c084486b92c9f643b96f59ab81fd4a87 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 27 Mar 2025 12:38:59 +0100 Subject: [PATCH 2/2] [DURACOM-243] Adds Test for JPEGFilter --- .../dspace/app/mediafilter/JPEGFilter.java | 53 ++-- .../app/mediafilter/JPEGFilterTest.java | 270 ++++++++++++++++++ .../dspace/app/mediafilter/cat-rotated-90.jpg | Bin 0 -> 36813 bytes .../org/dspace/app/mediafilter/cat.jpg | Bin 0 -> 36813 bytes 4 files changed, 290 insertions(+), 33 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/cat-rotated-90.jpg create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/cat.jpg diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java index 181e3bcc4b..2ccc2afbb2 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java @@ -37,7 +37,6 @@ 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 @@ -117,25 +116,6 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats } } - /** - * 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 * @@ -261,14 +241,20 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats } } - int rotation = getImageRotationUsingImageReader(new FileInputStream(tempFile)); - // read in bitstream's image - BufferedImage buf = ImageIO.read(new FileInputStream(tempFile)); + int rotation = 0; + try (FileInputStream fis = new FileInputStream(tempFile)) { + rotation = getImageRotationUsingImageReader(fis); + } - return getThumbDim( - currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation, - brandFont - ); + 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) @@ -354,13 +340,14 @@ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats g2d.drawImage(brandImage, 0, ysize, xsize, 20, null); } + + ByteArrayInputStream bais; // now create an input stream for the thumbnail buffer and return it - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - ImageIO.write(thumbnail, "jpeg", baos); - - // now get the array - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + ImageIO.write(thumbnail, "jpeg", baos); + // now get the array + bais = new ByteArrayInputStream(baos.toByteArray()); + } return bais; // hope this gets written out before its garbage collected! } diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java new file mode 100644 index 0000000000..1181dc7a60 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java @@ -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()); + } + +} diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/cat-rotated-90.jpg b/dspace-api/src/test/resources/org/dspace/app/mediafilter/cat-rotated-90.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c0f91c4eda737b23e82a092905cacbf9dc56f29 GIT binary patch literal 36813 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 literal 0 HcmV?d00001