본문 바로가기
front-end/script

[javascript] 이미지 용량 줄이기 (이미지 리사이징) Compressor.js

by moonsiri 2020. 11. 13.
728x90
반응형

spring.servlet.multipart.max-file-size 설정하여 파일 업로드 용량 제한을 걸어두었는데, 프론트단에서 이미지 파일 용량 상관없이 이미지 파일 업로드를 하려고 합니다.

 

이미지 용량을 줄이기 위한 방법을 찾으려고 구글링을 했는데 열에 아홉은 아래 스크립트에 관한 글이었습니다.

window.resize = (function () {
	function Resize(){
		
	}
	
	Resize.prototype = {
		init: function(outputQuality) {
			this.outputQuality = (outputQuality === 'undefined' ? 1 : outputQuality);
		},
		photo: function(file, maxSize, outputType, callback) {
			var _this = this;
			var reader = new FileReader();
			reader.onload = function (readerEvent) {
				_this.resize(readerEvent.target.result, maxSize, outputType, callback);
			};
			reader.readAsDataURL(file);
		},
		resize: function(dataURL, maxSize, outputType, callback) {
			var _this = this;
			var image = new Image();

			image.onload = function () {

				// Resize image
				var canvas = document.createElement('canvas'),
					width = image.width,
					height = image.height;
				if (width > height) {//가로모드
					if (width > maxSize) {
						height *= maxSize / width;
						width = maxSize;
					}
				} else {//세로모드
					if (height > maxSize) {
						width *= maxSize / height;
						height = maxSize;
					}
				}
				canvas.width = width;
				canvas.height = height;
					
				canvas.getContext('2d').drawImage(image, 0, 0, width, height);
					
				_this.output(canvas, outputType, callback);
			};
			image.onerror=function(){
				return;
			};
			image.src = dataURL;
		},
		output: function(canvas, outputType, callback) {
			switch (outputType) {

				case 'file':
					canvas.toBlob(function (blob) {
						callback(blob);
					}, 'image/png', 0.8);
					break;

				case 'dataURL':
					callback(canvas.toDataURL('image/png', 0.8));
					break;

			}
		}
	};//prototype end
	
	return Resize;

}());

 

사용법은 다음과 같습니다. resize.photo(파일, maxLength, type, callback함수)

let resize = new window.resize();
resize.init();
resize.photo(files[0], 1000, 'file', function (resizedFile) {
	console.log(resizedFile);  // blob
});
resize.photo(files[0], 1000, 'dataURL', function (url) {
	img.src = url;
});

 

간단하게 사용하기는 좋은데, 다만 몇 가지 문제가 있었습니다.

1. maxLength(최고 길이)보다 작은 이미지 일 경우, 사진 사이즈가 커지면서 용량이 커짐

2. 캔버스에 그리고서 이미지로 변경하는 방법이라 오히려 용량이 커지는 경우가 있음

3. 기타 등등 저와 같은 생각을 가진 포스팅 : post.flow.team/developer/img-resizing-2/

 

maxLength를 애초에 작게 잡아놓으면 max-file-size보다 작은 용량으로 리사이징이 가능하긴 하지만, 근본적인 해결방법이 아니라 생각하여 프론트단에서 이미지 용량을 줄일 수 있는 방법을 더 찾아보았습니다.

하지만.... 원하는 기술 관련 내용을 찾을 수가 없더군요.

대신 위 스크립트보다 나은 플러그인을 찾았습니다.

 

github.com/fengyuanchen/compressorjs

 

fengyuanchen/compressorjs

JavaScript image compressor. Contribute to fengyuanchen/compressorjs development by creating an account on GitHub.

github.com

 

더보기

compressor.js

