/* * Lightweight EXIF orientation reader + conditional JPEG -> PNG converter. * We only care about orientation values 3,6,8 (rotations). Others return as-is. */ export async function readOrientation(blob: Blob): Promise { try { console.log(`[readOrientation] Blob type: ${blob.type}, size: ${blob.size}`); if (blob.type !== 'image/jpeg' && blob.type !== 'image/jpg') { console.log(`[readOrientation] Not a JPEG, blob type: ${blob.type}`); return null; } const buf = await blob.arrayBuffer(); const view = new DataView(buf); console.log(`[readOrientation] ArrayBuffer length: ${buf.byteLength}`); // JPEG starts with 0xFFD8 if (view.getUint16(0) !== 0xFFD8) { console.log(`[readOrientation] Not a valid JPEG, header: ${view.getUint16(0).toString(16)}`); return null; } console.log(`[readOrientation] Valid JPEG detected`); let offset = 2; const length = view.byteLength; while (offset < length) { if (view.getUint8(offset) !== 0xFF) break; const marker = view.getUint8(offset + 1); console.log(`[readOrientation] Processing marker: 0xFF${marker.toString(16).padStart(2, '0')} at offset ${offset}`); if (marker === 0xE1) { // APP1 EXIF const size = view.getUint16(offset + 2, false); const exifHeader = offset + 4; console.log(`[readOrientation] Found APP1 segment, size: ${size}`); if (view.getUint32(exifHeader, false) === 0x45786966) { // 'Exif' console.log(`[readOrientation] Found EXIF header`); const tiff = exifHeader + 6; const endian = view.getUint16(tiff, false); const little = endian === 0x4949; // 'II' console.log(`[readOrientation] Endian: ${little ? 'little' : 'big'} (${endian.toString(16)})`); const getU16 = (p:number) => view.getUint16(p, little); const getU32 = (p:number) => view.getUint32(p, little); if (getU16(tiff + 2) !== 0x002A) { console.log(`[readOrientation] Invalid TIFF magic: ${getU16(tiff + 2).toString(16)}`); return null; } const ifdOffset = getU32(tiff + 4); let dir = tiff + ifdOffset; const entries = getU16(dir); console.log(`[readOrientation] IFD has ${entries} entries`); dir += 2; for (let i=0;i { console.log(`[exif-orientation] Processing ${filename}, blob type: ${blob.type}, size: ${blob.size}`); const ori = await readOrientation(blob); console.log(`[exif-orientation] Detected orientation for ${filename}: ${ori}`); return new Promise((resolve) => { const url = URL.createObjectURL(blob); const img = new Image(); img.onload = () => { const w = img.naturalWidth; const h = img.naturalHeight; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { URL.revokeObjectURL(url); resolve({ blob, filename, changed: false, orientation: ori }); return; } canvas.width = w; canvas.height = h; ctx.drawImage(img, 0, 0); canvas.toBlob(b => { URL.revokeObjectURL(url); if (!b) { resolve({ blob, filename, changed: false, orientation: ori }); return; } const pngName = filename.replace(/\.(jpe?g)$/i, '') + '_converted.png'; resolve({ blob: b, filename: pngName, changed: true, orientation: ori }); }, 'image/png'); }; img.onerror = () => { URL.revokeObjectURL(url); resolve({ blob, filename, changed: false, orientation: ori }); }; img.src = url; }); }