Implement browser-side image processing: convert any selected file to 172x320 PNG with cover aspect-ratio crop before upload

This commit is contained in:
drjones
2026-07-04 02:24:46 -07:00
parent 1c5be95d4c
commit 617741f773

View File

@@ -132,7 +132,7 @@ input[type=range]{width:100%;accent-color:var(--accent)}
<div class="icon">&#x1F4F7;</div>
<p>Tap to choose a photo<br><strong>from your camera roll</strong></p>
</div>
<input type="file" id="photoFile" accept=".png,.jpg,.jpeg" onchange="onPhotoSelected(this)">
<input type="file" id="photoFile" accept="image/*" onchange="onPhotoSelected(this)">
<img id="previewImg" class="preview-img" alt="preview">
<div id="progress1" class="progress-wrap">
<div class="progress-bar"><div class="progress-fill" id="fill1"></div></div>
@@ -344,19 +344,105 @@ function uploadFile(fileInput, endpoint, progressWrap, fill, txt, onDone) {
}
function uploadPhoto() {
var fileInput = document.getElementById('photoFile');
var file = fileInput.files[0];
if (!file) return;
document.getElementById('nextStep1').disabled = true;
uploadFile(
document.getElementById('photoFile'),
'/api/upload/photo',
document.getElementById('progress1'),
document.getElementById('fill1'),
document.getElementById('txt1'),
function(filename){
if(filename){ uploadedPhoto=filename; goStep(2); }
else showToast('Photo upload failed. Try again.', false);
var progressWrap = document.getElementById('progress1');
var fill = document.getElementById('fill1');
var txt = document.getElementById('txt1');
progressWrap.style.display = 'block';
txt.textContent = 'Preparing photo...';
fill.style.width = '10%';
var reader = new FileReader();
reader.onload = function(e) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = 172;
canvas.height = 320;
var ctx = canvas.getContext('2d');
// Center crop cover math
var srcWidth = img.width;
var srcHeight = img.height;
var ratio = Math.max(172 / srcWidth, 320 / srcHeight);
var newWidth = srcWidth * ratio;
var newHeight = srcHeight * ratio;
var x = (172 - newWidth) / 2;
var y = (320 - newHeight) / 2;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, 172, 320);
ctx.drawImage(img, x, y, newWidth, newHeight);
canvas.toBlob(function(blob) {
if (!blob) {
showToast('Photo preparation failed.', false);
document.getElementById('nextStep1').disabled = false;
progressWrap.style.display = 'none';
return;
}
);
var origName = file.name;
var lastDot = origName.lastIndexOf('.');
var baseName = lastDot >= 0 ? origName.substring(0, lastDot) : 'photo';
var newFilename = baseName + '.png';
var fd = new FormData();
fd.append('file', blob, newFilename);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload/photo');
xhr.upload.onprogress = function(progressEvt) {
if (progressEvt.lengthComputable) {
var pct = Math.round(progressEvt.loaded / progressEvt.total * 100);
fill.style.width = pct + '%';
txt.textContent = 'Uploading photo... ' + pct + '%';
}
};
xhr.onload = function() {
progressWrap.style.display = 'none';
try {
var res = JSON.parse(xhr.responseText);
if (res.ok && res.filename) {
uploadedPhoto = res.filename;
goStep(2);
} else {
showToast('Photo upload failed: ' + (res.error || 'unknown error'), false);
}
} catch(err) {
showToast('Photo upload failed.', false);
}
document.getElementById('nextStep1').disabled = false;
};
xhr.onerror = function() {
progressWrap.style.display = 'none';
showToast('Photo upload failed.', false);
document.getElementById('nextStep1').disabled = false;
};
xhr.send(fd);
}, 'image/png');
};
img.onerror = function() {
showToast('Invalid image file.', false);
document.getElementById('nextStep1').disabled = false;
progressWrap.style.display = 'none';
};
img.src = e.target.result;
};
reader.onerror = function() {
showToast('Failed to read file.', false);
document.getElementById('nextStep1').disabled = false;
progressWrap.style.display = 'none';
};
reader.readAsDataURL(file);
}
function uploadAudio() {