Erweiterungen

This commit is contained in:
2025-10-13 14:19:48 +02:00
parent b5b25dc04d
commit b1ac7944ae

View File

@@ -41,8 +41,19 @@ import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId; 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.IOException;
import java.io.InputStream; 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.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; 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 TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy"); 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 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, public MessageDetailsView(AppUserService appUserService, MessageService messageService,
SecurityService securityService, MessageBroadcaster messageBroadcaster) { SecurityService securityService, MessageBroadcaster messageBroadcaster) {
@@ -258,8 +271,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
upload.addSucceededListener(event -> { upload.addSucceededListener(event -> {
try (InputStream inputStream = buffer.getInputStream()) { try (InputStream inputStream = buffer.getInputStream()) {
byte[] bytes = inputStream.readAllBytes(); byte[] rawBytes = inputStream.readAllBytes();
if (bytes.length == 0) { if (rawBytes.length == 0) {
base64Ref.set(null); base64Ref.set(null);
preview.setVisible(false); preview.setVisible(false);
confirmButton.setEnabled(false); confirmButton.setEnabled(false);
@@ -268,13 +281,9 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
return; return;
} }
String base64 = Base64.getEncoder().encodeToString(bytes); String processedBase64 = processImageToJpegBase64(rawBytes);
base64Ref.set(base64); if (processedBase64 == null) {
base64Ref.set(null);
String mimeType = Optional.ofNullable(event.getMIMEType()).filter(value -> !value.isBlank())
.orElseGet(() -> detectImageMimeType(bytes));
String dataUrl = createDataUrlFromContent(base64, mimeType);
if (dataUrl == null) {
preview.setVisible(false); preview.setVisible(false);
confirmButton.setEnabled(false); confirmButton.setEnabled(false);
Notification.show("Das Bild konnte nicht verarbeitet werden.", 3000, Notification.show("Das Bild konnte nicht verarbeitet werden.", 3000,
@@ -282,6 +291,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
return; return;
} }
base64Ref.set(processedBase64);
String dataUrl = "data:image/jpeg;base64," + processedBase64;
preview.setSrc(dataUrl); preview.setSrc(dataUrl);
preview.setVisible(true); preview.setVisible(true);
confirmButton.setEnabled(true); confirmButton.setEnabled(true);
@@ -517,6 +528,96 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
return "image/png"; 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<ImageWriter> 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() { private UploadI18N createGermanUploadI18n() {
UploadI18N i18n = new UploadI18N(); UploadI18N i18n = new UploadI18N();