compressorjs-next 1.1.2 → 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 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 1.1.0—follow [the changelog](https://github.com/j9t/compressorjs-next/blob/main/CHANGELOG.md) from there):
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 `noConflict()` method has been removed
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 v1.1.2
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 resetAndGetOrientation(arrayBuffer) {
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 (URL && !checkOrientation && !retainExif) {
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
- const {
682
- result
683
- } = target;
684
- const data = {};
685
- let orientation = 1;
686
- if (checkOrientation) {
687
- // Reset the orientation value to its default value (1)
688
- // as some iOS browsers will render image with its orientation
689
- orientation = resetAndGetOrientation(result);
690
- if (orientation > 1) {
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
- if (checkOrientation || retainExif) {
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(destX, destY, destWidth, destHeight);
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
- this.fail(new Error('Failed to read the compressed image with Blob.arrayBuffer().'));
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);