EXIF + opticscript.* Metadaten als Bild-Overlay

Lädt ein Foto, liest alles aus, was Engine.loadImage automatisch in img.meta() befüllt — den opticscript.*-Bereich (fileSize, mime, format, Dimensionen, colorSpace, bitDepth) und den exif.*-Bereich (Make/Model/DateTimeOriginal/FNumber/ISO/FocalLength/…) — und malt die nützlichsten Felder als halbtransparentes Text-Overlay aufs Bild. Demonstriert setMeta/getMeta/mergeMeta für eigene Processing- Trail-Daten.

INPUT
INPUT — EXIF + opticscript.* Metadaten als Bild-Overlay
Photo with metadata overlay
Photo with metadata overlay — EXIF + opticscript.* Metadaten als Bild-Overlay
JavaScript
// Metadata layer demo — read EXIF + opticscript.* facts from the
// loaded image and paint them as an overlay onto the photo.
//
// Engine.loadImage auto-populates:
//   meta.opticscript.*  → fileSize, mime, format, width/height, …
//   meta.exif.*         → Make, Model, FNumber, ISO, ExposureTime,
//                         FocalLength, DateTimeOriginal, GPSInfo, …
//
// User code can attach its own tags via setMeta / mergeMeta — see the
// "processing" object below for an example.
//
// demo_metadata.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: FONT_SCALE:number=0.025,min=0.005,max=0.08,step=0.001
//!PARAM: TEXT_COLOR:string=#ffffff
//!PARAM: PANEL_OPACITY:number=0.55,min=0,max=1,step=0.05

// ── 1. Load and read what we know ─────────────────────────────────────
const img = Engine.loadImage(INPUT);
const meta = img.meta();
const os = meta.opticscript || {};
const exif = meta.exif || {};

// FONT_SCALE is a fraction of image height — keeps the overlay legible
// across the full range of input sizes (a 600×400 thumbnail and a
// 4288×2929 DSLR both end up with text at the same VISUAL size,
// roughly 2.5% of the image height). Floored at 10 so the demo still
// renders on tiny test fixtures.
const FONT_SIZE = Math.max(10, Math.round(img.height * FONT_SCALE));

// Attach our own processing trail under a free-form key.
img.setMeta("processing", {
  pipeline: "demo_metadata",
  timestamp: new Date().toISOString(),
  runner: "mlc OpticScript",
});

Engine.log("Metadata summary:");
Engine.log("  Source     :", os.sourcePath);
Engine.log("  Format     :", os.mime, "(" + os.format + ")");
Engine.log("  File size  :", os.fileSize ? (os.fileSize / 1024).toFixed(1) + " KB" : "(buffer)");
Engine.log("  Pixels     :", os.width + " × " + os.height);
Engine.log("  EXIF keys  :", exif ? Object.keys(exif).length : 0);

// ── 2. Build the overlay text from EXIF (best-effort) ────────────────
// Each line is "Label  Value" — missing tags get skipped so the panel
// shrinks gracefully for photos with little/no EXIF.
const lines = [];
function add(label, value, fmt) {
  if (value === undefined || value === null || value === "") return;
  const text = fmt ? fmt(value) : String(value);
  lines.push({ label, value: text });
}
add("Camera",     [exif.Make, exif.Model].filter(Boolean).join(" "));
add("Lens",       exif.LensModel);
add("Date",       exif.DateTimeOriginal || exif.DateTime);
add("Aperture",   exif.FNumber,        v => "f/" + v);
add("Shutter",    exif.ExposureTime,   v => v >= 1 ? v + " s" : "1/" + Math.round(1 / v) + " s");
add("ISO",        exif.ISOSpeedRatings || exif.PhotographicSensitivity);
add("Focal len.", exif.FocalLength,    v => v + " mm");
add("Format",     os.format,           v => v.toUpperCase() + " · " + os.width + "×" + os.height);

if (lines.length === 0) {
  lines.push({ label: "Note", value: "no EXIF found in this image" });
}

// ── 3. Paint a translucent panel + the text on top ────────────────────
const panelW   = Math.min(560, Math.floor(os.width  * 0.55));
const panelH   = lines.length * Math.round(FONT_SIZE * 1.55) + Math.round(FONT_SIZE * 1.8);
const margin   = Math.max(16, Math.round(FONT_SIZE * 0.9));

// Background panel via a translucent black canvas blended onto the image.
const panel = Engine.createCanvas(panelW, panelH);
panel.fill([0, 0, 0, PANEL_OPACITY])
     .drawPath(Engine.createPath().rect(0, 0, panelW, panelH));
img.blendAt(panel.toImage(), px(margin, margin), 1.0, Blend.Over);
panel.free();

// Headline — bigger, accented colour.
img.drawText("EXIF + opticscript.*", margin + 16, margin + Math.round(FONT_SIZE * 1.2), {
  size: Math.round(FONT_SIZE * 1.1),
  color: "#e94560",
  font: "Inter",
  anchor: "start",
});

// Body lines — label + value, monospace for alignment.
const lineH = Math.round(FONT_SIZE * 1.55);
const labelX = margin + 16;
const valueX = margin + 16 + Math.round(FONT_SIZE * 6.2);
let y = margin + Math.round(FONT_SIZE * 2.5);

for (const l of lines) {
  img.drawText(l.label, labelX, y, {
    size: FONT_SIZE, color: TEXT_COLOR, anchor: "start", font: "Inter",
  });
  img.drawText(l.value, valueX, y, {
    size: FONT_SIZE, color: TEXT_COLOR, anchor: "start", font: "JetBrains Mono",
  });
  y += lineH;
}

img.save(OUTPUT);

`overlay painted with ${lines.length} EXIF fields`;

// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0