Fix bug wrong orientation caused by EXIF
This commit is contained in:
@@ -44,6 +44,13 @@
|
|||||||
<version>3.0.5</version>
|
<version>3.0.5</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Elytron secrity used for username/password login -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.wildfly.security</groupId>
|
<groupId>org.wildfly.security</groupId>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package marketing.heyday.hartmann.fotodocumentation.core.utils;
|
|||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -17,6 +18,12 @@ import javax.imageio.stream.ImageOutputStream;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
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>
|
* <p>Copyright: Copyright (c) 2024</p>
|
||||||
@@ -44,10 +51,10 @@ public class ImageUtil {
|
|||||||
public byte[] getImage(String base64, int size) {
|
public byte[] getImage(String base64, int size) {
|
||||||
byte[] original = Base64.getDecoder().decode(base64);
|
byte[] original = Base64.getDecoder().decode(base64);
|
||||||
return switch (size) {
|
return switch (size) {
|
||||||
case 1 -> original;
|
case 1 -> applyExifOrientation(original);
|
||||||
case 2 -> normal(original);
|
case 2 -> normal(original);
|
||||||
case 3 -> thumbnail(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) {
|
private byte[] resize(byte[] original, int maxWidth, float quality) {
|
||||||
try {
|
try {
|
||||||
|
int orientation = getExifOrientation(original);
|
||||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(original));
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(original));
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
LOG.error("Failed to read image from byte array");
|
LOG.error("Failed to read image from byte array");
|
||||||
return original;
|
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();
|
int originalWidth = image.getWidth();
|
||||||
|
|
||||||
if (originalWidth <= maxWidth) {
|
if (originalWidth <= maxWidth) {
|
||||||
return original;
|
return writeJpeg(image, quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
double scale = (double) maxWidth / originalWidth;
|
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 {
|
private byte[] writeJpeg(BufferedImage image, float quality) throws IOException {
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
|
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public class PictureResourceTest extends AbstractRestTest {
|
|||||||
|
|
||||||
String authorization = getAuthorization();
|
String authorization = getAuthorization();
|
||||||
LOG.info("authorization: " + authorization);
|
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")
|
Request request = Request.Get(path).addHeader("Accept", "image/jpg")
|
||||||
.addHeader("Authorization", authorization);
|
.addHeader("Authorization", authorization);
|
||||||
|
|
||||||
@@ -178,4 +178,16 @@ public class PictureResourceTest extends AbstractRestTest {
|
|||||||
int code = httpResponse.getStatusLine().getStatusCode();
|
int code = httpResponse.getStatusLine().getStatusCode();
|
||||||
assertEquals(404, code);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user