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
Click "Select photos" to choose one or more images (max 20 per batch). Supports JPG, PNG, GIF, and ARW files.
(Optional) Enable and configure the watermark settings. You can upload your own logo.
Click "Upload and Process" to start.
Once finished, download a .zip archive or click "Send More" to start over.
Upload and Process
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();
});