diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java index fb72feaa..95dad73f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java @@ -380,7 +380,7 @@ public class PetController { setGraphic(null); return; } - DesktopImageSupport.loadImageInto(imageView, item, 24, 24); + DesktopImageSupport.loadImageInto(imageView, item, 48, 48); setGraphic(container); } }); diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java index 59cbcb98..e9521231 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; public final class DesktopImageSupport { private static final Map IMAGE_CACHE = new ConcurrentHashMap<>(); + private static final Map ORIENTATION_CACHE = new ConcurrentHashMap<>(); private DesktopImageSupport() { } @@ -22,32 +23,49 @@ public final class DesktopImageSupport { imageView.setPreserveRatio(true); imageView.setSmooth(true); imageView.setImage(null); + imageView.setRotate(0); if (imageUrl == null || imageUrl.isBlank()) { return; } if (imageUrl.startsWith("file:")) { - Image image = new Image(imageUrl, 0, 0, true, true); - if (!image.isError()) { - imageView.setImage(image); - } + new Thread(() -> { + try { + byte[] bytes = java.net.URI.create(imageUrl).toURL().openStream().readAllBytes(); + int orientation = readExifOrientation(bytes); + Image image = new Image(new ByteArrayInputStream(bytes)); + if (!image.isError()) { + Platform.runLater(() -> { + imageView.setImage(image); + imageView.setRotate(exifOrientationToAngle(orientation)); + }); + } + } catch (Exception ignored) { + } + }, "desktop-image-loader").start(); return; } Image cached = IMAGE_CACHE.get(imageUrl); if (cached != null) { imageView.setImage(cached); + imageView.setRotate(exifOrientationToAngle(ORIENTATION_CACHE.getOrDefault(imageUrl, 1))); return; } new Thread(() -> { try { byte[] bytes = ApiClient.getInstance().getBytes(imageUrl); + int orientation = readExifOrientation(bytes); Image image = new Image(new ByteArrayInputStream(bytes)); if (!image.isError()) { IMAGE_CACHE.put(imageUrl, image); - Platform.runLater(() -> imageView.setImage(image)); + ORIENTATION_CACHE.put(imageUrl, orientation); + Platform.runLater(() -> { + imageView.setImage(image); + imageView.setRotate(exifOrientationToAngle(orientation)); + }); } } catch (Exception ignored) { } @@ -57,6 +75,59 @@ public final class DesktopImageSupport { public static void evict(String imageUrl) { if (imageUrl != null && !imageUrl.isBlank()) { IMAGE_CACHE.remove(imageUrl); + ORIENTATION_CACHE.remove(imageUrl); } } + + private static double exifOrientationToAngle(int orientation) { + return switch (orientation) { + case 3 -> 180; + case 6 -> 90; + case 8 -> -90; + default -> 0; + }; + } + + private static int readExifOrientation(byte[] data) { + try { + if (data.length < 2 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) return 1; + int offset = 2; + while (offset + 4 < data.length) { + if ((data[offset] & 0xFF) != 0xFF) break; + int marker = data[offset + 1] & 0xFF; + int segLen = ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF); + if (marker == 0xE1 && offset + 9 < data.length + && data[offset + 4] == 'E' && data[offset + 5] == 'x' + && data[offset + 6] == 'i' && data[offset + 7] == 'f' + && data[offset + 8] == 0 && data[offset + 9] == 0) { + int tiff = offset + 10; + boolean le = data[tiff] == 'I'; + int ifdOffset = readInt(data, tiff + 4, le); + int ifd = tiff + ifdOffset; + int entries = readShort(data, ifd, le); + for (int i = 0; i < entries; i++) { + int e = ifd + 2 + i * 12; + if (e + 9 < data.length && readShort(data, e, le) == 0x0112) { + return readShort(data, e + 8, le); + } + } + } + if (marker == 0xDA) break; + offset += 2 + segLen; + } + } catch (Exception ignored) {} + return 1; + } + + private static int readShort(byte[] data, int offset, boolean le) { + return le + ? (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) + : ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); + } + + private static int readInt(byte[] data, int offset, boolean le) { + return le + ? (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) | ((data[offset + 3] & 0xFF) << 24) + : ((data[offset] & 0xFF) << 24) | ((data[offset + 1] & 0xFF) << 16) | ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF); + } }