Een dating-app op basis van brieven, niet van gezichten!
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import { PHOTO_MAX_PX, PHOTO_MAX_BYTES } from "../constants";
import { log } from "./log";
/*
* Een gekozen bestand klaarmaken om mee te sturen met een brief.
*
* De foto wordt hier, op het toestel zelf, verkleind en opnieuw als JPEG gecodeerd. Dat is niet
* alleen om de bytes: een opnieuw gecodeerde JPEG bevat geen EXIF meer, en daar zitten normaal de
* gps-coördinaten in van waar de foto genomen is. Die verlaten het toestel dus nooit. De server
* krijgt alleen nog pixels, en hoeft niets te bewerken.
*
* Geeft { data (base64), mime, width, height, previewUrl } — previewUrl is voor het voorbeeld in de
* schrijfdialoog en moet met URL.revokeObjectURL() worden opgeruimd.
*/
/* Kwaliteitsstappen: net zo lang zakken tot de foto onder de limiet blijft */
const QUALITIES = [0.85, 0.75, 0.65, 0.5];
export async function preparePhoto(file) {
if (!file.type.startsWith("image/")) throw new Error("Kies een afbeelding");
// imageOrientation: de draaiing uit de EXIF wordt in de pixels verwerkt vóór die EXIF verdwijnt,
// anders zou een staande telefoonfoto liggend aankomen
let bitmap;
try {
bitmap = await createImageBitmap(file, { imageOrientation: "from-image" });
} catch (e) {
/*
* De zin hierboven is wat de schrijver eraan heeft; wat de browser zei is wat wíj eraan hebben.
* Een bestandsformaat dat deze webview niet kent (HEIC bijvoorbeeld) ziet er op het scherm uit
* als elke andere onleesbare foto, en dat verschil is alleen hier nog te zien.
*/
log("✗ createImageBitmap", `type=${file.type}`, `bytes=${file.size}`, e?.message ?? String(e));
throw new Error("Deze afbeelding kon niet gelezen worden");
}
const scale = Math.min(1, PHOTO_MAX_PX / Math.max(bitmap.width, bitmap.height));
const width = Math.max(1, Math.round(bitmap.width * scale));
const height = Math.max(1, Math.round(bitmap.height * scale));
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.imageSmoothingQuality = "high";
ctx.drawImage(bitmap, 0, 0, width, height);
bitmap.close?.();
let blob = null;
for (const quality of QUALITIES) {
blob = await toBlob(canvas, quality);
if (blob.size <= PHOTO_MAX_BYTES) break;
}
if (!blob || blob.size > PHOTO_MAX_BYTES) throw new Error("Deze foto blijft te groot — probeer een andere");
return { data: await toBase64(blob), mime: "image/jpeg", width, height, previewUrl: URL.createObjectURL(blob) };
}
const toBlob = (canvas, quality) =>
new Promise((resolve, reject) =>
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error("Deze foto kon niet verwerkt worden"))), "image/jpeg", quality)
);
/* Blob → kale base64 (zonder het "data:image/jpeg;base64," ervoor) */
const toBase64 = (blob) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result).split(",")[1]);
reader.onerror = () => reject(new Error("Deze foto kon niet gelezen worden"));
reader.readAsDataURL(blob);
});