Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

faster image compression when alwaysKeepResolution is set to true #221

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ If your website has CSP enabled and you want to use Web Worker (useWebWorker: tr

## Contribution
1. fork the repo and git clone it
3. run `npm install` # node version greater than `v14.21.3` (`lts/fermium`) is not supported
2. run `npm run watch` # it will watch code change in lib/ folder and generate js in dist/ folder
3. add/update code in lib/ folder
4. try the code by opening example/development.html which will load the js in dist/ folder
Expand Down
14 changes: 10 additions & 4 deletions example/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
<label for="maxSizeMB">maxSizeMB:
<input type="number" id="maxSizeMB" name="maxSizeMB" value="1" /></label><br />
<label for="maxWidthOrHeight">maxWidthOrHeight:
<input type="number" id="maxWidthOrHeight" name="maxWidthOrHeight" value="1024" /></label>
<input type="number" id="maxWidthOrHeight" name="maxWidthOrHeight" value="1024" /></label><br />
<label for="alwaysKeepResolution">alwaysKeepResolution:
<input type="checkbox" id="alwaysKeepResolution" name="alwaysKeepResolution" value="false" /></label>
<hr />
<div>
<label for="web-worker">
Expand Down Expand Up @@ -117,6 +119,7 @@
var controller
document.querySelector("#version").innerHTML = imageCompression.version;
function compressImage(event, useWebWorker) {
var startTime = performance.now();
var file = event.target.files[0];
var logDom, progressDom;
if (useWebWorker) {
Expand Down Expand Up @@ -145,13 +148,16 @@
useWebWorker: useWebWorker,
onProgress: onProgress,
preserveExif: true,
libURL: "https://cdn.jsdelivr.net/npm/browser-image-compression@"+selectedVersion+"/dist/browser-image-compression.js"
libURL: "https://cdn.jsdelivr.net/npm/browser-image-compression@"+selectedVersion+"/dist/browser-image-compression.js",
alwaysKeepResolution: document.querySelector("#alwaysKeepResolution").checked,
};
if (controller) {
options.signal = controller.signal;
}
imageCompression(file, options)
.then(function (output) {
var endTime = performance.now();

logDom.innerHTML +=
", output size:" + (output.size / 1024 / 1024).toFixed(2) + "mb";
console.log("output", output);
Expand All @@ -161,7 +167,7 @@
downloadLink +
'" download="' +
file.name +
'">download compressed image</a>';
'">download compressed image</a>,&nbsp; execution time: '+`${endTime-startTime}`+'ms';
// document.getElementById('preview-after-compress').src = downloadLink
return uploadToServer(output);
})
Expand Down Expand Up @@ -193,4 +199,4 @@
</script>
</body>

</html>
</html>
14 changes: 9 additions & 5 deletions example/development.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
<label for="maxSizeMB">maxSizeMB:
<input type="number" id="maxSizeMB" name="maxSizeMB" value="1" /></label><br />
<label for="maxWidthOrHeight">maxWidthOrHeight:
<input type="number" id="maxWidthOrHeight" name="maxWidthOrHeight" value="1024" /></label>
<input type="number" id="maxWidthOrHeight" name="maxWidthOrHeight" value="1024" /></label><br />
<label for="alwaysKeepResolution">alwaysKeepResolution:
<input type="checkbox" id="alwaysKeepResolution" name="alwaysKeepResolution" value="false" /></label>
<hr />
<div>
<label for="web-worker">
Expand Down Expand Up @@ -94,6 +96,7 @@
var controller
document.querySelector("#version").innerHTML = imageCompression.version;
function compressImage(event, useWebWorker) {
var startTime = performance.now();
var file = event.target.files[0];
var logDom, progressDom;
if (useWebWorker) {
Expand All @@ -113,7 +116,6 @@
});

controller = typeof AbortController !== 'undefined' && new AbortController();

var options = {
maxSizeMB: parseFloat(document.querySelector("#maxSizeMB").value),
maxWidthOrHeight: parseFloat(
Expand All @@ -122,13 +124,15 @@
useWebWorker: useWebWorker,
onProgress: onProgress,
preserveExif: true,
libURL: location.origin + '/dist/browser-image-compression.js'
libURL: location.origin + '/dist/browser-image-compression.js',
alwaysKeepResolution: document.querySelector("#alwaysKeepResolution").checked,
};
if (controller) {
options.signal = controller.signal;
}
imageCompression(file, options)
.then(function (output) {
var endTime = performance.now();
logDom.innerHTML +=
", output size:" + (output.size / 1024 / 1024).toFixed(2) + "mb";
console.log("output", output);
Expand All @@ -138,7 +142,7 @@
downloadLink +
'" download="' +
file.name +
'">download compressed image</a>';
'">download compressed image</a>,&nbsp; execution time: '+`${endTime-startTime}`+'ms';
// document.getElementById('preview-after-compress').src = downloadLink
return uploadToServer(output);
})
Expand Down Expand Up @@ -170,4 +174,4 @@
</script>
</body>

</html>
</html>
57 changes: 52 additions & 5 deletions lib/image-compression.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export default async function compress(file, options, previousProgress = 0) {
// exifOrientation
const exifOrientation = options.exifOrientation || await getExifOrientation(file);
incProgress();
const orientationFixedCanvas = (await isAutoOrientationInBrowser()) ? maxWidthOrHeightFixedCanvas : followExifOrientation(maxWidthOrHeightFixedCanvas, exifOrientation);
const orientationFixedCanvas = (await isAutoOrientationInBrowser())
? maxWidthOrHeightFixedCanvas : followExifOrientation(maxWidthOrHeightFixedCanvas, exifOrientation);
incProgress();

let quality = options.initialQuality || 1.0;
Expand Down Expand Up @@ -103,9 +104,10 @@ export default async function compress(file, options, previousProgress = 0) {
let ctx;
let canvas = orientationFixedCanvas;
const shouldReduceResolution = !options.alwaysKeepResolution && (origExceedMaxSize || outputFileType === 'image/bmp');
while (remainingTrials-- && (currentSize > maxSizeByte || currentSize > sourceSize)) {
const newWidth = shouldReduceResolution ? canvas.width * 0.95 : canvas.width;
const newHeight = shouldReduceResolution ? canvas.height * 0.95 : canvas.height;

while (shouldReduceResolution && remainingTrials-- && (currentSize > maxSizeByte || currentSize > sourceSize)) {
const newWidth = canvas.width * 0.95;
const newHeight = canvas.height * 0.95;
if (process.env.BUILD === 'development') {
console.log('current width', newWidth);
console.log('current height', newHeight);
Expand All @@ -132,8 +134,53 @@ export default async function compress(file, options, previousProgress = 0) {
setProgress(Math.min(99, Math.floor(((renderedSize - currentSize) / (renderedSize - maxSizeByte)) * 100)));
}

// if we can't reduce resolution, then we decrease the quality of the image.
// To find the highest quality where image size is less than maxSizeBytes we perform binary search on quality
if (!shouldReduceResolution) {
if (process.env.BUILD === 'development') console.log('image compression by reducing quality only');

let low = 0; // lowest possible quality
let high = quality; // higest possible quality
let tmpCompressedFile;

while (((high - low) > 0.05 || !compressedFile) && remainingTrials-- > 0) {
quality = (low + high) / 2;

if (process.env.BUILD === 'development') {
console.log(`minimum quality: ${low}`);
console.log(`max quality: ${high}`);
console.log(`current quality: ${quality}`);
}

// eslint-disable-next-line no-await-in-loop
tmpCompressedFile = await canvasToFile(canvas, outputFileType, file.name, file.lastModified, quality);
currentSize = tmpCompressedFile.size;

// If true, adjust the lower bound to narrow the search quality.
if (currentSize <= maxSizeByte) {
low = quality;
compressedFile = tmpCompressedFile;
if (process.env.BUILD === 'development') console.log(`found acceptable size: ${currentSize} at quality: ${quality}`);
} else high = quality; // else adjust the upper bound to narrow the search quality.

if (process.env.BUILD === 'development') {
console.log(`Compressed image size:${currentSize}`);
console.log(`Iteration step count:${remainingTrials}`);
}

setProgress(Math.min(99, Math.floor(((renderedSize - currentSize) / (renderedSize - maxSizeByte)) * 100)));
}

// if we can't generate compressedFile within remainingTrials, then we try to get compressedFile with last known quality
if (!compressedFile) {
compressedFile = await canvasToFile(canvas, outputFileType, file.name, file.lastModified, (low + high) / 2);
currentSize = compressedFile.size;
setProgress(Math.min(99, Math.floor(((renderedSize - currentSize) / (renderedSize - maxSizeByte)) * 100)));
}
}

cleanupCanvasMemory(canvas);
cleanupCanvasMemory(newCanvas);
if (newCanvas) cleanupCanvasMemory(newCanvas);
cleanupCanvasMemory(maxWidthOrHeightFixedCanvas);
cleanupCanvasMemory(orientationFixedCanvas);
cleanupCanvasMemory(origCanvas);
Expand Down