Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134// iiif.mjs// IIIF (International Image Interoperability Framework) surfaces for the// paintings — the image-native counterpart to the Linked Art records. Getty and// peer institutions drive their image viewers (Mirador, Universal Viewer) and// harvesters off IIIF, so exposing a Presentation v3 Manifest + a Level-0 Image// service makes AC paintings loadable in the exact tools researchers use.//// Pure builders: dimensions are read separately (pngDimensions) and passed in.// Identifiers live under DATA_BASE/iiif/{code} so they never collide with the// Linked Art record at DATA_BASE/painting/{code}.// 2026.07.01
import { DATA_BASE, WEB_BASE, LICENSES, RIGHTS_STATEMENTS } from "./linked-art.mjs";
const PRES_CONTEXT = "http://iiif.io/api/presentation/3/context.json";const IMAGE_CONTEXT = "http://iiif.io/api/image/3/context.json";
// IIIF `rights` must be a single CC or rightsstatements.org URI, and the spec// requires the http:// scheme for both (our Linked Art registry uses https for// Creative Commons, which is fine there but rejected by IIIF validators).function rightsUrl(key) { const entry = LICENSES[key] || RIGHTS_STATEMENTS[key]; if (!entry) return undefined; return entry[0].replace(/^https:\/\/(creativecommons\.org|rightsstatements\.org)/, "http://$1");}
// Read a PNG's pixel dimensions from its IHDR header (first 24 bytes), fetched// via a Range request so we don't download the whole image. Falls back to a full// GET if the CDN ignores Range. Returns null if it can't be read.export async function pngDimensions(url) { try { const res = await fetch(url, { headers: { Range: "bytes=0-33" } }); if (!res.ok && res.status !== 206) return null; const buf = Buffer.from(await res.arrayBuffer()); if (buf.length < 24 || buf[0] !== 0x89 || buf[1] !== 0x50) return null; // PNG sig return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) }; } catch { return null; }}
// IIIF Image API 3.0 info.json, Level 0 (only the full static image is served;// the full-image request redirects to the CDN — see crm.mjs).export function imageInfo({ code, width, height }) { return { "@context": IMAGE_CONTEXT, id: `${DATA_BASE}/iiif/${code}`, type: "ImageService3", protocol: "http://iiif.io/api/image", profile: "level0", width, height, sizes: [{ width, height }], preferredFormats: ["png"], };}
// IIIF Presentation API 3.0 Manifest — one Canvas carrying the painting, with// rights, attribution, a homepage back to aesthetic.computer, and a seeAlso to// the Linked Art (CIDOC CRM) record.export function paintingManifest({ code, handle, when, license, width, height }) { const clean = handle.replace(/^@/, ""); const base = `${DATA_BASE}/iiif/${code}`; const rights = rightsUrl(license); const label = `painting ${code} by @${clean}`;
const body = { id: `${base}/full/max/0/default.png`, type: "Image", format: "image/png", width, height, service: [{ id: base, type: "ImageService3", profile: "level0" }], };
return { "@context": PRES_CONTEXT, id: `${base}/manifest`, type: "Manifest", label: { en: [label] }, ...(rights ? { rights } : {}), requiredStatement: { label: { en: ["Attribution"] }, value: { en: [`@${clean} — Aesthetic Computer`] }, }, homepage: [ { id: `${WEB_BASE}/painting/${code}`, type: "Text", label: { en: ["View on Aesthetic Computer"] }, format: "text/html", }, ], seeAlso: [ { id: `${DATA_BASE}/painting/${code}`, type: "Dataset", label: { en: ["Linked Art (CIDOC CRM) record"] }, format: "application/ld+json", }, ], metadata: [ ...(when ? [{ label: { en: ["Created"] }, value: { en: [new Date(when).toISOString().slice(0, 10)] } }] : []), { label: { en: ["Artist"] }, value: { en: [`@${clean}`] } }, { label: { en: ["Type"] }, value: { en: ["digital painting"] } }, ], items: [ { id: `${base}/canvas`, type: "Canvas", height, width, items: [ { id: `${base}/canvas/page`, type: "AnnotationPage", items: [ { id: `${base}/canvas/page/annotation`, type: "Annotation", motivation: "painting", target: `${base}/canvas`, body, }, ], }, ], }, ], };}