compressorjs-next 1.1.2 → 2.0.1

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.1
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()) {
@@ -623,17 +535,18 @@ class Compressor {
623
535
  target
624
536
  }) => {
625
537
  if (this.aborted) return;
626
- let result;
538
+ let blob;
627
539
  try {
628
- const stripped = stripExif(target.result);
629
- result = uint8ArrayToBlob(stripped, mimeType);
540
+ blob = uint8ArrayToBlob(stripExif(target.result), mimeType);
630
541
  } catch {
631
542
  this.fail(new Error('Failed to process the image data.'));
632
543
  return;
633
544
  }
634
545
  const date = new Date();
635
- result.name = file.name;
636
- result.lastModified = date.getTime();
546
+ const result = new File([blob], file.name || '', {
547
+ type: mimeType,
548
+ lastModified: date.getTime()
549
+ });
637
550
  this.result = result;
638
551
  if (options.success) {
639
552
  options.success.call(this, result);
@@ -663,9 +576,8 @@ class Compressor {
663
576
  return;
664
577
  }
665
578
  const isJPEGImage = mimeType === 'image/jpeg';
666
- const checkOrientation = isJPEGImage && options.checkOrientation;
667
579
  const retainExif = isJPEGImage && options.retainExif;
668
- if (URL && !checkOrientation && !retainExif) {
580
+ if (!retainExif) {
669
581
  this.url = URL.createObjectURL(file);
670
582
  this.load({
671
583
  url: this.url
@@ -676,36 +588,16 @@ class Compressor {
676
588
  reader.onload = ({
677
589
  target
678
590
  }) => {
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);
591
+ if (this.aborted) return;
592
+
593
+ // Normalize EXIF orientation to 1 before extracting, since the browser
594
+ // handles rotation natively via `image-orientation: from-image`
595
+ resetOrientation(target.result);
596
+ this.exif = getExif(target.result);
597
+ this.url = URL.createObjectURL(file);
598
+ this.load({
599
+ url: this.url
600
+ });
709
601
  };
710
602
  reader.onabort = () => {
711
603
  this.fail(new Error('Aborted to read the image with FileReader.'));
@@ -716,11 +608,7 @@ class Compressor {
716
608
  reader.onloadend = () => {
717
609
  this.reader = null;
718
610
  };
719
- if (checkOrientation || retainExif) {
720
- reader.readAsArrayBuffer(file);
721
- } else {
722
- reader.readAsDataURL(file);
723
- }
611
+ reader.readAsArrayBuffer(file);
724
612
  }
725
613
  }
726
614
  load(data) {
@@ -753,10 +641,7 @@ class Compressor {
753
641
  }
754
642
  draw({
755
643
  naturalWidth,
756
- naturalHeight,
757
- rotate = 0,
758
- scaleX = 1,
759
- scaleY = 1
644
+ naturalHeight
760
645
  }) {
761
646
  const {
762
647
  file,
@@ -765,7 +650,6 @@ class Compressor {
765
650
  } = this;
766
651
  const canvas = document.createElement('canvas');
767
652
  const context = canvas.getContext('2d');
768
- const is90DegreesRotated = Math.abs(rotate) % 180 === 90;
769
653
  const resizable = (options.resize === 'contain' || options.resize === 'cover') && isPositiveNumber(options.width) && isPositiveNumber(options.height);
770
654
  let maxWidth = Math.max(options.maxWidth, 0) || Infinity;
771
655
  let maxHeight = Math.max(options.maxHeight, 0) || Infinity;
@@ -776,11 +660,6 @@ class Compressor {
776
660
  width,
777
661
  height
778
662
  } = options;
779
- if (is90DegreesRotated) {
780
- [maxWidth, maxHeight] = [maxHeight, maxWidth];
781
- [minWidth, minHeight] = [minHeight, minWidth];
782
- [width, height] = [height, width];
783
- }
784
663
  if (resizable) {
785
664
  aspectRatio = width / height;
786
665
  }
@@ -821,10 +700,6 @@ class Compressor {
821
700
  }
822
701
  width = Math.floor(normalizeDecimalNumber(Math.min(Math.max(width, minWidth), maxWidth)));
823
702
  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
703
  const params = [];
829
704
  if (resizable) {
830
705
  const {
@@ -842,10 +717,7 @@ class Compressor {
842
717
  const srcY = (naturalHeight - srcHeight) / 2;
843
718
  params.push(srcX, srcY, srcWidth, srcHeight);
844
719
  }
845
- params.push(destX, destY, destWidth, destHeight);
846
- if (is90DegreesRotated) {
847
- [width, height] = [height, width];
848
- }
720
+ params.push(0, 0, width, height);
849
721
  canvas.width = width;
850
722
  canvas.height = height;
851
723
  if (!isImageType(options.mimeType)) {
@@ -872,12 +744,7 @@ class Compressor {
872
744
  if (this.aborted) {
873
745
  return;
874
746
  }
875
- context.save();
876
- context.translate(width / 2, height / 2);
877
- context.rotate(rotate * Math.PI / 180);
878
- context.scale(scaleX, scaleY);
879
747
  context.drawImage(image, ...params);
880
- context.restore();
881
748
  if (options.drew) {
882
749
  options.drew.call(this, context, canvas);
883
750
  }
@@ -893,12 +760,46 @@ class Compressor {
893
760
  });
894
761
  if (blob && isJPEGImage && options.retainExif && this.exif && this.exif.length > 0) {
895
762
  const next = arrayBuffer => {
763
+ if (this.aborted) return;
896
764
  const withExif = insertExif(arrayBuffer, this.exif);
897
765
  done(uint8ArrayToBlob(withExif, options.mimeType));
898
766
  };
899
767
  if (blob.arrayBuffer) {
900
768
  blob.arrayBuffer().then(next).catch(() => {
901
- this.fail(new Error('Failed to read the compressed image with Blob.arrayBuffer().'));
769
+ if (this.aborted) return;
770
+ this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
771
+ });
772
+ } else {
773
+ const reader = new FileReader();
774
+ this.reader = reader;
775
+ reader.onload = ({
776
+ target
777
+ }) => {
778
+ next(target.result);
779
+ };
780
+ reader.onabort = () => {
781
+ this.fail(new Error('Aborted to read the compressed image with FileReader.'));
782
+ };
783
+ reader.onerror = () => {
784
+ this.fail(new Error('Failed to read the compressed image with FileReader.'));
785
+ };
786
+ reader.onloadend = () => {
787
+ this.reader = null;
788
+ };
789
+ reader.readAsArrayBuffer(blob);
790
+ }
791
+ } else if (blob && isJPEGImage && !options.retainExif) {
792
+ // Strip any EXIF that may be present in the canvas output
793
+ // (most browsers strip it automatically, but WebKit preserves the
794
+ // source EXIF—this ensures consistent, privacy-safe output)
795
+ const next = arrayBuffer => {
796
+ if (this.aborted) return;
797
+ done(uint8ArrayToBlob(stripExif(arrayBuffer), options.mimeType));
798
+ };
799
+ if (blob.arrayBuffer) {
800
+ blob.arrayBuffer().then(next).catch(() => {
801
+ if (this.aborted) return;
802
+ this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
902
803
  });
903
804
  } else {
904
805
  const reader = new FileReader();
@@ -936,25 +837,89 @@ class Compressor {
936
837
  options
937
838
  } = this;
938
839
  this.revokeUrl();
840
+ let strictFallback = false;
939
841
  if (result) {
940
842
  // Returns original file if the result is greater than it and without size-related options
941
843
  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
844
  result = file;
845
+ strictFallback = true;
943
846
  } else {
944
847
  const date = new Date();
945
- result.lastModified = date.getTime();
946
- result.name = file.name;
848
+ let name = file.name || '';
947
849
 
948
850
  // Convert the extension to match its type
949
- if (result.name && result.type !== file.type) {
950
- result.name = result.name.replace(REGEXP_EXTENSION, imageTypeToExtension(result.type));
851
+ if (name && result.type !== file.type) {
852
+ name = name.replace(REGEXP_EXTENSION, imageTypeToExtension(result.type));
951
853
  }
854
+ result = new File([result], name, {
855
+ type: result.type,
856
+ lastModified: date.getTime()
857
+ });
952
858
  }
953
859
  } else {
954
860
  // Returns original file if the result is null in some cases
955
861
  console.warn('Compressor.js Next: Canvas produced no output—returning the original image');
956
862
  result = file;
957
863
  }
864
+
865
+ // When strict returns the original file, it may still contain EXIF—strip it
866
+ // asynchronously so the output is consistently EXIF-free across all browsers
867
+ if (strictFallback && file.type === 'image/jpeg') {
868
+ if (file.arrayBuffer) {
869
+ file.arrayBuffer().then(arrayBuffer => {
870
+ if (this.aborted) return;
871
+ const strippedBlob = uint8ArrayToBlob(stripExif(arrayBuffer), file.type);
872
+ const stripped = new File([strippedBlob], file.name || '', {
873
+ type: file.type,
874
+ lastModified: file.lastModified || Date.now()
875
+ });
876
+ this.result = stripped;
877
+ if (options.success) {
878
+ options.success.call(this, stripped);
879
+ }
880
+ }).catch(err => {
881
+ if (this.aborted) return;
882
+ 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}` : ''}`);
883
+ this.result = file;
884
+ if (options.success) {
885
+ options.success.call(this, file);
886
+ }
887
+ });
888
+ } else {
889
+ const reader = new FileReader();
890
+ this.reader = reader;
891
+ reader.onload = ({
892
+ target
893
+ }) => {
894
+ if (this.aborted) return;
895
+ const strippedBlob = uint8ArrayToBlob(stripExif(target.result), file.type);
896
+ const stripped = new File([strippedBlob], file.name || '', {
897
+ type: file.type,
898
+ lastModified: file.lastModified || Date.now()
899
+ });
900
+ this.result = stripped;
901
+ if (options.success) {
902
+ options.success.call(this, stripped);
903
+ }
904
+ };
905
+ reader.onabort = () => {
906
+ this.fail(new Error('Aborted to read the original file with FileReader.'));
907
+ };
908
+ reader.onerror = () => {
909
+ if (this.aborted) return;
910
+ console.warn(`Compressor.js Next: Failed to strip EXIF from original file—returning original with EXIF intact${file.name ? ` [${file.name}]` : ''}`);
911
+ this.result = file;
912
+ if (options.success) {
913
+ options.success.call(this, file);
914
+ }
915
+ };
916
+ reader.onloadend = () => {
917
+ this.reader = null;
918
+ };
919
+ reader.readAsArrayBuffer(file);
920
+ }
921
+ return;
922
+ }
958
923
  this.result = result;
959
924
  if (options.success) {
960
925
  options.success.call(this, result);