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.
package/src/index.js CHANGED
@@ -9,9 +9,7 @@ import {
9
9
  isImageType,
10
10
  isPositiveNumber,
11
11
  normalizeDecimalNumber,
12
- parseOrientation,
13
- resetAndGetOrientation,
14
- arrayBufferToDataURL,
12
+ resetOrientation,
15
13
  getExif,
16
14
  insertExif,
17
15
  stripExif,
@@ -69,7 +67,6 @@ export default class Compressor {
69
67
  }
70
68
 
71
69
  if (!ArrayBuffer) {
72
- options.checkOrientation = false;
73
70
  options.retainExif = false;
74
71
  }
75
72
 
@@ -86,21 +83,20 @@ export default class Compressor {
86
83
  reader.onload = ({ target }) => {
87
84
  if (this.aborted) return;
88
85
 
89
- let result;
86
+ let blob;
90
87
 
91
88
  try {
92
- const stripped = stripExif(target.result);
93
-
94
- result = uint8ArrayToBlob(stripped, mimeType);
89
+ blob = uint8ArrayToBlob(stripExif(target.result), mimeType);
95
90
  } catch {
96
91
  this.fail(new Error('Failed to process the image data.'));
97
92
  return;
98
93
  }
99
94
 
100
95
  const date = new Date();
101
-
102
- result.name = file.name;
103
- result.lastModified = date.getTime();
96
+ const result = new File([blob], file.name || '', {
97
+ type: mimeType,
98
+ lastModified: date.getTime(),
99
+ });
104
100
 
105
101
  this.result = result;
106
102
 
@@ -136,10 +132,9 @@ export default class Compressor {
136
132
  }
137
133
 
138
134
  const isJPEGImage = mimeType === 'image/jpeg';
139
- const checkOrientation = isJPEGImage && options.checkOrientation;
140
135
  const retainExif = isJPEGImage && options.retainExif;
141
136
 
142
- if (URL && !checkOrientation && !retainExif) {
137
+ if (!retainExif) {
143
138
  this.url = URL.createObjectURL(file);
144
139
  this.load({
145
140
  url: this.url,
@@ -149,41 +144,16 @@ export default class Compressor {
149
144
 
150
145
  this.reader = reader;
151
146
  reader.onload = ({ target }) => {
152
- const { result } = target;
153
- const data = {};
154
- let orientation = 1;
155
-
156
- if (checkOrientation) {
157
- // Reset the orientation value to its default value (1)
158
- // as some iOS browsers will render image with its orientation
159
- orientation = resetAndGetOrientation(result);
160
-
161
- if (orientation > 1) {
162
- Object.assign(data, parseOrientation(orientation));
163
- }
164
- }
165
-
166
- if (retainExif) {
167
- this.exif = getExif(result);
168
- }
169
-
170
- if (checkOrientation || retainExif) {
171
- if (
172
- !URL
173
-
174
- // Generate a new URL with the default orientation value (1)
175
- || orientation > 1
176
- ) {
177
- data.url = arrayBufferToDataURL(result, mimeType);
178
- } else {
179
- this.url = URL.createObjectURL(file);
180
- data.url = this.url;
181
- }
182
- } else {
183
- data.url = result;
184
- }
185
-
186
- this.load(data);
147
+ if (this.aborted) return;
148
+
149
+ // Normalize EXIF orientation to 1 before extracting, since the browser
150
+ // handles rotation natively via `image-orientation: from-image`
151
+ resetOrientation(target.result);
152
+ this.exif = getExif(target.result);
153
+ this.url = URL.createObjectURL(file);
154
+ this.load({
155
+ url: this.url,
156
+ });
187
157
  };
188
158
  reader.onabort = () => {
189
159
  this.fail(new Error('Aborted to read the image with FileReader.'));
@@ -194,12 +164,7 @@ export default class Compressor {
194
164
  reader.onloadend = () => {
195
165
  this.reader = null;
196
166
  };
197
-
198
- if (checkOrientation || retainExif) {
199
- reader.readAsArrayBuffer(file);
200
- } else {
201
- reader.readAsDataURL(file);
202
- }
167
+ reader.readAsArrayBuffer(file);
203
168
  }
204
169
  }
205
170
 
@@ -231,17 +196,10 @@ export default class Compressor {
231
196
  image.src = data.url;
232
197
  }
233
198
 
234
- draw({
235
- naturalWidth,
236
- naturalHeight,
237
- rotate = 0,
238
- scaleX = 1,
239
- scaleY = 1,
240
- }) {
199
+ draw({ naturalWidth, naturalHeight }) {
241
200
  const { file, image, options } = this;
242
201
  const canvas = document.createElement('canvas');
243
202
  const context = canvas.getContext('2d');
244
- const is90DegreesRotated = Math.abs(rotate) % 180 === 90;
245
203
  const resizable = (options.resize === 'contain' || options.resize === 'cover') && isPositiveNumber(options.width) && isPositiveNumber(options.height);
246
204
  let maxWidth = Math.max(options.maxWidth, 0) || Infinity;
247
205
  let maxHeight = Math.max(options.maxHeight, 0) || Infinity;
@@ -250,12 +208,6 @@ export default class Compressor {
250
208
  let aspectRatio = naturalWidth / naturalHeight;
251
209
  let { width, height } = options;
252
210
 
253
- if (is90DegreesRotated) {
254
- [maxWidth, maxHeight] = [maxHeight, maxWidth];
255
- [minWidth, minHeight] = [minHeight, minWidth];
256
- [width, height] = [height, width];
257
- }
258
-
259
211
  if (resizable) {
260
212
  aspectRatio = width / height;
261
213
  }
@@ -288,10 +240,6 @@ export default class Compressor {
288
240
  width = Math.floor(normalizeDecimalNumber(Math.min(Math.max(width, minWidth), maxWidth)));
289
241
  height = Math.floor(normalizeDecimalNumber(Math.min(Math.max(height, minHeight), maxHeight)));
290
242
 
291
- const destX = -width / 2;
292
- const destY = -height / 2;
293
- const destWidth = width;
294
- const destHeight = height;
295
243
  const params = [];
296
244
 
297
245
  if (resizable) {
@@ -309,11 +257,7 @@ export default class Compressor {
309
257
  params.push(srcX, srcY, srcWidth, srcHeight);
310
258
  }
311
259
 
312
- params.push(destX, destY, destWidth, destHeight);
313
-
314
- if (is90DegreesRotated) {
315
- [width, height] = [height, width];
316
- }
260
+ params.push(0, 0, width, height);
317
261
 
318
262
  canvas.width = width;
319
263
  canvas.height = height;
@@ -349,12 +293,7 @@ export default class Compressor {
349
293
  return;
350
294
  }
351
295
 
352
- context.save();
353
- context.translate(width / 2, height / 2);
354
- context.rotate((rotate * Math.PI) / 180);
355
- context.scale(scaleX, scaleY);
356
296
  context.drawImage(image, ...params);
357
- context.restore();
358
297
 
359
298
  if (options.drew) {
360
299
  options.drew.call(this, context, canvas);
@@ -374,13 +313,47 @@ export default class Compressor {
374
313
 
375
314
  if (blob && isJPEGImage && options.retainExif && this.exif && this.exif.length > 0) {
376
315
  const next = (arrayBuffer) => {
316
+ if (this.aborted) return;
377
317
  const withExif = insertExif(arrayBuffer, this.exif);
378
318
  done(uint8ArrayToBlob(withExif, options.mimeType));
379
319
  };
380
320
 
381
321
  if (blob.arrayBuffer) {
382
322
  blob.arrayBuffer().then(next).catch(() => {
383
- this.fail(new Error('Failed to read the compressed image with Blob.arrayBuffer().'));
323
+ if (this.aborted) return;
324
+ this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
325
+ });
326
+ } else {
327
+ const reader = new FileReader();
328
+
329
+ this.reader = reader;
330
+ reader.onload = ({ target }) => {
331
+ next(target.result);
332
+ };
333
+ reader.onabort = () => {
334
+ this.fail(new Error('Aborted to read the compressed image with FileReader.'));
335
+ };
336
+ reader.onerror = () => {
337
+ this.fail(new Error('Failed to read the compressed image with FileReader.'));
338
+ };
339
+ reader.onloadend = () => {
340
+ this.reader = null;
341
+ };
342
+ reader.readAsArrayBuffer(blob);
343
+ }
344
+ } else if (blob && isJPEGImage && !options.retainExif) {
345
+ // Strip any EXIF that may be present in the canvas output
346
+ // (most browsers strip it automatically, but WebKit preserves the
347
+ // source EXIF—this ensures consistent, privacy-safe output)
348
+ const next = (arrayBuffer) => {
349
+ if (this.aborted) return;
350
+ done(uint8ArrayToBlob(stripExif(arrayBuffer), options.mimeType));
351
+ };
352
+
353
+ if (blob.arrayBuffer) {
354
+ blob.arrayBuffer().then(next).catch(() => {
355
+ if (this.aborted) return;
356
+ this.fail(new Error('Failed to read the compressed image with `Blob.arrayBuffer()`.'));
384
357
  });
385
358
  } else {
386
359
  const reader = new FileReader();
@@ -418,6 +391,8 @@ export default class Compressor {
418
391
 
419
392
  this.revokeUrl();
420
393
 
394
+ let strictFallback = false;
395
+
421
396
  if (result) {
422
397
  // Returns original file if the result is greater than it and without size-related options
423
398
  if (
@@ -435,19 +410,23 @@ export default class Compressor {
435
410
  )
436
411
  ) {
437
412
  result = file;
413
+ strictFallback = true;
438
414
  } else {
439
415
  const date = new Date();
440
-
441
- result.lastModified = date.getTime();
442
- result.name = file.name;
416
+ let name = file.name || '';
443
417
 
444
418
  // Convert the extension to match its type
445
- if (result.name && result.type !== file.type) {
446
- result.name = result.name.replace(
419
+ if (name && result.type !== file.type) {
420
+ name = name.replace(
447
421
  REGEXP_EXTENSION,
448
422
  imageTypeToExtension(result.type),
449
423
  );
450
424
  }
425
+
426
+ result = new File([result], name, {
427
+ type: result.type,
428
+ lastModified: date.getTime(),
429
+ });
451
430
  }
452
431
  } else {
453
432
  // Returns original file if the result is null in some cases
@@ -455,6 +434,81 @@ export default class Compressor {
455
434
  result = file;
456
435
  }
457
436
 
437
+ // When strict returns the original file, it may still contain EXIF—strip it
438
+ // asynchronously so the output is consistently EXIF-free across all browsers
439
+ if (strictFallback && file.type === 'image/jpeg') {
440
+ if (file.arrayBuffer) {
441
+ file.arrayBuffer().then((arrayBuffer) => {
442
+ if (this.aborted) return;
443
+
444
+ const strippedBlob = uint8ArrayToBlob(stripExif(arrayBuffer), file.type);
445
+ const stripped = new File([strippedBlob], file.name || '', {
446
+ type: file.type,
447
+ lastModified: file.lastModified || Date.now(),
448
+ });
449
+
450
+ this.result = stripped;
451
+
452
+ if (options.success) {
453
+ options.success.call(this, stripped);
454
+ }
455
+ }).catch((err) => {
456
+ if (this.aborted) return;
457
+
458
+ console.warn(
459
+ `Compressor.js Next: Failed to strip EXIF from original file—returning original with EXIF intact${file.name ? ` [${file.name}]` : ''}${err?.message ? `: ${err.message}` : ''}`,
460
+ );
461
+
462
+ this.result = file;
463
+
464
+ if (options.success) {
465
+ options.success.call(this, file);
466
+ }
467
+ });
468
+ } else {
469
+ const reader = new FileReader();
470
+
471
+ this.reader = reader;
472
+ reader.onload = ({ target }) => {
473
+ if (this.aborted) return;
474
+
475
+ const strippedBlob = uint8ArrayToBlob(stripExif(target.result), file.type);
476
+ const stripped = new File([strippedBlob], file.name || '', {
477
+ type: file.type,
478
+ lastModified: file.lastModified || Date.now(),
479
+ });
480
+
481
+ this.result = stripped;
482
+
483
+ if (options.success) {
484
+ options.success.call(this, stripped);
485
+ }
486
+ };
487
+ reader.onabort = () => {
488
+ this.fail(new Error('Aborted to read the original file with FileReader.'));
489
+ };
490
+ reader.onerror = () => {
491
+ if (this.aborted) return;
492
+
493
+ console.warn(
494
+ `Compressor.js Next: Failed to strip EXIF from original file—returning original with EXIF intact${file.name ? ` [${file.name}]` : ''}`,
495
+ );
496
+
497
+ this.result = file;
498
+
499
+ if (options.success) {
500
+ options.success.call(this, file);
501
+ }
502
+ };
503
+ reader.onloadend = () => {
504
+ this.reader = null;
505
+ };
506
+ reader.readAsArrayBuffer(file);
507
+ }
508
+
509
+ return;
510
+ }
511
+
458
512
  this.result = result;
459
513
 
460
514
  if (options.success) {
package/src/utilities.js CHANGED
@@ -90,7 +90,7 @@ export function arrayBufferToDataURL(arrayBuffer, mimeType) {
90
90
  * @param {ArrayBuffer} arrayBuffer - The array buffer to read.
91
91
  * @returns {number} The read orientation value.
92
92
  */
93
- export function resetAndGetOrientation(arrayBuffer) {
93
+ export function resetOrientation(arrayBuffer) {
94
94
  const dataView = new DataView(arrayBuffer);
95
95
  let orientation;
96
96
 
@@ -164,63 +164,6 @@ export function resetAndGetOrientation(arrayBuffer) {
164
164
  return orientation;
165
165
  }
166
166
 
167
- /**
168
- * Parse Exif Orientation value.
169
- * @param {number} orientation - The orientation to parse.
170
- * @returns {Object} The parsed result.
171
- */
172
- export function parseOrientation(orientation) {
173
- let rotate = 0;
174
- let scaleX = 1;
175
- let scaleY = 1;
176
-
177
- switch (orientation) {
178
- // Flip horizontal
179
- case 2:
180
- scaleX = -1;
181
- break;
182
-
183
- // Rotate left 180°
184
- case 3:
185
- rotate = -180;
186
- break;
187
-
188
- // Flip vertical
189
- case 4:
190
- scaleY = -1;
191
- break;
192
-
193
- // Flip vertical and rotate right 90°
194
- case 5:
195
- rotate = 90;
196
- scaleY = -1;
197
- break;
198
-
199
- // Rotate right 90°
200
- case 6:
201
- rotate = 90;
202
- break;
203
-
204
- // Flip horizontal and rotate right 90°
205
- case 7:
206
- rotate = 90;
207
- scaleX = -1;
208
- break;
209
-
210
- // Rotate left 90°
211
- case 8:
212
- rotate = -90;
213
- break;
214
-
215
- default:
216
- }
217
-
218
- return {
219
- rotate,
220
- scaleX,
221
- scaleY,
222
- };
223
- }
224
167
 
225
168
  let cachedCanvasReliable;
226
169
 
package/types/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  declare namespace Compressor {
2
2
  export interface Options {
3
3
  strict?: boolean;
4
- checkOrientation?: boolean;
5
4
  retainExif?: boolean;
6
5
  maxWidth?: number;
7
6
  maxHeight?: number;
File without changes