Compress MSR analysis images before upload
This commit is contained in:
@@ -57,6 +57,57 @@ function resolveCandidateImageUrls(originNode) {
|
|||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function blobToOptimizedDataUrl(blob, maxDim = 1024, quality = 0.82) {
|
||||||
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
|
try {
|
||||||
|
const image = await new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = () => reject(new Error("Failed to decode image blob"));
|
||||||
|
img.src = objectUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
const width = image.naturalWidth || image.width || 0;
|
||||||
|
const height = image.naturalHeight || image.height || 0;
|
||||||
|
const scale = width > 0 && height > 0 ? Math.min(1, maxDim / Math.max(width, height)) : 1;
|
||||||
|
const targetWidth = Math.max(1, Math.round(width * scale)) || width || 1;
|
||||||
|
const targetHeight = Math.max(1, Math.round(height * scale)) || height || 1;
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = targetWidth;
|
||||||
|
canvas.height = targetHeight;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("Could not acquire canvas context");
|
||||||
|
}
|
||||||
|
ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
|
||||||
|
|
||||||
|
const optimizedBlob = await new Promise((resolve, reject) => {
|
||||||
|
canvas.toBlob((result) => {
|
||||||
|
if (result) resolve(result);
|
||||||
|
else reject(new Error("Canvas toBlob failed"));
|
||||||
|
}, "image/jpeg", quality);
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataUrl = await new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => resolve(reader.result);
|
||||||
|
reader.readAsDataURL(optimizedBlob);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataUrl,
|
||||||
|
originalWidth: width,
|
||||||
|
originalHeight: height,
|
||||||
|
outputWidth: targetWidth,
|
||||||
|
outputHeight: targetHeight,
|
||||||
|
outputBytes: optimizedBlob.size || 0,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function imageInputToDataUrl(node, inputName) {
|
async function imageInputToDataUrl(node, inputName) {
|
||||||
const originNode = getOriginNodeForInput(node, inputName);
|
const originNode = getOriginNodeForInput(node, inputName);
|
||||||
const candidateUrls = resolveCandidateImageUrls(originNode);
|
const candidateUrls = resolveCandidateImageUrls(originNode);
|
||||||
@@ -72,6 +123,11 @@ async function imageInputToDataUrl(node, inputName) {
|
|||||||
mimeType: null,
|
mimeType: null,
|
||||||
blobBytes: 0,
|
blobBytes: 0,
|
||||||
dataUrlLength: 0,
|
dataUrlLength: 0,
|
||||||
|
originalWidth: 0,
|
||||||
|
originalHeight: 0,
|
||||||
|
outputWidth: 0,
|
||||||
|
outputHeight: 0,
|
||||||
|
outputBytes: 0,
|
||||||
error: "No candidate image URLs found",
|
error: "No candidate image URLs found",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -87,11 +143,8 @@ async function imageInputToDataUrl(node, inputName) {
|
|||||||
if (!blob.type.startsWith("image/")) {
|
if (!blob.type.startsWith("image/")) {
|
||||||
throw new Error(`Unexpected blob type: ${blob.type || "unknown"}`);
|
throw new Error(`Unexpected blob type: ${blob.type || "unknown"}`);
|
||||||
}
|
}
|
||||||
const dataUrl = await new Promise((resolve) => {
|
const optimized = await blobToOptimizedDataUrl(blob);
|
||||||
const reader = new FileReader();
|
const dataUrl = optimized.dataUrl;
|
||||||
reader.onloadend = () => resolve(reader.result);
|
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
dataUrl,
|
dataUrl,
|
||||||
debug: {
|
debug: {
|
||||||
@@ -103,6 +156,11 @@ async function imageInputToDataUrl(node, inputName) {
|
|||||||
mimeType: blob.type || null,
|
mimeType: blob.type || null,
|
||||||
blobBytes: blob.size || 0,
|
blobBytes: blob.size || 0,
|
||||||
dataUrlLength: typeof dataUrl === "string" ? dataUrl.length : 0,
|
dataUrlLength: typeof dataUrl === "string" ? dataUrl.length : 0,
|
||||||
|
originalWidth: optimized.originalWidth,
|
||||||
|
originalHeight: optimized.originalHeight,
|
||||||
|
outputWidth: optimized.outputWidth,
|
||||||
|
outputHeight: optimized.outputHeight,
|
||||||
|
outputBytes: optimized.outputBytes,
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -122,6 +180,11 @@ async function imageInputToDataUrl(node, inputName) {
|
|||||||
mimeType: null,
|
mimeType: null,
|
||||||
blobBytes: 0,
|
blobBytes: 0,
|
||||||
dataUrlLength: 0,
|
dataUrlLength: 0,
|
||||||
|
originalWidth: 0,
|
||||||
|
originalHeight: 0,
|
||||||
|
outputWidth: 0,
|
||||||
|
outputHeight: 0,
|
||||||
|
outputBytes: 0,
|
||||||
error: "All candidate image URLs failed",
|
error: "All candidate image URLs failed",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user