compressorjs-next 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -16
- package/dist/compressor.common.js +108 -151
- package/dist/compressor.esm.js +108 -151
- package/dist/compressor.js +108 -151
- package/dist/compressor.min.js +2 -2
- package/package.json +8 -5
- package/src/defaults.js +0 -7
- package/src/index.js +124 -76
- package/src/utilities.js +1 -58
- package/types/index.d.ts +0 -1
package/README.md
CHANGED
|
@@ -22,11 +22,12 @@ A JavaScript image compressor and converter. Uses the browser’s native [HTMLCa
|
|
|
22
22
|
|
|
23
23
|
Change the package name from `compressorjs` to `compressorjs-next` in your `package.json` and imports (`import Compressor from 'compressorjs-next'`).
|
|
24
24
|
|
|
25
|
-
The API is otherwise the same, with these exceptions (as of
|
|
25
|
+
The API is otherwise the same, with these exceptions (as of 2.0.0—follow [the changelog](https://github.com/j9t/compressorjs-next/blob/main/CHANGELOG.md) from there):
|
|
26
26
|
|
|
27
27
|
* ESM is now the default module format (CommonJS is still supported)
|
|
28
|
-
* The `
|
|
28
|
+
* The `checkOrientation` option has been removed, as all supported browsers now handle EXIF orientation natively
|
|
29
29
|
* The default for `convertTypes` has changed from `['image/png']` to `[]`
|
|
30
|
+
* The `noConflict()` method has been removed
|
|
30
31
|
* Internet Explorer is no longer supported
|
|
31
32
|
|
|
32
33
|
## Main files
|
|
@@ -85,7 +86,7 @@ document.getElementById('file').addEventListener('change', (e) => {
|
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
new Compressor(file, {
|
|
89
|
+
const compressor = new Compressor(file, {
|
|
89
90
|
quality: 0.6,
|
|
90
91
|
|
|
91
92
|
// The compression process is asynchronous,
|
|
@@ -130,19 +131,6 @@ Indicates whether to output the original image instead of the compressed one whe
|
|
|
130
131
|
* The `maxWidth` option is set and its value is less than the natural width of the image.
|
|
131
132
|
* The `maxHeight` option is set and its value is less than the natural height of the image.
|
|
132
133
|
|
|
133
|
-
### `checkOrientation`
|
|
134
|
-
|
|
135
|
-
* Type: `boolean`
|
|
136
|
-
* Default: `true`
|
|
137
|
-
|
|
138
|
-
Indicates whether to read the image’s Exif Orientation value (JPEG image only), and then rotate or flip the image automatically with the value.
|
|
139
|
-
|
|
140
|
-
**Notes:**
|
|
141
|
-
|
|
142
|
-
* Don’t trust this all the time as some JPEG images have incorrect (not standard) `Orientation` values.
|
|
143
|
-
* If the size of the target image is too large (e.g., greater than 10 MB), you should disable this option to avoid an out-of-memory crash.
|
|
144
|
-
* The image’s Exif information will be removed after compressed, so if you need the Exif information, you may need to upload the original image as well.
|
|
145
|
-
|
|
146
134
|
### `retainExif`
|
|
147
135
|
|
|
148
136
|
* Type: `boolean`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Compressor.js Next
|
|
2
|
+
* Compressor.js Next v2.0.0
|
|
3
3
|
* https://github.com/j9t/compressorjs-next
|
|
4
4
|
*
|
|
5
5
|
* Copyright 2018–2024 Chen Fengyuan
|
|
@@ -16,12 +16,6 @@ var DEFAULTS = {
|
|
|
16
16
|
* @type {boolean}
|
|
17
17
|
*/
|
|
18
18
|
strict: true,
|
|
19
|
-
/**
|
|
20
|
-
* Indicates if read the image’s Exif Orientation information,
|
|
21
|
-
* and then rotate or flip the image automatically.
|
|
22
|
-
* @type {boolean}
|
|
23
|
-
*/
|
|
24
|
-
checkOrientation: true,
|
|
25
19
|
/**
|
|
26
20
|
* Indicates if retain the image’s Exif information after compressed.
|
|
27
21
|
* @type {boolean}
|
|
@@ -187,40 +181,13 @@ function getStringFromCharCode(dataView, start, length) {
|
|
|
187
181
|
}
|
|
188
182
|
return str;
|
|
189
183
|
}
|
|
190
|
-
const {
|
|
191
|
-
btoa
|
|
192
|
-
} = WINDOW;
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Transform array buffer to Data URL.
|
|
196
|
-
* @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
|
|
197
|
-
* @param {string} mimeType - The mime type of the Data URL.
|
|
198
|
-
* @returns {string} The result Data URL.
|
|
199
|
-
*/
|
|
200
|
-
function arrayBufferToDataURL(arrayBuffer, mimeType) {
|
|
201
|
-
const uint8 = new Uint8Array(arrayBuffer);
|
|
202
|
-
const {
|
|
203
|
-
length
|
|
204
|
-
} = uint8;
|
|
205
|
-
const chunkSize = 8192;
|
|
206
|
-
let binary = '';
|
|
207
|
-
for (let i = 0; i < length; i += chunkSize) {
|
|
208
|
-
const end = Math.min(i + chunkSize, length);
|
|
209
|
-
let chunk = '';
|
|
210
|
-
for (let j = i; j < end; j += 1) {
|
|
211
|
-
chunk += fromCharCode(uint8[j]);
|
|
212
|
-
}
|
|
213
|
-
binary += chunk;
|
|
214
|
-
}
|
|
215
|
-
return `data:${mimeType};base64,${btoa(binary)}`;
|
|
216
|
-
}
|
|
217
184
|
|
|
218
185
|
/**
|
|
219
186
|
* Get orientation value from given array buffer.
|
|
220
187
|
* @param {ArrayBuffer} arrayBuffer - The array buffer to read.
|
|
221
188
|
* @returns {number} The read orientation value.
|
|
222
189
|
*/
|
|
223
|
-
function
|
|
190
|
+
function resetOrientation(arrayBuffer) {
|
|
224
191
|
const dataView = new DataView(arrayBuffer);
|
|
225
192
|
let orientation;
|
|
226
193
|
|
|
@@ -282,60 +249,6 @@ function resetAndGetOrientation(arrayBuffer) {
|
|
|
282
249
|
}
|
|
283
250
|
return orientation;
|
|
284
251
|
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Parse Exif Orientation value.
|
|
288
|
-
* @param {number} orientation - The orientation to parse.
|
|
289
|
-
* @returns {Object} The parsed result.
|
|
290
|
-
*/
|
|
291
|
-
function parseOrientation(orientation) {
|
|
292
|
-
let rotate = 0;
|
|
293
|
-
let scaleX = 1;
|
|
294
|
-
let scaleY = 1;
|
|
295
|
-
switch (orientation) {
|
|
296
|
-
// Flip horizontal
|
|
297
|
-
case 2:
|
|
298
|
-
scaleX = -1;
|
|
299
|
-
break;
|
|
300
|
-
|
|
301
|
-
// Rotate left 180°
|
|
302
|
-
case 3:
|
|
303
|
-
rotate = -180;
|
|
304
|
-
break;
|
|
305
|
-
|
|
306
|
-
// Flip vertical
|
|
307
|
-
case 4:
|
|
308
|
-
scaleY = -1;
|
|
309
|
-
break;
|
|
310
|
-
|
|
311
|
-
// Flip vertical and rotate right 90°
|
|
312
|
-
case 5:
|
|
313
|
-
rotate = 90;
|
|
314
|
-
scaleY = -1;
|
|
315
|
-
break;
|
|
316
|
-
|
|
317
|
-
// Rotate right 90°
|
|
318
|
-
case 6:
|
|
319
|
-
rotate = 90;
|
|
320
|
-
break;
|
|
321
|
-
|
|
322
|
-
// Flip horizontal and rotate right 90°
|
|
323
|
-
case 7:
|
|
324
|
-
rotate = 90;
|
|
325
|
-
scaleX = -1;
|
|
326
|
-
break;
|
|
327
|
-
|
|
328
|
-
// Rotate left 90°
|
|
329
|
-
case 8:
|
|
330
|
-
rotate = -90;
|
|
331
|
-
break;
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
rotate,
|
|
335
|
-
scaleX,
|
|
336
|
-
scaleY
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
252
|
let cachedCanvasReliable;
|
|
340
253
|
|
|
341
254
|
/**
|
|
@@ -609,7 +522,6 @@ class Compressor {
|
|
|
609
522
|
return;
|
|
610
523
|
}
|
|
611
524
|
if (!ArrayBuffer) {
|
|
612
|
-
options.checkOrientation = false;
|
|
613
525
|
options.retainExif = false;
|
|
614
526
|
}
|
|
615
527
|
if (!isCanvasReliable()) {
|
|
@@ -665,9 +577,8 @@ class Compressor {
|
|
|
665
577
|
return;
|
|
666
578
|
}
|
|
667
579
|
const isJPEGImage = mimeType === 'image/jpeg';
|
|
668
|
-
const checkOrientation = isJPEGImage && options.checkOrientation;
|
|
669
580
|
const retainExif = isJPEGImage && options.retainExif;
|
|
670
|
-
if (
|
|
581
|
+
if (!retainExif) {
|
|
671
582
|
this.url = URL.createObjectURL(file);
|
|
672
583
|
this.load({
|
|
673
584
|
url: this.url
|
|
@@ -678,36 +589,16 @@ class Compressor {
|
|
|
678
589
|
reader.onload = ({
|
|
679
590
|
target
|
|
680
591
|
}) => {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
Object.assign(data, parseOrientation(orientation));
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
if (retainExif) {
|
|
695
|
-
this.exif = getExif(result);
|
|
696
|
-
}
|
|
697
|
-
if (checkOrientation || retainExif) {
|
|
698
|
-
if (!URL
|
|
699
|
-
|
|
700
|
-
// Generate a new URL with the default orientation value (1)
|
|
701
|
-
|| orientation > 1) {
|
|
702
|
-
data.url = arrayBufferToDataURL(result, mimeType);
|
|
703
|
-
} else {
|
|
704
|
-
this.url = URL.createObjectURL(file);
|
|
705
|
-
data.url = this.url;
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
data.url = result;
|
|
709
|
-
}
|
|
710
|
-
this.load(data);
|
|
592
|
+
if (this.aborted) return;
|
|
593
|
+
|
|
594
|
+
// Normalize EXIF orientation to 1 before extracting, since the browser
|
|
595
|
+
// handles rotation natively via `image-orientation: from-image`
|
|
596
|
+
resetOrientation(target.result);
|
|
597
|
+
this.exif = getExif(target.result);
|
|
598
|
+
this.url = URL.createObjectURL(file);
|
|
599
|
+
this.load({
|
|
600
|
+
url: this.url
|
|
601
|
+
});
|
|
711
602
|
};
|
|
712
603
|
reader.onabort = () => {
|
|
713
604
|
this.fail(new Error('Aborted to read the image with FileReader.'));
|
|
@@ -718,11 +609,7 @@ class Compressor {
|
|
|
718
609
|
reader.onloadend = () => {
|
|
719
610
|
this.reader = null;
|
|
720
611
|
};
|
|
721
|
-
|
|
722
|
-
reader.readAsArrayBuffer(file);
|
|
723
|
-
} else {
|
|
724
|
-
reader.readAsDataURL(file);
|
|
725
|
-
}
|
|
612
|
+
reader.readAsArrayBuffer(file);
|
|
726
613
|
}
|
|
727
614
|
}
|
|
728
615
|
load(data) {
|
|
@@ -755,10 +642,7 @@ class Compressor {
|
|
|
755
642
|
}
|
|
756
643
|
draw({
|
|
757
644
|
naturalWidth,
|
|
758
|
-
naturalHeight
|
|
759
|
-
rotate = 0,
|
|
760
|
-
scaleX = 1,
|
|
761
|
-
scaleY = 1
|
|
645
|
+
naturalHeight
|
|
762
646
|
}) {
|
|
763
647
|
const {
|
|
764
648
|
file,
|
|
@@ -767,7 +651,6 @@ class Compressor {
|
|
|
767
651
|
} = this;
|
|
768
652
|
const canvas = document.createElement('canvas');
|
|
769
653
|
const context = canvas.getContext('2d');
|
|
770
|
-
const is90DegreesRotated = Math.abs(rotate) % 180 === 90;
|
|
771
654
|
const resizable = (options.resize === 'contain' || options.resize === 'cover') && isPositiveNumber(options.width) && isPositiveNumber(options.height);
|
|
772
655
|
let maxWidth = Math.max(options.maxWidth, 0) || Infinity;
|
|
773
656
|
let maxHeight = Math.max(options.maxHeight, 0) || Infinity;
|
|
@@ -778,11 +661,6 @@ class Compressor {
|
|
|
778
661
|
width,
|
|
779
662
|
height
|
|
780
663
|
} = options;
|
|
781
|
-
if (is90DegreesRotated) {
|
|
782
|
-
[maxWidth, maxHeight] = [maxHeight, maxWidth];
|
|
783
|
-
[minWidth, minHeight] = [minHeight, minWidth];
|
|
784
|
-
[width, height] = [height, width];
|
|
785
|
-
}
|
|
786
664
|
if (resizable) {
|
|
787
665
|
aspectRatio = width / height;
|
|
788
666
|
}
|
|
@@ -823,10 +701,6 @@ class Compressor {
|
|
|
823
701
|
}
|
|
824
702
|
width = Math.floor(normalizeDecimalNumber(Math.min(Math.max(width, minWidth), maxWidth)));
|
|
825
703
|
height = Math.floor(normalizeDecimalNumber(Math.min(Math.max(height, minHeight), maxHeight)));
|
|
826
|
-
const destX = -width / 2;
|
|
827
|
-
const destY = -height / 2;
|
|
828
|
-
const destWidth = width;
|
|
829
|
-
const destHeight = height;
|
|
830
704
|
const params = [];
|
|
831
705
|
if (resizable) {
|
|
832
706
|
const {
|
|
@@ -844,10 +718,7 @@ class Compressor {
|
|
|
844
718
|
const srcY = (naturalHeight - srcHeight) / 2;
|
|
845
719
|
params.push(srcX, srcY, srcWidth, srcHeight);
|
|
846
720
|
}
|
|
847
|
-
params.push(
|
|
848
|
-
if (is90DegreesRotated) {
|
|
849
|
-
[width, height] = [height, width];
|
|
850
|
-
}
|
|
721
|
+
params.push(0, 0, width, height);
|
|
851
722
|
canvas.width = width;
|
|
852
723
|
canvas.height = height;
|
|
853
724
|
if (!isImageType(options.mimeType)) {
|
|
@@ -874,12 +745,7 @@ class Compressor {
|
|
|
874
745
|
if (this.aborted) {
|
|
875
746
|
return;
|
|
876
747
|
}
|
|
877
|
-
context.save();
|
|
878
|
-
context.translate(width / 2, height / 2);
|
|
879
|
-
context.rotate(rotate * Math.PI / 180);
|
|
880
|
-
context.scale(scaleX, scaleY);
|
|
881
748
|
context.drawImage(image, ...params);
|
|
882
|
-
context.restore();
|
|
883
749
|
if (options.drew) {
|
|
884
750
|
options.drew.call(this, context, canvas);
|
|
885
751
|
}
|
|
@@ -895,12 +761,46 @@ class Compressor {
|
|
|
895
761
|
});
|
|
896
762
|
if (blob && isJPEGImage && options.retainExif && this.exif && this.exif.length > 0) {
|
|
897
763
|
const next = arrayBuffer => {
|
|
764
|
+
if (this.aborted) return;
|
|
898
765
|
const withExif = insertExif(arrayBuffer, this.exif);
|
|
899
766
|
done(uint8ArrayToBlob(withExif, options.mimeType));
|
|
900
767
|
};
|
|
901
768
|
if (blob.arrayBuffer) {
|
|
902
769
|
blob.arrayBuffer().then(next).catch(() => {
|
|
903
|
-
|
|
770
|
+
if (this.aborted) return;
|
|
771
|
+
this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
|
|
772
|
+
});
|
|
773
|
+
} else {
|
|
774
|
+
const reader = new FileReader();
|
|
775
|
+
this.reader = reader;
|
|
776
|
+
reader.onload = ({
|
|
777
|
+
target
|
|
778
|
+
}) => {
|
|
779
|
+
next(target.result);
|
|
780
|
+
};
|
|
781
|
+
reader.onabort = () => {
|
|
782
|
+
this.fail(new Error('Aborted to read the compressed image with FileReader.'));
|
|
783
|
+
};
|
|
784
|
+
reader.onerror = () => {
|
|
785
|
+
this.fail(new Error('Failed to read the compressed image with FileReader.'));
|
|
786
|
+
};
|
|
787
|
+
reader.onloadend = () => {
|
|
788
|
+
this.reader = null;
|
|
789
|
+
};
|
|
790
|
+
reader.readAsArrayBuffer(blob);
|
|
791
|
+
}
|
|
792
|
+
} else if (blob && isJPEGImage && !options.retainExif) {
|
|
793
|
+
// Strip any EXIF that may be present in the canvas output
|
|
794
|
+
// (most browsers strip it automatically, but WebKit preserves the
|
|
795
|
+
// source EXIF—this ensures consistent, privacy-safe output)
|
|
796
|
+
const next = arrayBuffer => {
|
|
797
|
+
if (this.aborted) return;
|
|
798
|
+
done(uint8ArrayToBlob(stripExif(arrayBuffer), options.mimeType));
|
|
799
|
+
};
|
|
800
|
+
if (blob.arrayBuffer) {
|
|
801
|
+
blob.arrayBuffer().then(next).catch(() => {
|
|
802
|
+
if (this.aborted) return;
|
|
803
|
+
this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
|
|
904
804
|
});
|
|
905
805
|
} else {
|
|
906
806
|
const reader = new FileReader();
|
|
@@ -938,10 +838,12 @@ class Compressor {
|
|
|
938
838
|
options
|
|
939
839
|
} = this;
|
|
940
840
|
this.revokeUrl();
|
|
841
|
+
let strictFallback = false;
|
|
941
842
|
if (result) {
|
|
942
843
|
// Returns original file if the result is greater than it and without size-related options
|
|
943
844
|
if (options.strict && !options.retainExif && result.size > file.size && options.mimeType === file.type && !(options.width > naturalWidth || options.height > naturalHeight || options.minWidth > naturalWidth || options.minHeight > naturalHeight || options.maxWidth < naturalWidth || options.maxHeight < naturalHeight)) {
|
|
944
845
|
result = file;
|
|
846
|
+
strictFallback = true;
|
|
945
847
|
} else {
|
|
946
848
|
const date = new Date();
|
|
947
849
|
result.lastModified = date.getTime();
|
|
@@ -957,6 +859,61 @@ class Compressor {
|
|
|
957
859
|
console.warn('Compressor.js Next: Canvas produced no output—returning the original image');
|
|
958
860
|
result = file;
|
|
959
861
|
}
|
|
862
|
+
|
|
863
|
+
// When strict returns the original file, it may still contain EXIF—strip it
|
|
864
|
+
// asynchronously so the output is consistently EXIF-free across all browsers
|
|
865
|
+
if (strictFallback && file.type === 'image/jpeg') {
|
|
866
|
+
if (file.arrayBuffer) {
|
|
867
|
+
file.arrayBuffer().then(arrayBuffer => {
|
|
868
|
+
if (this.aborted) return;
|
|
869
|
+
const stripped = uint8ArrayToBlob(stripExif(arrayBuffer), file.type);
|
|
870
|
+
stripped.name = file.name;
|
|
871
|
+
stripped.lastModified = file.lastModified;
|
|
872
|
+
this.result = stripped;
|
|
873
|
+
if (options.success) {
|
|
874
|
+
options.success.call(this, stripped);
|
|
875
|
+
}
|
|
876
|
+
}).catch(err => {
|
|
877
|
+
if (this.aborted) return;
|
|
878
|
+
console.warn(`Compressor.js Next: Failed to strip EXIF from original file—returning original with EXIF intact${file.name ? ` [${file.name}]` : ''}${err?.message ? `: ${err.message}` : ''}`);
|
|
879
|
+
this.result = file;
|
|
880
|
+
if (options.success) {
|
|
881
|
+
options.success.call(this, file);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
} else {
|
|
885
|
+
const reader = new FileReader();
|
|
886
|
+
this.reader = reader;
|
|
887
|
+
reader.onload = ({
|
|
888
|
+
target
|
|
889
|
+
}) => {
|
|
890
|
+
if (this.aborted) return;
|
|
891
|
+
const stripped = uint8ArrayToBlob(stripExif(target.result), file.type);
|
|
892
|
+
stripped.name = file.name;
|
|
893
|
+
stripped.lastModified = file.lastModified;
|
|
894
|
+
this.result = stripped;
|
|
895
|
+
if (options.success) {
|
|
896
|
+
options.success.call(this, stripped);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
reader.onabort = () => {
|
|
900
|
+
this.fail(new Error('Aborted to read the original file with FileReader.'));
|
|
901
|
+
};
|
|
902
|
+
reader.onerror = () => {
|
|
903
|
+
if (this.aborted) return;
|
|
904
|
+
console.warn(`Compressor.js Next: Failed to strip EXIF from original file—returning original with EXIF intact${file.name ? ` [${file.name}]` : ''}`);
|
|
905
|
+
this.result = file;
|
|
906
|
+
if (options.success) {
|
|
907
|
+
options.success.call(this, file);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
reader.onloadend = () => {
|
|
911
|
+
this.reader = null;
|
|
912
|
+
};
|
|
913
|
+
reader.readAsArrayBuffer(file);
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
960
917
|
this.result = result;
|
|
961
918
|
if (options.success) {
|
|
962
919
|
options.success.call(this, result);
|