JpgToPdfModal: Bilder auf 2400px runterskalieren

Stage: 2 Handy-JPGs → 23 MB PDF. Smartphone-Fotos haben
4000-6000 px Kante, das macht auch ohne Re-Encode 5-10 MB pro
Bild → PDF wird riesig.

Beim Hinzufügen werden Bilder jetzt auf max 2400 px lange Kante
runterskaliert (~290 DPI auf A4 = Druckqualität) und als JPEG mit
Quality 0.92 (Lightroom-Default) persistiert. Vorschau, Rotation/
Flip und PDF-Embed laufen alle auf dem skalierten Bild.

Erwartete Größe: 2 Handy-Fotos ≈ 1-2 MB PDF.
This commit is contained in:
2026-06-03 18:29:04 +02:00
parent 2fee13d09e
commit 523eab30d5
2 changed files with 50 additions and 5 deletions
+39 -5
View File
@@ -37,6 +37,14 @@ interface JpgToPdfModalProps {
const MAX_IMAGES = 50;
const MAX_IMAGE_BYTES = 25 * 1024 * 1024;
// Smartphone-Fotos haben oft 4000-6000 px Kante. Bei JPEG-Quality
// 0.95 sind das 5-10 MB pro Seite, zwei Bilder = >10 MB PDF.
// 2400 px lange Kante entspricht ~290 DPI auf A4 (Druckqualität) und
// reduziert die Pixelmenge auf 25-36 % vom Original → PDF wird
// drastisch kleiner, sichtbarer Unterschied praktisch null.
const MAX_DIMENSION = 2400;
const EMBED_QUALITY = 0.92;
function makeId() {
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
@@ -59,6 +67,23 @@ function loadImage(dataUrl: string): Promise<HTMLImageElement> {
});
}
function downscaleIfNeeded(image: HTMLImageElement): HTMLCanvasElement | null {
const w = image.naturalWidth;
const h = image.naturalHeight;
if (w <= MAX_DIMENSION && h <= MAX_DIMENSION) return null;
const scale = MAX_DIMENSION / Math.max(w, h);
const newW = Math.round(w * scale);
const newH = Math.round(h * scale);
const canvas = document.createElement('canvas');
canvas.width = newW;
canvas.height = newH;
const ctx = canvas.getContext('2d');
if (!ctx) return null;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(image, 0, 0, newW, newH);
return canvas;
}
function renderImageToCanvas(image: HTMLImageElement, item: ImageItem): HTMLCanvasElement {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
@@ -123,13 +148,22 @@ export default function JpgToPdfModal({
break;
}
try {
const dataUrl = await readFileAsDataUrl(file);
const img = await loadImage(dataUrl);
const rawDataUrl = await readFileAsDataUrl(file);
const img = await loadImage(rawDataUrl);
// Beim Hinzufügen direkt auf MAX_DIMENSION runterskalieren, damit
// die Vorschau, das Rendern in der PDF und die finale Dateigröße
// alle auf vernünftigen Pixelmaßen arbeiten.
const downscaled = downscaleIfNeeded(img);
const dataUrl = downscaled
? downscaled.toDataURL('image/jpeg', EMBED_QUALITY)
: rawDataUrl;
const finalW = downscaled ? downscaled.width : img.naturalWidth;
const finalH = downscaled ? downscaled.height : img.naturalHeight;
added.push({
id: makeId(),
dataUrl,
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight,
naturalWidth: finalW,
naturalHeight: finalH,
rotation: 0,
flipH: false,
flipV: false,
@@ -271,7 +305,7 @@ export default function JpgToPdfModal({
} else {
const img = await loadImage(item.dataUrl);
const canvas = renderImageToCanvas(img, item);
imageData = canvas.toDataURL('image/jpeg', 0.95);
imageData = canvas.toDataURL('image/jpeg', EMBED_QUALITY);
imageFormat = 'JPEG';
srcW = canvas.width;
srcH = canvas.height;