@@ 2618-2903 (lines=286) @@ | ||
2615 | })(); |
|
2616 | ||
2617 | // customized version of https://github.com/exif-js/exif-js |
|
2618 | ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) { |
|
2619 | var upload = UploadResize; |
|
2620 | ||
2621 | upload.isExifSupported = function () { |
|
2622 | return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported(); |
|
2623 | }; |
|
2624 | ||
2625 | function applyTransform(ctx, orientation, width, height) { |
|
2626 | switch (orientation) { |
|
2627 | case 2: |
|
2628 | return ctx.transform(-1, 0, 0, 1, width, 0); |
|
2629 | case 3: |
|
2630 | return ctx.transform(-1, 0, 0, -1, width, height); |
|
2631 | case 4: |
|
2632 | return ctx.transform(1, 0, 0, -1, 0, height); |
|
2633 | case 5: |
|
2634 | return ctx.transform(0, 1, 1, 0, 0, 0); |
|
2635 | case 6: |
|
2636 | return ctx.transform(0, 1, -1, 0, height, 0); |
|
2637 | case 7: |
|
2638 | return ctx.transform(0, -1, -1, 0, height, width); |
|
2639 | case 8: |
|
2640 | return ctx.transform(0, -1, 1, 0, 0, width); |
|
2641 | } |
|
2642 | } |
|
2643 | ||
2644 | upload.readOrientation = function (file) { |
|
2645 | var defer = $q.defer(); |
|
2646 | var reader = new FileReader(); |
|
2647 | var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file; |
|
2648 | reader.readAsArrayBuffer(slicedFile); |
|
2649 | reader.onerror = function (e) { |
|
2650 | return defer.reject(e); |
|
2651 | }; |
|
2652 | reader.onload = function (e) { |
|
2653 | var result = {orientation: 1}; |
|
2654 | var view = new DataView(this.result); |
|
2655 | if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result); |
|
2656 | ||
2657 | var length = view.byteLength, |
|
2658 | offset = 2; |
|
2659 | while (offset < length) { |
|
2660 | var marker = view.getUint16(offset, false); |
|
2661 | offset += 2; |
|
2662 | if (marker === 0xFFE1) { |
|
2663 | if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result); |
|
2664 | ||
2665 | var little = view.getUint16(offset += 6, false) === 0x4949; |
|
2666 | offset += view.getUint32(offset + 4, little); |
|
2667 | var tags = view.getUint16(offset, little); |
|
2668 | offset += 2; |
|
2669 | for (var i = 0; i < tags; i++) |
|
2670 | if (view.getUint16(offset + (i * 12), little) === 0x0112) { |
|
2671 | var orientation = view.getUint16(offset + (i * 12) + 8, little); |
|
2672 | if (orientation >= 2 && orientation <= 8) { |
|
2673 | view.setUint16(offset + (i * 12) + 8, 1, little); |
|
2674 | result.fixedArrayBuffer = e.target.result; |
|
2675 | } |
|
2676 | result.orientation = orientation; |
|
2677 | return defer.resolve(result); |
|
2678 | } |
|
2679 | } else if ((marker & 0xFF00) !== 0xFF00) break; |
|
2680 | else offset += view.getUint16(offset, false); |
|
2681 | } |
|
2682 | return defer.resolve(result); |
|
2683 | }; |
|
2684 | return defer.promise; |
|
2685 | }; |
|
2686 | ||
2687 | function arrayBufferToBase64(buffer) { |
|
2688 | var binary = ''; |
|
2689 | var bytes = new Uint8Array(buffer); |
|
2690 | var len = bytes.byteLength; |
|
2691 | for (var i = 0; i < len; i++) { |
|
2692 | binary += String.fromCharCode(bytes[i]); |
|
2693 | } |
|
2694 | return window.btoa(binary); |
|
2695 | } |
|
2696 | ||
2697 | upload.applyExifRotation = function (file) { |
|
2698 | if (file.type.indexOf('image/jpeg') !== 0) { |
|
2699 | return upload.emptyPromise(file); |
|
2700 | } |
|
2701 | ||
2702 | var deferred = $q.defer(); |
|
2703 | upload.readOrientation(file).then(function (result) { |
|
2704 | if (result.orientation < 2 || result.orientation > 8) { |
|
2705 | return deferred.resolve(file); |
|
2706 | } |
|
2707 | upload.dataUrl(file, true).then(function (url) { |
|
2708 | var canvas = document.createElement('canvas'); |
|
2709 | var img = document.createElement('img'); |
|
2710 | ||
2711 | img.onload = function () { |
|
2712 | try { |
|
2713 | canvas.width = result.orientation > 4 ? img.height : img.width; |
|
2714 | canvas.height = result.orientation > 4 ? img.width : img.height; |
|
2715 | var ctx = canvas.getContext('2d'); |
|
2716 | applyTransform(ctx, result.orientation, img.width, img.height); |
|
2717 | ctx.drawImage(img, 0, 0); |
|
2718 | var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934); |
|
2719 | dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl); |
|
2720 | var blob = upload.dataUrltoBlob(dataUrl, file.name); |
|
2721 | deferred.resolve(blob); |
|
2722 | } catch (e) { |
|
2723 | return deferred.reject(e); |
|
2724 | } |
|
2725 | }; |
|
2726 | img.onerror = function () { |
|
2727 | deferred.reject(); |
|
2728 | }; |
|
2729 | img.src = url; |
|
2730 | }, function (e) { |
|
2731 | deferred.reject(e); |
|
2732 | }); |
|
2733 | }, function (e) { |
|
2734 | deferred.reject(e); |
|
2735 | }); |
|
2736 | return deferred.promise; |
|
2737 | }; |
|
2738 | ||
2739 | upload.restoreExif = function (orig, resized) { |
|
2740 | var ExifRestorer = {}; |
|
2741 | ||
2742 | ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' + |
|
2743 | 'QRSTUVWXYZabcdef' + |
|
2744 | 'ghijklmnopqrstuv' + |
|
2745 | 'wxyz0123456789+/' + |
|
2746 | '='; |
|
2747 | ||
2748 | ExifRestorer.encode64 = function (input) { |
|
2749 | var output = '', |
|
2750 | chr1, chr2, chr3 = '', |
|
2751 | enc1, enc2, enc3, enc4 = '', |
|
2752 | i = 0; |
|
2753 | ||
2754 | do { |
|
2755 | chr1 = input[i++]; |
|
2756 | chr2 = input[i++]; |
|
2757 | chr3 = input[i++]; |
|
2758 | ||
2759 | enc1 = chr1 >> 2; |
|
2760 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); |
|
2761 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); |
|
2762 | enc4 = chr3 & 63; |
|
2763 | ||
2764 | if (isNaN(chr2)) { |
|
2765 | enc3 = enc4 = 64; |
|
2766 | } else if (isNaN(chr3)) { |
|
2767 | enc4 = 64; |
|
2768 | } |
|
2769 | ||
2770 | output = output + |
|
2771 | this.KEY_STR.charAt(enc1) + |
|
2772 | this.KEY_STR.charAt(enc2) + |
|
2773 | this.KEY_STR.charAt(enc3) + |
|
2774 | this.KEY_STR.charAt(enc4); |
|
2775 | chr1 = chr2 = chr3 = ''; |
|
2776 | enc1 = enc2 = enc3 = enc4 = ''; |
|
2777 | } while (i < input.length); |
|
2778 | ||
2779 | return output; |
|
2780 | }; |
|
2781 | ||
2782 | ExifRestorer.restore = function (origFileBase64, resizedFileBase64) { |
|
2783 | if (origFileBase64.match('data:image/jpeg;base64,')) { |
|
2784 | origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', ''); |
|
2785 | } |
|
2786 | ||
2787 | var rawImage = this.decode64(origFileBase64); |
|
2788 | var segments = this.slice2Segments(rawImage); |
|
2789 | ||
2790 | var image = this.exifManipulation(resizedFileBase64, segments); |
|
2791 | ||
2792 | return 'data:image/jpeg;base64,' + this.encode64(image); |
|
2793 | }; |
|
2794 | ||
2795 | ||
2796 | ExifRestorer.exifManipulation = function (resizedFileBase64, segments) { |
|
2797 | var exifArray = this.getExifArray(segments), |
|
2798 | newImageArray = this.insertExif(resizedFileBase64, exifArray); |
|
2799 | return new Uint8Array(newImageArray); |
|
2800 | }; |
|
2801 | ||
2802 | ||
2803 | ExifRestorer.getExifArray = function (segments) { |
|
2804 | var seg; |
|
2805 | for (var x = 0; x < segments.length; x++) { |
|
2806 | seg = segments[x]; |
|
2807 | if (seg[0] === 255 & seg[1] === 225) //(ff e1) |
|
2808 | { |
|
2809 | return seg; |
|
2810 | } |
|
2811 | } |
|
2812 | return []; |
|
2813 | }; |
|
2814 | ||
2815 | ||
2816 | ExifRestorer.insertExif = function (resizedFileBase64, exifArray) { |
|
2817 | var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''), |
|
2818 | buf = this.decode64(imageData), |
|
2819 | separatePoint = buf.indexOf(255, 3), |
|
2820 | mae = buf.slice(0, separatePoint), |
|
2821 | ato = buf.slice(separatePoint), |
|
2822 | array = mae; |
|
2823 | ||
2824 | array = array.concat(exifArray); |
|
2825 | array = array.concat(ato); |
|
2826 | return array; |
|
2827 | }; |
|
2828 | ||
2829 | ||
2830 | ExifRestorer.slice2Segments = function (rawImageArray) { |
|
2831 | var head = 0, |
|
2832 | segments = []; |
|
2833 | ||
2834 | while (1) { |
|
2835 | if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) { |
|
2836 | break; |
|
2837 | } |
|
2838 | if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) { |
|
2839 | head += 2; |
|
2840 | } |
|
2841 | else { |
|
2842 | var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3], |
|
2843 | endPoint = head + length + 2, |
|
2844 | seg = rawImageArray.slice(head, endPoint); |
|
2845 | segments.push(seg); |
|
2846 | head = endPoint; |
|
2847 | } |
|
2848 | if (head > rawImageArray.length) { |
|
2849 | break; |
|
2850 | } |
|
2851 | } |
|
2852 | ||
2853 | return segments; |
|
2854 | }; |
|
2855 | ||
2856 | ||
2857 | ExifRestorer.decode64 = function (input) { |
|
2858 | var chr1, chr2, chr3 = '', |
|
2859 | enc1, enc2, enc3, enc4 = '', |
|
2860 | i = 0, |
|
2861 | buf = []; |
|
2862 | ||
2863 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = |
|
2864 | var base64test = /[^A-Za-z0-9\+\/\=]/g; |
|
2865 | if (base64test.exec(input)) { |
|
2866 | console.log('There were invalid base64 characters in the input text.\n' + |
|
2867 | 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' + |
|
2868 | 'Expect errors in decoding.'); |
|
2869 | } |
|
2870 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); |
|
2871 | ||
2872 | do { |
|
2873 | enc1 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2874 | enc2 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2875 | enc3 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2876 | enc4 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2877 | ||
2878 | chr1 = (enc1 << 2) | (enc2 >> 4); |
|
2879 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); |
|
2880 | chr3 = ((enc3 & 3) << 6) | enc4; |
|
2881 | ||
2882 | buf.push(chr1); |
|
2883 | ||
2884 | if (enc3 !== 64) { |
|
2885 | buf.push(chr2); |
|
2886 | } |
|
2887 | if (enc4 !== 64) { |
|
2888 | buf.push(chr3); |
|
2889 | } |
|
2890 | ||
2891 | chr1 = chr2 = chr3 = ''; |
|
2892 | enc1 = enc2 = enc3 = enc4 = ''; |
|
2893 | ||
2894 | } while (i < input.length); |
|
2895 | ||
2896 | return buf; |
|
2897 | }; |
|
2898 | ||
2899 | return ExifRestorer.restore(orig, resized); //<= EXIF |
|
2900 | }; |
|
2901 | ||
2902 | return upload; |
|
2903 | }]); |
|
2904 | ||
2905 |
@@ 2196-2481 (lines=286) @@ | ||
2193 | })(); |
|
2194 | ||
2195 | // customized version of https://github.com/exif-js/exif-js |
|
2196 | ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) { |
|
2197 | var upload = UploadResize; |
|
2198 | ||
2199 | upload.isExifSupported = function () { |
|
2200 | return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported(); |
|
2201 | }; |
|
2202 | ||
2203 | function applyTransform(ctx, orientation, width, height) { |
|
2204 | switch (orientation) { |
|
2205 | case 2: |
|
2206 | return ctx.transform(-1, 0, 0, 1, width, 0); |
|
2207 | case 3: |
|
2208 | return ctx.transform(-1, 0, 0, -1, width, height); |
|
2209 | case 4: |
|
2210 | return ctx.transform(1, 0, 0, -1, 0, height); |
|
2211 | case 5: |
|
2212 | return ctx.transform(0, 1, 1, 0, 0, 0); |
|
2213 | case 6: |
|
2214 | return ctx.transform(0, 1, -1, 0, height, 0); |
|
2215 | case 7: |
|
2216 | return ctx.transform(0, -1, -1, 0, height, width); |
|
2217 | case 8: |
|
2218 | return ctx.transform(0, -1, 1, 0, 0, width); |
|
2219 | } |
|
2220 | } |
|
2221 | ||
2222 | upload.readOrientation = function (file) { |
|
2223 | var defer = $q.defer(); |
|
2224 | var reader = new FileReader(); |
|
2225 | var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file; |
|
2226 | reader.readAsArrayBuffer(slicedFile); |
|
2227 | reader.onerror = function (e) { |
|
2228 | return defer.reject(e); |
|
2229 | }; |
|
2230 | reader.onload = function (e) { |
|
2231 | var result = {orientation: 1}; |
|
2232 | var view = new DataView(this.result); |
|
2233 | if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result); |
|
2234 | ||
2235 | var length = view.byteLength, |
|
2236 | offset = 2; |
|
2237 | while (offset < length) { |
|
2238 | var marker = view.getUint16(offset, false); |
|
2239 | offset += 2; |
|
2240 | if (marker === 0xFFE1) { |
|
2241 | if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result); |
|
2242 | ||
2243 | var little = view.getUint16(offset += 6, false) === 0x4949; |
|
2244 | offset += view.getUint32(offset + 4, little); |
|
2245 | var tags = view.getUint16(offset, little); |
|
2246 | offset += 2; |
|
2247 | for (var i = 0; i < tags; i++) |
|
2248 | if (view.getUint16(offset + (i * 12), little) === 0x0112) { |
|
2249 | var orientation = view.getUint16(offset + (i * 12) + 8, little); |
|
2250 | if (orientation >= 2 && orientation <= 8) { |
|
2251 | view.setUint16(offset + (i * 12) + 8, 1, little); |
|
2252 | result.fixedArrayBuffer = e.target.result; |
|
2253 | } |
|
2254 | result.orientation = orientation; |
|
2255 | return defer.resolve(result); |
|
2256 | } |
|
2257 | } else if ((marker & 0xFF00) !== 0xFF00) break; |
|
2258 | else offset += view.getUint16(offset, false); |
|
2259 | } |
|
2260 | return defer.resolve(result); |
|
2261 | }; |
|
2262 | return defer.promise; |
|
2263 | }; |
|
2264 | ||
2265 | function arrayBufferToBase64(buffer) { |
|
2266 | var binary = ''; |
|
2267 | var bytes = new Uint8Array(buffer); |
|
2268 | var len = bytes.byteLength; |
|
2269 | for (var i = 0; i < len; i++) { |
|
2270 | binary += String.fromCharCode(bytes[i]); |
|
2271 | } |
|
2272 | return window.btoa(binary); |
|
2273 | } |
|
2274 | ||
2275 | upload.applyExifRotation = function (file) { |
|
2276 | if (file.type.indexOf('image/jpeg') !== 0) { |
|
2277 | return upload.emptyPromise(file); |
|
2278 | } |
|
2279 | ||
2280 | var deferred = $q.defer(); |
|
2281 | upload.readOrientation(file).then(function (result) { |
|
2282 | if (result.orientation < 2 || result.orientation > 8) { |
|
2283 | return deferred.resolve(file); |
|
2284 | } |
|
2285 | upload.dataUrl(file, true).then(function (url) { |
|
2286 | var canvas = document.createElement('canvas'); |
|
2287 | var img = document.createElement('img'); |
|
2288 | ||
2289 | img.onload = function () { |
|
2290 | try { |
|
2291 | canvas.width = result.orientation > 4 ? img.height : img.width; |
|
2292 | canvas.height = result.orientation > 4 ? img.width : img.height; |
|
2293 | var ctx = canvas.getContext('2d'); |
|
2294 | applyTransform(ctx, result.orientation, img.width, img.height); |
|
2295 | ctx.drawImage(img, 0, 0); |
|
2296 | var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934); |
|
2297 | dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl); |
|
2298 | var blob = upload.dataUrltoBlob(dataUrl, file.name); |
|
2299 | deferred.resolve(blob); |
|
2300 | } catch (e) { |
|
2301 | return deferred.reject(e); |
|
2302 | } |
|
2303 | }; |
|
2304 | img.onerror = function () { |
|
2305 | deferred.reject(); |
|
2306 | }; |
|
2307 | img.src = url; |
|
2308 | }, function (e) { |
|
2309 | deferred.reject(e); |
|
2310 | }); |
|
2311 | }, function (e) { |
|
2312 | deferred.reject(e); |
|
2313 | }); |
|
2314 | return deferred.promise; |
|
2315 | }; |
|
2316 | ||
2317 | upload.restoreExif = function (orig, resized) { |
|
2318 | var ExifRestorer = {}; |
|
2319 | ||
2320 | ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' + |
|
2321 | 'QRSTUVWXYZabcdef' + |
|
2322 | 'ghijklmnopqrstuv' + |
|
2323 | 'wxyz0123456789+/' + |
|
2324 | '='; |
|
2325 | ||
2326 | ExifRestorer.encode64 = function (input) { |
|
2327 | var output = '', |
|
2328 | chr1, chr2, chr3 = '', |
|
2329 | enc1, enc2, enc3, enc4 = '', |
|
2330 | i = 0; |
|
2331 | ||
2332 | do { |
|
2333 | chr1 = input[i++]; |
|
2334 | chr2 = input[i++]; |
|
2335 | chr3 = input[i++]; |
|
2336 | ||
2337 | enc1 = chr1 >> 2; |
|
2338 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); |
|
2339 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); |
|
2340 | enc4 = chr3 & 63; |
|
2341 | ||
2342 | if (isNaN(chr2)) { |
|
2343 | enc3 = enc4 = 64; |
|
2344 | } else if (isNaN(chr3)) { |
|
2345 | enc4 = 64; |
|
2346 | } |
|
2347 | ||
2348 | output = output + |
|
2349 | this.KEY_STR.charAt(enc1) + |
|
2350 | this.KEY_STR.charAt(enc2) + |
|
2351 | this.KEY_STR.charAt(enc3) + |
|
2352 | this.KEY_STR.charAt(enc4); |
|
2353 | chr1 = chr2 = chr3 = ''; |
|
2354 | enc1 = enc2 = enc3 = enc4 = ''; |
|
2355 | } while (i < input.length); |
|
2356 | ||
2357 | return output; |
|
2358 | }; |
|
2359 | ||
2360 | ExifRestorer.restore = function (origFileBase64, resizedFileBase64) { |
|
2361 | if (origFileBase64.match('data:image/jpeg;base64,')) { |
|
2362 | origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', ''); |
|
2363 | } |
|
2364 | ||
2365 | var rawImage = this.decode64(origFileBase64); |
|
2366 | var segments = this.slice2Segments(rawImage); |
|
2367 | ||
2368 | var image = this.exifManipulation(resizedFileBase64, segments); |
|
2369 | ||
2370 | return 'data:image/jpeg;base64,' + this.encode64(image); |
|
2371 | }; |
|
2372 | ||
2373 | ||
2374 | ExifRestorer.exifManipulation = function (resizedFileBase64, segments) { |
|
2375 | var exifArray = this.getExifArray(segments), |
|
2376 | newImageArray = this.insertExif(resizedFileBase64, exifArray); |
|
2377 | return new Uint8Array(newImageArray); |
|
2378 | }; |
|
2379 | ||
2380 | ||
2381 | ExifRestorer.getExifArray = function (segments) { |
|
2382 | var seg; |
|
2383 | for (var x = 0; x < segments.length; x++) { |
|
2384 | seg = segments[x]; |
|
2385 | if (seg[0] === 255 & seg[1] === 225) //(ff e1) |
|
2386 | { |
|
2387 | return seg; |
|
2388 | } |
|
2389 | } |
|
2390 | return []; |
|
2391 | }; |
|
2392 | ||
2393 | ||
2394 | ExifRestorer.insertExif = function (resizedFileBase64, exifArray) { |
|
2395 | var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''), |
|
2396 | buf = this.decode64(imageData), |
|
2397 | separatePoint = buf.indexOf(255, 3), |
|
2398 | mae = buf.slice(0, separatePoint), |
|
2399 | ato = buf.slice(separatePoint), |
|
2400 | array = mae; |
|
2401 | ||
2402 | array = array.concat(exifArray); |
|
2403 | array = array.concat(ato); |
|
2404 | return array; |
|
2405 | }; |
|
2406 | ||
2407 | ||
2408 | ExifRestorer.slice2Segments = function (rawImageArray) { |
|
2409 | var head = 0, |
|
2410 | segments = []; |
|
2411 | ||
2412 | while (1) { |
|
2413 | if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) { |
|
2414 | break; |
|
2415 | } |
|
2416 | if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) { |
|
2417 | head += 2; |
|
2418 | } |
|
2419 | else { |
|
2420 | var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3], |
|
2421 | endPoint = head + length + 2, |
|
2422 | seg = rawImageArray.slice(head, endPoint); |
|
2423 | segments.push(seg); |
|
2424 | head = endPoint; |
|
2425 | } |
|
2426 | if (head > rawImageArray.length) { |
|
2427 | break; |
|
2428 | } |
|
2429 | } |
|
2430 | ||
2431 | return segments; |
|
2432 | }; |
|
2433 | ||
2434 | ||
2435 | ExifRestorer.decode64 = function (input) { |
|
2436 | var chr1, chr2, chr3 = '', |
|
2437 | enc1, enc2, enc3, enc4 = '', |
|
2438 | i = 0, |
|
2439 | buf = []; |
|
2440 | ||
2441 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = |
|
2442 | var base64test = /[^A-Za-z0-9\+\/\=]/g; |
|
2443 | if (base64test.exec(input)) { |
|
2444 | console.log('There were invalid base64 characters in the input text.\n' + |
|
2445 | 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' + |
|
2446 | 'Expect errors in decoding.'); |
|
2447 | } |
|
2448 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); |
|
2449 | ||
2450 | do { |
|
2451 | enc1 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2452 | enc2 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2453 | enc3 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2454 | enc4 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
2455 | ||
2456 | chr1 = (enc1 << 2) | (enc2 >> 4); |
|
2457 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); |
|
2458 | chr3 = ((enc3 & 3) << 6) | enc4; |
|
2459 | ||
2460 | buf.push(chr1); |
|
2461 | ||
2462 | if (enc3 !== 64) { |
|
2463 | buf.push(chr2); |
|
2464 | } |
|
2465 | if (enc4 !== 64) { |
|
2466 | buf.push(chr3); |
|
2467 | } |
|
2468 | ||
2469 | chr1 = chr2 = chr3 = ''; |
|
2470 | enc1 = enc2 = enc3 = enc4 = ''; |
|
2471 | ||
2472 | } while (i < input.length); |
|
2473 | ||
2474 | return buf; |
|
2475 | }; |
|
2476 | ||
2477 | return ExifRestorer.restore(orig, resized); //<= EXIF |
|
2478 | }; |
|
2479 | ||
2480 | return upload; |
|
2481 | }]); |
|
2482 | ||
2483 |
@@ 2-287 (lines=286) @@ | ||
1 | // customized version of https://github.com/exif-js/exif-js |
|
2 | ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) { |
|
3 | var upload = UploadResize; |
|
4 | ||
5 | upload.isExifSupported = function () { |
|
6 | return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported(); |
|
7 | }; |
|
8 | ||
9 | function applyTransform(ctx, orientation, width, height) { |
|
10 | switch (orientation) { |
|
11 | case 2: |
|
12 | return ctx.transform(-1, 0, 0, 1, width, 0); |
|
13 | case 3: |
|
14 | return ctx.transform(-1, 0, 0, -1, width, height); |
|
15 | case 4: |
|
16 | return ctx.transform(1, 0, 0, -1, 0, height); |
|
17 | case 5: |
|
18 | return ctx.transform(0, 1, 1, 0, 0, 0); |
|
19 | case 6: |
|
20 | return ctx.transform(0, 1, -1, 0, height, 0); |
|
21 | case 7: |
|
22 | return ctx.transform(0, -1, -1, 0, height, width); |
|
23 | case 8: |
|
24 | return ctx.transform(0, -1, 1, 0, 0, width); |
|
25 | } |
|
26 | } |
|
27 | ||
28 | upload.readOrientation = function (file) { |
|
29 | var defer = $q.defer(); |
|
30 | var reader = new FileReader(); |
|
31 | var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file; |
|
32 | reader.readAsArrayBuffer(slicedFile); |
|
33 | reader.onerror = function (e) { |
|
34 | return defer.reject(e); |
|
35 | }; |
|
36 | reader.onload = function (e) { |
|
37 | var result = {orientation: 1}; |
|
38 | var view = new DataView(this.result); |
|
39 | if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result); |
|
40 | ||
41 | var length = view.byteLength, |
|
42 | offset = 2; |
|
43 | while (offset < length) { |
|
44 | var marker = view.getUint16(offset, false); |
|
45 | offset += 2; |
|
46 | if (marker === 0xFFE1) { |
|
47 | if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result); |
|
48 | ||
49 | var little = view.getUint16(offset += 6, false) === 0x4949; |
|
50 | offset += view.getUint32(offset + 4, little); |
|
51 | var tags = view.getUint16(offset, little); |
|
52 | offset += 2; |
|
53 | for (var i = 0; i < tags; i++) |
|
54 | if (view.getUint16(offset + (i * 12), little) === 0x0112) { |
|
55 | var orientation = view.getUint16(offset + (i * 12) + 8, little); |
|
56 | if (orientation >= 2 && orientation <= 8) { |
|
57 | view.setUint16(offset + (i * 12) + 8, 1, little); |
|
58 | result.fixedArrayBuffer = e.target.result; |
|
59 | } |
|
60 | result.orientation = orientation; |
|
61 | return defer.resolve(result); |
|
62 | } |
|
63 | } else if ((marker & 0xFF00) !== 0xFF00) break; |
|
64 | else offset += view.getUint16(offset, false); |
|
65 | } |
|
66 | return defer.resolve(result); |
|
67 | }; |
|
68 | return defer.promise; |
|
69 | }; |
|
70 | ||
71 | function arrayBufferToBase64(buffer) { |
|
72 | var binary = ''; |
|
73 | var bytes = new Uint8Array(buffer); |
|
74 | var len = bytes.byteLength; |
|
75 | for (var i = 0; i < len; i++) { |
|
76 | binary += String.fromCharCode(bytes[i]); |
|
77 | } |
|
78 | return window.btoa(binary); |
|
79 | } |
|
80 | ||
81 | upload.applyExifRotation = function (file) { |
|
82 | if (file.type.indexOf('image/jpeg') !== 0) { |
|
83 | return upload.emptyPromise(file); |
|
84 | } |
|
85 | ||
86 | var deferred = $q.defer(); |
|
87 | upload.readOrientation(file).then(function (result) { |
|
88 | if (result.orientation < 2 || result.orientation > 8) { |
|
89 | return deferred.resolve(file); |
|
90 | } |
|
91 | upload.dataUrl(file, true).then(function (url) { |
|
92 | var canvas = document.createElement('canvas'); |
|
93 | var img = document.createElement('img'); |
|
94 | ||
95 | img.onload = function () { |
|
96 | try { |
|
97 | canvas.width = result.orientation > 4 ? img.height : img.width; |
|
98 | canvas.height = result.orientation > 4 ? img.width : img.height; |
|
99 | var ctx = canvas.getContext('2d'); |
|
100 | applyTransform(ctx, result.orientation, img.width, img.height); |
|
101 | ctx.drawImage(img, 0, 0); |
|
102 | var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934); |
|
103 | dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl); |
|
104 | var blob = upload.dataUrltoBlob(dataUrl, file.name); |
|
105 | deferred.resolve(blob); |
|
106 | } catch (e) { |
|
107 | return deferred.reject(e); |
|
108 | } |
|
109 | }; |
|
110 | img.onerror = function () { |
|
111 | deferred.reject(); |
|
112 | }; |
|
113 | img.src = url; |
|
114 | }, function (e) { |
|
115 | deferred.reject(e); |
|
116 | }); |
|
117 | }, function (e) { |
|
118 | deferred.reject(e); |
|
119 | }); |
|
120 | return deferred.promise; |
|
121 | }; |
|
122 | ||
123 | upload.restoreExif = function (orig, resized) { |
|
124 | var ExifRestorer = {}; |
|
125 | ||
126 | ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' + |
|
127 | 'QRSTUVWXYZabcdef' + |
|
128 | 'ghijklmnopqrstuv' + |
|
129 | 'wxyz0123456789+/' + |
|
130 | '='; |
|
131 | ||
132 | ExifRestorer.encode64 = function (input) { |
|
133 | var output = '', |
|
134 | chr1, chr2, chr3 = '', |
|
135 | enc1, enc2, enc3, enc4 = '', |
|
136 | i = 0; |
|
137 | ||
138 | do { |
|
139 | chr1 = input[i++]; |
|
140 | chr2 = input[i++]; |
|
141 | chr3 = input[i++]; |
|
142 | ||
143 | enc1 = chr1 >> 2; |
|
144 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); |
|
145 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); |
|
146 | enc4 = chr3 & 63; |
|
147 | ||
148 | if (isNaN(chr2)) { |
|
149 | enc3 = enc4 = 64; |
|
150 | } else if (isNaN(chr3)) { |
|
151 | enc4 = 64; |
|
152 | } |
|
153 | ||
154 | output = output + |
|
155 | this.KEY_STR.charAt(enc1) + |
|
156 | this.KEY_STR.charAt(enc2) + |
|
157 | this.KEY_STR.charAt(enc3) + |
|
158 | this.KEY_STR.charAt(enc4); |
|
159 | chr1 = chr2 = chr3 = ''; |
|
160 | enc1 = enc2 = enc3 = enc4 = ''; |
|
161 | } while (i < input.length); |
|
162 | ||
163 | return output; |
|
164 | }; |
|
165 | ||
166 | ExifRestorer.restore = function (origFileBase64, resizedFileBase64) { |
|
167 | if (origFileBase64.match('data:image/jpeg;base64,')) { |
|
168 | origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', ''); |
|
169 | } |
|
170 | ||
171 | var rawImage = this.decode64(origFileBase64); |
|
172 | var segments = this.slice2Segments(rawImage); |
|
173 | ||
174 | var image = this.exifManipulation(resizedFileBase64, segments); |
|
175 | ||
176 | return 'data:image/jpeg;base64,' + this.encode64(image); |
|
177 | }; |
|
178 | ||
179 | ||
180 | ExifRestorer.exifManipulation = function (resizedFileBase64, segments) { |
|
181 | var exifArray = this.getExifArray(segments), |
|
182 | newImageArray = this.insertExif(resizedFileBase64, exifArray); |
|
183 | return new Uint8Array(newImageArray); |
|
184 | }; |
|
185 | ||
186 | ||
187 | ExifRestorer.getExifArray = function (segments) { |
|
188 | var seg; |
|
189 | for (var x = 0; x < segments.length; x++) { |
|
190 | seg = segments[x]; |
|
191 | if (seg[0] === 255 & seg[1] === 225) //(ff e1) |
|
192 | { |
|
193 | return seg; |
|
194 | } |
|
195 | } |
|
196 | return []; |
|
197 | }; |
|
198 | ||
199 | ||
200 | ExifRestorer.insertExif = function (resizedFileBase64, exifArray) { |
|
201 | var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''), |
|
202 | buf = this.decode64(imageData), |
|
203 | separatePoint = buf.indexOf(255, 3), |
|
204 | mae = buf.slice(0, separatePoint), |
|
205 | ato = buf.slice(separatePoint), |
|
206 | array = mae; |
|
207 | ||
208 | array = array.concat(exifArray); |
|
209 | array = array.concat(ato); |
|
210 | return array; |
|
211 | }; |
|
212 | ||
213 | ||
214 | ExifRestorer.slice2Segments = function (rawImageArray) { |
|
215 | var head = 0, |
|
216 | segments = []; |
|
217 | ||
218 | while (1) { |
|
219 | if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) { |
|
220 | break; |
|
221 | } |
|
222 | if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) { |
|
223 | head += 2; |
|
224 | } |
|
225 | else { |
|
226 | var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3], |
|
227 | endPoint = head + length + 2, |
|
228 | seg = rawImageArray.slice(head, endPoint); |
|
229 | segments.push(seg); |
|
230 | head = endPoint; |
|
231 | } |
|
232 | if (head > rawImageArray.length) { |
|
233 | break; |
|
234 | } |
|
235 | } |
|
236 | ||
237 | return segments; |
|
238 | }; |
|
239 | ||
240 | ||
241 | ExifRestorer.decode64 = function (input) { |
|
242 | var chr1, chr2, chr3 = '', |
|
243 | enc1, enc2, enc3, enc4 = '', |
|
244 | i = 0, |
|
245 | buf = []; |
|
246 | ||
247 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = |
|
248 | var base64test = /[^A-Za-z0-9\+\/\=]/g; |
|
249 | if (base64test.exec(input)) { |
|
250 | console.log('There were invalid base64 characters in the input text.\n' + |
|
251 | 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' + |
|
252 | 'Expect errors in decoding.'); |
|
253 | } |
|
254 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); |
|
255 | ||
256 | do { |
|
257 | enc1 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
258 | enc2 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
259 | enc3 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
260 | enc4 = this.KEY_STR.indexOf(input.charAt(i++)); |
|
261 | ||
262 | chr1 = (enc1 << 2) | (enc2 >> 4); |
|
263 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); |
|
264 | chr3 = ((enc3 & 3) << 6) | enc4; |
|
265 | ||
266 | buf.push(chr1); |
|
267 | ||
268 | if (enc3 !== 64) { |
|
269 | buf.push(chr2); |
|
270 | } |
|
271 | if (enc4 !== 64) { |
|
272 | buf.push(chr3); |
|
273 | } |
|
274 | ||
275 | chr1 = chr2 = chr3 = ''; |
|
276 | enc1 = enc2 = enc3 = enc4 = ''; |
|
277 | ||
278 | } while (i < input.length); |
|
279 | ||
280 | return buf; |
|
281 | }; |
|
282 | ||
283 | return ExifRestorer.restore(orig, resized); //<= EXIF |
|
284 | }; |
|
285 | ||
286 | return upload; |
|
287 | }]); |
|
288 | ||
289 |