UNPKG

9.03 kBJavaScriptView Raw
1// a canvas lib to compress or crop images
2
3const isNumber = num => (typeof num === 'number');
4const imageReg = /[./](png|jpeg|jpg|gif|bmp)/;
5
6const defaultConfig = {
7 ratio: 1,
8 compress: 80,
9 enableWebWorker: false,
10};
11
12
13module.exports = {
14 setConfig(config) {
15 this._config = Object.assign(defaultConfig, config)
16 },
17
18 /**
19 * init image for reset size and rotation
20 */
21 init(src, callback) {
22 const scrTypes = src.split(';');
23 let srcType = null;
24 const image = this._createImage(src);
25 if (scrTypes.length > 1) {
26 srcType = scrTypes[0].replace('data:', '');
27 }
28 image.onload = () => {
29 const cvs = this._getCanvas(image.naturalWidth, image.naturalHeight);
30 const ctx = cvs.getContext('2d');
31 ctx.drawImage(image, 0, 0);
32 const newImageData = cvs.toDataURL(srcType);
33 callback(newImageData);
34 };
35 },
36
37 /**
38 * encode image to base64
39 * @param {Element|String} el
40 * @param {Function} callback
41 */
42 base64(el, callback) {
43 const { src, type } = this._getSrc(el);
44 if (type === 'file') {
45 return this._readFile(src, callback);
46 } else if (type === 'video') {
47 const video = el;
48 const cvs = this._getCanvas(video.videoWidth, video.videoHeight);
49 const ctx = cvs.getContext('2d');
50 ctx.drawImage(video, 0, 0);
51 const newImageData = cvs.toDataURL();
52 callback(newImageData, cvs);
53 }
54 return this.init(src, callback);
55 },
56
57 /**
58 * compress image
59 * @param {el|String} src the source of image
60 * @param {Number} the quality of image ( 100 = the highest quality)
61 * @param {Function} callback
62 */
63 compress(source, quality, callback) {
64 const { src, type } = this._getSrc(source);
65 if (type === 'file') {
66 return this._readFile(src, (data) => {
67 this._compress(data, source, quality, callback);
68 });
69 }
70 this._compress(src, source, quality, callback);
71 },
72
73 _compress(src, source, quality, callback) {
74 this._loadImage(src, (image) => {
75 const mimeType = this._getImageType(source);
76 const cvs = this._getCanvas(image.naturalWidth, image.naturalHeight);
77 const ctx = cvs.getContext('2d');
78 ctx.drawImage(image, 0, 0);
79 const newImageData = cvs.toDataURL(mimeType, quality / 100);
80 callback(newImageData);
81 });
82 },
83
84 /**
85 * crop image via canvas and generate data
86 */
87 crop(source, options, callback) {
88 const { src, type } = this._getSrc(source);
89 if (type === 'file') {
90 return this._readFile(src, (data) => {
91 this._crop(data, source, options, callback);
92 })
93 }
94 this._crop(src, source, options, callback);
95 },
96
97 _crop(src, source, options, callback) {
98 this._loadImage(src, (image) => {
99 // alias w and h props
100 if (!options.w && options.width) {
101 options.w = options.width;
102 options.h = options.height;
103 }
104
105 // check crop options
106 if (isNumber(options.x) &&
107 isNumber(options.y) &&
108 options.w > 0 &&
109 options.h > 0) {
110 let { w, h } = options;
111 if (options.maxWidth && options.maxWidth < w) {
112 w = options.maxWidth;
113 h = (options.h * w) / options.w;
114 }
115 if (options.maxHeight && options.maxHeight < h) {
116 h = options.maxHeight;
117 }
118 if (options.fixedWidth && options.fixedHeight) {
119 w = options.fixedWidth;
120 h = options.fixedHeight;
121 }
122 const cvs = this._getCanvas(w, h);
123 cvs.getContext('2d').drawImage(image, options.x, options.y, options.w, options.h, 0, 0, w, h);
124 const mimeType = this._getImageType(source);
125 const data = cvs.toDataURL(mimeType, options.compress / 100);
126 callback(data);
127 }
128 });
129 },
130
131 resize(source, ratio, callback) {
132 const { src, type } = this._getSrc(source);
133 let options = {};
134 if (typeof ratio === 'number' || typeof ratio === 'string') {
135 options = {
136 ratio,
137 compress: defaultConfig.compress,
138 };
139 }
140 if (typeof ratio === 'object') {
141 options = ratio;
142 }
143 if (type === 'file') {
144 return this._readFile(src, (data) => {
145 this._resize(data, source, options, callback);
146 });
147 }
148 this._resize(src, source, options, callback);
149 },
150
151 _resize(src, source, options, callback) {
152 function isNeedCrop(w, h, ow, oh) {
153 return (w / h) === (ow / oh);
154 }
155 this._loadImage(src, (image) => {
156 let w = image.naturalWidth;
157 let h = image.naturalHeight;
158 const aspect = w / h;
159 if (options.ratio > 0) {
160 w = Math.floor(image.naturalWidth * options.ratio);
161 h = Math.floor(image.naturalHeight * options.ratio);
162 } else if (typeof options.width === 'number' && typeof options.height === 'number') {
163 if (!isNeedCrop(options.width, options.height, w, h)) {
164 w = Math.floor(options.width);
165 h = Math.floor(options.height);
166 } else {
167 if (w > options.width || h > options.height) {
168 options.x = (w - options.width) / 2;
169 options.y = (h - options.height) / 2;
170 return this._crop(src, source, options, callback);
171 } else if (w < options.width) {
172 w = options.width;
173 h = w / aspect;
174 options.x = 0;
175 options.y = (h - options.height) / 2;
176 return this._crop(src, source, options, callback);
177 } else if (h < options.height) {
178 h = options.height;
179 w = h * aspect;
180 options.y = 0;
181 options.x = (w - options.width) / 2;
182 return this._crop(src, source, options, callback);
183 }
184 }
185 }
186 const cvs = this._getCanvas(w, h);
187 cvs.getContext('2d').drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight, 0, 0, w, h);
188 const mimeType = this._getImageType(source);
189 const data = cvs.toDataURL(mimeType, options.compress / 100);
190 callback(data);
191 });
192 },
193
194 /**
195 * rotate image
196 */
197 rotate(source, degree, callback) {
198 const { src, type } = this._getSrc(source);
199 if (type === 'file') {
200 return this._readFile(src, () => {
201 this._rotate(src, source, degree, callback);
202 });
203 }
204 if (degree % 360 === 0) {
205 return callback(src);
206 }
207 this._rotate(src, source, degree, callback);
208 },
209
210 _rotate(src, source, degree, callback) {
211 this._loadImage(src, (image) => {
212 let w = image.naturalWidth;
213 let h = image.naturalHeight;
214 degree %= 360;
215 if (degree === 90 || degree === 270) {
216 w = image.naturalHeight;
217 h = image.naturalWidth;
218 }
219 let cvs = this._getCanvas(w, h);
220 let ctx = cvs.getContext('2d');
221 ctx.clearRect(0, 0, w, h);
222 ctx.fillStyle = 'white';
223 ctx.fillRect(0, 0, w, h);
224 ctx.translate(w / 2, h / 2);
225 ctx.rotate((degree * Math.PI) / 180);
226 ctx.drawImage(image, -image.naturalWidth / 2, -image.naturalHeight / 2);
227 const mimeType = this._getImageType(source);
228 const data = cvs.toDataURL(mimeType, 1);
229 callback(data, w, h);
230 cvs = null;
231 ctx = null;
232 });
233 },
234
235 _loadImage(src, callback) {
236 const image = this._createImage(src);
237 image.onload = () => {
238 callback(image);
239 };
240 },
241
242 _readFile(file, callback) {
243 const reader = new FileReader();
244 reader.onload = (event) => {
245 const data = event.target.result;
246 callback(data);
247 };
248 reader.readAsDataURL(file);
249 },
250
251 _getCanvas(width, height) {
252 const canvas = document.createElement('canvas');
253 canvas.width = width;
254 canvas.height = height;
255 return canvas;
256 },
257
258 _createImage(src) {
259 const image = new Image();
260 image.src = src;
261 image.crossOrigin = 'anonymous';
262 return image;
263 },
264
265 _getSrc(source) {
266 let src = source;
267 let type = 'url';
268 if (this._isImageElement(source)) {
269 const imgSrc = source.src;
270 if (!imgSrc) {
271 throw new Error('Element must hava src');
272 }
273 src = imgSrc;
274 type = 'element';
275 } else if (this._isVideoElement(source)) {
276 src = source;
277 type = 'video';
278 } else if (this._isFileObject(source)) {
279 src = source;
280 type = 'file';
281 }
282 return {
283 src,
284 type,
285 };
286 },
287
288 _isFileObject(file) {
289 return (typeof file === 'object' && file.type && file.size > 0);
290 },
291
292 _isImageElement(el) {
293 return (typeof el === 'object' && el.tagName === 'IMG');
294 },
295
296 _isVideoElement(el) {
297 return (typeof el === 'object' && el.tagName === 'VIDEO');
298 },
299
300 _getImageType(source) {
301 const { src, type } = this._getSrc(source);
302 let mimeType = 'image/jpeg';
303 if (type === 'file') {
304 const fileType = source.type;
305 const outputType = fileType.match(/(image\/[\w]+)\.*/)[0];
306 if (typeof outputType !== 'undefined') {
307 mimeType = outputType;
308 }
309 } else {
310 const arr = imageReg.exec(src);
311 if (arr && arr[1]) {
312 mimeType = `image/${arr[1]}`;
313 }
314 }
315 return mimeType;
316 },
317
318};