diff --git a/hartmann-foto-documentation-app/pom.xml b/hartmann-foto-documentation-app/pom.xml index 920d058..ab7b148 100644 --- a/hartmann-foto-documentation-app/pom.xml +++ b/hartmann-foto-documentation-app/pom.xml @@ -44,6 +44,13 @@ 3.0.5 + + + com.drewnoakes + metadata-extractor + 2.19.0 + + org.wildfly.security diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageUtil.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageUtil.java index b1b607d..7725ca8 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageUtil.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageUtil.java @@ -2,6 +2,7 @@ package marketing.heyday.hartmann.fotodocumentation.core.utils; import java.awt.Graphics2D; import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -17,13 +18,19 @@ import javax.imageio.stream.ImageOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +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; + /** - * + * *

Copyright: Copyright (c) 2024

*

Company: heyday Marketing GmbH

* @author Patrick Verboom * @version 1.0 - * + * * created: 2 Feb 2026 */ @@ -44,10 +51,10 @@ public class ImageUtil { public byte[] getImage(String base64, int size) { byte[] original = Base64.getDecoder().decode(base64); return switch (size) { - case 1 -> original; + case 1 -> applyExifOrientation(original); case 2 -> normal(original); case 3 -> thumbnail(original); - default -> original; + default -> applyExifOrientation(original); }; } @@ -61,16 +68,27 @@ public class ImageUtil { private byte[] resize(byte[] original, int maxWidth, float quality) { try { + int orientation = getExifOrientation(original); BufferedImage image = ImageIO.read(new ByteArrayInputStream(original)); if (image == null) { LOG.error("Failed to read image from byte array"); return original; } + // For rotated images (orientation 5, 6, 7, 8), width and height are swapped + int effectiveWidth = (orientation >= 5 && orientation <= 8) ? image.getHeight() : image.getWidth(); + + // If no resize needed and no orientation fix needed, return original + if (effectiveWidth <= maxWidth && orientation == 1) { + return original; + } + + image = applyOrientation(image, orientation); + int originalWidth = image.getWidth(); if (originalWidth <= maxWidth) { - return original; + return writeJpeg(image, quality); } double scale = (double) maxWidth / originalWidth; @@ -92,6 +110,104 @@ public class ImageUtil { } } + private byte[] applyExifOrientation(byte[] original) { + try { + int orientation = getExifOrientation(original); + if (orientation == 1) { + return original; + } + + BufferedImage image = ImageIO.read(new ByteArrayInputStream(original)); + if (image == null) { + return original; + } + + BufferedImage rotated = applyOrientation(image, orientation); + return writeJpeg(rotated, 1.0F); + } catch (IOException e) { + LOG.error("Failed to apply EXIF orientation", e); + return original; + } + } + + private int getExifOrientation(byte[] imageBytes) { + try { + Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageBytes)); + ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); + if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { + return directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); + } + } catch (ImageProcessingException | IOException | MetadataException e) { + LOG.debug("Could not read EXIF orientation: " + e.getMessage()); + } + return 1; + } + + private BufferedImage applyOrientation(BufferedImage image, int orientation) { + int width = image.getWidth(); + int height = image.getHeight(); + + AffineTransform transform = new AffineTransform(); + int newWidth = width; + int newHeight = height; + + switch (orientation) { + case 1 -> { + return image; + } + case 2 -> { + transform.scale(-1.0, 1.0); + transform.translate(-width, 0); + } + case 3 -> { + transform.translate(width, height); + transform.rotate(Math.PI); + } + case 4 -> { + transform.scale(1.0, -1.0); + transform.translate(0, -height); + } + case 5 -> { + newWidth = height; + newHeight = width; + transform.rotate(-Math.PI / 2); + transform.scale(-1.0, 1.0); + } + case 6 -> { + newWidth = height; + newHeight = width; + transform.translate(height, 0); + transform.rotate(Math.PI / 2); + } + case 7 -> { + newWidth = height; + newHeight = width; + transform.scale(-1.0, 1.0); + transform.translate(-height, 0); + transform.translate(0, width); + transform.rotate(3 * Math.PI / 2); + } + case 8 -> { + newWidth = height; + newHeight = width; + transform.translate(0, width); + transform.rotate(-Math.PI / 2); + } + default -> { + return image; + } + } + + BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = rotated.createGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.drawImage(image, transform, null); + g2d.dispose(); + + return rotated; + } + private byte[] writeJpeg(BufferedImage image, float quality) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); diff --git a/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResourceTest.java b/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResourceTest.java index cb0073f..449ebb6 100644 --- a/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResourceTest.java +++ b/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResourceTest.java @@ -150,7 +150,7 @@ public class PictureResourceTest extends AbstractRestTest { String authorization = getAuthorization(); LOG.info("authorization: " + authorization); - String path = deploymentURL + PATH + "/image/1?size=1"; + String path = deploymentURL + PATH + "/image/37?size=1"; Request request = Request.Get(path).addHeader("Accept", "image/jpg") .addHeader("Authorization", authorization); @@ -178,4 +178,16 @@ public class PictureResourceTest extends AbstractRestTest { int code = httpResponse.getStatusLine().getStatusCode(); assertEquals(404, code); } + + public static void main(String[] args) throws IOException { + + var test = new PictureResourceTest(); + + test.deploymentURL = "http://localhost:8080/"; + test.deploymentURL = "https://hartmann-cue.heydevelop.de/"; + test.username = "adm"; + test.password = "x1t0e7Pb49"; + + test.doGetPicture(); + } }