compressorjs-next 1.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/LICENSE +22 -0
- package/README.md +335 -0
- package/dist/compressor.common.js +866 -0
- package/dist/compressor.esm.js +864 -0
- package/dist/compressor.js +872 -0
- package/dist/compressor.min.js +10 -0
- package/package.json +94 -0
- package/src/constants.js +3 -0
- package/src/defaults.js +142 -0
- package/src/index.js +445 -0
- package/src/utilities.js +363 -0
- package/types/index.d.ts +32 -0
package/src/utilities.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WINDOW,
|
|
3
|
+
} from './constants';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if the given value is a positive number.
|
|
7
|
+
* @param {*} value - The value to check.
|
|
8
|
+
* @returns {boolean} Returns `true` if the given value is a positive number, else `false`.
|
|
9
|
+
*/
|
|
10
|
+
export const isPositiveNumber = (value) => value > 0 && value < Infinity;
|
|
11
|
+
|
|
12
|
+
const REGEXP_IMAGE_TYPE = /^image\/.+$/;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if the given value is a mime type of image.
|
|
16
|
+
* @param {*} value - The value to check.
|
|
17
|
+
* @returns {boolean} Returns `true` if the given is a mime type of image, else `false`.
|
|
18
|
+
*/
|
|
19
|
+
export function isImageType(value) {
|
|
20
|
+
return REGEXP_IMAGE_TYPE.test(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert image type to extension.
|
|
25
|
+
* @param {string} value - The image type to convert.
|
|
26
|
+
* @returns {boolean} Returns the image extension.
|
|
27
|
+
*/
|
|
28
|
+
export function imageTypeToExtension(value) {
|
|
29
|
+
let extension = isImageType(value) ? value.slice(6) : '';
|
|
30
|
+
|
|
31
|
+
if (extension === 'jpeg') {
|
|
32
|
+
extension = 'jpg';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return `.${extension}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { fromCharCode } = String;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get string from char code in data view.
|
|
42
|
+
* @param {DataView} dataView - The data view for read.
|
|
43
|
+
* @param {number} start - The start index.
|
|
44
|
+
* @param {number} length - The read length.
|
|
45
|
+
* @returns {string} The read result.
|
|
46
|
+
*/
|
|
47
|
+
function getStringFromCharCode(dataView, start, length) {
|
|
48
|
+
let str = '';
|
|
49
|
+
let i;
|
|
50
|
+
|
|
51
|
+
length += start;
|
|
52
|
+
|
|
53
|
+
for (i = start; i < length; i += 1) {
|
|
54
|
+
str += fromCharCode(dataView.getUint8(i));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return str;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { btoa } = WINDOW;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Transform array buffer to Data URL.
|
|
64
|
+
* @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
|
|
65
|
+
* @param {string} mimeType - The mime type of the Data URL.
|
|
66
|
+
* @returns {string} The result Data URL.
|
|
67
|
+
*/
|
|
68
|
+
export function arrayBufferToDataURL(arrayBuffer, mimeType) {
|
|
69
|
+
const uint8 = new Uint8Array(arrayBuffer);
|
|
70
|
+
const { length } = uint8;
|
|
71
|
+
const chunkSize = 8192;
|
|
72
|
+
let binary = '';
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < length; i += chunkSize) {
|
|
75
|
+
const end = Math.min(i + chunkSize, length);
|
|
76
|
+
let chunk = '';
|
|
77
|
+
|
|
78
|
+
for (let j = i; j < end; j += 1) {
|
|
79
|
+
chunk += fromCharCode(uint8[j]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
binary += chunk;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return `data:${mimeType};base64,${btoa(binary)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get orientation value from given array buffer.
|
|
90
|
+
* @param {ArrayBuffer} arrayBuffer - The array buffer to read.
|
|
91
|
+
* @returns {number} The read orientation value.
|
|
92
|
+
*/
|
|
93
|
+
export function resetAndGetOrientation(arrayBuffer) {
|
|
94
|
+
const dataView = new DataView(arrayBuffer);
|
|
95
|
+
let orientation;
|
|
96
|
+
|
|
97
|
+
// Ignores range error when the image does not have correct Exif information
|
|
98
|
+
try {
|
|
99
|
+
let littleEndian;
|
|
100
|
+
let app1Start;
|
|
101
|
+
let ifdStart;
|
|
102
|
+
|
|
103
|
+
// Only handle JPEG image (start by 0xFFD8)
|
|
104
|
+
if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
|
|
105
|
+
const length = dataView.byteLength;
|
|
106
|
+
let offset = 2;
|
|
107
|
+
|
|
108
|
+
while (offset + 1 < length) {
|
|
109
|
+
if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
|
|
110
|
+
app1Start = offset;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
offset += 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (app1Start) {
|
|
119
|
+
const exifIDCode = app1Start + 4;
|
|
120
|
+
const tiffOffset = app1Start + 10;
|
|
121
|
+
|
|
122
|
+
if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
|
|
123
|
+
const endianness = dataView.getUint16(tiffOffset);
|
|
124
|
+
|
|
125
|
+
littleEndian = endianness === 0x4949;
|
|
126
|
+
|
|
127
|
+
if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
|
|
128
|
+
if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
|
|
129
|
+
const firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
|
|
130
|
+
|
|
131
|
+
if (firstIFDOffset >= 0x00000008) {
|
|
132
|
+
ifdStart = tiffOffset + firstIFDOffset;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (ifdStart) {
|
|
140
|
+
const length = dataView.getUint16(ifdStart, littleEndian);
|
|
141
|
+
let offset;
|
|
142
|
+
let i;
|
|
143
|
+
|
|
144
|
+
for (i = 0; i < length; i += 1) {
|
|
145
|
+
offset = ifdStart + (i * 12) + 2;
|
|
146
|
+
|
|
147
|
+
if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
|
|
148
|
+
// 8 is the offset of the current tag's value
|
|
149
|
+
offset += 8;
|
|
150
|
+
|
|
151
|
+
// Get the original orientation value
|
|
152
|
+
orientation = dataView.getUint16(offset, littleEndian);
|
|
153
|
+
|
|
154
|
+
// Override the orientation with its default value
|
|
155
|
+
dataView.setUint16(offset, 1, littleEndian);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
orientation = 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return orientation;
|
|
165
|
+
}
|
|
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
|
+
|
|
225
|
+
const REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Normalize decimal number.
|
|
229
|
+
* Check out {@link https://0.30000000000000004.com/}
|
|
230
|
+
* @param {number} value - The value to normalize.
|
|
231
|
+
* @param {number} [times=100000000000] - The times for normalizing.
|
|
232
|
+
* @returns {number} Returns the normalized number.
|
|
233
|
+
*/
|
|
234
|
+
export function normalizeDecimalNumber(value, times = 100000000000) {
|
|
235
|
+
return REGEXP_DECIMALS.test(value) ? (Math.round(value * times) / times) : value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get the max sizes in a rectangle under the given aspect ratio.
|
|
240
|
+
* @param {Object} data - The original sizes.
|
|
241
|
+
* @param {string} [type='contain'] - The adjust type.
|
|
242
|
+
* @returns {Object} The result sizes.
|
|
243
|
+
*/
|
|
244
|
+
export function getAdjustedSizes(
|
|
245
|
+
{
|
|
246
|
+
aspectRatio,
|
|
247
|
+
height,
|
|
248
|
+
width,
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// `none` | `contain` | `cover`
|
|
252
|
+
type = 'none',
|
|
253
|
+
) {
|
|
254
|
+
const isValidWidth = isPositiveNumber(width);
|
|
255
|
+
const isValidHeight = isPositiveNumber(height);
|
|
256
|
+
|
|
257
|
+
if (isValidWidth && isValidHeight) {
|
|
258
|
+
const adjustedWidth = height * aspectRatio;
|
|
259
|
+
|
|
260
|
+
if (((type === 'contain' || type === 'none') && adjustedWidth > width) || (type === 'cover' && adjustedWidth < width)) {
|
|
261
|
+
height = width / aspectRatio;
|
|
262
|
+
} else {
|
|
263
|
+
width = height * aspectRatio;
|
|
264
|
+
}
|
|
265
|
+
} else if (isValidWidth) {
|
|
266
|
+
height = width / aspectRatio;
|
|
267
|
+
} else if (isValidHeight) {
|
|
268
|
+
width = height * aspectRatio;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
width,
|
|
273
|
+
height,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get Exif information from the given array buffer.
|
|
279
|
+
* @param {ArrayBuffer} arrayBuffer - The array buffer to read.
|
|
280
|
+
* @returns {Array} The read Exif information.
|
|
281
|
+
*/
|
|
282
|
+
export function getExif(arrayBuffer) {
|
|
283
|
+
const dataView = new DataView(arrayBuffer);
|
|
284
|
+
const { byteLength } = dataView;
|
|
285
|
+
const exifArray = [];
|
|
286
|
+
let start = 0;
|
|
287
|
+
|
|
288
|
+
while (start + 3 < byteLength) {
|
|
289
|
+
const value = dataView.getUint8(start);
|
|
290
|
+
const next = dataView.getUint8(start + 1);
|
|
291
|
+
|
|
292
|
+
// SOS (Start of Scan)
|
|
293
|
+
if (value === 0xFF && next === 0xDA) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// SOI (Start of Image)
|
|
298
|
+
if (value === 0xFF && next === 0xD8) {
|
|
299
|
+
start += 2;
|
|
300
|
+
} else {
|
|
301
|
+
const segmentLength = dataView.getUint16(start + 2);
|
|
302
|
+
const end = start + segmentLength + 2;
|
|
303
|
+
|
|
304
|
+
// APP1 marker (EXIF)
|
|
305
|
+
if (value === 0xFF && next === 0xE1) {
|
|
306
|
+
for (let i = start; i < end && i < byteLength; i += 1) {
|
|
307
|
+
exifArray.push(dataView.getUint8(i));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
start = end;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return exifArray;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Insert Exif information into the given array buffer.
|
|
320
|
+
* @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
|
|
321
|
+
* @param {Array} exifArray - The Exif information to insert.
|
|
322
|
+
* @returns {Uint8Array} The transformed array as Uint8Array.
|
|
323
|
+
*/
|
|
324
|
+
export function insertExif(arrayBuffer, exifArray) {
|
|
325
|
+
const dataView = new DataView(arrayBuffer);
|
|
326
|
+
const uint8 = new Uint8Array(arrayBuffer);
|
|
327
|
+
|
|
328
|
+
// Check for APP0 marker (JFIF)
|
|
329
|
+
if (dataView.getUint8(2) !== 0xFF || dataView.getUint8(3) !== 0xE0) {
|
|
330
|
+
return uint8;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const app0Length = dataView.getUint16(4);
|
|
334
|
+
const restStart = 4 + app0Length;
|
|
335
|
+
const restLength = uint8.byteLength - restStart;
|
|
336
|
+
|
|
337
|
+
// Create new buffer: SOI (2) + EXIF + rest of image
|
|
338
|
+
const result = new Uint8Array(2 + exifArray.length + restLength);
|
|
339
|
+
|
|
340
|
+
// SOI marker
|
|
341
|
+
result[0] = 0xFF;
|
|
342
|
+
result[1] = 0xD8;
|
|
343
|
+
|
|
344
|
+
// EXIF data
|
|
345
|
+
for (let i = 0; i < exifArray.length; i += 1) {
|
|
346
|
+
result[2 + i] = exifArray[i];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Rest of image (skip SOI and APP0)
|
|
350
|
+
result.set(uint8.subarray(restStart), 2 + exifArray.length);
|
|
351
|
+
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Convert a Uint8Array to a Blob.
|
|
357
|
+
* @param {Uint8Array} uint8Array - The Uint8Array to convert.
|
|
358
|
+
* @param {string} mimeType - The mime type of the Blob.
|
|
359
|
+
* @returns {Blob} The resulting Blob.
|
|
360
|
+
*/
|
|
361
|
+
export function uint8ArrayToBlob(uint8Array, mimeType) {
|
|
362
|
+
return new Blob([uint8Array], { type: mimeType });
|
|
363
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
declare namespace Compressor {
|
|
2
|
+
export interface Options {
|
|
3
|
+
strict?: boolean;
|
|
4
|
+
checkOrientation?: boolean;
|
|
5
|
+
retainExif?: boolean;
|
|
6
|
+
maxWidth?: number;
|
|
7
|
+
maxHeight?: number;
|
|
8
|
+
minWidth?: number;
|
|
9
|
+
minHeight?: number;
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
resize?: 'contain' | 'cover' | 'none';
|
|
13
|
+
quality?: number;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
convertTypes?: string | string[];
|
|
16
|
+
convertSize?: number;
|
|
17
|
+
beforeDraw?(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void;
|
|
18
|
+
drew?(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void;
|
|
19
|
+
success?(file: File | Blob): void;
|
|
20
|
+
error?(error: Error): void;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare class Compressor {
|
|
25
|
+
constructor(file: File | Blob, options?: Compressor.Options);
|
|
26
|
+
abort(): void;
|
|
27
|
+
static setDefaults(options: Compressor.Options): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare module 'compressorjs-next' {
|
|
31
|
+
export default Compressor;
|
|
32
|
+
}
|