1 | /* |
---|
2 | * JavaScript Load Image 1.6 |
---|
3 | * https://github.com/blueimp/JavaScript-Load-Image |
---|
4 | * |
---|
5 | * Copyright 2011, Sebastian Tschan |
---|
6 | * https://blueimp.net |
---|
7 | * |
---|
8 | * iOS image scaling fixes based on |
---|
9 | * https://github.com/stomita/ios-imagefile-megapixel |
---|
10 | * |
---|
11 | * Licensed under the MIT license: |
---|
12 | * http://www.opensource.org/licenses/MIT |
---|
13 | */ |
---|
14 | |
---|
15 | /*jslint nomen: true, bitwise: true */ |
---|
16 | /*global window, document, URL, webkitURL, Blob, File, FileReader, define */ |
---|
17 | |
---|
18 | (function ($) { |
---|
19 | 'use strict'; |
---|
20 | |
---|
21 | // Loads an image for a given File object. |
---|
22 | // Invokes the callback with an img or optional canvas |
---|
23 | // element (if supported by the browser) as parameter: |
---|
24 | var loadImage = function (file, callback, options) { |
---|
25 | var img = document.createElement('img'), |
---|
26 | url, |
---|
27 | oUrl; |
---|
28 | img.onerror = callback; |
---|
29 | img.onload = function () { |
---|
30 | if (oUrl && !(options && options.noRevoke)) { |
---|
31 | loadImage.revokeObjectURL(oUrl); |
---|
32 | } |
---|
33 | if (callback) { |
---|
34 | callback(loadImage.scale(img, options)); |
---|
35 | } |
---|
36 | }; |
---|
37 | if (loadImage.isInstanceOf('Blob', file) || |
---|
38 | // Files are also Blob instances, but some browsers |
---|
39 | // (Firefox 3.6) support the File API but not Blobs: |
---|
40 | loadImage.isInstanceOf('File', file)) { |
---|
41 | url = oUrl = loadImage.createObjectURL(file); |
---|
42 | // Store the file type for resize processing: |
---|
43 | img._type = file.type; |
---|
44 | } else if (typeof file === 'string') { |
---|
45 | url = file; |
---|
46 | if (options && options.crossOrigin) { |
---|
47 | img.crossOrigin = options.crossOrigin; |
---|
48 | } |
---|
49 | } else { |
---|
50 | return false; |
---|
51 | } |
---|
52 | if (url) { |
---|
53 | img.src = url; |
---|
54 | return img; |
---|
55 | } |
---|
56 | return loadImage.readFile(file, function (e) { |
---|
57 | var target = e.target; |
---|
58 | if (target && target.result) { |
---|
59 | img.src = target.result; |
---|
60 | } else { |
---|
61 | if (callback) { |
---|
62 | callback(e); |
---|
63 | } |
---|
64 | } |
---|
65 | }); |
---|
66 | }, |
---|
67 | // The check for URL.revokeObjectURL fixes an issue with Opera 12, |
---|
68 | // which provides URL.createObjectURL but doesn't properly implement it: |
---|
69 | urlAPI = (window.createObjectURL && window) || |
---|
70 | (window.URL && URL.revokeObjectURL && URL) || |
---|
71 | (window.webkitURL && webkitURL); |
---|
72 | |
---|
73 | loadImage.isInstanceOf = function (type, obj) { |
---|
74 | // Cross-frame instanceof check |
---|
75 | return Object.prototype.toString.call(obj) === '[object ' + type + ']'; |
---|
76 | }; |
---|
77 | |
---|
78 | // Transform image orientation based on the given EXIF orientation data: |
---|
79 | loadImage.transformCoordinates = function (canvas, orientation) { |
---|
80 | var ctx = canvas.getContext('2d'), |
---|
81 | width = canvas.width, |
---|
82 | height = canvas.height; |
---|
83 | if (orientation > 4) { |
---|
84 | canvas.width = height; |
---|
85 | canvas.height = width; |
---|
86 | } |
---|
87 | switch (orientation) { |
---|
88 | case 2: |
---|
89 | // horizontal flip |
---|
90 | ctx.translate(width, 0); |
---|
91 | ctx.scale(-1, 1); |
---|
92 | break; |
---|
93 | case 3: |
---|
94 | // 180 rotate left |
---|
95 | ctx.translate(width, height); |
---|
96 | ctx.rotate(Math.PI); |
---|
97 | break; |
---|
98 | case 4: |
---|
99 | // vertical flip |
---|
100 | ctx.translate(0, height); |
---|
101 | ctx.scale(1, -1); |
---|
102 | break; |
---|
103 | case 5: |
---|
104 | // vertical flip + 90 rotate right |
---|
105 | ctx.rotate(0.5 * Math.PI); |
---|
106 | ctx.scale(1, -1); |
---|
107 | break; |
---|
108 | case 6: |
---|
109 | // 90 rotate right |
---|
110 | ctx.rotate(0.5 * Math.PI); |
---|
111 | ctx.translate(0, -height); |
---|
112 | break; |
---|
113 | case 7: |
---|
114 | // horizontal flip + 90 rotate right |
---|
115 | ctx.rotate(0.5 * Math.PI); |
---|
116 | ctx.translate(width, -height); |
---|
117 | ctx.scale(-1, 1); |
---|
118 | break; |
---|
119 | case 8: |
---|
120 | // 90 rotate left |
---|
121 | ctx.rotate(-0.5 * Math.PI); |
---|
122 | ctx.translate(-width, 0); |
---|
123 | break; |
---|
124 | } |
---|
125 | }; |
---|
126 | |
---|
127 | // Detects subsampling in JPEG images: |
---|
128 | loadImage.detectSubsampling = function (img) { |
---|
129 | var canvas, |
---|
130 | context; |
---|
131 | if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images |
---|
132 | canvas = document.createElement('canvas'); |
---|
133 | canvas.width = canvas.height = 1; |
---|
134 | context = canvas.getContext('2d'); |
---|
135 | context.drawImage(img, -img.width + 1, 0); |
---|
136 | // subsampled image becomes half smaller in rendering size. |
---|
137 | // check alpha channel value to confirm image is covering edge pixel or not. |
---|
138 | // if alpha value is 0 image is not covering, hence subsampled. |
---|
139 | return context.getImageData(0, 0, 1, 1).data[3] === 0; |
---|
140 | } |
---|
141 | return false; |
---|
142 | }; |
---|
143 | |
---|
144 | // Detects vertical squash in JPEG images: |
---|
145 | loadImage.detectVerticalSquash = function (img, correctedHeight) { |
---|
146 | var canvas = document.createElement('canvas'), |
---|
147 | context = canvas.getContext('2d'), |
---|
148 | data, |
---|
149 | sy, |
---|
150 | ey, |
---|
151 | py, |
---|
152 | alpha; |
---|
153 | canvas.width = 1; |
---|
154 | canvas.height = correctedHeight; |
---|
155 | context.drawImage(img, 0, 0); |
---|
156 | data = context.getImageData(0, 0, 1, correctedHeight).data; |
---|
157 | // search image edge pixel position in case it is squashed vertically: |
---|
158 | sy = 0; |
---|
159 | ey = correctedHeight; |
---|
160 | py = correctedHeight; |
---|
161 | while (py > sy) { |
---|
162 | alpha = data[(py - 1) * 4 + 3]; |
---|
163 | if (alpha === 0) { |
---|
164 | ey = py; |
---|
165 | } else { |
---|
166 | sy = py; |
---|
167 | } |
---|
168 | py = (ey + sy) >> 1; |
---|
169 | } |
---|
170 | return (py / correctedHeight) || 1; |
---|
171 | }; |
---|
172 | |
---|
173 | // Renders image to canvas while working around iOS image scaling bugs: |
---|
174 | // https://github.com/blueimp/JavaScript-Load-Image/issues/13 |
---|
175 | loadImage.renderImageToCanvas = function ( |
---|
176 | canvas, |
---|
177 | img, |
---|
178 | sourceX, |
---|
179 | sourceY, |
---|
180 | sourceWidth, |
---|
181 | sourceHeight, |
---|
182 | destX, |
---|
183 | destY, |
---|
184 | destWidth, |
---|
185 | destHeight |
---|
186 | ) { |
---|
187 | var context = canvas.getContext('2d'), |
---|
188 | tmpCanvas = document.createElement('canvas'), |
---|
189 | tileSize = tmpCanvas.width = tmpCanvas.height = 1024, |
---|
190 | tmpContext = tmpCanvas.getContext('2d'), |
---|
191 | vertSquashRatio, |
---|
192 | tileX, |
---|
193 | tileY; |
---|
194 | context.save(); |
---|
195 | if (loadImage.detectSubsampling(img)) { |
---|
196 | sourceWidth /= 2; |
---|
197 | sourceHeight /= 2; |
---|
198 | } |
---|
199 | vertSquashRatio = loadImage.detectVerticalSquash(img, sourceHeight); |
---|
200 | destWidth = Math.ceil(tileSize * destWidth / sourceWidth); |
---|
201 | destHeight = Math.ceil( |
---|
202 | tileSize * destHeight / sourceHeight / vertSquashRatio |
---|
203 | ); |
---|
204 | destY = 0; |
---|
205 | tileY = 0; |
---|
206 | while (tileY < sourceHeight) { |
---|
207 | destX = 0; |
---|
208 | tileX = 0; |
---|
209 | while (tileX < sourceWidth) { |
---|
210 | tmpContext.clearRect(0, 0, tileSize, tileSize); |
---|
211 | tmpContext.drawImage( |
---|
212 | img, |
---|
213 | sourceX, |
---|
214 | sourceY, |
---|
215 | sourceWidth, |
---|
216 | sourceHeight, |
---|
217 | -tileX, |
---|
218 | -tileY, |
---|
219 | sourceWidth, |
---|
220 | sourceHeight |
---|
221 | ); |
---|
222 | context.drawImage( |
---|
223 | tmpCanvas, |
---|
224 | 0, |
---|
225 | 0, |
---|
226 | tileSize, |
---|
227 | tileSize, |
---|
228 | destX, |
---|
229 | destY, |
---|
230 | destWidth, |
---|
231 | destHeight |
---|
232 | ); |
---|
233 | tileX += tileSize; |
---|
234 | destX += destWidth; |
---|
235 | } |
---|
236 | tileY += tileSize; |
---|
237 | destY += destHeight; |
---|
238 | } |
---|
239 | context.restore(); |
---|
240 | }; |
---|
241 | |
---|
242 | // Scales the given image (img or canvas HTML element) |
---|
243 | // using the given options. |
---|
244 | // Returns a canvas object if the browser supports canvas |
---|
245 | // and the canvas or crop option is true or a canvas object |
---|
246 | // is passed as image, else the scaled image: |
---|
247 | loadImage.scale = function (img, options) { |
---|
248 | options = options || {}; |
---|
249 | var canvas = document.createElement('canvas'), |
---|
250 | useCanvas = img.getContext || |
---|
251 | ((options.canvas || options.crop || options.orientation) && |
---|
252 | canvas.getContext), |
---|
253 | width = img.width, |
---|
254 | height = img.height, |
---|
255 | sourceWidth = width, |
---|
256 | sourceHeight = height, |
---|
257 | sourceX = 0, |
---|
258 | sourceY = 0, |
---|
259 | destX = 0, |
---|
260 | destY = 0, |
---|
261 | maxWidth, |
---|
262 | maxHeight, |
---|
263 | minWidth, |
---|
264 | minHeight, |
---|
265 | destWidth, |
---|
266 | destHeight, |
---|
267 | scale; |
---|
268 | if (useCanvas && options.orientation > 4) { |
---|
269 | maxWidth = options.maxHeight; |
---|
270 | maxHeight = options.maxWidth; |
---|
271 | minWidth = options.minHeight; |
---|
272 | minHeight = options.minWidth; |
---|
273 | } else { |
---|
274 | maxWidth = options.maxWidth; |
---|
275 | maxHeight = options.maxHeight; |
---|
276 | minWidth = options.minWidth; |
---|
277 | minHeight = options.minHeight; |
---|
278 | } |
---|
279 | if (useCanvas && maxWidth && maxHeight && options.crop) { |
---|
280 | destWidth = maxWidth; |
---|
281 | destHeight = maxHeight; |
---|
282 | if (width / height < maxWidth / maxHeight) { |
---|
283 | sourceHeight = maxHeight * width / maxWidth; |
---|
284 | sourceY = (height - sourceHeight) / 2; |
---|
285 | } else { |
---|
286 | sourceWidth = maxWidth * height / maxHeight; |
---|
287 | sourceX = (width - sourceWidth) / 2; |
---|
288 | } |
---|
289 | } else { |
---|
290 | destWidth = width; |
---|
291 | destHeight = height; |
---|
292 | scale = Math.max( |
---|
293 | (minWidth || destWidth) / destWidth, |
---|
294 | (minHeight || destHeight) / destHeight |
---|
295 | ); |
---|
296 | if (scale > 1) { |
---|
297 | destWidth = Math.ceil(destWidth * scale); |
---|
298 | destHeight = Math.ceil(destHeight * scale); |
---|
299 | } |
---|
300 | scale = Math.min( |
---|
301 | (maxWidth || destWidth) / destWidth, |
---|
302 | (maxHeight || destHeight) / destHeight |
---|
303 | ); |
---|
304 | if (scale < 1) { |
---|
305 | destWidth = Math.ceil(destWidth * scale); |
---|
306 | destHeight = Math.ceil(destHeight * scale); |
---|
307 | } |
---|
308 | } |
---|
309 | if (useCanvas) { |
---|
310 | canvas.width = destWidth; |
---|
311 | canvas.height = destHeight; |
---|
312 | loadImage.transformCoordinates( |
---|
313 | canvas, |
---|
314 | options.orientation |
---|
315 | ); |
---|
316 | if (img._type === 'image/jpeg') { |
---|
317 | loadImage.renderImageToCanvas( |
---|
318 | canvas, |
---|
319 | img, |
---|
320 | sourceX, |
---|
321 | sourceY, |
---|
322 | sourceWidth, |
---|
323 | sourceHeight, |
---|
324 | destX, |
---|
325 | destY, |
---|
326 | destWidth, |
---|
327 | destHeight |
---|
328 | ); |
---|
329 | } else { |
---|
330 | canvas.getContext('2d').drawImage( |
---|
331 | img, |
---|
332 | sourceX, |
---|
333 | sourceY, |
---|
334 | sourceWidth, |
---|
335 | sourceHeight, |
---|
336 | destX, |
---|
337 | destY, |
---|
338 | destWidth, |
---|
339 | destHeight |
---|
340 | ); |
---|
341 | } |
---|
342 | return canvas; |
---|
343 | } |
---|
344 | img.width = destWidth; |
---|
345 | img.height = destHeight; |
---|
346 | return img; |
---|
347 | }; |
---|
348 | |
---|
349 | loadImage.createObjectURL = function (file) { |
---|
350 | return urlAPI ? urlAPI.createObjectURL(file) : false; |
---|
351 | }; |
---|
352 | |
---|
353 | loadImage.revokeObjectURL = function (url) { |
---|
354 | return urlAPI ? urlAPI.revokeObjectURL(url) : false; |
---|
355 | }; |
---|
356 | |
---|
357 | // Loads a given File object via FileReader interface, |
---|
358 | // invokes the callback with the event object (load or error). |
---|
359 | // The result can be read via event.target.result: |
---|
360 | loadImage.readFile = function (file, callback, method) { |
---|
361 | if (window.FileReader && FileReader.prototype.readAsDataURL) { |
---|
362 | var fileReader = new FileReader(); |
---|
363 | fileReader.onload = fileReader.onerror = callback; |
---|
364 | method = method || 'readAsDataURL'; |
---|
365 | if (!fileReader[method]) { |
---|
366 | return false; |
---|
367 | } |
---|
368 | fileReader[method](file); |
---|
369 | return fileReader; |
---|
370 | } |
---|
371 | return false; |
---|
372 | }; |
---|
373 | |
---|
374 | if (typeof define === 'function' && define.amd) { |
---|
375 | define(function () { |
---|
376 | return loadImage; |
---|
377 | }); |
---|
378 | } else { |
---|
379 | $.loadImage = loadImage; |
---|
380 | } |
---|
381 | }(this)); |
---|