// Gemeinsame Transform-Logik fΓΌr Editor-Vorschau und finale Anzeige export const FRAMES = { avatar: { id: 'avatar', aspectRatio: 1, label: 'Profilbild', cssClass: 'framed-image framed-image--avatar', previewClass: 'framed-image--avatar-xl', sizes: { sm: 'framed-image--avatar-sm', md: 'framed-image--avatar-md', lg: 'framed-image--avatar-lg' }, }, gallery: { id: 'gallery', aspectRatio: 1, label: 'Quadrat', cssClass: 'framed-image framed-image--gallery', previewClass: 'framed-image--gallery-xl', sizes: { sm: 'framed-image--gallery-sm', md: 'framed-image--gallery-md', lg: 'framed-image--gallery-lg' }, }, banner: { id: 'banner', aspectRatio: 3, label: 'Banner', cssClass: 'framed-image framed-image--banner', previewClass: 'framed-image--banner-xl', sizes: { sm: 'framed-image--banner-sm', md: 'framed-image--banner-md', lg: 'framed-image--banner-lg' }, }, card: { id: 'card', aspectRatio: 2 / 3, label: 'Karte', cssClass: 'framed-image framed-image--card', previewClass: 'framed-image--card-xl', sizes: { sm: 'framed-image--card-sm', md: 'framed-image--card-md', lg: 'framed-image--card-lg' }, }, }; export function computeFramedLayout(imgW, imgH, frameW, frameH, focus, zoom) { const fx = focus?.x ?? 0.5; const fy = focus?.y ?? 0.5; const z = Math.max(1, zoom ?? 1); const cover = Math.max(frameW / imgW, frameH / imgH); const scale = cover * z; const w = imgW * scale; const h = imgH * scale; let left = frameW / 2 - fx * w; let top = frameH / 2 - fy * h; left = Math.min(0, Math.max(frameW - w, left)); top = Math.min(0, Math.max(frameH - h, top)); return { width: w, height: h, left, top, scale }; } export function focusFromLayout(imgW, imgH, frameW, frameH, layout, zoom) { const z = Math.max(1, zoom ?? 1); const cover = Math.max(frameW / imgW, frameH / imgH); const scale = cover * z; const w = imgW * scale; const h = imgH * scale; return { x: Math.min(1, Math.max(0, (frameW / 2 - layout.left) / w)), y: Math.min(1, Math.max(0, (frameH / 2 - layout.top) / h)), }; } export function applyFramedLayout(imgEl, layout) { imgEl.style.width = `${layout.width}px`; imgEl.style.height = `${layout.height}px`; imgEl.style.left = `${layout.left}px`; imgEl.style.top = `${layout.top}px`; } export function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error('Bild konnte nicht geladen werden')); img.src = src; }); } function sizeClass(frame, size) { const preset = FRAMES[frame] || FRAMES.gallery; return preset.sizes[size] || preset.sizes.sm; } export function isImageAvatar(avatar) { return avatar && typeof avatar === 'object' && avatar.type === 'image' && avatar.url; } export function avatarEmoji(avatar) { if (!avatar) return 'π'; if (typeof avatar === 'string') return avatar; if (isImageAvatar(avatar)) return avatar.emoji || 'π'; return avatar.value || 'π'; } export function renderFramedImage({ url, focus, zoom, frame = 'gallery', size = 'sm', alt = '' }) { const preset = FRAMES[frame] || FRAMES.gallery; const fx = focus?.x ?? 0.5; const fy = focus?.y ?? 0.5; const z = zoom ?? 1; return `