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.
@@ -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
@@ -14,12 +14,6 @@ var DEFAULTS = {
14
14
  * @type {boolean}
15
15
  */
16
16
  strict: true,
17
- /**
18
- * Indicates if read the image’s Exif Orientation information,
19
- * and then rotate or flip the image automatically.
20
- * @type {boolean}
21
- */
22
- checkOrientation: true,
23
17
  /**
24
18
  * Indicates if retain the image’s Exif information after compressed.
25
19
  * @type {boolean}
@@ -185,40 +179,13 @@ function getStringFromCharCode(dataView, start, length) {
185
179
  }
186
180
  return str;
187
181
  }
188
- const {
189
- btoa
190
- } = WINDOW;
191
-
192
- /**
193
- * Transform array buffer to Data URL.
194
- * @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
195
- * @param {string} mimeType - The mime type of the Data URL.
196
- * @returns {string} The result Data URL.
197
- */
198
- function arrayBufferToDataURL(arrayBuffer, mimeType) {
199
- const uint8 = new Uint8Array(arrayBuffer);
200
- const {
201
- length
202
- } = uint8;
203
- const chunkSize = 8192;
204
- let binary = '';
205
- for (let i = 0; i < length; i += chunkSize) {
206
- const end = Math.min(i + chunkSize, length);
207
- let chunk = '';
208
- for (let j = i; j < end; j += 1) {
209
- chunk += fromCharCode(uint8[j]);
210
- }
211
- binary += chunk;
212
- }
213
- return `data:${mimeType};base64,${btoa(binary)}`;
214
- }
215
182
 
216
183
  /**
217
184
  * Get orientation value from given array buffer.
218
185
  * @param {ArrayBuffer} arrayBuffer - The array buffer to read.
219
186
  * @returns {number} The read orientation value.
220
187
  */
221
- function resetAndGetOrientation(arrayBuffer) {
188
+ function resetOrientation(arrayBuffer) {
222
189
  const dataView = new DataView(arrayBuffer);
223
190
  let orientation;
224
191
 
@@ -280,60 +247,6 @@ function resetAndGetOrientation(arrayBuffer) {
280
247
  }
281
248
  return orientation;
282
249
  }
283
-
284
- /**
285
- * Parse Exif Orientation value.
286
- * @param {number} orientation - The orientation to parse.
287
- * @returns {Object} The parsed result.
288
- */
289
- function parseOrientation(orientation) {
290
- let rotate = 0;
291
- let scaleX = 1;
292
- let scaleY = 1;
293
- switch (orientation) {
294
- // Flip horizontal
295
- case 2:
296
- scaleX = -1;
297
- break;
298
-
299
- // Rotate left 180°
300
- case 3:
301
- rotate = -180;
302
- break;
303
-
304
- // Flip vertical
305
- case 4:
306
- scaleY = -1;
307
- break;
308
-
309
- // Flip vertical and rotate right 90°
310
- case 5:
311
- rotate = 90;
312
- scaleY = -1;
313
- break;
314
-
315
- // Rotate right 90°
316
- case 6:
317
- rotate = 90;
318
- break;
319
-
320
- // Flip horizontal and rotate right 90°
321
- case 7:
322
- rotate = 90;
323
- scaleX = -1;
324
- break;
325
-
326
- // Rotate left 90°
327
- case 8:
328
- rotate = -90;
329
- break;
330
- }
331
- return {
332
- rotate,
333
- scaleX,
334
- scaleY
335
- };
336
- }
337
250
  let cachedCanvasReliable;
338
251
 
339
252
  /**
@@ -607,7 +520,6 @@ class Compressor {
607
520
  return;
608
521
  }
609
522
  if (!ArrayBuffer) {
610
- options.checkOrientation = false;
611
523
  options.retainExif = false;
612
524
  }
613
525
  if (!isCanvasReliable()) {
@@ -663,9 +575,8 @@ class Compressor {
663
575
  return;
664
576
  }
665
577
  const isJPEGImage = mimeType === 'image/jpeg';
666
- const checkOrientation = isJPEGImage && options.checkOrientation;
667
578
  const retainExif = isJPEGImage && options.retainExif;
668
- if (URL && !checkOrientation && !retainExif) {
579
+ if (!retainExif) {
669
580
  this.url = URL.createObjectURL(file);
670
581
  this.load({
671
582
  url: this.url
@@ -676,36 +587,16 @@ class Compressor {
676
587
  reader.onload = ({
677
588
  target
678
589
  }) => {
679
- const {
680
- result
681
- } = target;
682
- const data = {};
683
- let orientation = 1;
684
- if (checkOrientation) {
685
- // Reset the orientation value to its default value (1)
686
- // as some iOS browsers will render image with its orientation
687
- orientation = resetAndGetOrientation(result);
688
- if (orientation > 1) {
689
- Object.assign(data, parseOrientation(orientation));
690
- }
691
- }
692
- if (retainExif) {
693
- this.exif = getExif(result);
694
- }
695
- if (checkOrientation || retainExif) {
696
- if (!URL
697
-
698
- // Generate a new URL with the default orientation value (1)
699
- || orientation > 1) {
700
- data.url = arrayBufferToDataURL(result, mimeType);
701
- } else {
702
- this.url = URL.createObjectURL(file);
703
- data.url = this.url;
704
- }
705
- } else {
706
- data.url = result;
707
- }
708
- this.load(data);
590
+ if (this.aborted) return;
591
+
592
+ // Normalize EXIF orientation to 1 before extracting, since the browser
593
+ // handles rotation natively via `image-orientation: from-image`
594
+ resetOrientation(target.result);
595
+ this.exif = getExif(target.result);
596
+ this.url = URL.createObjectURL(file);
597
+ this.load({
598
+ url: this.url
599
+ });
709
600
  };
710
601
  reader.onabort = () => {
711
602
  this.fail(new Error('Aborted to read the image with FileReader.'));
@@ -716,11 +607,7 @@ class Compressor {
716
607
  reader.onloadend = () => {
717
608
  this.reader = null;
718
609
  };
719
- if (checkOrientation || retainExif) {
720
- reader.readAsArrayBuffer(file);
721
- } else {
722
- reader.readAsDataURL(file);
723
- }
610
+ reader.readAsArrayBuffer(file);
724
611
  }
725
612
  }
726
613
  load(data) {
@@ -753,10 +640,7 @@ class Compressor {
753
640
  }
754
641
  draw({
755
642
  naturalWidth,
756
- naturalHeight,
757
- rotate = 0,
758
- scaleX = 1,
759
- scaleY = 1
643
+ naturalHeight
760
644
  }) {
761
645
  const {
762
646
  file,
@@ -765,7 +649,6 @@ class Compressor {
765
649
  } = this;
766
650
  const canvas = document.createElement('canvas');
767
651
  const context = canvas.getContext('2d');
768
- const is90DegreesRotated = Math.abs(rotate) % 180 === 90;
769
652
  const resizable = (options.resize === 'contain' || options.resize === 'cover') && isPositiveNumber(options.width) && isPositiveNumber(options.height);
770
653
  let maxWidth = Math.max(options.maxWidth, 0) || Infinity;
771
654
  let maxHeight = Math.max(options.maxHeight, 0) || Infinity;
@@ -776,11 +659,6 @@ class Compressor {
776
659
  width,
777
660
  height
778
661
  } = options;
779
- if (is90DegreesRotated) {
780
- [maxWidth, maxHeight] = [maxHeight, maxWidth];
781
- [minWidth, minHeight] = [minHeight, minWidth];
782
- [width, height] = [height, width];
783
- }
784
662
  if (resizable) {
785
663
  aspectRatio = width / height;
786
664
  }
@@ -821,10 +699,6 @@ class Compressor {
821
699
  }
822
700
  width = Math.floor(normalizeDecimalNumber(Math.min(Math.max(width, minWidth), maxWidth)));
823
701
  height = Math.floor(normalizeDecimalNumber(Math.min(Math.max(height, minHeight), maxHeight)));
824
- const destX = -width / 2;
825
- const destY = -height / 2;
826
- const destWidth = width;
827
- const destHeight = height;
828
702
  const params = [];
829
703
  if (resizable) {
830
704
  const {
@@ -842,10 +716,7 @@ class Compressor {
842
716
  const srcY = (naturalHeight - srcHeight) / 2;
843
717
  params.push(srcX, srcY, srcWidth, srcHeight);
844
718
  }
845
- params.push(destX, destY, destWidth, destHeight);
846
- if (is90DegreesRotated) {
847
- [width, height] = [height, width];
848
- }
719
+ params.push(0, 0, width, height);
849
720
  canvas.width = width;
850
721
  canvas.height = height;
851
722
  if (!isImageType(options.mimeType)) {
@@ -872,12 +743,7 @@ class Compressor {
872
743
  if (this.aborted) {
873
744
  return;
874
745
  }
875
- context.save();
876
- context.translate(width / 2, height / 2);
877
- context.rotate(rotate * Math.PI / 180);
878
- context.scale(scaleX, scaleY);
879
746
  context.drawImage(image, ...params);
880
- context.restore();
881
747
  if (options.drew) {
882
748
  options.drew.call(this, context, canvas);
883
749
  }
@@ -893,12 +759,46 @@ class Compressor {
893
759
  });
894
760
  if (blob && isJPEGImage && options.retainExif && this.exif && this.exif.length > 0) {
895
761
  const next = arrayBuffer => {
762
+ if (this.aborted) return;
896
763
  const withExif = insertExif(arrayBuffer, this.exif);
897
764
  done(uint8ArrayToBlob(withExif, options.mimeType));
898
765
  };
899
766
  if (blob.arrayBuffer) {
900
767
  blob.arrayBuffer().then(next).catch(() => {
901
- this.fail(new Error('Failed to read the compressed image with Blob.arrayBuffer().'));
768
+ if (this.aborted) return;
769
+ this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
770
+ });
771
+ } else {
772
+ const reader = new FileReader();
773
+ this.reader = reader;
774
+ reader.onload = ({
775
+ target
776
+ }) => {
777
+ next(target.result);
778
+ };
779
+ reader.onabort = () => {
780
+ this.fail(new Error('Aborted to read the compressed image with FileReader.'));
781
+ };
782
+ reader.onerror = () => {
783
+ this.fail(new Error('Failed to read the compressed image with FileReader.'));
784
+ };
785
+ reader.onloadend = () => {
786
+ this.reader = null;
787
+ };
788
+ reader.readAsArrayBuffer(blob);
789
+ }
790
+ } else if (blob && isJPEGImage && !options.retainExif) {
791
+ // Strip any EXIF that may be present in the canvas output
792
+ // (most browsers strip it automatically, but WebKit preserves the
793
+ // source EXIF—this ensures consistent, privacy-safe output)
794
+ const next = arrayBuffer => {
795
+ if (this.aborted) return;
796
+ done(uint8ArrayToBlob(stripExif(arrayBuffer), options.mimeType));
797
+ };
798
+ if (blob.arrayBuffer) {
799
+ blob.arrayBuffer().then(next).catch(() => {
800
+ if (this.aborted) return;
801
+ this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
902
802
  });
903
803
  } else {
904
804
  const reader = new FileReader();
@@ -936,10 +836,12 @@ class Compressor {
936
836
  options
937
837
  } = this;
938
838
  this.revokeUrl();
839
+ let strictFallback = false;
939
840
  if (result) {
940
841
  // Returns original file if the result is greater than it and without size-related options
941
842
  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)) {
942
843
  result = file;
844
+ strictFallback = true;
943
845
  } else {
944
846
  const date = new Date();
945
847
  result.lastModified = date.getTime();
@@ -955,6 +857,61 @@ class Compressor {
955
857
  console.warn('Compressor.js Next: Canvas produced no output—returning the original image');
956
858
  result = file;
957
859
  }
860
+
861
+ // When strict returns the original file, it may still contain EXIF—strip it
862
+ // asynchronously so the output is consistently EXIF-free across all browsers
863
+ if (strictFallback && file.type === 'image/jpeg') {
864
+ if (file.arrayBuffer) {
865
+ file.arrayBuffer().then(arrayBuffer => {
866
+ if (this.aborted) return;
867
+ const stripped = uint8ArrayToBlob(stripExif(arrayBuffer), file.type);
868
+ stripped.name = file.name;
869
+ stripped.lastModified = file.lastModified;
870
+ this.result = stripped;
871
+ if (options.success) {
872
+ options.success.call(this, stripped);
873
+ }
874
+ }).catch(err => {
875
+ if (this.aborted) return;
876
+ 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}` : ''}`);
877
+ this.result = file;
878
+ if (options.success) {
879
+ options.success.call(this, file);
880
+ }
881
+ });
882
+ } else {
883
+ const reader = new FileReader();
884
+ this.reader = reader;
885
+ reader.onload = ({
886
+ target
887
+ }) => {
888
+ if (this.aborted) return;
889
+ const stripped = uint8ArrayToBlob(stripExif(target.result), file.type);
890
+ stripped.name = file.name;
891
+ stripped.lastModified = file.lastModified;
892
+ this.result = stripped;
893
+ if (options.success) {
894
+ options.success.call(this, stripped);
895
+ }
896
+ };
897
+ reader.onabort = () => {
898
+ this.fail(new Error('Aborted to read the original file with FileReader.'));
899
+ };
900
+ reader.onerror = () => {
901
+ if (this.aborted) return;
902
+ console.warn(`Compressor.js Next: Failed to strip EXIF from original file—returning original with EXIF intact${file.name ? ` [${file.name}]` : ''}`);
903
+ this.result = file;
904
+ if (options.success) {
905
+ options.success.call(this, file);
906
+ }
907
+ };
908
+ reader.onloadend = () => {
909
+ this.reader = null;
910
+ };
911
+ reader.readAsArrayBuffer(file);
912
+ }
913
+ return;
914
+ }
958
915
  this.result = result;
959
916
  if (options.success) {
960
917
  options.success.call(this, result);