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
Photo with metadata 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