/*!
 * Compressor.js v1.0.6
 * https://fengyuanchen.github.io/compressorjs
 *
 * Copyright 2018-present Chen Fengyuan
 * Released under the MIT license
 *
 * Date: 2019-11-23T04:43:12.442Z
 */

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global = global || self, global.Compressor = factory());
}(this, (function () { 'use strict';

    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) {
            throw new TypeError("Cannot call a class as a function");
        }
    }

    function _defineProperties(target, props) {
        for (let i = 0; i < props.length; i++) {
            const descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }

    function _createClass(Constructor, protoProps, staticProps) {
        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
        if (staticProps) _defineProperties(Constructor, staticProps);
        return Constructor;
    }

    function _defineProperty(obj, key, value) {
        if (key in obj) {
            Object.defineProperty(obj, key, {
                value: value,
                enumerable: true,
                configurable: true,
                writable: true
            });
        } else {
            obj[key] = value;
        }

        return obj;
    }

    function _extends() {
        _extends = Object.assign || function (target) {
            for (let i = 1; i < arguments.length; i++) {
                const source = arguments[i];

                for (const key in source) {
                    if (Object.prototype.hasOwnProperty.call(source, key)) {
                        target[key] = source[key];
                    }
                }
            }

            return target;
        };

        return _extends.apply(this, arguments);
    }

    function ownKeys(object, enumerableOnly) {
        const keys = Object.keys(object);

        if (Object.getOwnPropertySymbols) {
            let symbols = Object.getOwnPropertySymbols(object);
            if (enumerableOnly) symbols = symbols.filter(function (sym) {
                return Object.getOwnPropertyDescriptor(object, sym).enumerable;
            });
            keys.push.apply(keys, symbols);
        }

        return keys;
    }

    function _objectSpread2(target) {
        for (let i = 1; i < arguments.length; i++) {
            const source = arguments[i] != null ? arguments[i] : {};

            if (i % 2) {
                ownKeys(Object(source), true).forEach(function (key) {
                    _defineProperty(target, key, source[key]);
                });
            } else if (Object.getOwnPropertyDescriptors) {
                Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
            } else {
                ownKeys(Object(source)).forEach(function (key) {
                    Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
                });
            }
        }

        return target;
    }

    function createCommonjsModule(fn, module) {
        return module = { exports: {} }, fn(module, module.exports), module.exports;
    }

    const canvasToBlob = createCommonjsModule(function (module) {
        if (typeof window === 'undefined') {
            return;
        }

        (function (window) {

            const CanvasPrototype = window.HTMLCanvasElement && window.HTMLCanvasElement.prototype;

            const hasBlobConstructor = window.Blob && function () {
                try {
                    return Boolean(new Blob());
                } catch (e) {
                    return false;
                }
            }();

            const hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && function () {
                try {
                    return new Blob([new Uint8Array(100)]).size === 100;
                } catch (e) {
                    return false;
                }
            }();

            const BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
            const dataURIPattern = /^data:((.*?)(;charset=.*?)?)(;base64)?,/;

            const dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array && function (dataURI) {
                let matches, mediaType, isBase64, dataString, byteString, arrayBuffer, intArray, i, bb; // Parse the dataURI components as per RFC 2397

                matches = dataURI.match(dataURIPattern);

                if (!matches) {
                    throw new Error('invalid data URI');
                } // Default to text/plain;charset=US-ASCII


                mediaType = matches[2] ? matches[1] : 'text/plain' + (matches[3] || ';charset=US-ASCII');
                isBase64 = !!matches[4];
                dataString = dataURI.slice(matches[0].length);

                if (isBase64) {
                    // Convert base64 to raw binary data held in a string:
                    byteString = atob(dataString);
                } else {
                    // Convert base64/URLEncoded data component to raw binary:
                    byteString = decodeURIComponent(dataString);
                } // Write the bytes of the string to an ArrayBuffer:


                arrayBuffer = new ArrayBuffer(byteString.length);
                intArray = new Uint8Array(arrayBuffer);

                for (i = 0; i < byteString.length; i += 1) {
                    intArray[i] = byteString.charCodeAt(i);
                } // Write the ArrayBuffer (or ArrayBufferView) to a blob:


                if (hasBlobConstructor) {
                    return new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], {
                        type: mediaType
                    });
                }

                bb = new BlobBuilder();
                bb.append(arrayBuffer);
                return bb.getBlob(mediaType);
            };

            if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
                if (CanvasPrototype.mozGetAsFile) {
                    CanvasPrototype.toBlob = function (callback, type, quality) {
                        const self = this;
                        setTimeout(function () {
                            if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
                                callback(dataURLtoBlob(self.toDataURL(type, quality)));
                            } else {
                                callback(self.mozGetAsFile('blob', type));
                            }
                        });
                    };
                } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
                    CanvasPrototype.toBlob = function (callback, type, quality) {
                        const self = this;
                        setTimeout(function () {
                            callback(dataURLtoBlob(self.toDataURL(type, quality)));
                        });
                    };
                }
            }

            if ( module.exports) {
                module.exports = dataURLtoBlob;
            } else {
                window.dataURLtoBlob = dataURLtoBlob;
            }
        })(window);
    });

    const isBlob = function isBlob(input) {
        if (typeof Blob === 'undefined') {
            return false;
        }

        return input instanceof Blob || Object.prototype.toString.call(input) === '[object Blob]';
    };

    const DEFAULTS = {
        /**
         * Indicates if output the original image instead of the compressed one
         * when the size of the compressed image is greater than the original one's
         * @type {boolean}
         */
        strict: true,

        /**
         * Indicates if read the image's Exif Orientation information,
         * and then rotate or flip the image automatically.
         * @type {boolean}
         */
        checkOrientation: true,

        /**
         * The max width of the output image.
         * @type {number}
         */
        maxWidth: Infinity,

        /**
         * The max height of the output image.
         * @type {number}
         */
        maxHeight: Infinity,

        /**
         * The min width of the output image.
         * @type {number}
         */
        minWidth: 0,

        /**
         * The min height of the output image.
         * @type {number}
         */
        minHeight: 0,

        /**
         * The width of the output image.
         * If not specified, the natural width of the source image will be used.
         * @type {number}
         */
        width: undefined,

        /**
         * The height of the output image.
         * If not specified, the natural height of the source image will be used.
         * @type {number}
         */
        height: undefined,

        /**
         * The quality of the output image.
         * It must be a number between `0` and `1`,
         * and only available for `image/jpeg` and `image/webp` images.
         * Check out {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob canvas.toBlob}.
         * @type {number}
         */
        quality: 0.8,

        /**
         * The mime type of the output image.
         * By default, the original mime type of the source image file will be used.
         * @type {string}
         */
        mimeType: 'auto',

        /**
         * PNG files over this value (5 MB by default) will be converted to JPEGs.
         * To disable this, just set the value to `Infinity`.
         * @type {number}
         */
        convertSize: 5000000,

        /**
         * The hook function to execute before draw the image into the canvas for compression.
         * @type {Function}
         * @param {CanvasRenderingContext2D} context - The 2d rendering context of the canvas.
         * @param {HTMLCanvasElement} canvas - The canvas for compression.
         * @example
         * function (context, canvas) {
         *   context.fillStyle = '#fff';
         * }
         */
        beforeDraw: null,

        /**
         * The hook function to execute after drew the image into the canvas for compression.
         * @type {Function}
         * @param {CanvasRenderingContext2D} context - The 2d rendering context of the canvas.
         * @param {HTMLCanvasElement} canvas - The canvas for compression.
         * @example
         * function (context, canvas) {
         *   context.filter = 'grayscale(100%)';
         * }
         */
        drew: null,

        /**
         * The hook function to execute when success to compress the image.
         * @type {Function}
         * @param {File} file - The compressed image File object.
         * @example
         * function (file) {
         *   console.log(file);
         * }
         */
        success: null,

        /**
         * The hook function to execute when fail to compress the image.
         * @type {Function}
         * @param {Error} err - An Error object.
         * @example
         * function (err) {
         *   console.log(err.message);
         * }
         */
        error: null
    };

    const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
    const WINDOW = IS_BROWSER ? window : {};

    const slice = Array.prototype.slice;

    /**
     * Convert array-like or iterable object to an array.
     * @param {*} value - The value to convert.
     * @returns {Array} Returns a new array.
     */
    function toArray(value) {
        return Array.from ? Array.from(value) : slice.call(value);
    }
    const REGEXP_IMAGE_TYPE = /^image\/.+$/;

    /**
     * Check if the given value is a mime type of image.
     * @param {*} value - The value to check.
     * @returns {boolean} Returns `true` if the given is a mime type of image, else `false`.
     */
    function isImageType(value) {
        return REGEXP_IMAGE_TYPE.test(value);
    }

    /**
     * Convert image type to extension.
     * @param {string} value - The image type to convert.
     * @returns {boolean} Returns the image extension.
     */
    function imageTypeToExtension(value) {
        let extension = isImageType(value) ? value.substr(6) : '';

        if (extension === 'jpeg') {
            extension = 'jpg';
        }

        return ".".concat(extension);
    }
    const fromCharCode = String.fromCharCode;

    /**
     * Get string from char code in data view.
     * @param {DataView} dataView - The data view for read.
     * @param {number} start - The start index.
     * @param {number} length - The read length.
     * @returns {string} The read result.
     */
    function getStringFromCharCode(dataView, start, length) {
        let str = '';
        let i;
        length += start;

        for (i = start; i < length; i += 1) {
            str += fromCharCode(dataView.getUint8(i));
        }

        return str;
    }
    const btoa = WINDOW.btoa;

    /**
     * Transform array buffer to Data URL.
     * @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
     * @param {string} mimeType - The mime type of the Data URL.
     * @returns {string} The result Data URL.
     */
    function arrayBufferToDataURL(arrayBuffer, mimeType) {
        const chunks = [];
        const chunkSize = 8192;
        let uint8 = new Uint8Array(arrayBuffer);

        while (uint8.length > 0) {
            // XXX: Babel's `toConsumableArray` helper will throw error in IE or Safari 9
            // eslint-disable-next-line prefer-spread
            chunks.push(fromCharCode.apply(null, toArray(uint8.subarray(0, chunkSize))));
            uint8 = uint8.subarray(chunkSize);
        }

        return "data:".concat(mimeType, ";base64,").concat(btoa(chunks.join('')));
    }

    /**
     * Get orientation value from given array buffer.
     * @param {ArrayBuffer} arrayBuffer - The array buffer to read.
     * @returns {number} The read orientation value.
     */
    function resetAndGetOrientation(arrayBuffer) {
        const dataView = new DataView(arrayBuffer);
        let orientation; // Ignores range error when the image does not have correct Exif information

        try {
            let littleEndian;
            let app1Start;
            let ifdStart; // Only handle JPEG image (start by 0xFFD8)

            if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
                const length = dataView.byteLength;
                let offset = 2;

                while (offset + 1 < length) {
                    if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
                        app1Start = offset;
                        break;
                    }

                    offset += 1;
                }
            }

            if (app1Start) {
                const exifIDCode = app1Start + 4;
                const tiffOffset = app1Start + 10;

                if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
                    const endianness = dataView.getUint16(tiffOffset);
                    littleEndian = endianness === 0x4949;

                    if (littleEndian || endianness === 0x4D4D
                    /* bigEndian */
                    ) {
                        if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
                            const firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);

                            if (firstIFDOffset >= 0x00000008) {
                                ifdStart = tiffOffset + firstIFDOffset;
                            }
                        }
                    }
                }
            }

            if (ifdStart) {
                const _length = dataView.getUint16(ifdStart, littleEndian);

                let _offset;

                let i;

                for (i = 0; i < _length; i += 1) {
                    _offset = ifdStart + i * 12 + 2;

                    if (dataView.getUint16(_offset, littleEndian) === 0x0112
                    /* Orientation */
                    ) {
                        // 8 is the offset of the current tag's value
                        _offset += 8; // Get the original orientation value

                        orientation = dataView.getUint16(_offset, littleEndian); // Override the orientation with its default value

                        dataView.setUint16(_offset, 1, littleEndian);
                        break;
                    }
                }
            }
        } catch (e) {
            orientation = 1;
        }

        return orientation;
    }

    /**
     * Parse Exif Orientation value.
     * @param {number} orientation - The orientation to parse.
     * @returns {Object} The parsed result.
     */
    function parseOrientation(orientation) {
        let rotate = 0;
        let scaleX = 1;
        let scaleY = 1;

        switch (orientation) {
            // Flip horizontal
            case 2:
                scaleX = -1;
                break;
            // Rotate left 180°

            case 3:
                rotate = -180;
                break;
            // Flip vertical

            case 4:
                scaleY = -1;
                break;
            // Flip vertical and rotate right 90°

            case 5:
                rotate = 90;
                scaleY = -1;
                break;
            // Rotate right 90°

            case 6:
                rotate = 90;
                break;
            // Flip horizontal and rotate right 90°

            case 7:
                rotate = 90;
                scaleX = -1;
                break;
            // Rotate left 90°

            case 8:
                rotate = -90;
                break;
        }

        return {
            rotate: rotate,
            scaleX: scaleX,
            scaleY: scaleY
        };
    }
    const REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/;

    /**
     * Normalize decimal number.
     * Check out {@link https://0.30000000000000004.com/}
     * @param {number} value - The value to normalize.
     * @param {number} [times=100000000000] - The times for normalizing.
     * @returns {number} Returns the normalized number.
     */
    function normalizeDecimalNumber(value) {
        const times = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100000000000;
        return REGEXP_DECIMALS.test(value) ? Math.round(value * times) / times : value;
    }

    const ArrayBuffer$1 = WINDOW.ArrayBuffer,
        FileReader = WINDOW.FileReader;
    const URL = WINDOW.URL || WINDOW.webkitURL;
    const REGEXP_EXTENSION = /\.\w+$/;
    const AnotherCompressor = WINDOW.Compressor;

    /**
     * Creates a new image compressor.
     * @class
     */
    let Compressor = function () {
        /**
         * The constructor of Compressor.
         * @param {File|Blob} file - The target image file for compressing.
         * @param {Object} [options] - The options for compressing.
         */
        function Compressor(file, options) {
            _classCallCheck(this, Compressor);

            this.file = file;
            this.image = new Image();
            this.options = _objectSpread2({}, DEFAULTS, {}, options);
            this.aborted = false;
            this.result = null;
            this.init();
        }

        _createClass(Compressor, [{
            key: "init",
            value: function init() {
                const _this = this;

                const file = this.file,
                    options = this.options;

                if (!isBlob(file)) {
                    this.fail(new Error('The first argument must be a File or Blob object.'));
                    return;
                }

                const mimeType = file.type;

                if (!isImageType(mimeType)) {
                    this.fail(new Error('The first argument must be an image File or Blob object.'));
                    return;
                }

                if (!URL || !FileReader) {
                    this.fail(new Error('The current browser does not support image compression.'));
                    return;
                }

                if (!ArrayBuffer$1) {
                    options.checkOrientation = false;
                }

                if (URL && !options.checkOrientation) {
                    this.load({
                        url: URL.createObjectURL(file)
                    });
                } else {
                    const reader = new FileReader();
                    const checkOrientation = options.checkOrientation && mimeType === 'image/jpeg';
                    this.reader = reader;

                    reader.onload = function (_ref) {
                        const target = _ref.target;
                        const result = target.result;
                        const data = {};

                        if (checkOrientation) {
                            // Reset the orientation value to its default value 1
                            // as some iOS browsers will render image with its orientation
                            const orientation = resetAndGetOrientation(result);

                            if (orientation > 1 || !URL) {
                                // Generate a new URL which has the default orientation value
                                data.url = arrayBufferToDataURL(result, mimeType);

                                if (orientation > 1) {
                                    _extends(data, parseOrientation(orientation));
                                }
                            } else {
                                data.url = URL.createObjectURL(file);
                            }
                        } else {
                            data.url = result;
                        }

                        _this.load(data);
                    };

                    reader.onabort = function () {
                        _this.fail(new Error('Aborted to read the image with FileReader.'));
                    };

                    reader.onerror = function () {
                        _this.fail(new Error('Failed to read the image with FileReader.'));
                    };

                    reader.onloadend = function () {
                        _this.reader = null;
                    };

                    if (checkOrientation) {
                        reader.readAsArrayBuffer(file);
                    } else {
                        reader.readAsDataURL(file);
                    }
                }
            }
        }, {
            key: "load",
            value: function load(data) {
                const _this2 = this;

                const file = this.file,
                    image = this.image;

                image.onload = function () {
                    _this2.draw(_objectSpread2({}, data, {
                        naturalWidth: image.naturalWidth,
                        naturalHeight: image.naturalHeight
                    }));
                };

                image.onabort = function () {
                    _this2.fail(new Error('Aborted to load the image.'));
                };

                image.onerror = function () {
                    _this2.fail(new Error('Failed to load the image.'));
                };

                // Match all browsers that use WebKit as the layout engine in iOS devices,
                // such as Safari for iOS, Chrome for iOS, and in-app browsers.
                if (WINDOW.navigator && /(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(WINDOW.navigator.userAgent)) {
                    // Fix the `The operation is insecure` error (#57)
                    image.crossOrigin = 'anonymous';
                }

                image.alt = file.name;
                image.src = data.url;
            }
        }, {
            key: "draw",
            value: function draw(_ref2) {
                const _this3 = this;

                let naturalWidth = _ref2.naturalWidth,
                    naturalHeight = _ref2.naturalHeight,
                    _ref2$rotate = _ref2.rotate,
                    rotate = _ref2$rotate === void 0 ? 0 : _ref2$rotate,
                    _ref2$scaleX = _ref2.scaleX,
                    scaleX = _ref2$scaleX === void 0 ? 1 : _ref2$scaleX,
                    _ref2$scaleY = _ref2.scaleY,
                    scaleY = _ref2$scaleY === void 0 ? 1 : _ref2$scaleY;
                const file = this.file,
                    image = this.image,
                    options = this.options;
                const canvas = document.createElement('canvas');
                const context = canvas.getContext('2d');
                const aspectRatio = naturalWidth / naturalHeight;
                const is90DegreesRotated = Math.abs(rotate) % 180 === 90;
                let maxWidth = Math.max(options.maxWidth, 0) || Infinity;
                let maxHeight = Math.max(options.maxHeight, 0) || Infinity;
                let minWidth = Math.max(options.minWidth, 0) || 0;
                let minHeight = Math.max(options.minHeight, 0) || 0;
                let width = Math.max(options.width, 0) || naturalWidth;
                let height = Math.max(options.height, 0) || naturalHeight;

                if (is90DegreesRotated) {
                    const _ref3 = [maxHeight, maxWidth];
                    maxWidth = _ref3[0];
                    maxHeight = _ref3[1];
                    const _ref4 = [minHeight, minWidth];
                    minWidth = _ref4[0];
                    minHeight = _ref4[1];
                    const _ref5 = [height, width];
                    width = _ref5[0];
                    height = _ref5[1];
                }

                if (maxWidth < Infinity && maxHeight < Infinity) {
                    if (maxHeight * aspectRatio > maxWidth) {
                        maxHeight = maxWidth / aspectRatio;
                    } else {
                        maxWidth = maxHeight * aspectRatio;
                    }
                } else if (maxWidth < Infinity) {
                    maxHeight = maxWidth / aspectRatio;
                } else if (maxHeight < Infinity) {
                    maxWidth = maxHeight * aspectRatio;
                }

                if (minWidth > 0 && minHeight > 0) {
                    if (minHeight * aspectRatio > minWidth) {
                        minHeight = minWidth / aspectRatio;
                    } else {
                        minWidth = minHeight * aspectRatio;
                    }
                } else if (minWidth > 0) {
                    minHeight = minWidth / aspectRatio;
                } else if (minHeight > 0) {
                    minWidth = minHeight * aspectRatio;
                }

                if (height * aspectRatio > width) {
                    height = width / aspectRatio;
                } else {
                    width = height * aspectRatio;
                }

                width = Math.floor(normalizeDecimalNumber(Math.min(Math.max(width, minWidth), maxWidth)));
                height = Math.floor(normalizeDecimalNumber(Math.min(Math.max(height, minHeight), maxHeight)));
                const destX = -width / 2;
                const destY = -height / 2;
                const destWidth = width;
                const destHeight = height;

                if (is90DegreesRotated) {
                    const _ref6 = [height, width];
                    width = _ref6[0];
                    height = _ref6[1];
                }

                canvas.width = width;
                canvas.height = height;

                if (!isImageType(options.mimeType)) {
                    options.mimeType = file.type;
                }

                let fillStyle = 'transparent'; // Converts PNG files over the `convertSize` to JPEGs.

                if (file.size > options.convertSize && options.mimeType === 'image/png') {
                    fillStyle = '#fff';
                    options.mimeType = 'image/jpeg';
                } // Override the default fill color (#000, black)


                context.fillStyle = fillStyle;
                context.fillRect(0, 0, width, height);

                if (options.beforeDraw) {
                    options.beforeDraw.call(this, context, canvas);
                }

                if (this.aborted) {
                    return;
                }

                context.save();
                context.translate(width / 2, height / 2);
                context.rotate(rotate * Math.PI / 180);
                context.scale(scaleX, scaleY);
                context.drawImage(image, destX, destY, destWidth, destHeight);
                context.restore();

                if (options.drew) {
                    options.drew.call(this, context, canvas);
                }

                if (this.aborted) {
                    return;
                }

                const done = function done(result) {
                    if (!_this3.aborted) {
                        _this3.done({
                            naturalWidth: naturalWidth,
                            naturalHeight: naturalHeight,
                            result: result
                        });
                    }
                };

                if (canvas.toBlob) {
                    canvas.toBlob(done, options.mimeType, options.quality);
                } else {
                    done(canvasToBlob(canvas.toDataURL(options.mimeType, options.quality)));
                }
            }
        }, {
            key: "done",
            value: function done(_ref7) {
                let naturalWidth = _ref7.naturalWidth,
                    naturalHeight = _ref7.naturalHeight,
                    result = _ref7.result;
                const file = this.file,
                    image = this.image,
                    options = this.options;

                if (URL && !options.checkOrientation) {
                    URL.revokeObjectURL(image.src);
                }

                if (result) {
                    // Returns original file if the result is greater than it and without size related options
                    if (options.strict && result.size > file.size && options.mimeType === file.type && !(options.width > naturalWidth || options.height > naturalHeight || options.minWidth > naturalWidth || options.minHeight > naturalHeight)) {
                        result = file;
                    } else {
                        const date = new Date();
                        result.lastModified = date.getTime();
                        result.lastModifiedDate = date;
                        result.name = file.name; // Convert the extension to match its type

                        if (result.name && result.type !== file.type) {
                            result.name = result.name.replace(REGEXP_EXTENSION, imageTypeToExtension(result.type));
                        }
                    }
                } else {
                    // Returns original file if the result is null in some cases.
                    result = file;
                }

                this.result = result;

                if (options.success) {
                    options.success.call(this, result);
                }
            }
        }, {
            key: "fail",
            value: function fail(err) {
                const options = this.options;

                if (options.error) {
                    options.error.call(this, err);
                } else {
                    throw err;
                }
            }
        }, {
            key: "abort",
            value: function abort() {
                if (!this.aborted) {
                    this.aborted = true;

                    if (this.reader) {
                        this.reader.abort();
                    } else if (!this.image.complete) {
                        this.image.onload = null;
                        this.image.onabort();
                    } else {
                        this.fail(new Error('The compression process has been aborted.'));
                    }
                }
            }

            /**
             * Get the no conflict compressor class.
             * @returns {Compressor} The compressor class.
             */
        }], [{
            key: "noConflict",
            value: function noConflict() {
                window.Compressor = AnotherCompressor;
                return Compressor;
            }

            /**
             * Change the default options.
             * @param {Object} options - The new default options.
             */
        }, {
            key: "setDefaults",
            value: function setDefaults(options) {
                _extends(DEFAULTS, options);
            }
        }]);

        return Compressor;
    }();

    return Compressor;

})));

 

