Photo Proofing Uploader

About this App

This tool helps photographers quickly process images for client previews. It compresses photos for fast uploads, applies a custom watermark on the server, and generates thumbnails. Now supports Sony ARW raw files!

Instructions

  1. Click "Select photos" to choose one or more images (max 20 per batch). Supports JPG, PNG, GIF, and ARW files.
  2. (Optional) Enable and configure the watermark settings. You can upload your own logo.
  3. Click "Upload and Process" to start.
  4. Once finished, download a .zip archive or click "Send More" to start over.
Admin mode?

Watermark Options
made with ❤️ by WowkDigital
* * @param {File} arwFile - Plik .ARW wybrany przez użytkownika (obiekt File). * @returns {Promise} - Promise, które po rozwiązaniu zwraca obiekt Blob z finalnym plikiem .JPG. */ async function processArwToJpeg(arwFile) { // --- Helper 1: Ekstraktor podglądu JPG z pliku RAW --- const rawJpegExtractor = { extract: async function(arrayBuffer) { const view = new Uint8Array(arrayBuffer); const SOI = [0xFF, 0xD8]; // Znacznik początku obrazu JPG const EOI = [0xFF, 0xD9]; // Znacznik końca obrazu JPG let lastSoiIndex = -1; for (let i = view.length - 2; i >= 0; i--) { if (view[i] === SOI[0] && view[i + 1] === SOI[1]) { lastSoiIndex = i; break; } } if (lastSoiIndex === -1) { throw new Error("Nie znaleziono znacznika początku obrazu JPG (SOI)."); } let eoiIndex = -1; for (let i = lastSoiIndex + 2; i < view.length - 1; i++) { if (view[i] === EOI[0] && view[i + 1] === EOI[1]) { eoiIndex = i; break; } } if (eoiIndex !== -1) { return view.slice(lastSoiIndex, eoiIndex + 2); } else { // Obejście dla plików bez wyraźnego znacznika końca return view.slice(lastSoiIndex); } } }; // --- Helper 2: Funkcja do fizycznego obracania obrazu za pomocą Canvas --- const getRotatedImageBlob = (blob, orientation) => { return new Promise((resolve, reject) => { const img = new Image(); const url = URL.createObjectURL(blob); img.src = url; img.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const width = img.width; const height = img.height; if (orientation > 4 && orientation < 9) { canvas.width = height; canvas.height = width; } else { canvas.width = width; canvas.height = height; } switch (orientation) { case 2: ctx.transform(-1, 0, 0, 1, width, 0); break; case 3: ctx.transform(-1, 0, 0, -1, width, height); break; case 4: ctx.transform(1, 0, 0, -1, 0, height); break; case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; case 6: ctx.transform(0, 1, -1, 0, height, 0); break; case 7: ctx.transform(0, -1, -1, 0, height, width); break; case 8: ctx.transform(0, -1, 1, 0, 0, width); break; default: break; } ctx.drawImage(img, 0, 0); URL.revokeObjectURL(url); canvas.toBlob(resolve, 'image/jpeg', 0.95); }; img.onerror = (error) => { URL.revokeObjectURL(url); reject(error); }; }); }; // --- Główna logika funkcji --- if (!arwFile || !arwFile.name.toLowerCase().endsWith('.arw')) { return Promise.reject(new Error("Nieprawidłowy plik. Wymagany jest plik .ARW.")); } // 1. Wczytaj plik ARW do ArrayBuffer const arrayBuffer = await arwFile.arrayBuffer(); // 2. Odczytaj orientację z głównego pliku ARW let orientation = 1; try { const tags = ExifReader.load(arrayBuffer); if (tags && tags.Orientation) { orientation = tags.Orientation.value; } } catch (exifError) { console.warn("Nie można było odczytać danych EXIF z pliku ARW.", exifError); } // 3. Wyodrębnij podgląd JPG const jpegBytes = await rawJpegExtractor.extract(arrayBuffer); if (!jpegBytes) { throw new Error('Nie udało się wyodrębnić podglądu JPG z pliku.'); } const initialBlob = new Blob([jpegBytes], { type: 'image/jpeg' }); // 4. Jeśli potrzeba, obróć obraz i zwróć finalny Blob if (orientation > 1) { return await getRotatedImageBlob(initialBlob, orientation); } else { return initialBlob; } } // --- Initialize the application --- initEventListeners(); });