Een dating-app op basis van brieven, niet van gezichten!

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

3.0 kB Ruw bestand
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);
  });