사용법은 다음과 같습니다.

    const options = {
        maxWidth: 1500,
        maxHeight: 1000,
        success: function (result) {
            if (result.size > 5*1024*1024) { // 리사이징 했는데도 용량이 큰 경우
                alert(파일 용량이 초과되어 업로드가 불가 합니다.);
                return;
            }
            // console.log('Output: ', result);
            console.log(new File([result], result.name, { type: result.type }));

            const _URL = window.URL || window.webkitURL;
            if (_URL) {
                img.src = _URL.createObjectURL(result);
            }
        },
        error: function (err) {
            console.log(err);
        }
    };
    new Compressor(files[0], options);

 

options 값을 설정할 수 있는데, 설정하지 않은 값들은 default 값으로 설정됩니다.

  • strict: true - 압축된 이미지의 크기가 원래 이미지보다 클 때 압축된 이미지 대신 원본 이미지를 출력
  • checkOrientation: true - 이미지의 Exif Orientation 정보를 읽은 다음 이미지를 자동으로 회전 또는 플립 할 것인지 여부를 표시
  • maxWidth: Infinity
  • maxHeight: Infinity
  • minWidht: 0
  • minHeight: 0
  • width: undefined
  • height: undefined
  • quality: 0.8 - 출력 이미지의 품질. 0~1
  • mimeType: 'auto'
  • convertSize: 5000000 - PNG 파일 사이즈가 5MB 이상일 경우 JPEG로 변경. 기능 미사용시, Infinity 설정
  • beforeDraw: null
  • drew: null
  • success: null
  • error: null

 

fengyuanchen.github.io/compressorjs/

위 사이트에서 기능을 테스트해보실 수 있습니다.

 

11256*3637 크기의 61.9MB 용량을 가진 PNG 파일(jpg 파일일 경우 9.3MB)로 maxWidth는 1500px, maxHeight는 1000px 옵션 값을 주어 실행해보았습니다.

 

그랬더니 1500*484 사이즈의 238KB 용량을 가진 jpg 파일로 리사이징 되었습니다.

 

용량 큰 이미지를 리사이징 했음에도 문구가 많이 깨지지도 않고 결과가 나쁘지않습니다.

 

라이센스는 다음과 같습니다.

 

728x90
반응형

댓글