Fix bug wrong orientation caused by EXIF

This commit is contained in:
verboomp
2026-02-06 09:37:03 +01:00
parent bb4d7fbf68
commit b6158d933f
3 changed files with 141 additions and 6 deletions

View File

@@ -44,6 +44,13 @@
<version>3.0.5</version>
</dependency>
<!-- EXIF metadata extraction for image orientation -->
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.19.0</version>
</dependency>
<!-- Elytron secrity used for username/password login -->
<dependency>
<groupId>org.wildfly.security</groupId>

View File

@@ -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,6 +18,12 @@ 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;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
@@ -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();

View File

@@ -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();
}
}