From b1ac7944aeca9e9f94ee78b4e8a62f51e99c2638 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Mon, 13 Oct 2025 14:19:48 +0200 Subject: [PATCH] Erweiterungen --- .../pages/view/MessageDetailsView.java | 119 ++++++++++++++++-- 1 file changed, 110 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java b/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java index ef672c7..c35bbfe 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java @@ -41,8 +41,19 @@ import jakarta.annotation.security.RolesAllowed; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Iterator; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -79,6 +90,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy"); private static final int MAX_IMAGE_FILE_SIZE = 32 * 1024 * 1024; // 32 MB aligns with Spring settings + private static final int TARGET_IMAGE_WIDTH = 1920; + private static final float JPEG_COMPRESSION_QUALITY = 0.8f; public MessageDetailsView(AppUserService appUserService, MessageService messageService, SecurityService securityService, MessageBroadcaster messageBroadcaster) { @@ -258,8 +271,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { upload.addSucceededListener(event -> { try (InputStream inputStream = buffer.getInputStream()) { - byte[] bytes = inputStream.readAllBytes(); - if (bytes.length == 0) { + byte[] rawBytes = inputStream.readAllBytes(); + if (rawBytes.length == 0) { base64Ref.set(null); preview.setVisible(false); confirmButton.setEnabled(false); @@ -268,13 +281,9 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { return; } - String base64 = Base64.getEncoder().encodeToString(bytes); - base64Ref.set(base64); - - String mimeType = Optional.ofNullable(event.getMIMEType()).filter(value -> !value.isBlank()) - .orElseGet(() -> detectImageMimeType(bytes)); - String dataUrl = createDataUrlFromContent(base64, mimeType); - if (dataUrl == null) { + String processedBase64 = processImageToJpegBase64(rawBytes); + if (processedBase64 == null) { + base64Ref.set(null); preview.setVisible(false); confirmButton.setEnabled(false); Notification.show("Das Bild konnte nicht verarbeitet werden.", 3000, @@ -282,6 +291,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { return; } + base64Ref.set(processedBase64); + String dataUrl = "data:image/jpeg;base64," + processedBase64; preview.setSrc(dataUrl); preview.setVisible(true); confirmButton.setEnabled(true); @@ -517,6 +528,96 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { return "image/png"; } + private String processImageToJpegBase64(byte[] originalBytes) { + try { + BufferedImage original = ImageIO.read(new ByteArrayInputStream(originalBytes)); + if (original == null) { + log.warn("Bild konnte nicht geladen werden (ImageIO liefert null)"); + return null; + } + + int originalWidth = original.getWidth(); + int originalHeight = original.getHeight(); + if (originalWidth <= 0 || originalHeight <= 0) { + log.warn("Ungültige Bildmaße: {}x{}", originalWidth, originalHeight); + return null; + } + + int targetWidth = Math.min(originalWidth, TARGET_IMAGE_WIDTH); + int targetHeight = (int) Math.round((double) targetWidth / originalWidth * originalHeight); + if (targetHeight <= 0) { + targetHeight = originalHeight; + } + + BufferedImage scaled = scaleImage(original, targetWidth, targetHeight); + byte[] jpegBytes = encodeJpeg(scaled, JPEG_COMPRESSION_QUALITY); + if (jpegBytes == null || jpegBytes.length == 0) { + log.warn("JPEG-Kodierung lieferte leeres Ergebnis"); + return null; + } + + return Base64.getEncoder().encodeToString(jpegBytes); + } catch (IOException ex) { + log.error("Fehler beim Skalieren/Konvertieren des Bildes", ex); + return null; + } + } + + private BufferedImage scaleImage(BufferedImage original, int targetWidth, int targetHeight) { + if (original.getWidth() == targetWidth && original.getHeight() == targetHeight) { + return ensureRgbFormat(original); + } + + BufferedImage output = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = output.createGraphics(); + try { + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.drawImage(original, 0, 0, targetWidth, targetHeight, null); + } finally { + g2d.dispose(); + } + return output; + } + + private BufferedImage ensureRgbFormat(BufferedImage image) { + if (image.getType() == BufferedImage.TYPE_INT_RGB) { + return image; + } + BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = rgbImage.createGraphics(); + try { + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2d.drawImage(image, 0, 0, null); + } finally { + g2d.dispose(); + } + return rgbImage; + } + + private byte[] encodeJpeg(BufferedImage image, float quality) throws IOException { + Iterator writers = ImageIO.getImageWritersByFormatName("jpeg"); + if (!writers.hasNext()) { + throw new IOException("Kein JPEG-Writer verfügbar"); + } + ImageWriter writer = writers.next(); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { + writer.setOutput(ios); + + ImageWriteParam params = writer.getDefaultWriteParam(); + if (params.canWriteCompressed()) { + params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + params.setCompressionQuality(Math.max(0f, Math.min(1f, quality))); + } + writer.write(null, new IIOImage(image, null, null), params); + return baos.toByteArray(); + } finally { + writer.dispose(); + } + } + private UploadI18N createGermanUploadI18n() { UploadI18N i18n = new UploadI18N();