├── .editorconfig ├── .env ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docs ├── Pretendard-Black.subset-CVWqp8MN.woff2 ├── Pretendard-Bold.subset-C7miX2zx.woff2 ├── Pretendard-ExtraBold.subset-C2-BvFUE.woff2 ├── Pretendard-ExtraLight.subset-BOZr4nRl.woff2 ├── Pretendard-Light.subset-B94xHKuS.woff2 ├── Pretendard-Medium.subset-Y06xfiVw.woff2 ├── Pretendard-Regular.subset-ZBqo5aGF.woff2 ├── Pretendard-SemiBold.subset-ZPzDHilX.woff2 ├── Pretendard-Thin.subset-DWc8mLUC.woff2 ├── favicon.ico ├── index-C0EeogAJ.js ├── index-DhP0pGVv.css └── index.html ├── libs ├── ImageResize.es.js ├── ImageResize.umd.js └── types.d.ts ├── package.json ├── public └── favicon.ico ├── src ├── demo │ ├── fonts.css │ ├── fonts │ │ ├── Pretendard-Black.subset.woff2 │ │ ├── Pretendard-Bold.subset.woff2 │ │ ├── Pretendard-ExtraBold.subset.woff2 │ │ ├── Pretendard-ExtraLight.subset.woff2 │ │ ├── Pretendard-Light.subset.woff2 │ │ ├── Pretendard-Medium.subset.woff2 │ │ ├── Pretendard-Regular.subset.woff2 │ │ ├── Pretendard-SemiBold.subset.woff2 │ │ └── Pretendard-Thin.subset.woff2 │ ├── index.css │ ├── index.html │ ├── index.js │ └── libs.js └── image-resize │ ├── index.js │ ├── libs │ ├── checks.js │ ├── consts.js │ ├── files.js │ ├── filter-sharpen.js │ ├── numbers.js │ └── output.js │ └── worker.js ├── types.d.ts └── vite.config ├── vite.docs.js └── vite.libs.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # dev-server 2 | VITE_HOST="0.0.0.0" 3 | VITE_PORT="3000" 4 | VITE_OPEN_BROWSER="false" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | 4 | .DS_Store 5 | .env.local 6 | yarn.lock 7 | package-lock.json 8 | bun.lockb 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | docs/ 4 | 5 | .DS_Store 6 | .env.local 7 | yarn.lock 8 | package-lock.json 9 | bun.lockb 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 redgoose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image-resize 2 | 3 | 개인적으로 마음에 드는 이미지 리사이즈 도구를 못찾아서 이렇게 만들게 되었다. 4 | 이 도구는 웹 브라우저에서 사용하는 ``엘리먼트를 사용하여 이미지 리사이즈 한다. 5 | 6 | 7 | ## Demo 8 | 9 | 데모를 통하여 이미지 url, 이미지 첨부파일을 리사이즈하는 모습을 볼 수 확인할 수 있다. 10 | 11 | > https://kode-team.github.io/image-resize/ 12 | 13 | 14 | ## Installation 15 | 16 | `CLI`에서 설치할 프로젝트에서 다음과 같은 명령을 실행한다. 17 | 18 | ``` 19 | npm install image-resize 20 | yarn add image-resize 21 | bun add -d image-resize 22 | ``` 23 | 24 | 25 | ## Usage 26 | 27 | `import`로 함수를 불러서 비동기 방식으로 사용하면 된다. 28 | 29 | ### Module environment 30 | 31 | ```javascript 32 | import imageResize from 'image-resize' 33 | 34 | let res = await imageResize('image.jpg', { 35 | format: 'png', 36 | width: 640, 37 | }) 38 | ``` 39 | 40 | ### Vanilla environment 41 | 42 | ```html 43 | 44 | 45 | 51 | ``` 52 | 53 | 54 | ## Source 55 | 56 | 사용하기 먼저 어떤 형식의 이미지 데이터를 지원하는지 확인해볼 필요가 있다. 57 | 지원하는 이미지 데이터의 형식은 다음과 같다. 58 | 59 | - `String`: 이미지 url 60 | - `File`: File 형식의 객체 61 | - `Blob`: Blob 타입의 객체 62 | - `HTMLCanvasElement`: canvas 엘리먼트 63 | 64 | 65 | ## Options 66 | 67 | 다음과 같은 옵션을 사용할 수 있다. 68 | 69 | | Name | Type | Default | Description | 70 | |:----------:|:------:|:-------------:|:--------------------------------------------------------| 71 | | width | number | `320` | 조절할 가로사이즈 | 72 | | height | number | `undefined` | 조절할 세로 사이즈. 한쪽값이 있는쪽으로 기준이 되어 조절한다. | 73 | | format | string | `jpg` | 출력할 포맷. `png,jpg,webp` | 74 | | outputType | string | `base64` | 출력방식. `base64,canvas,blob` | 75 | | quality | number | `.75` | jpg 이미지일때의 이미지 퀄리티값 | 76 | | reSample | number | `2` | 리샘플링 횟수. 수치가 높을수록 경계선이 부드러워지지만 처리속도는 느려진다. 최대 4까지 적용된다. | 77 | | sharpen | number | `0.75` | 선명함의 강도 | 78 | | bgColor | string | `transparent` | 캔버스 배경색 (배경이 투명한 이미지를 사용하면 영향을 받을 수 있다.) | 79 | 80 | ### 사이즈 값 조정 81 | 82 | width, height 사이즈 값은 다음과 같은 조건을 따른다. 83 | 84 | - width, height 둘다 있을때: width 값이 기준이 된다. 85 | - width 값이 없을때: height 값이 기준이 된다. 86 | - height 값이 없을때: width 값이 기준이 된다. 87 | - width, height 둘다 없을때: width 값이 기준이 된다. 88 | 89 | 만약 height 값을 기준으로 사용하고 싶다면 `width`값을 `0`이나 `undefined`으로 넣어줘야한다. 90 | 91 | ### 배경색 92 | 93 | 기본값으로 `transparent`로 설정되어있다. 94 | 투명한 배경 이미지에 배경색을 넣고싶다면 `#ffffff`같은 값으로 넣어주면 배경색이 들어가게 된다. 95 | 96 | 97 | ## Output 98 | 99 | 함수를 실행하고 반환해주는 데이터의 형식이다. 100 | 옵션에서 `outputType` 값에 맞는 형식으로 데이터가 리턴된다. 101 | 102 | - `base64`: base64 형식의 문자 103 | - `blob`: Blob 타입의 객체 104 | - `canvas`: canvas 엘리먼트 105 | 106 | 107 | ## Development 108 | 109 | 이 도구를 직접 수정할 수 있다. 110 | `/src`에 있는 소스를 수정하고 다음과 같이 cli 명령을 통하여 빌드할 수 있다. 111 | 112 | ``` 113 | // development 114 | npm run dev 115 | 116 | // production 117 | npm run build 118 | 119 | // preview 120 | npm run preview 121 | ``` 122 | -------------------------------------------------------------------------------- /docs/Pretendard-Black.subset-CVWqp8MN.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-Black.subset-CVWqp8MN.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-Bold.subset-C7miX2zx.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-Bold.subset-C7miX2zx.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-ExtraBold.subset-C2-BvFUE.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-ExtraBold.subset-C2-BvFUE.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-ExtraLight.subset-BOZr4nRl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-ExtraLight.subset-BOZr4nRl.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-Light.subset-B94xHKuS.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-Light.subset-B94xHKuS.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-Medium.subset-Y06xfiVw.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-Medium.subset-Y06xfiVw.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-Regular.subset-ZBqo5aGF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-Regular.subset-ZBqo5aGF.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-SemiBold.subset-ZPzDHilX.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-SemiBold.subset-ZPzDHilX.woff2 -------------------------------------------------------------------------------- /docs/Pretendard-Thin.subset-DWc8mLUC.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/Pretendard-Thin.subset-DWc8mLUC.woff2 -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index-C0EeogAJ.js: -------------------------------------------------------------------------------- 1 | (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))l(n);new MutationObserver(n=>{for(const d of n)if(d.type==="childList")for(const i of d.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&l(i)}).observe(document,{childList:!0,subtree:!0});function t(n){const d={};return n.integrity&&(d.integrity=n.integrity),n.referrerPolicy&&(d.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?d.credentials="include":n.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function l(n){if(n.ep)return;n.ep=!0;const d=t(n);fetch(n.href,d)}})();const f="1.4.2",Y="KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IG89e1NUQVJUOiJTVEFSVCIsRU5EOiJFTkQiLE5FV19DQU5WQVM6Ik5FV19DQU5WQVMiLEVSUk9SOiJFUlJPUiJ9LFQ9e2NhbnZhczpudWxsLHJlU2FtcGxlOjIsd2lkdGg6MzIwLGhlaWdodDoyNDAsY3g6MCxjeTowLGN3OjAsY2g6MCxkeDowLGR5OjAsZHc6MCxkaDowLGJnQ29sb3I6InRyYW5zcGFyZW50In07YXN5bmMgZnVuY3Rpb24gRShlKXtyZXR1cm4gYXdhaXQoYXdhaXQgZmV0Y2goZSkpLmJsb2IoKX1mdW5jdGlvbiBrKGUpe3JldHVybiBuZXcgUHJvbWlzZSgodCxhKT0+e2NvbnN0IG49bmV3IEZpbGVSZWFkZXI7bi5vbmxvYWQ9cj0+e3ZhciBjO3JldHVybiB0KChjPXIudGFyZ2V0KT09bnVsbD92b2lkIDA6Yy5yZXN1bHQpfSxuLm9uZXJyb3I9YSxuLnJlYWRBc0RhdGFVUkwoZSl9KX1mdW5jdGlvbiBJKGUpe2NvbnN0IHQ9YXRvYihlLnNwbGl0KCIsIilbMV0pLGE9ZS5tYXRjaCgvW146XVx3K1wvW1x3LStcZC5dKyg/PTt8LCkvKVswXSxuPXQubGVuZ3RoLHI9bmV3IFVpbnQ4QXJyYXkobik7Zm9yKGxldCBjPTA7YzxuO2MrKylyW2NdPXQuY2hhckNvZGVBdChjKTtyZXR1cm4gbmV3IEJsb2IoW3IuYnVmZmVyXSx7dHlwZTphfSl9ZnVuY3Rpb24gRChlLHQsYSxuKXtsZXQgcj1lLGM9dDtyZXR1cm4gYSYmbiYmKGE+bj9uPXZvaWQgMDphPXZvaWQgMCksYT8ocj1hLGM9dCooYS9lKSk6biYmKHI9ZSoobi90KSxjPW4pLHt3aWR0aDpOdW1iZXIociksaGVpZ2h0Ok51bWJlcihjKX19ZnVuY3Rpb24gTyhlLHQ9MCxhKXtpZih0PD0wKXJldHVybiBlO2NvbnN0IG49YS5nZXRDb250ZXh0KCIyZCIpO24uZHJhd0ltYWdlKGUsMCwwKTtjb25zdCByPW4uZ2V0SW1hZ2VEYXRhKDAsMCxhLndpZHRoLGEuaGVpZ2h0KSxjPW4uY3JlYXRlSW1hZ2VEYXRhKGEud2lkdGgsYS5oZWlnaHQpLGk9QihyLGMsWzAsLTEsMCwtMSw1LC0xLDAsLTEsMF0pO24ucHV0SW1hZ2VEYXRhKGksMCwwKTtjb25zdCBzPWUuZ2V0Q29udGV4dCgiMmQiKTtyZXR1cm4gcy5nbG9iYWxBbHBoYT10fHwxLHMuZHJhd0ltYWdlKGEsMCwwKSxlfWZ1bmN0aW9uIEIoZSx0LGEpe2NvbnN0IG49ZS5kYXRhLHI9dC5kYXRhLGM9ZS53aWR0aCxpPWUuaGVpZ2h0LHM9TWF0aC5yb3VuZChNYXRoLnNxcnQoYS5sZW5ndGgpKSxsPU1hdGguZmxvb3Iocy8yKSx5PWMsSz1pO2ZvcihsZXQgZD0wO2Q8SztkKyspZm9yKGxldCBoPTA7aDx5O2grKyl7bGV0IHA9MCxBPTAsTj0wLE09MDtmb3IobGV0IGY9MDtmPHM7ZisrKWZvcihsZXQgZz0wO2c8cztnKyspe2NvbnN0IHg9ZCtmLWwsUz1oK2ctbDtpZih4Pj0wJiZ4PGkmJlM+PTAmJlM8Yyl7bGV0IGI9KHgqYytTKSo0LG09YVtmKnMrZ107cCs9bltiXSptLEErPW5bYisxXSptLE4rPW5bYisyXSptLE0rPW5bYiszXSptfX1jb25zdCB3PShkKnkraCkqNDtyW3ddPXAsclt3KzFdPUEsclt3KzJdPU4sclt3KzNdPU19cmV0dXJuIHR9ZnVuY3Rpb24geihlLHQ9ImltYWdlL2pwZWciLGE9Ljc1KXtyZXR1cm4gdD1SKHQpLG5ldyBQcm9taXNlKGFzeW5jKG4scik9Pnt0cnl7Y29uc3QgYz1hd2FpdCBqKGUsdCxhKTtuKGMpfWNhdGNoKGMpe3IoYyl9fSl9ZnVuY3Rpb24gUChlLHQ9ImltYWdlL2pwZWciLGE9Ljc1KXtyZXR1cm4gdD1SKHQpLG5ldyBQcm9taXNlKGFzeW5jKG4scik9Pnt0cnl7Y29uc3QgYz1hd2FpdCBlLmNvbnZlcnRUb0Jsb2Ioe3R5cGU6dCxxdWFsaXR5OmF9KTtuKGMpfWNhdGNoKGMpe3IoYyl9fSl9ZnVuY3Rpb24gUihlKXtsZXQgdDtzd2l0Y2goZSl7Y2FzZSJqcGciOmNhc2UianBlZyI6dD0iaW1hZ2UvanBlZyI7YnJlYWs7Y2FzZSJwbmciOnQ9ImltYWdlL3BuZyI7YnJlYWs7Y2FzZSJ3ZWJwIjp0PSJpbWFnZS93ZWJwIjticmVhaztkZWZhdWx0OnQ9ZTticmVha31yZXR1cm4gdH1hc3luYyBmdW5jdGlvbiBqKGUsdCxhKXtjb25zdCBuPWF3YWl0IGUuY29udmVydFRvQmxvYih7dHlwZTp0LHF1YWxpdHk6YX0pLHI9bmV3IEZpbGVSZWFkZXI7cmV0dXJuIG5ldyBQcm9taXNlKGM9PntyLm9ubG9hZGVuZD0oKT0+YyhyLnJlc3VsdCksci5yZWFkQXNEYXRhVVJMKG4pfSl9bGV0IF89MDtjb25zdCBDPW5ldyBNYXA7b25tZXNzYWdlPWFzeW5jIGZ1bmN0aW9uKGUpe2NvbnN0e2tleTp0LGlkOmEsc3JjOm4sb3B0aW9uczpyfT1lLmRhdGE7dHJ5e2lmKGEhPT12b2lkIDApe2NvbnN0IGM9Qy5nZXQoYSk7YyYmYyhlLmRhdGEpfWVsc2Ugc3dpdGNoKHQpe2Nhc2Ugby5TVEFSVDpsZXQgYz1hd2FpdCBxKG4scik7Yz1hd2FpdCBVKGMsciksYz1hd2FpdCB2KGMsciksYXdhaXQgRyhjLHIpO2JyZWFrO2Nhc2Ugby5ORVdfQ0FOVkFTOmJyZWFrfX1jYXRjaChjKXtwb3N0TWVzc2FnZSh7a2V5Om8uRVJST1IsZXJyb3I6Y30pfX07ZnVuY3Rpb24gRihlLHQpe3JldHVybiBuZXcgUHJvbWlzZShhPT57Y29uc3Qgbj1fKys7Qy5zZXQobixhKSxwb3N0TWVzc2FnZSh7aWQ6bixrZXk6ZSwuLi50fSl9KX1hc3luYyBmdW5jdGlvbiBxKGUsdCl7aWYodHlwZW9mIGU9PSJzdHJpbmciKXtjb25zdCBhPWF3YWl0IEUoZSksbj1hd2FpdCBjcmVhdGVJbWFnZUJpdG1hcChhKSxyPWF3YWl0IHUobi53aWR0aCxuLmhlaWdodCx0LmJnQ29sb3IpO3JldHVybiByLmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKG4sMCwwKSxyfWVsc2UgaWYoZSBpbnN0YW5jZW9mIEZpbGV8fGUgaW5zdGFuY2VvZiBCbG9iKXtjb25zdCBhPWF3YWl0IGsoZSksbj1JKGEpLHI9YXdhaXQgY3JlYXRlSW1hZ2VCaXRtYXAobiksYz1hd2FpdCB1KHIud2lkdGgsci5oZWlnaHQsdC5iZ0NvbG9yKTtyZXR1cm4gYy5nZXRDb250ZXh0KCIyZCIpLmRyYXdJbWFnZShyLDAsMCksY319YXN5bmMgZnVuY3Rpb24gdShlPTMyMCx0PTI0MCxhPSJ0cmFuc3BhcmVudCIpe2NvbnN0IHI9KGF3YWl0IEYoby5ORVdfQ0FOVkFTLHt3aWR0aDplLGhlaWdodDp0fSkpLmNhbnZhcyxjPXIuZ2V0Q29udGV4dCgiMmQiKTtyZXR1cm4gYy5maWxsU3R5bGU9YSxjLmZpbGxSZWN0KDAsMCxlLHQpLHJ9YXN5bmMgZnVuY3Rpb24gVShlLHQpe2NvbnN0IGE9RChlLndpZHRoLGUuaGVpZ2h0LHQud2lkdGgsdC5oZWlnaHQpO3JldHVybiBhd2FpdCBWKHtjYW52YXM6ZSxyZVNhbXBsZTp0LnJlU2FtcGxlLHdpZHRoOmEud2lkdGgsaGVpZ2h0OmEuaGVpZ2h0LGN4OjAsY3k6MCxjdzplLndpZHRoLGNoOmUuaGVpZ2h0LGR4OjAsZHk6MCxkdzphLndpZHRoLGRoOmEuaGVpZ2h0LGJnQ29sb3I6dC5iZ0NvbG9yfSl9ZnVuY3Rpb24gVihlKXtlPU9iamVjdC5hc3NpZ24oe30sVCxlKSxlLnJlU2FtcGxlPU1hdGgubWluKDQsZS5yZVNhbXBsZSksZS5yZVNhbXBsZT1NYXRoLm1heCgwLGUucmVTYW1wbGUpO2NvbnN0IHQ9TWF0aC5wb3coMixlLnJlU2FtcGxlKTtyZXR1cm4gbmV3IFByb21pc2UoYXN5bmMoYSxuKT0+e3RyeXtjb25zdCByPWF3YWl0IHUoZS53aWR0aCp0LGUuaGVpZ2h0KnQsZS5iZ0NvbG9yKTtyLmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKGUuY2FudmFzLGUuY3gsZS5jeSxlLmN3LGUuY2gsZS5keCp0LGUuZHkqdCxlLmR3KnQsZS5kaCp0KSxlLnJlU2FtcGxlPjA/TChlLGUucmVTYW1wbGUscikudGhlbihhKTphKHIpfWNhdGNoKHIpe24ocil9fSl9ZnVuY3Rpb24gTChlLHQsYSl7cmV0dXJuIG5ldyBQcm9taXNlKG49Pnthc3luYyBmdW5jdGlvbiByKGMsaSl7Y29uc3Qgcz1NYXRoLnBvdygyLGMpLGw9YXdhaXQgdShlLndpZHRoKnMsZS5oZWlnaHQqcyxlLmJnQ29sb3IpO2wuZ2V0Q29udGV4dCgiMmQiKS5kcmF3SW1hZ2UoaSwwLDAsaS53aWR0aCouNSxpLmhlaWdodCouNSksYz4wP3IoYy0xLGwpLnRoZW4oKTpuKGwpfXIodC0xLGEpLnRoZW4oKX0pfWFzeW5jIGZ1bmN0aW9uIHYoZSx0KXtjb25zdCBhPWlzTmFOKHQ9PW51bGw/dm9pZCAwOnQuc2hhcnBlbik/MDp0LnNoYXJwZW47aWYoYTw9MClyZXR1cm4gZTtjb25zdCBuPWF3YWl0IHUoZS53aWR0aCxlLmhlaWdodCx0LmJnQ29sb3IpO3JldHVybiBPKGUsYSxuKX1hc3luYyBmdW5jdGlvbiBHKGUsdCl7bGV0IGE7c3dpdGNoKHQub3V0cHV0VHlwZSl7Y2FzZSJiYXNlNjQiOmE9YXdhaXQgeihlLHQuZm9ybWF0LHQucXVhbGl0eSkscG9zdE1lc3NhZ2Uoe2tleTpvLkVORCxvdXRwdXQ6YX0pO2JyZWFrO2Nhc2UiYmxvYiI6YT1hd2FpdCBQKGUsdC5mb3JtYXQsdC5xdWFsaXR5KSxwb3N0TWVzc2FnZSh7a2V5Om8uRU5ELG91dHB1dDphfSk7YnJlYWs7Y2FzZSJjYW52YXMiOmRlZmF1bHQ6Y29uc3Qgbj1lLnRyYW5zZmVyVG9JbWFnZUJpdG1hcCgpO3Bvc3RNZXNzYWdlKHtrZXk6by5FTkQsb3V0cHV0Om59LFtuXSk7YnJlYWt9fX0pKCk7Cg==",S=e=>Uint8Array.from(atob(e),t=>t.charCodeAt(0)),p=typeof window<"u"&&window.Blob&&new Blob([S(Y)],{type:"text/javascript;charset=utf-8"});function X(e){let t;try{if(t=p&&(window.URL||window.webkitURL).createObjectURL(p),!t)throw"";const l=new Worker(t,{name:e==null?void 0:e.name});return l.addEventListener("error",()=>{(window.URL||window.webkitURL).revokeObjectURL(t)}),l}catch{return new Worker("data:text/javascript;base64,"+Y,{name:e==null?void 0:e.name})}finally{t&&(window.URL||window.webkitURL).revokeObjectURL(t)}}const o={START:"START",END:"END",NEW_CANVAS:"NEW_CANVAS",ERROR:"ERROR"},b={width:320,height:240,format:"jpg",outputType:"base64",quality:.75,reSample:2,sharpen:0,bgColor:"transparent"};function g(e={}){let t={};return Object.keys(b).forEach(l=>{t[l]=e[l]!==void 0?e[l]:b[l]}),e.width===void 0&&e.height===void 0?(t.width=b.width,t.height=void 0):e.width===void 0?(t.width=void 0,t.height=Number(t.height)||0):e.height===void 0?(t.width=Number(t.width)||0,t.height=void 0):(t.width=Number(t.width)||0,t.height=Number(t.height)||0),t.quality=Number(t.quality)||0,t.reSample=Number(t.reSample)||0,t}function K(e=void 0){if(typeof e!="string"){{if(e instanceof File||e instanceof Blob)return;if(e instanceof HTMLCanvasElement)return}throw new Error("Invalid image data.")}}function V(e){return new Promise((t,l)=>{e instanceof HTMLCanvasElement?e.toBlob(n=>t(n)):l(new Error("The provided element is not a Canvas."))})}function R(e,t=void 0){return new Promise(async(l,n)=>{const d=g(t),i=new X;i.onmessage=async r=>{const{id:W,key:y,output:c,error:L}=r.data;switch(y){case o.END:if(i&&i.terminate(),c instanceof ImageBitmap){const a=document.createElement("canvas"),h=a.getContext("2d");a.width=c.width,a.height=c.height,h.fillStyle=d.bgColor,h.fillRect(0,0,c.width,c.height),h.drawImage(c,0,0),l(a)}else l(c);i&&i.terminate();break;case o.ERROR:n(L),i&&i.terminate();break;case o.NEW_CANVAS:const Z=new OffscreenCanvas(r.data.width,r.data.height);i.postMessage({id:W,key:o.NEW_CANVAS,canvas:Z},[Z]);break}},K(e),e instanceof HTMLCanvasElement&&(e=await V(e)),i.postMessage({key:o.START,src:e,options:d})})}function C(e={}){return new Promise((t,l)=>{const n=document.createElement("input");n.type="file",e.accept&&(n.accept=e.accept),e.multiple===!0&&(n.multiple=!0),n.addEventListener("change",d=>{const i=Object.assign([],d.target.files);if(i.length<=0)return t([]);n.value=null,t(e.multiple===!0?i:i[0])}),n.addEventListener("cancel",()=>{t(e.multiple===!0?[]:null)}),n.click()})}function I(e){const t=["Bytes","KB","MB","GB","TB"];if(e===0)return"0 Byte";let l=Math.floor(Math.log(e)/Math.log(1024));return String(Math.round(e/Math.pow(1024,l)))+t[l]}const w=document.getElementById("form"),m=document.getElementById("result"),s=new Proxy({},{get:(e,t)=>e[t],set:(e,t,l)=>{switch(t){case"width":case"height":case"quality":case"reSample":case"sharpen":l=Number(l);break}return e[t]=l,!0}}),u={submit:document.querySelector(".form__submit > button[type=submit]"),fileUpload:document.getElementById("file-upload"),fileUploadInfo:document.getElementById("file-upload-info"),version:document.getElementById("version")};async function N(){const e=await C({accept:"image/*"});e&&(s.upload=e,u.fileUploadInfo.innerText=`${e.name} (${I(e.size)})`)}async function H(e){e.preventDefault(),G(!0);const l=e.target.querySelectorAll("[name]");for(const d of l)s[d.name]=d.value;let n;if(s.upload)n=s.upload;else if(s.url)n=s.url;else return alert("not found source"),!1;try{const d={...s};delete d.url,delete d.upload;const i=await R(n,d);Q(d.outputType,i)}catch(d){k(d)}finally{G(!1)}}function G(e){const t=u.submit;e?(t.setAttribute("disabled",""),t.innerText="처리중.."):(t.removeAttribute("disabled"),t.innerText="이미지 변환")}function Q(e,t){switch(m.innerHTML="",e){case"base64":const l=new Image;l.src=t,m.appendChild(l);break;case"canvas":m.appendChild(t);break;case"blob":default:console.log("RESULT:",t);break}}function k(e){console.error("[ERROR / IMAGE RESIZE]",e.message),alert(`Error resize: ${e.message}`)}w.addEventListener("submit",H);u.fileUpload.addEventListener("click",N);u.version.innerText=f; 2 | -------------------------------------------------------------------------------- /docs/index-DhP0pGVv.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Pretendard;font-weight:900;font-display:swap;src:local("Pretendard Black"),url(./Pretendard-Black.subset-CVWqp8MN.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:800;font-display:swap;src:local("Pretendard ExtraBold"),url(./Pretendard-ExtraBold.subset-C2-BvFUE.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:700;font-display:swap;src:local("Pretendard Bold"),url(./Pretendard-Bold.subset-C7miX2zx.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:600;font-display:swap;src:local("Pretendard SemiBold"),url(./Pretendard-SemiBold.subset-ZPzDHilX.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:500;font-display:swap;src:local("Pretendard Medium"),url(./Pretendard-Medium.subset-Y06xfiVw.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:400;font-display:swap;src:local("Pretendard Regular"),url(./Pretendard-Regular.subset-ZBqo5aGF.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:300;font-display:swap;src:local("Pretendard Light"),url(./Pretendard-Light.subset-B94xHKuS.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:200;font-display:swap;src:local("Pretendard ExtraLight"),url(./Pretendard-ExtraLight.subset-BOZr4nRl.woff2) format("woff2")}@font-face{font-family:Pretendard;font-weight:100;font-display:swap;src:local("Pretendard Thin"),url(./Pretendard-Thin.subset-DWc8mLUC.woff2) format("woff2")}:root{--color-base: #111111;--color-weak: #666666;--color-main: hsl(159deg 90% 42%);--color-shape: #f4f4f4;--color-field-line: hsl(0 0% 0% / 10%);--size-radius: 3px;--shadow-box: 0 1px 5px rgba(0, 0, 0, .061), 0 3px 18px rgba(0, 0, 0, .089), 0 12px 80px rgba(0, 0, 0, .15)}html{touch-action:manipulation}body{min-width:640px;margin:0;padding:0 50px 24px;-webkit-text-size-adjust:none;color:var(--color-base);font-size:16px;line-height:1.6}body,button,input,textarea,select{font-family:Pretendard,-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol;-webkit-text-size-adjust:100%;text-size-adjust:none;color:var(--color-base)}main{margin:0 auto;max-width:1000px}.header{padding:42px 0 24px;text-align:center;-webkit-user-select:none;user-select:none}.header>h1{margin:0;font-size:36px;color:var(--color-main);letter-spacing:-.5px;font-weight:900;line-height:1;text-transform:uppercase;-webkit-user-select:none;user-select:none}.header>p{margin:4px 0 0;font-size:13px;line-height:1.15;color:var(--color-weak)}.form{margin:0;padding:24px;background:#fff;box-shadow:var(--shadow-box);border-radius:4px}.form .form__fieldset{margin:0;padding:0;border:none}.form .form__fieldset>legend{font-size:0}.form .form__body{display:grid;margin:0;gap:0 0}.form .form__submit{display:flex;margin:20px 0 0;justify-content:center}.form .form__submit .button{width:160px;height:36px}.box-title{margin:0 0 8px;font-size:18px;font-weight:700;line-height:1.05;letter-spacing:-.5px;-webkit-user-select:none;user-select:none}.field{margin:0;padding:10px 12px;display:grid;grid-template-columns:120px 1fr;align-items:center;border-bottom:1px solid var(--color-field-line)}.field:first-child{border-top:1px solid var(--color-field-line)}.field .field__label{margin:0;line-height:1.15;font-weight:600;font-size:12px;-webkit-user-select:none;user-select:none}.field .field__body{margin:0}.field .field__description{margin:5px 0 0;font-size:11px;color:var(--color-weak);font-weight:400;line-height:1.15}.field-inline{display:grid;align-items:center;grid-template-columns:120px auto;gap:0 8px}.field-inline>span{display:block;font-size:12px;line-height:1}.field-inlines{display:flex;flex-direction:row;flex-wrap:wrap;gap:16px}.field-inlines+.field-inlines{margin-top:8px}.field-inlines .field-inline{grid-template-columns:auto auto;gap:8px}.input-text{display:block;margin:0;padding:0 10px;height:32px;font-size:13px;border-radius:var(--size-radius);border:1px solid #bbb;background:#fff;outline:none;box-sizing:border-box}.input-text.input-text--block{width:100%}.input-select{position:relative;display:inline-block}.input-select>select{display:inline-block;margin:0;padding:0 10px;height:32px;border:1px solid #bbb;font-size:13px;border-radius:var(--size-radius);background:#fff;min-width:100px;outline:none;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;appearance:none}.input-select:after{content:"";display:block;position:absolute;right:6px;top:50%;width:16px;aspect-ratio:1/1;translate:0 -50%;pointer-events:none;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtY2hldnJvbi1kb3duIj48cGF0aCBkPSJtNiA5IDYgNiA2LTYiLz48L3N2Zz4=);background-size:100%}.input-file{display:grid;margin:0;padding:0 8px;height:32px;border:1px solid #bbb;background:#fff;font-size:13px;border-radius:var(--size-radius);outline:none;box-sizing:border-box}.input-file--block{width:100%}.input-text:focus-visible,.input-file:focus-visible,.input-select:focus-visible{box-shadow:0 0 0 2px var(--color-main)}.result{margin:48px 0 0;padding:24px 24px 32px;background:#fff;box-shadow:var(--shadow-box);border-radius:4px}.result .box-title{margin:0 0 16px}.result .result__image{margin:0}.result .result__image>*{display:block;margin:0 auto;max-width:100%}.result .result__image:empty:after{display:grid;place-content:center;height:120px;content:"...";background:#0000000a;font-size:13px;color:var(--color-weak)}.button{display:block;margin:0;padding:0;border:none;background:var(--color-main);color:#fff;font-size:15px;font-weight:800;letter-spacing:-.5px;cursor:pointer;outline:none;border-radius:var(--size-radius);transition:box-shadow .2s ease-out;-webkit-user-select:none;user-select:none;box-sizing:border-box}.button:disabled{opacity:.5;cursor:not-allowed}.button:where(:active,:focus-visible){box-shadow:0 0 0 4px #00000026}.file-upload{display:flex;align-items:center;gap:0 12px}.file-upload button{width:100px;height:28px;font-size:13px;font-weight:600}.file-upload p{margin:0;font-size:12px;line-height:1.15;font-weight:700;-webkit-user-select:none;user-select:none}.file-upload p:empty:before{content:"업로드한 파일이 없습니다.";font-weight:500;color:var(--color-weak)}.footer{margin:52px 0 0;text-align:center;-webkit-user-select:none;user-select:none}.footer>.info{margin:0;font-size:13px;line-height:1.05;font-weight:600}.footer>.info em{font-style:normal}.footer>.copyright{margin:4px 0 0;color:var(--color-weak);font-size:10px;line-height:1.05}.github-corner{display:block;position:fixed;right:0;top:0}.github-corner>svg{position:absolute;top:0;border:0;right:0;fill:#222;color:#fff}.github-corner .octo-arm{transform-origin:130px 106px}.github-corner:hover .octo-arm{animation:octocat-wave .56s ease-in-out}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave .56s ease-in-out}}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}} 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Image Resize demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

이미지 리사이즈

16 |

이미지 크기 조절을 할 수 있는 자바스크립트 오픈소스 패키지입니다.

17 |
18 |
19 |
20 | 설정 옵션들 21 |

리사이즈 옵션

22 |
23 |
24 |

25 | 26 |

27 |
28 | 35 |

36 | 이미지 URL로 이미지 리사이즈 합니다. 37 |

38 |
39 |
40 |
41 |

42 | 43 |

44 |
45 |
46 | 49 |

50 |
51 |

52 | 선택한 이미지로 리사이즈 합니다. 이 폼에서 선택한 이미지가 우선으로 사용됩니다. 53 |

54 |
55 |
56 |
57 |

58 | 59 |

60 |
61 |
62 | 73 | 83 |
84 |
85 |
86 |
87 |

88 | 89 |

90 |
91 |
92 | 102 | 112 |
113 |
114 | 124 | 136 |
137 |
138 |
139 |
140 |

141 | 142 |

143 |
144 | 153 |
154 |
155 |
156 |
157 | 160 |
161 |
162 |

결과물

163 |
164 |
165 |
166 |

Version: , MIT

167 | 168 |
169 |
170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /libs/ImageResize.es.js: -------------------------------------------------------------------------------- 1 | const W = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IG89e1NUQVJUOiJTVEFSVCIsRU5EOiJFTkQiLE5FV19DQU5WQVM6Ik5FV19DQU5WQVMiLEVSUk9SOiJFUlJPUiJ9LFQ9e2NhbnZhczpudWxsLHJlU2FtcGxlOjIsd2lkdGg6MzIwLGhlaWdodDoyNDAsY3g6MCxjeTowLGN3OjAsY2g6MCxkeDowLGR5OjAsZHc6MCxkaDowLGJnQ29sb3I6InRyYW5zcGFyZW50In07YXN5bmMgZnVuY3Rpb24gRShlKXtyZXR1cm4gYXdhaXQoYXdhaXQgZmV0Y2goZSkpLmJsb2IoKX1mdW5jdGlvbiBrKGUpe3JldHVybiBuZXcgUHJvbWlzZSgodCxhKT0+e2NvbnN0IG49bmV3IEZpbGVSZWFkZXI7bi5vbmxvYWQ9cj0+e3ZhciBjO3JldHVybiB0KChjPXIudGFyZ2V0KT09bnVsbD92b2lkIDA6Yy5yZXN1bHQpfSxuLm9uZXJyb3I9YSxuLnJlYWRBc0RhdGFVUkwoZSl9KX1mdW5jdGlvbiBJKGUpe2NvbnN0IHQ9YXRvYihlLnNwbGl0KCIsIilbMV0pLGE9ZS5tYXRjaCgvW146XVx3K1wvW1x3LStcZC5dKyg/PTt8LCkvKVswXSxuPXQubGVuZ3RoLHI9bmV3IFVpbnQ4QXJyYXkobik7Zm9yKGxldCBjPTA7YzxuO2MrKylyW2NdPXQuY2hhckNvZGVBdChjKTtyZXR1cm4gbmV3IEJsb2IoW3IuYnVmZmVyXSx7dHlwZTphfSl9ZnVuY3Rpb24gRChlLHQsYSxuKXtsZXQgcj1lLGM9dDtyZXR1cm4gYSYmbiYmKGE+bj9uPXZvaWQgMDphPXZvaWQgMCksYT8ocj1hLGM9dCooYS9lKSk6biYmKHI9ZSoobi90KSxjPW4pLHt3aWR0aDpOdW1iZXIociksaGVpZ2h0Ok51bWJlcihjKX19ZnVuY3Rpb24gTyhlLHQ9MCxhKXtpZih0PD0wKXJldHVybiBlO2NvbnN0IG49YS5nZXRDb250ZXh0KCIyZCIpO24uZHJhd0ltYWdlKGUsMCwwKTtjb25zdCByPW4uZ2V0SW1hZ2VEYXRhKDAsMCxhLndpZHRoLGEuaGVpZ2h0KSxjPW4uY3JlYXRlSW1hZ2VEYXRhKGEud2lkdGgsYS5oZWlnaHQpLGk9QihyLGMsWzAsLTEsMCwtMSw1LC0xLDAsLTEsMF0pO24ucHV0SW1hZ2VEYXRhKGksMCwwKTtjb25zdCBzPWUuZ2V0Q29udGV4dCgiMmQiKTtyZXR1cm4gcy5nbG9iYWxBbHBoYT10fHwxLHMuZHJhd0ltYWdlKGEsMCwwKSxlfWZ1bmN0aW9uIEIoZSx0LGEpe2NvbnN0IG49ZS5kYXRhLHI9dC5kYXRhLGM9ZS53aWR0aCxpPWUuaGVpZ2h0LHM9TWF0aC5yb3VuZChNYXRoLnNxcnQoYS5sZW5ndGgpKSxsPU1hdGguZmxvb3Iocy8yKSx5PWMsSz1pO2ZvcihsZXQgZD0wO2Q8SztkKyspZm9yKGxldCBoPTA7aDx5O2grKyl7bGV0IHA9MCxBPTAsTj0wLE09MDtmb3IobGV0IGY9MDtmPHM7ZisrKWZvcihsZXQgZz0wO2c8cztnKyspe2NvbnN0IHg9ZCtmLWwsUz1oK2ctbDtpZih4Pj0wJiZ4PGkmJlM+PTAmJlM8Yyl7bGV0IGI9KHgqYytTKSo0LG09YVtmKnMrZ107cCs9bltiXSptLEErPW5bYisxXSptLE4rPW5bYisyXSptLE0rPW5bYiszXSptfX1jb25zdCB3PShkKnkraCkqNDtyW3ddPXAsclt3KzFdPUEsclt3KzJdPU4sclt3KzNdPU19cmV0dXJuIHR9ZnVuY3Rpb24geihlLHQ9ImltYWdlL2pwZWciLGE9Ljc1KXtyZXR1cm4gdD1SKHQpLG5ldyBQcm9taXNlKGFzeW5jKG4scik9Pnt0cnl7Y29uc3QgYz1hd2FpdCBqKGUsdCxhKTtuKGMpfWNhdGNoKGMpe3IoYyl9fSl9ZnVuY3Rpb24gUChlLHQ9ImltYWdlL2pwZWciLGE9Ljc1KXtyZXR1cm4gdD1SKHQpLG5ldyBQcm9taXNlKGFzeW5jKG4scik9Pnt0cnl7Y29uc3QgYz1hd2FpdCBlLmNvbnZlcnRUb0Jsb2Ioe3R5cGU6dCxxdWFsaXR5OmF9KTtuKGMpfWNhdGNoKGMpe3IoYyl9fSl9ZnVuY3Rpb24gUihlKXtsZXQgdDtzd2l0Y2goZSl7Y2FzZSJqcGciOmNhc2UianBlZyI6dD0iaW1hZ2UvanBlZyI7YnJlYWs7Y2FzZSJwbmciOnQ9ImltYWdlL3BuZyI7YnJlYWs7Y2FzZSJ3ZWJwIjp0PSJpbWFnZS93ZWJwIjticmVhaztkZWZhdWx0OnQ9ZTticmVha31yZXR1cm4gdH1hc3luYyBmdW5jdGlvbiBqKGUsdCxhKXtjb25zdCBuPWF3YWl0IGUuY29udmVydFRvQmxvYih7dHlwZTp0LHF1YWxpdHk6YX0pLHI9bmV3IEZpbGVSZWFkZXI7cmV0dXJuIG5ldyBQcm9taXNlKGM9PntyLm9ubG9hZGVuZD0oKT0+YyhyLnJlc3VsdCksci5yZWFkQXNEYXRhVVJMKG4pfSl9bGV0IF89MDtjb25zdCBDPW5ldyBNYXA7b25tZXNzYWdlPWFzeW5jIGZ1bmN0aW9uKGUpe2NvbnN0e2tleTp0LGlkOmEsc3JjOm4sb3B0aW9uczpyfT1lLmRhdGE7dHJ5e2lmKGEhPT12b2lkIDApe2NvbnN0IGM9Qy5nZXQoYSk7YyYmYyhlLmRhdGEpfWVsc2Ugc3dpdGNoKHQpe2Nhc2Ugby5TVEFSVDpsZXQgYz1hd2FpdCBxKG4scik7Yz1hd2FpdCBVKGMsciksYz1hd2FpdCB2KGMsciksYXdhaXQgRyhjLHIpO2JyZWFrO2Nhc2Ugby5ORVdfQ0FOVkFTOmJyZWFrfX1jYXRjaChjKXtwb3N0TWVzc2FnZSh7a2V5Om8uRVJST1IsZXJyb3I6Y30pfX07ZnVuY3Rpb24gRihlLHQpe3JldHVybiBuZXcgUHJvbWlzZShhPT57Y29uc3Qgbj1fKys7Qy5zZXQobixhKSxwb3N0TWVzc2FnZSh7aWQ6bixrZXk6ZSwuLi50fSl9KX1hc3luYyBmdW5jdGlvbiBxKGUsdCl7aWYodHlwZW9mIGU9PSJzdHJpbmciKXtjb25zdCBhPWF3YWl0IEUoZSksbj1hd2FpdCBjcmVhdGVJbWFnZUJpdG1hcChhKSxyPWF3YWl0IHUobi53aWR0aCxuLmhlaWdodCx0LmJnQ29sb3IpO3JldHVybiByLmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKG4sMCwwKSxyfWVsc2UgaWYoZSBpbnN0YW5jZW9mIEZpbGV8fGUgaW5zdGFuY2VvZiBCbG9iKXtjb25zdCBhPWF3YWl0IGsoZSksbj1JKGEpLHI9YXdhaXQgY3JlYXRlSW1hZ2VCaXRtYXAobiksYz1hd2FpdCB1KHIud2lkdGgsci5oZWlnaHQsdC5iZ0NvbG9yKTtyZXR1cm4gYy5nZXRDb250ZXh0KCIyZCIpLmRyYXdJbWFnZShyLDAsMCksY319YXN5bmMgZnVuY3Rpb24gdShlPTMyMCx0PTI0MCxhPSJ0cmFuc3BhcmVudCIpe2NvbnN0IHI9KGF3YWl0IEYoby5ORVdfQ0FOVkFTLHt3aWR0aDplLGhlaWdodDp0fSkpLmNhbnZhcyxjPXIuZ2V0Q29udGV4dCgiMmQiKTtyZXR1cm4gYy5maWxsU3R5bGU9YSxjLmZpbGxSZWN0KDAsMCxlLHQpLHJ9YXN5bmMgZnVuY3Rpb24gVShlLHQpe2NvbnN0IGE9RChlLndpZHRoLGUuaGVpZ2h0LHQud2lkdGgsdC5oZWlnaHQpO3JldHVybiBhd2FpdCBWKHtjYW52YXM6ZSxyZVNhbXBsZTp0LnJlU2FtcGxlLHdpZHRoOmEud2lkdGgsaGVpZ2h0OmEuaGVpZ2h0LGN4OjAsY3k6MCxjdzplLndpZHRoLGNoOmUuaGVpZ2h0LGR4OjAsZHk6MCxkdzphLndpZHRoLGRoOmEuaGVpZ2h0LGJnQ29sb3I6dC5iZ0NvbG9yfSl9ZnVuY3Rpb24gVihlKXtlPU9iamVjdC5hc3NpZ24oe30sVCxlKSxlLnJlU2FtcGxlPU1hdGgubWluKDQsZS5yZVNhbXBsZSksZS5yZVNhbXBsZT1NYXRoLm1heCgwLGUucmVTYW1wbGUpO2NvbnN0IHQ9TWF0aC5wb3coMixlLnJlU2FtcGxlKTtyZXR1cm4gbmV3IFByb21pc2UoYXN5bmMoYSxuKT0+e3RyeXtjb25zdCByPWF3YWl0IHUoZS53aWR0aCp0LGUuaGVpZ2h0KnQsZS5iZ0NvbG9yKTtyLmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKGUuY2FudmFzLGUuY3gsZS5jeSxlLmN3LGUuY2gsZS5keCp0LGUuZHkqdCxlLmR3KnQsZS5kaCp0KSxlLnJlU2FtcGxlPjA/TChlLGUucmVTYW1wbGUscikudGhlbihhKTphKHIpfWNhdGNoKHIpe24ocil9fSl9ZnVuY3Rpb24gTChlLHQsYSl7cmV0dXJuIG5ldyBQcm9taXNlKG49Pnthc3luYyBmdW5jdGlvbiByKGMsaSl7Y29uc3Qgcz1NYXRoLnBvdygyLGMpLGw9YXdhaXQgdShlLndpZHRoKnMsZS5oZWlnaHQqcyxlLmJnQ29sb3IpO2wuZ2V0Q29udGV4dCgiMmQiKS5kcmF3SW1hZ2UoaSwwLDAsaS53aWR0aCouNSxpLmhlaWdodCouNSksYz4wP3IoYy0xLGwpLnRoZW4oKTpuKGwpfXIodC0xLGEpLnRoZW4oKX0pfWFzeW5jIGZ1bmN0aW9uIHYoZSx0KXtjb25zdCBhPWlzTmFOKHQ9PW51bGw/dm9pZCAwOnQuc2hhcnBlbik/MDp0LnNoYXJwZW47aWYoYTw9MClyZXR1cm4gZTtjb25zdCBuPWF3YWl0IHUoZS53aWR0aCxlLmhlaWdodCx0LmJnQ29sb3IpO3JldHVybiBPKGUsYSxuKX1hc3luYyBmdW5jdGlvbiBHKGUsdCl7bGV0IGE7c3dpdGNoKHQub3V0cHV0VHlwZSl7Y2FzZSJiYXNlNjQiOmE9YXdhaXQgeihlLHQuZm9ybWF0LHQucXVhbGl0eSkscG9zdE1lc3NhZ2Uoe2tleTpvLkVORCxvdXRwdXQ6YX0pO2JyZWFrO2Nhc2UiYmxvYiI6YT1hd2FpdCBQKGUsdC5mb3JtYXQsdC5xdWFsaXR5KSxwb3N0TWVzc2FnZSh7a2V5Om8uRU5ELG91dHB1dDphfSk7YnJlYWs7Y2FzZSJjYW52YXMiOmRlZmF1bHQ6Y29uc3Qgbj1lLnRyYW5zZmVyVG9JbWFnZUJpdG1hcCgpO3Bvc3RNZXNzYWdlKHtrZXk6by5FTkQsb3V0cHV0Om59LFtuXSk7YnJlYWt9fX0pKCk7Cg==", u = (l) => Uint8Array.from(atob(l), (d) => d.charCodeAt(0)), s = typeof window < "u" && window.Blob && new Blob([u(W)], { type: "text/javascript;charset=utf-8" }); 2 | function L(l) { 3 | let d; 4 | try { 5 | if (d = s && (window.URL || window.webkitURL).createObjectURL(s), !d) 6 | throw ""; 7 | const e = new Worker(d, { 8 | name: l == null ? void 0 : l.name 9 | }); 10 | return e.addEventListener("error", () => { 11 | (window.URL || window.webkitURL).revokeObjectURL(d); 12 | }), e; 13 | } catch { 14 | return new Worker( 15 | "data:text/javascript;base64," + W, 16 | { 17 | name: l == null ? void 0 : l.name 18 | } 19 | ); 20 | } finally { 21 | d && (window.URL || window.webkitURL).revokeObjectURL(d); 22 | } 23 | } 24 | const Z = { 25 | START: "START", 26 | END: "END", 27 | NEW_CANVAS: "NEW_CANVAS", 28 | ERROR: "ERROR" 29 | }, G = { 30 | width: 320, 31 | height: 240, 32 | format: "jpg", 33 | // png,jpg,webp 34 | outputType: "base64", 35 | // base64,canvas,blob 36 | quality: 0.75, 37 | reSample: 2, 38 | sharpen: 0, 39 | bgColor: "transparent" 40 | }; 41 | function X(l = {}) { 42 | let d = {}; 43 | return Object.keys(G).forEach((e) => { 44 | d[e] = l[e] !== void 0 ? l[e] : G[e]; 45 | }), l.width === void 0 && l.height === void 0 ? (d.width = G.width, d.height = void 0) : l.width === void 0 ? (d.width = void 0, d.height = Number(d.height) || 0) : l.height === void 0 ? (d.width = Number(d.width) || 0, d.height = void 0) : (d.width = Number(d.width) || 0, d.height = Number(d.height) || 0), d.quality = Number(d.quality) || 0, d.reSample = Number(d.reSample) || 0, d; 46 | } 47 | function S(l = void 0) { 48 | if (typeof l != "string") { 49 | { 50 | if (l instanceof File || l instanceof Blob) 51 | return; 52 | if (l instanceof HTMLCanvasElement) 53 | return; 54 | } 55 | throw new Error("Invalid image data."); 56 | } 57 | } 58 | function K(l) { 59 | return new Promise((d, e) => { 60 | l instanceof HTMLCanvasElement ? l.toBlob((b) => d(b)) : e(new Error("The provided element is not a Canvas.")); 61 | }); 62 | } 63 | function V(l, d = void 0) { 64 | return new Promise(async (e, b) => { 65 | const Y = X(d), n = new L(); 66 | n.onmessage = async (i) => { 67 | const { id: o, key: m, output: t, error: p } = i.data; 68 | switch (m) { 69 | case Z.END: 70 | if (n && n.terminate(), t instanceof ImageBitmap) { 71 | const h = document.createElement("canvas"), c = h.getContext("2d"); 72 | h.width = t.width, h.height = t.height, c.fillStyle = Y.bgColor, c.fillRect(0, 0, t.width, t.height), c.drawImage(t, 0, 0), e(h); 73 | } else 74 | e(t); 75 | n && n.terminate(); 76 | break; 77 | case Z.ERROR: 78 | b(p), n && n.terminate(); 79 | break; 80 | case Z.NEW_CANVAS: 81 | const a = new OffscreenCanvas(i.data.width, i.data.height); 82 | n.postMessage({ 83 | id: o, 84 | key: Z.NEW_CANVAS, 85 | canvas: a 86 | }, [a]); 87 | break; 88 | } 89 | }, S(l), l instanceof HTMLCanvasElement && (l = await K(l)), n.postMessage({ 90 | key: Z.START, 91 | src: l, 92 | options: Y 93 | }); 94 | }); 95 | } 96 | export { 97 | V as default 98 | }; 99 | -------------------------------------------------------------------------------- /libs/ImageResize.umd.js: -------------------------------------------------------------------------------- 1 | (function(i,Z){typeof exports=="object"&&typeof module<"u"?module.exports=Z():typeof define=="function"&&define.amd?define(Z):(i=typeof globalThis<"u"?globalThis:i||self,i.imageResize=Z())})(this,function(){"use strict";const i="KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IG89e1NUQVJUOiJTVEFSVCIsRU5EOiJFTkQiLE5FV19DQU5WQVM6Ik5FV19DQU5WQVMiLEVSUk9SOiJFUlJPUiJ9LFQ9e2NhbnZhczpudWxsLHJlU2FtcGxlOjIsd2lkdGg6MzIwLGhlaWdodDoyNDAsY3g6MCxjeTowLGN3OjAsY2g6MCxkeDowLGR5OjAsZHc6MCxkaDowLGJnQ29sb3I6InRyYW5zcGFyZW50In07YXN5bmMgZnVuY3Rpb24gRShlKXtyZXR1cm4gYXdhaXQoYXdhaXQgZmV0Y2goZSkpLmJsb2IoKX1mdW5jdGlvbiBrKGUpe3JldHVybiBuZXcgUHJvbWlzZSgodCxhKT0+e2NvbnN0IG49bmV3IEZpbGVSZWFkZXI7bi5vbmxvYWQ9cj0+e3ZhciBjO3JldHVybiB0KChjPXIudGFyZ2V0KT09bnVsbD92b2lkIDA6Yy5yZXN1bHQpfSxuLm9uZXJyb3I9YSxuLnJlYWRBc0RhdGFVUkwoZSl9KX1mdW5jdGlvbiBJKGUpe2NvbnN0IHQ9YXRvYihlLnNwbGl0KCIsIilbMV0pLGE9ZS5tYXRjaCgvW146XVx3K1wvW1x3LStcZC5dKyg/PTt8LCkvKVswXSxuPXQubGVuZ3RoLHI9bmV3IFVpbnQ4QXJyYXkobik7Zm9yKGxldCBjPTA7YzxuO2MrKylyW2NdPXQuY2hhckNvZGVBdChjKTtyZXR1cm4gbmV3IEJsb2IoW3IuYnVmZmVyXSx7dHlwZTphfSl9ZnVuY3Rpb24gRChlLHQsYSxuKXtsZXQgcj1lLGM9dDtyZXR1cm4gYSYmbiYmKGE+bj9uPXZvaWQgMDphPXZvaWQgMCksYT8ocj1hLGM9dCooYS9lKSk6biYmKHI9ZSoobi90KSxjPW4pLHt3aWR0aDpOdW1iZXIociksaGVpZ2h0Ok51bWJlcihjKX19ZnVuY3Rpb24gTyhlLHQ9MCxhKXtpZih0PD0wKXJldHVybiBlO2NvbnN0IG49YS5nZXRDb250ZXh0KCIyZCIpO24uZHJhd0ltYWdlKGUsMCwwKTtjb25zdCByPW4uZ2V0SW1hZ2VEYXRhKDAsMCxhLndpZHRoLGEuaGVpZ2h0KSxjPW4uY3JlYXRlSW1hZ2VEYXRhKGEud2lkdGgsYS5oZWlnaHQpLGk9QihyLGMsWzAsLTEsMCwtMSw1LC0xLDAsLTEsMF0pO24ucHV0SW1hZ2VEYXRhKGksMCwwKTtjb25zdCBzPWUuZ2V0Q29udGV4dCgiMmQiKTtyZXR1cm4gcy5nbG9iYWxBbHBoYT10fHwxLHMuZHJhd0ltYWdlKGEsMCwwKSxlfWZ1bmN0aW9uIEIoZSx0LGEpe2NvbnN0IG49ZS5kYXRhLHI9dC5kYXRhLGM9ZS53aWR0aCxpPWUuaGVpZ2h0LHM9TWF0aC5yb3VuZChNYXRoLnNxcnQoYS5sZW5ndGgpKSxsPU1hdGguZmxvb3Iocy8yKSx5PWMsSz1pO2ZvcihsZXQgZD0wO2Q8SztkKyspZm9yKGxldCBoPTA7aDx5O2grKyl7bGV0IHA9MCxBPTAsTj0wLE09MDtmb3IobGV0IGY9MDtmPHM7ZisrKWZvcihsZXQgZz0wO2c8cztnKyspe2NvbnN0IHg9ZCtmLWwsUz1oK2ctbDtpZih4Pj0wJiZ4PGkmJlM+PTAmJlM8Yyl7bGV0IGI9KHgqYytTKSo0LG09YVtmKnMrZ107cCs9bltiXSptLEErPW5bYisxXSptLE4rPW5bYisyXSptLE0rPW5bYiszXSptfX1jb25zdCB3PShkKnkraCkqNDtyW3ddPXAsclt3KzFdPUEsclt3KzJdPU4sclt3KzNdPU19cmV0dXJuIHR9ZnVuY3Rpb24geihlLHQ9ImltYWdlL2pwZWciLGE9Ljc1KXtyZXR1cm4gdD1SKHQpLG5ldyBQcm9taXNlKGFzeW5jKG4scik9Pnt0cnl7Y29uc3QgYz1hd2FpdCBqKGUsdCxhKTtuKGMpfWNhdGNoKGMpe3IoYyl9fSl9ZnVuY3Rpb24gUChlLHQ9ImltYWdlL2pwZWciLGE9Ljc1KXtyZXR1cm4gdD1SKHQpLG5ldyBQcm9taXNlKGFzeW5jKG4scik9Pnt0cnl7Y29uc3QgYz1hd2FpdCBlLmNvbnZlcnRUb0Jsb2Ioe3R5cGU6dCxxdWFsaXR5OmF9KTtuKGMpfWNhdGNoKGMpe3IoYyl9fSl9ZnVuY3Rpb24gUihlKXtsZXQgdDtzd2l0Y2goZSl7Y2FzZSJqcGciOmNhc2UianBlZyI6dD0iaW1hZ2UvanBlZyI7YnJlYWs7Y2FzZSJwbmciOnQ9ImltYWdlL3BuZyI7YnJlYWs7Y2FzZSJ3ZWJwIjp0PSJpbWFnZS93ZWJwIjticmVhaztkZWZhdWx0OnQ9ZTticmVha31yZXR1cm4gdH1hc3luYyBmdW5jdGlvbiBqKGUsdCxhKXtjb25zdCBuPWF3YWl0IGUuY29udmVydFRvQmxvYih7dHlwZTp0LHF1YWxpdHk6YX0pLHI9bmV3IEZpbGVSZWFkZXI7cmV0dXJuIG5ldyBQcm9taXNlKGM9PntyLm9ubG9hZGVuZD0oKT0+YyhyLnJlc3VsdCksci5yZWFkQXNEYXRhVVJMKG4pfSl9bGV0IF89MDtjb25zdCBDPW5ldyBNYXA7b25tZXNzYWdlPWFzeW5jIGZ1bmN0aW9uKGUpe2NvbnN0e2tleTp0LGlkOmEsc3JjOm4sb3B0aW9uczpyfT1lLmRhdGE7dHJ5e2lmKGEhPT12b2lkIDApe2NvbnN0IGM9Qy5nZXQoYSk7YyYmYyhlLmRhdGEpfWVsc2Ugc3dpdGNoKHQpe2Nhc2Ugby5TVEFSVDpsZXQgYz1hd2FpdCBxKG4scik7Yz1hd2FpdCBVKGMsciksYz1hd2FpdCB2KGMsciksYXdhaXQgRyhjLHIpO2JyZWFrO2Nhc2Ugby5ORVdfQ0FOVkFTOmJyZWFrfX1jYXRjaChjKXtwb3N0TWVzc2FnZSh7a2V5Om8uRVJST1IsZXJyb3I6Y30pfX07ZnVuY3Rpb24gRihlLHQpe3JldHVybiBuZXcgUHJvbWlzZShhPT57Y29uc3Qgbj1fKys7Qy5zZXQobixhKSxwb3N0TWVzc2FnZSh7aWQ6bixrZXk6ZSwuLi50fSl9KX1hc3luYyBmdW5jdGlvbiBxKGUsdCl7aWYodHlwZW9mIGU9PSJzdHJpbmciKXtjb25zdCBhPWF3YWl0IEUoZSksbj1hd2FpdCBjcmVhdGVJbWFnZUJpdG1hcChhKSxyPWF3YWl0IHUobi53aWR0aCxuLmhlaWdodCx0LmJnQ29sb3IpO3JldHVybiByLmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKG4sMCwwKSxyfWVsc2UgaWYoZSBpbnN0YW5jZW9mIEZpbGV8fGUgaW5zdGFuY2VvZiBCbG9iKXtjb25zdCBhPWF3YWl0IGsoZSksbj1JKGEpLHI9YXdhaXQgY3JlYXRlSW1hZ2VCaXRtYXAobiksYz1hd2FpdCB1KHIud2lkdGgsci5oZWlnaHQsdC5iZ0NvbG9yKTtyZXR1cm4gYy5nZXRDb250ZXh0KCIyZCIpLmRyYXdJbWFnZShyLDAsMCksY319YXN5bmMgZnVuY3Rpb24gdShlPTMyMCx0PTI0MCxhPSJ0cmFuc3BhcmVudCIpe2NvbnN0IHI9KGF3YWl0IEYoby5ORVdfQ0FOVkFTLHt3aWR0aDplLGhlaWdodDp0fSkpLmNhbnZhcyxjPXIuZ2V0Q29udGV4dCgiMmQiKTtyZXR1cm4gYy5maWxsU3R5bGU9YSxjLmZpbGxSZWN0KDAsMCxlLHQpLHJ9YXN5bmMgZnVuY3Rpb24gVShlLHQpe2NvbnN0IGE9RChlLndpZHRoLGUuaGVpZ2h0LHQud2lkdGgsdC5oZWlnaHQpO3JldHVybiBhd2FpdCBWKHtjYW52YXM6ZSxyZVNhbXBsZTp0LnJlU2FtcGxlLHdpZHRoOmEud2lkdGgsaGVpZ2h0OmEuaGVpZ2h0LGN4OjAsY3k6MCxjdzplLndpZHRoLGNoOmUuaGVpZ2h0LGR4OjAsZHk6MCxkdzphLndpZHRoLGRoOmEuaGVpZ2h0LGJnQ29sb3I6dC5iZ0NvbG9yfSl9ZnVuY3Rpb24gVihlKXtlPU9iamVjdC5hc3NpZ24oe30sVCxlKSxlLnJlU2FtcGxlPU1hdGgubWluKDQsZS5yZVNhbXBsZSksZS5yZVNhbXBsZT1NYXRoLm1heCgwLGUucmVTYW1wbGUpO2NvbnN0IHQ9TWF0aC5wb3coMixlLnJlU2FtcGxlKTtyZXR1cm4gbmV3IFByb21pc2UoYXN5bmMoYSxuKT0+e3RyeXtjb25zdCByPWF3YWl0IHUoZS53aWR0aCp0LGUuaGVpZ2h0KnQsZS5iZ0NvbG9yKTtyLmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKGUuY2FudmFzLGUuY3gsZS5jeSxlLmN3LGUuY2gsZS5keCp0LGUuZHkqdCxlLmR3KnQsZS5kaCp0KSxlLnJlU2FtcGxlPjA/TChlLGUucmVTYW1wbGUscikudGhlbihhKTphKHIpfWNhdGNoKHIpe24ocil9fSl9ZnVuY3Rpb24gTChlLHQsYSl7cmV0dXJuIG5ldyBQcm9taXNlKG49Pnthc3luYyBmdW5jdGlvbiByKGMsaSl7Y29uc3Qgcz1NYXRoLnBvdygyLGMpLGw9YXdhaXQgdShlLndpZHRoKnMsZS5oZWlnaHQqcyxlLmJnQ29sb3IpO2wuZ2V0Q29udGV4dCgiMmQiKS5kcmF3SW1hZ2UoaSwwLDAsaS53aWR0aCouNSxpLmhlaWdodCouNSksYz4wP3IoYy0xLGwpLnRoZW4oKTpuKGwpfXIodC0xLGEpLnRoZW4oKX0pfWFzeW5jIGZ1bmN0aW9uIHYoZSx0KXtjb25zdCBhPWlzTmFOKHQ9PW51bGw/dm9pZCAwOnQuc2hhcnBlbik/MDp0LnNoYXJwZW47aWYoYTw9MClyZXR1cm4gZTtjb25zdCBuPWF3YWl0IHUoZS53aWR0aCxlLmhlaWdodCx0LmJnQ29sb3IpO3JldHVybiBPKGUsYSxuKX1hc3luYyBmdW5jdGlvbiBHKGUsdCl7bGV0IGE7c3dpdGNoKHQub3V0cHV0VHlwZSl7Y2FzZSJiYXNlNjQiOmE9YXdhaXQgeihlLHQuZm9ybWF0LHQucXVhbGl0eSkscG9zdE1lc3NhZ2Uoe2tleTpvLkVORCxvdXRwdXQ6YX0pO2JyZWFrO2Nhc2UiYmxvYiI6YT1hd2FpdCBQKGUsdC5mb3JtYXQsdC5xdWFsaXR5KSxwb3N0TWVzc2FnZSh7a2V5Om8uRU5ELG91dHB1dDphfSk7YnJlYWs7Y2FzZSJjYW52YXMiOmRlZmF1bHQ6Y29uc3Qgbj1lLnRyYW5zZmVyVG9JbWFnZUJpdG1hcCgpO3Bvc3RNZXNzYWdlKHtrZXk6by5FTkQsb3V0cHV0Om59LFtuXSk7YnJlYWt9fX0pKCk7Cg==",Z=e=>Uint8Array.from(atob(e),d=>d.charCodeAt(0)),a=typeof window<"u"&&window.Blob&&new Blob([Z(i)],{type:"text/javascript;charset=utf-8"});function u(e){let d;try{if(d=a&&(window.URL||window.webkitURL).createObjectURL(a),!d)throw"";const l=new Worker(d,{name:e==null?void 0:e.name});return l.addEventListener("error",()=>{(window.URL||window.webkitURL).revokeObjectURL(d)}),l}catch{return new Worker("data:text/javascript;base64,"+i,{name:e==null?void 0:e.name})}finally{d&&(window.URL||window.webkitURL).revokeObjectURL(d)}}const h={START:"START",END:"END",NEW_CANVAS:"NEW_CANVAS",ERROR:"ERROR"},c={width:320,height:240,format:"jpg",outputType:"base64",quality:.75,reSample:2,sharpen:0,bgColor:"transparent"};function m(e={}){let d={};return Object.keys(c).forEach(l=>{d[l]=e[l]!==void 0?e[l]:c[l]}),e.width===void 0&&e.height===void 0?(d.width=c.width,d.height=void 0):e.width===void 0?(d.width=void 0,d.height=Number(d.height)||0):e.height===void 0?(d.width=Number(d.width)||0,d.height=void 0):(d.width=Number(d.width)||0,d.height=Number(d.height)||0),d.quality=Number(d.quality)||0,d.reSample=Number(d.reSample)||0,d}function p(e=void 0){if(typeof e!="string"){{if(e instanceof File||e instanceof Blob)return;if(e instanceof HTMLCanvasElement)return}throw new Error("Invalid image data.")}}function L(e){return new Promise((d,l)=>{e instanceof HTMLCanvasElement?e.toBlob(s=>d(s)):l(new Error("The provided element is not a Canvas."))})}function X(e,d=void 0){return new Promise(async(l,s)=>{const o=m(d),n=new u;n.onmessage=async G=>{const{id:S,key:K,output:t,error:V}=G.data;switch(K){case h.END:if(n&&n.terminate(),t instanceof ImageBitmap){const b=document.createElement("canvas"),Y=b.getContext("2d");b.width=t.width,b.height=t.height,Y.fillStyle=o.bgColor,Y.fillRect(0,0,t.width,t.height),Y.drawImage(t,0,0),l(b)}else l(t);n&&n.terminate();break;case h.ERROR:s(V),n&&n.terminate();break;case h.NEW_CANVAS:const W=new OffscreenCanvas(G.data.width,G.data.height);n.postMessage({id:S,key:h.NEW_CANVAS,canvas:W},[W]);break}},p(e),e instanceof HTMLCanvasElement&&(e=await L(e)),n.postMessage({key:h.START,src:e,options:o})})}return X}); 2 | -------------------------------------------------------------------------------- /libs/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'image-resize' { 2 | 3 | // types 4 | type typeOptions = { 5 | width?: number 6 | height?: number 7 | format?: 'png'|'jpg'|'webp' 8 | outputType?: 'base64'|'canvas'|'blob' 9 | quality?: number 10 | reSample?: number 11 | sharpen?: number 12 | bgColor?: string 13 | } 14 | type typeSource = string|File|Blob|HTMLCanvasElement 15 | type typeOutput = Promise 16 | 17 | // function 18 | export default function imageResize(src: typeSource, options?: typeOptions): typeOutput 19 | 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-resize", 3 | "version": "1.4.2", 4 | "description": "Resize image for javascript", 5 | "files": [ 6 | "libs" 7 | ], 8 | "main": "libs/imageResize.umd.js", 9 | "module": "libs/imageResize.es.js", 10 | "exports": { 11 | ".": { 12 | "import": "./libs/imageResize.es.js", 13 | "require": "./libs/imageResize.umd.js" 14 | } 15 | }, 16 | "type": "module", 17 | "types": "libs/types.d.ts", 18 | "scripts": { 19 | "dev": "bun run --bun vite --config vite.config/vite.docs.js", 20 | "prebuild": "rm -rf ./docs && rm -rf ./libs", 21 | "build": "bun run --bun vite build --emptyOutDir --config vite.config/vite.docs.js && bun run --bun vite build --emptyOutDir --config vite.config/vite.libs.js && cp types.d.ts libs", 22 | "preview": "bun run --bun vite preview --config vite.config/vite.docs.js" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/kode-team/image-resize.git" 27 | }, 28 | "keywords": [ 29 | "image", 30 | "resize", 31 | "resize-image", 32 | "canvas", 33 | "javascript", 34 | "js" 35 | ], 36 | "author": { 37 | "name": "redgoose", 38 | "email": "scripter@me.com", 39 | "url": "https://redgoose.me" 40 | }, 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/kode-team/image-resize/issues" 44 | }, 45 | "homepage": "https://kode-team.github.io/image-resize/", 46 | "browserslist": [ 47 | "> 1%", 48 | "last 2 versions" 49 | ], 50 | "dependencies": {}, 51 | "devDependencies": { 52 | "vite": "^5.2.12" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/public/favicon.ico -------------------------------------------------------------------------------- /src/demo/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: Pretendard; 3 | font-weight: 900; 4 | font-display: swap; 5 | src: local('Pretendard Black'), url('./fonts/Pretendard-Black.subset.woff2') format('woff2') 6 | } 7 | @font-face{ 8 | font-family: Pretendard; 9 | font-weight: 800; 10 | font-display: swap; 11 | src: local('Pretendard ExtraBold'), url('./fonts/Pretendard-ExtraBold.subset.woff2') format('woff2') 12 | } 13 | @font-face{ 14 | font-family: Pretendard; 15 | font-weight: 700; 16 | font-display: swap; 17 | src: local('Pretendard Bold'), url('./fonts/Pretendard-Bold.subset.woff2') format('woff2') 18 | } 19 | @font-face{ 20 | font-family: Pretendard; 21 | font-weight: 600; 22 | font-display: swap; 23 | src: local('Pretendard SemiBold'), url('./fonts/Pretendard-SemiBold.subset.woff2') format('woff2') 24 | } 25 | @font-face{ 26 | font-family: Pretendard; 27 | font-weight: 500; 28 | font-display: swap; 29 | src: local('Pretendard Medium'), url('./fonts/Pretendard-Medium.subset.woff2') format('woff2') 30 | } 31 | @font-face{ 32 | font-family: Pretendard; 33 | font-weight: 400; 34 | font-display: swap; 35 | src: local('Pretendard Regular'), url('./fonts/Pretendard-Regular.subset.woff2') format('woff2') 36 | } 37 | @font-face{ 38 | font-family: Pretendard; 39 | font-weight: 300; 40 | font-display: swap; 41 | src: local('Pretendard Light'), url('./fonts/Pretendard-Light.subset.woff2') format('woff2') 42 | } 43 | @font-face{ 44 | font-family: Pretendard; 45 | font-weight: 200; 46 | font-display: swap; 47 | src: local('Pretendard ExtraLight'), url('./fonts/Pretendard-ExtraLight.subset.woff2') format('woff2') 48 | } 49 | @font-face{ 50 | font-family: Pretendard; 51 | font-weight: 100; 52 | font-display: swap; 53 | src: local('Pretendard Thin'), url('./fonts/Pretendard-Thin.subset.woff2') format('woff2') 54 | } 55 | -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-Black.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-Black.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-Bold.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-Bold.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-ExtraBold.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-ExtraBold.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-ExtraLight.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-ExtraLight.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-Light.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-Light.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-Medium.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-Medium.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-Regular.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-Regular.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-SemiBold.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-SemiBold.subset.woff2 -------------------------------------------------------------------------------- /src/demo/fonts/Pretendard-Thin.subset.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode-team/image-resize/44d1a9e79ff966ba5ddf1ac91606d883fc43b3c3/src/demo/fonts/Pretendard-Thin.subset.woff2 -------------------------------------------------------------------------------- /src/demo/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-base: #111111; 3 | --color-weak: #666666; 4 | --color-main: hsl(159deg 90% 42%); 5 | --color-shape: #f4f4f4; 6 | --color-field-line: hsl(0 0% 0% / 10%); 7 | --size-radius: 3px; 8 | --shadow-box: 9 | 0 1px 5px rgba(0, 0, 0, .061), 10 | 0 3px 18px rgba(0, 0, 0, .089), 11 | 0 12px 80px rgba(0, 0, 0, .15); 12 | } 13 | 14 | html { 15 | touch-action: manipulation; 16 | } 17 | body { 18 | min-width: 640px; 19 | margin: 0; 20 | padding: 0 50px 24px; 21 | -webkit-text-size-adjust: none; 22 | color: var(--color-base); 23 | font-size: 16px; 24 | line-height: 1.6; 25 | } 26 | body, button, input, textarea, select { 27 | font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 28 | -webkit-text-size-adjust: 100%; 29 | text-size-adjust: none; 30 | color: var(--color-base); 31 | } 32 | 33 | main { 34 | margin: 0 auto; 35 | max-width: 1000px; 36 | } 37 | 38 | /* header */ 39 | .header { 40 | padding: 42px 0 24px; 41 | text-align: center; 42 | user-select: none; 43 | > h1 { 44 | margin: 0; 45 | font-size: 36px; 46 | color: var(--color-main); 47 | letter-spacing: -.5px; 48 | font-weight: 900; 49 | line-height: 1; 50 | text-transform: uppercase; 51 | user-select: none; 52 | } 53 | > p { 54 | margin: 4px 0 0; 55 | font-size: 13px; 56 | line-height: 1.15; 57 | color: var(--color-weak); 58 | } 59 | } 60 | 61 | /* form */ 62 | .form { 63 | margin: 0; 64 | padding: 24px; 65 | background: hsl(0 0% 100%); 66 | box-shadow: var(--shadow-box); 67 | border-radius: 4px; 68 | .form__fieldset { 69 | margin: 0; 70 | padding: 0; 71 | border: none; 72 | > legend { 73 | font-size: 0; 74 | } 75 | } 76 | .form__body { 77 | display: grid; 78 | margin: 0; 79 | gap: 0 0; 80 | } 81 | .form__submit { 82 | display: flex; 83 | margin: 20px 0 0; 84 | justify-content: center; 85 | .button { 86 | width: 160px; 87 | height: 36px; 88 | } 89 | } 90 | } 91 | 92 | /* box title */ 93 | .box-title { 94 | margin: 0 0 8px; 95 | font-size: 18px; 96 | font-weight: 700; 97 | line-height: 1.05; 98 | letter-spacing: -.5px; 99 | user-select: none; 100 | } 101 | 102 | /* field */ 103 | .field { 104 | margin: 0; 105 | padding: 10px 12px; 106 | display: grid; 107 | grid-template-columns: 120px 1fr; 108 | align-items: center; 109 | border-bottom: 1px solid var(--color-field-line); 110 | &:first-child { 111 | border-top: 1px solid var(--color-field-line); 112 | } 113 | .field__label { 114 | margin: 0; 115 | line-height: 1.15; 116 | font-weight: 600; 117 | font-size: 12px; 118 | user-select: none; 119 | } 120 | .field__body { 121 | margin: 0; 122 | } 123 | .field__description { 124 | margin: 5px 0 0; 125 | font-size: 11px; 126 | color: var(--color-weak); 127 | font-weight: 400; 128 | line-height: 1.15; 129 | } 130 | } 131 | .field-inline { 132 | display: grid; 133 | align-items: center; 134 | grid-template-columns: 120px auto; 135 | gap: 0 8px; 136 | > span { 137 | display: block; 138 | font-size: 12px; 139 | line-height: 1; 140 | } 141 | } 142 | .field-inlines { 143 | display: flex; 144 | flex-direction: row; 145 | flex-wrap: wrap; 146 | gap: 16px; 147 | & + .field-inlines { 148 | margin-top: 8px; 149 | } 150 | .field-inline { 151 | grid-template-columns: auto auto; 152 | gap: 8px; 153 | } 154 | } 155 | 156 | /* input form */ 157 | .input-text { 158 | display: block; 159 | margin: 0; 160 | padding: 0 10px; 161 | height: 32px; 162 | font-size: 13px; 163 | border-radius: var(--size-radius); 164 | border: 1px solid #bbb; 165 | background: #fff; 166 | outline: none; 167 | box-sizing: border-box; 168 | &.input-text--block { 169 | width: 100%; 170 | } 171 | } 172 | .input-select { 173 | position: relative; 174 | display: inline-block; 175 | > select { 176 | display: inline-block; 177 | margin: 0; 178 | padding: 0 10px; 179 | height: 32px; 180 | border: 1px solid #bbb; 181 | font-size: 13px; 182 | border-radius: var(--size-radius); 183 | background: #fff; 184 | min-width: 100px; 185 | outline: none; 186 | box-sizing: border-box; 187 | appearance: none; 188 | } 189 | &:after { 190 | content: ''; 191 | display: block; 192 | position: absolute; 193 | right: 6px; 194 | top: 50%; 195 | width: 16px; 196 | aspect-ratio: 1/1; 197 | translate: 0 -50%; 198 | pointer-events: none; 199 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtY2hldnJvbi1kb3duIj48cGF0aCBkPSJtNiA5IDYgNiA2LTYiLz48L3N2Zz4='); 200 | background-size: 100%; 201 | } 202 | } 203 | .input-file { 204 | display: grid; 205 | margin: 0; 206 | padding: 0 8px; 207 | height: 32px; 208 | border: 1px solid #bbb; 209 | background: #fff; 210 | font-size: 13px; 211 | border-radius: var(--size-radius); 212 | outline: none; 213 | box-sizing: border-box; 214 | } 215 | .input-file--block { 216 | width: 100%; 217 | } 218 | .input-text:focus-visible, 219 | .input-file:focus-visible, 220 | .input-select:focus-visible { 221 | box-shadow: 0 0 0 2px var(--color-main); 222 | } 223 | 224 | /* result */ 225 | .result { 226 | margin: 48px 0 0; 227 | padding: 24px 24px 32px; 228 | background: hsl(0 0% 100%); 229 | box-shadow: var(--shadow-box); 230 | border-radius: 4px; 231 | .box-title { 232 | margin: 0 0 16px; 233 | } 234 | .result__image { 235 | margin: 0; 236 | > * { 237 | display: block; 238 | margin: 0 auto; 239 | max-width: 100%; 240 | } 241 | &:empty:after { 242 | display: grid; 243 | place-content: center; 244 | height: 120px; 245 | content: '...'; 246 | background: hsla(0 0% 0% / 4%); 247 | font-size: 13px; 248 | color: var(--color-weak); 249 | } 250 | } 251 | } 252 | 253 | /* button */ 254 | .button { 255 | display: block; 256 | margin: 0; 257 | padding: 0; 258 | border: none; 259 | background: var(--color-main); 260 | color: #fff; 261 | font-size: 15px; 262 | font-weight: 800; 263 | letter-spacing: -.5px; 264 | cursor: pointer; 265 | outline: none; 266 | border-radius: var(--size-radius); 267 | transition: box-shadow 200ms ease-out; 268 | user-select: none; 269 | box-sizing: border-box; 270 | &:disabled { 271 | opacity: .5; 272 | cursor: not-allowed; 273 | } 274 | &:where(:active, :focus-visible) { 275 | box-shadow: 0 0 0 4px rgb(0 0 0 / 15%); 276 | } 277 | } 278 | 279 | /* file upload */ 280 | .file-upload { 281 | display: flex; 282 | align-items: center; 283 | gap: 0 12px; 284 | button { 285 | width: 100px; 286 | height: 28px; 287 | font-size: 13px; 288 | font-weight: 600; 289 | } 290 | p { 291 | margin: 0; 292 | font-size: 12px; 293 | line-height: 1.15; 294 | font-weight: 700; 295 | user-select: none; 296 | &:empty:before { 297 | content: '업로드한 파일이 없습니다.'; 298 | font-weight: 500; 299 | color: var(--color-weak); 300 | } 301 | } 302 | } 303 | 304 | /* footer */ 305 | .footer { 306 | margin: 52px 0 0; 307 | text-align: center; 308 | user-select: none; 309 | > .info { 310 | margin: 0; 311 | font-size: 13px; 312 | line-height: 1.05; 313 | font-weight: 600; 314 | em { 315 | font-style: normal; 316 | } 317 | } 318 | > .copyright { 319 | margin: 4px 0 0; 320 | color: var(--color-weak); 321 | font-size: 10px; 322 | line-height: 1.05; 323 | } 324 | } 325 | 326 | /* github link */ 327 | .github-corner { 328 | display: block; 329 | position: fixed; 330 | right: 0; 331 | top: 0; 332 | > svg { 333 | position: absolute; 334 | top: 0; 335 | border: 0; 336 | right: 0; 337 | fill:#222222; 338 | color:#fff; 339 | } 340 | .octo-arm { 341 | transform-origin: 130px 106px; 342 | } 343 | &:hover .octo-arm { 344 | animation: octocat-wave 560ms ease-in-out; 345 | } 346 | @media (max-width:500px) { 347 | &:hover .octo-arm { 348 | animation: none; 349 | } 350 | .octo-arm { 351 | animation:octocat-wave 560ms ease-in-out; 352 | } 353 | } 354 | 355 | } 356 | @keyframes octocat-wave { 357 | 0%, 100% { 358 | transform: rotate(0); 359 | } 360 | 20%, 60% { 361 | transform: rotate(-25deg); 362 | } 363 | 40%, 80% { 364 | transform: rotate(10deg); 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Image Resize demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

이미지 리사이즈

15 |

이미지 크기 조절을 할 수 있는 자바스크립트 오픈소스 패키지입니다.

16 |
17 |
18 |
19 | 설정 옵션들 20 |

리사이즈 옵션

21 |
22 |
23 |

24 | 25 |

26 |
27 | 34 |

35 | 이미지 URL로 이미지 리사이즈 합니다. 36 |

37 |
38 |
39 |
40 |

41 | 42 |

43 |
44 |
45 | 48 |

49 |
50 |

51 | 선택한 이미지로 리사이즈 합니다. 이 폼에서 선택한 이미지가 우선으로 사용됩니다. 52 |

53 |
54 |
55 |
56 |

57 | 58 |

59 |
60 |
61 | 72 | 82 |
83 |
84 |
85 |
86 |

87 | 88 |

89 |
90 |
91 | 101 | 111 |
112 |
113 | 123 | 135 |
136 |
137 |
138 |
139 |

140 | 141 |

142 |
143 | 152 |
153 |
154 |
155 |
156 | 159 |
160 |
161 |

결과물

162 |
163 |
164 |
165 |

Version: , MIT

166 | 167 |
168 |
169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/demo/index.js: -------------------------------------------------------------------------------- 1 | import { version } from '../../package.json' 2 | import imageResize from '../image-resize/index.js' 3 | import { fileUploader, getByte } from './libs.js' 4 | import './fonts.css' 5 | import './index.css' 6 | 7 | const $form = document.getElementById('form') 8 | const $result = document.getElementById('result') 9 | const values = new Proxy({}, { 10 | get: (obj, name) => (obj[name]), 11 | set: (obj, prop, value) => { 12 | switch (prop) 13 | { 14 | case 'width': 15 | case 'height': 16 | case 'quality': 17 | case 'reSample': 18 | case 'sharpen': 19 | value = Number(value) 20 | break 21 | } 22 | obj[prop] = value 23 | return true 24 | }, 25 | }) 26 | const $el = { 27 | submit: document.querySelector('.form__submit > button[type=submit]'), 28 | fileUpload: document.getElementById('file-upload'), 29 | fileUploadInfo: document.getElementById('file-upload-info'), 30 | version: document.getElementById('version'), 31 | } 32 | 33 | async function onClickFileUpload() 34 | { 35 | const file = await fileUploader({ accept: 'image/*' }) 36 | if (!file) return 37 | values.upload = file 38 | $el.fileUploadInfo.innerText = `${file.name} (${getByte(file.size)})` 39 | } 40 | 41 | async function onSubmitForm(e) 42 | { 43 | e.preventDefault() 44 | processing(true) 45 | 46 | // set values 47 | const $self = e.target 48 | const $fields = $self.querySelectorAll('[name]') 49 | for (const $item of $fields) 50 | { 51 | values[$item.name] = $item.value 52 | } 53 | // set source 54 | let src 55 | if (values.upload) 56 | { 57 | src = values.upload 58 | // src = new Blob([src], { type: src.type }) // set Blob 59 | } 60 | else if (values.url) 61 | { 62 | src = values.url 63 | } 64 | else 65 | { 66 | alert('not found source') 67 | return false 68 | } 69 | 70 | try 71 | { 72 | // set values 73 | const pureValues = { ...values } 74 | delete pureValues.url 75 | delete pureValues.upload 76 | // method: function 77 | const res = await imageResize(src, pureValues) 78 | // to result output image 79 | completeResizeImage(pureValues.outputType, res) 80 | } 81 | catch(e) 82 | { 83 | errorResizeImage(e) 84 | } 85 | finally 86 | { 87 | processing(false) 88 | } 89 | } 90 | 91 | function processing(sw) 92 | { 93 | const $submit = $el.submit 94 | if (sw) 95 | { 96 | // on 97 | $submit.setAttribute('disabled', '') 98 | $submit.innerText = '처리중..' 99 | } 100 | else 101 | { 102 | // off 103 | $submit.removeAttribute('disabled') 104 | $submit.innerText = '이미지 변환' 105 | } 106 | } 107 | 108 | function completeResizeImage(outputType, res) 109 | { 110 | $result.innerHTML = '' 111 | switch(outputType) 112 | { 113 | case 'base64': 114 | const image = new Image() 115 | image.src = res 116 | $result.appendChild(image) 117 | break 118 | case 'canvas': 119 | $result.appendChild(res) 120 | break 121 | case 'blob': 122 | default: 123 | console.log('RESULT:', res) 124 | break 125 | } 126 | } 127 | function errorResizeImage(e) 128 | { 129 | console.error('[ERROR / IMAGE RESIZE]', e.message) 130 | alert(`Error resize: ${e.message}`) 131 | } 132 | 133 | // action 134 | $form.addEventListener('submit', onSubmitForm) 135 | $el.fileUpload.addEventListener('click', onClickFileUpload) 136 | $el.version.innerText = version 137 | -------------------------------------------------------------------------------- /src/demo/libs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sleep (delay tool) 3 | * @param {number} delay 4 | * @return {Promise} 5 | */ 6 | export const sleep = (delay = 3000) => { 7 | return new Promise(resolve => setTimeout(resolve, delay)) 8 | } 9 | 10 | /** 11 | * file uploader 12 | * @param {string} [options.accept] 13 | * @param {boolean} [options.multiple] 14 | * @return {Promise} 15 | */ 16 | export function fileUploader(options = {}) 17 | { 18 | return new Promise((resolve, reject) => { 19 | const input = document.createElement('input') 20 | input.type = 'file' 21 | if (options.accept) input.accept = options.accept 22 | if (options.multiple === true) input.multiple = true 23 | input.addEventListener('change', e => { 24 | const files = Object.assign([], e.target.files) 25 | if (files.length <= 0) return resolve([]) 26 | input.value = null 27 | resolve((options.multiple === true) ? files : files[0]) 28 | }) 29 | input.addEventListener('cancel', () => { 30 | resolve((options.multiple === true) ? [] : null) 31 | }) 32 | input.click() 33 | }) 34 | } 35 | 36 | /** 37 | * file loader 38 | * @param {string} url 39 | * @return {Blob} 40 | */ 41 | export async function fileLoader(url) 42 | { 43 | const res = await fetch(url) 44 | const blob = await res.blob() 45 | return blob 46 | } 47 | 48 | /** 49 | * blob to canvas 50 | * @param {Blob} blob 51 | * @return {Promise} 52 | */ 53 | export function blobToCanvas(blob) 54 | { 55 | return new Promise(resolve => { 56 | let img = new Image() 57 | img.onload = () => { 58 | const canvas = document.createElement('canvas') 59 | canvas.width = img.width 60 | canvas.height = img.height 61 | const ctx = canvas.getContext('2d') 62 | ctx.drawImage(img, 0, 0, img.width, img.height) 63 | resolve(canvas) 64 | } 65 | img.src = URL.createObjectURL(blob) 66 | }) 67 | } 68 | 69 | export function getByte(bytes) 70 | { 71 | const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB' ] 72 | if (bytes === 0) return '0 Byte' 73 | let i = Math.floor(Math.log(bytes) / Math.log(1024)) 74 | return String(Math.round(bytes / Math.pow(1024, i))) + sizes[i] 75 | } 76 | -------------------------------------------------------------------------------- /src/image-resize/index.js: -------------------------------------------------------------------------------- 1 | import ImageResizeWorker from './worker.js?worker&inline' 2 | import { WORKER_MESSAGE } from './libs/consts.js' 3 | import { checkOptions, checkSource } from './libs/checks.js' 4 | import { canvasToBlob } from './libs/files.js' 5 | 6 | /** 7 | * resize 8 | * 한번에 리사이즈 실행하는 함수 9 | * @param {string|File|Blob|HTMLCanvasElement} src 10 | * @param {object} options 11 | * @param {number} [options.width] 12 | * @param {number} [options.height] 13 | * @param {'png'|'jpg'|'webp'} [options.format] 14 | * @param {'base64'|'canvas'|'blob'} [options.outputType] 15 | * @param {number} [options.quality] 16 | * @param {number} [options.reSample] 17 | * @param {number} [options.sharpen] 18 | * @param {string} [options.bgColor] 19 | * @return {Promise} 20 | */ 21 | function imageResize(src, options = undefined) 22 | { 23 | return new Promise(async (resolve, reject) => { 24 | // check options 25 | const opts = checkOptions(options) 26 | 27 | // set worker 28 | const worker = new ImageResizeWorker() 29 | // const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }) 30 | worker.onmessage = async (e) => { 31 | const { id, key, output, error } = e.data 32 | switch (key) 33 | { 34 | case WORKER_MESSAGE.END: 35 | if (worker) worker.terminate() 36 | if (output instanceof ImageBitmap) 37 | { 38 | const canvas = document.createElement('canvas') 39 | const ctx = canvas.getContext('2d') 40 | canvas.width = output.width 41 | canvas.height = output.height 42 | ctx.fillStyle = opts.bgColor 43 | ctx.fillRect(0, 0, output.width, output.height) 44 | ctx.drawImage(output, 0, 0) 45 | resolve(canvas) 46 | } 47 | else 48 | { 49 | resolve(output) 50 | } 51 | if (worker) worker.terminate() 52 | break 53 | case WORKER_MESSAGE.ERROR: 54 | reject(error) 55 | if (worker) worker.terminate() 56 | break 57 | case WORKER_MESSAGE.NEW_CANVAS: 58 | const canvas = new OffscreenCanvas(e.data.width, e.data.height) 59 | worker.postMessage({ 60 | id, 61 | key: WORKER_MESSAGE.NEW_CANVAS, 62 | canvas, 63 | }, [ canvas ]) 64 | break 65 | } 66 | } 67 | 68 | // check source 69 | checkSource(src) 70 | 71 | // 캔버스 엘리먼트라면 `Blob`로 변환해줘야한다. 72 | if (src instanceof HTMLCanvasElement) 73 | { 74 | src = await canvasToBlob(src) 75 | } 76 | 77 | // send to worker thread 78 | worker.postMessage({ 79 | key: WORKER_MESSAGE.START, 80 | src, 81 | options: opts, 82 | }) 83 | }) 84 | } 85 | 86 | export default imageResize 87 | -------------------------------------------------------------------------------- /src/image-resize/libs/checks.js: -------------------------------------------------------------------------------- 1 | import { defaultOptions } from './consts.js' 2 | 3 | /** 4 | * Check options 5 | * @param {object} target 6 | */ 7 | export function checkOptions(target={}) 8 | { 9 | let result = {} 10 | Object.keys(defaultOptions).forEach((key) => { 11 | result[key] = target[key] !== undefined ? target[key] : defaultOptions[key] 12 | }) 13 | if (target.width === undefined && target.height === undefined) 14 | { 15 | result.width = defaultOptions.width 16 | result.height = undefined 17 | } 18 | else if (target.width === undefined) 19 | { 20 | result.width = undefined 21 | result.height = Number(result.height) || 0 22 | } 23 | else if (target.height === undefined) 24 | { 25 | result.width = Number(result.width) || 0 26 | result.height = undefined 27 | } 28 | else 29 | { 30 | result.width = Number(result.width) || 0 31 | result.height = Number(result.height) || 0 32 | } 33 | result.quality = Number(result.quality) || 0 34 | result.reSample = Number(result.reSample) || 0 35 | return result 36 | } 37 | 38 | /** 39 | * check source 40 | * @param {string|File|Blob|HTMLCanvasElement} src 41 | * @throws 42 | */ 43 | export function checkSource(src = undefined) 44 | { 45 | if (typeof src === 'string') return 46 | else if (src instanceof File || src instanceof Blob) return 47 | else if (src instanceof HTMLCanvasElement) return 48 | throw new Error('Invalid image data.') 49 | } 50 | -------------------------------------------------------------------------------- /src/image-resize/libs/consts.js: -------------------------------------------------------------------------------- 1 | // 웹 워커 통신용 메시지 2 | export const WORKER_MESSAGE = { 3 | START: 'START', 4 | END: 'END', 5 | NEW_CANVAS: 'NEW_CANVAS', 6 | ERROR: 'ERROR', 7 | } 8 | 9 | // 이미지 라시아즈 옵션 10 | export const defaultOptions = { 11 | width: 320, 12 | height: 240, 13 | format: 'jpg', // png,jpg,webp 14 | outputType: 'base64', // base64,canvas,blob 15 | quality: .75, 16 | reSample: 2, 17 | sharpen: 0, 18 | bgColor: 'transparent', 19 | } 20 | 21 | // default options for resize canvas 22 | export const defaultResizeCanvasOptions = { 23 | canvas: null, 24 | reSample: 2, 25 | width: 320, 26 | height: 240, 27 | cx: 0, 28 | cy: 0, 29 | cw: 0, 30 | ch: 0, 31 | dx: 0, 32 | dy: 0, 33 | dw: 0, 34 | dh: 0, 35 | bgColor: 'transparent', 36 | } 37 | -------------------------------------------------------------------------------- /src/image-resize/libs/files.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fetch image data 3 | * url 주소로 이미지를 가져온다. 4 | * @param {string} url 5 | * @return {Promise} 6 | */ 7 | export async function fetchImageData(url) 8 | { 9 | let image = await fetch(url) 10 | return await image.blob() 11 | } 12 | 13 | /** 14 | * file reader 15 | * @param {Blob|File} file 16 | * @return {Promise} 17 | */ 18 | export function fileReader(file) 19 | { 20 | return new Promise((resolve, reject) => { 21 | const reader = new FileReader() 22 | reader.onload = e => resolve(e.target?.result) 23 | reader.onerror = reject 24 | reader.readAsDataURL(file) 25 | }) 26 | } 27 | 28 | /** 29 | * base64 to Blob 30 | * @param {string} base64 31 | * @return {Blob} 32 | */ 33 | export function base64ToBlob(base64) 34 | { 35 | const binaryString = atob(base64.split(',')[1]) 36 | const mimeType = base64.match(/[^:]\w+\/[\w-+\d.]+(?=;|,)/)[0] 37 | const len = binaryString.length 38 | const bytes = new Uint8Array(len) 39 | for (let i = 0; i < len; i++) { 40 | bytes[i] = binaryString.charCodeAt(i); 41 | } 42 | return new Blob([ bytes.buffer ], { type: mimeType }) 43 | } 44 | 45 | /** 46 | * canvas to blob 47 | * @param {HTMLCanvasElement} canvas 48 | * @return {Promise} 49 | */ 50 | export function canvasToBlob(canvas) 51 | { 52 | return new Promise((resolve, reject) => { 53 | if (canvas instanceof HTMLCanvasElement) 54 | { 55 | canvas.toBlob(blob => resolve(blob)) 56 | } 57 | else 58 | { 59 | reject(new Error('The provided element is not a Canvas.')) 60 | } 61 | 62 | }) 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/image-resize/libs/filter-sharpen.js: -------------------------------------------------------------------------------- 1 | /** 2 | * filter sharpen 3 | * @param {OffscreenCanvas} canvas 4 | * @param {Number} amount 5 | * @param {OffscreenCanvas} newCanvas 6 | * @return {any} 7 | */ 8 | export default function filterSharpen(canvas, amount = 0, newCanvas) 9 | { 10 | if (amount <= 0) return canvas 11 | const newContext = newCanvas.getContext('2d') 12 | newContext.drawImage(canvas, 0, 0) 13 | const sourceImageData = newContext.getImageData(0, 0, newCanvas.width, newCanvas.height) 14 | const blankOutputImageData = newContext.createImageData(newCanvas.width, newCanvas.height) 15 | const filteredData = convolution(sourceImageData, blankOutputImageData, [ 16 | 0, -1, 0, 17 | -1, 5, -1, 18 | 0, -1, 0, 19 | ]) 20 | newContext.putImageData(filteredData, 0, 0) 21 | // merge sharpen image 22 | const context = canvas.getContext('2d') 23 | context.globalAlpha = amount || 1 24 | context.drawImage(newCanvas, 0, 0) 25 | // return 26 | return canvas 27 | } 28 | 29 | /** 30 | * convolution 31 | * https://img.ly/blog/how-to-apply-filters-in-javascript/ 32 | * @param {ImageData} sourceImageData 33 | * @param {ImageData} outputImageData 34 | * @param {Array} kernel 35 | */ 36 | function convolution(sourceImageData, outputImageData, kernel) 37 | { 38 | const src = sourceImageData.data 39 | const dst = outputImageData.data 40 | const srcWidth = sourceImageData.width 41 | const srcHeight = sourceImageData.height 42 | const side = Math.round(Math.sqrt(kernel.length)) 43 | const halfSide = Math.floor(side / 2) 44 | // padding the output by the convolution kernel 45 | const w = srcWidth 46 | const h = srcHeight 47 | // iterating through the output image pixels 48 | for (let y = 0; y < h; y++) 49 | { 50 | for (let x = 0; x < w; x++) 51 | { 52 | let r = 0, g = 0, b = 0, a = 0 53 | // calculating the weighed sum of the source image pixels that 54 | // fall under the convolution kernel 55 | for (let cy = 0; cy < side; cy++) 56 | { 57 | for (let cx = 0; cx < side; cx++) 58 | { 59 | const scy = y + cy - halfSide 60 | const scx = x + cx - halfSide 61 | if (scy >= 0 && scy < srcHeight && scx >= 0 && scx < srcWidth) 62 | { 63 | let srcOffset = (scy * srcWidth + scx) * 4 64 | let wt = kernel[cy * side + cx] 65 | r += src[srcOffset] * wt 66 | g += src[srcOffset + 1] * wt 67 | b += src[srcOffset + 2] * wt 68 | a += src[srcOffset + 3] * wt 69 | } 70 | } 71 | } 72 | const dstOffset = (y * w + x) * 4 73 | dst[dstOffset] = r 74 | dst[dstOffset + 1] = g 75 | dst[dstOffset + 2] = b 76 | dst[dstOffset + 3] = a 77 | } 78 | } 79 | return outputImageData 80 | } 81 | -------------------------------------------------------------------------------- /src/image-resize/libs/numbers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get image size 3 | * @param {number} width original width 4 | * @param {number} height original height 5 | * @param {number} targetWidth target width 6 | * @param {number} targetHeight target height 7 | * @return {object} 8 | */ 9 | export function getImageSize(width, height, targetWidth, targetHeight) 10 | { 11 | let w = width 12 | let h = height 13 | if (targetWidth && targetHeight) 14 | { 15 | if (targetWidth > targetHeight) targetHeight = undefined 16 | else targetWidth = undefined 17 | } 18 | if (targetWidth) 19 | { 20 | w = targetWidth 21 | h = height * (targetWidth / width) 22 | } 23 | else if (targetHeight) 24 | { 25 | w = width * (targetHeight / height) 26 | h = targetHeight 27 | } 28 | return { 29 | width: Number(w), 30 | height: Number(h), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/image-resize/libs/output.js: -------------------------------------------------------------------------------- 1 | /** 2 | * type - base64 3 | * @param {OffscreenCanvas} canvas 4 | * @param {string} format 5 | * @param {number} quality 6 | * @return {Promise} 7 | */ 8 | export function base64(canvas, format='image/jpeg', quality=.75) 9 | { 10 | // set format 11 | format = getFormat(format) 12 | return new Promise(async (resolve, reject) => { 13 | try 14 | { 15 | const uri = await canvasToDataURL(canvas, format, quality) 16 | resolve(uri) 17 | } 18 | catch (e) 19 | { 20 | reject(e) 21 | } 22 | }) 23 | } 24 | 25 | /** 26 | * type - blob 27 | * @param {OffscreenCanvas} canvas 28 | * @param {string} format 29 | * @param {number} quality 30 | * @return {Promise} 31 | */ 32 | export function blob(canvas, format='image/jpeg', quality=.75) 33 | { 34 | // set format 35 | format = getFormat(format) 36 | return new Promise(async (resolve, reject) => { 37 | try 38 | { 39 | const blob = await canvas.convertToBlob({ 40 | type: format, 41 | quality, 42 | }) 43 | resolve(blob) 44 | } 45 | catch (e) 46 | { 47 | reject(e) 48 | } 49 | }) 50 | } 51 | 52 | /** 53 | * Get format 54 | * @param {string} str 55 | * @return {string} 56 | */ 57 | function getFormat(str) 58 | { 59 | let format 60 | switch(str) 61 | { 62 | case 'jpg': 63 | case 'jpeg': 64 | format = 'image/jpeg' 65 | break 66 | case 'png': 67 | format = 'image/png' 68 | break 69 | case 'webp': 70 | format = 'image/webp' 71 | break 72 | default: 73 | format = str 74 | break 75 | } 76 | return format 77 | } 78 | 79 | /** 80 | * OffScreenCanvas 객체를 toDataURL 기능을 사용한다. 81 | * @param {OffscreenCanvas} canvas 82 | * @param {string} format 83 | * @param {number} quality 84 | * @return {any} 85 | */ 86 | async function canvasToDataURL(canvas, format, quality) 87 | { 88 | const blob = await canvas.convertToBlob({ 89 | type: format, 90 | quality, 91 | }) 92 | const reader = new FileReader() 93 | return new Promise(resolve => { 94 | reader.onloadend = () => resolve(reader.result) 95 | reader.readAsDataURL(blob) 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /src/image-resize/worker.js: -------------------------------------------------------------------------------- 1 | import { WORKER_MESSAGE, defaultResizeCanvasOptions } from './libs/consts.js' 2 | import { fileReader, base64ToBlob, fetchImageData } from './libs/files.js' 3 | import { getImageSize } from './libs/numbers.js' 4 | import filterSharpen from './libs/filter-sharpen.js' 5 | import { base64, blob } from './libs/output.js' 6 | 7 | let messageId = 0 8 | const pendingRequests = new Map() 9 | 10 | // worker events 11 | onmessage = async function(e) 12 | { 13 | const { key, id, src, options } = e.data 14 | try 15 | { 16 | if (id !== undefined) 17 | { 18 | const resolve = pendingRequests.get(id) 19 | if (resolve) resolve(e.data) 20 | } 21 | else 22 | { 23 | switch (key) 24 | { 25 | case WORKER_MESSAGE.START: 26 | // 캔버스로 변환한다. 27 | let canvas = await convertSourceToCanvas(src, options) 28 | canvas = await resize(canvas, options) 29 | canvas = await sharpen(canvas, options) 30 | await output(canvas, options) 31 | break 32 | case WORKER_MESSAGE.NEW_CANVAS: 33 | break 34 | } 35 | } 36 | } 37 | catch (e) 38 | { 39 | postMessage({ 40 | key: WORKER_MESSAGE.ERROR, 41 | error: e, 42 | }) 43 | } 44 | } 45 | 46 | /** 47 | * 메인 쓰레드에 메시지를 비동기 방식으로 보낸다. 이 함수를 활용하면 await을 이용할 수 있게된다. 48 | * @param {string} key 통신 메시지를 구분하는 값 49 | * @param {any} params 50 | */ 51 | function sendMessageToMainThread(key, params) 52 | { 53 | return new Promise(resolve => { 54 | const id = messageId++ 55 | pendingRequests.set(id, resolve) 56 | postMessage({ id, key, ...params }) 57 | }) 58 | } 59 | 60 | /** 61 | * 소스 데이터를 캔버스로 변환한다. 62 | * @return 63 | */ 64 | async function convertSourceToCanvas(src, options) 65 | { 66 | if (typeof src === 'string') 67 | { 68 | // image url address 69 | const blob = await fetchImageData(src) 70 | const bitmap = await createImageBitmap(blob) 71 | const canvas = await createCanvas(bitmap.width, bitmap.height, options.bgColor) 72 | const ctx = canvas.getContext('2d') 73 | ctx.drawImage(bitmap, 0, 0) 74 | return canvas 75 | } 76 | else if (src instanceof File || src instanceof Blob) 77 | { 78 | // File,Blob 79 | const base64 = await fileReader(src) 80 | const blob = base64ToBlob(base64) 81 | const bitmap = await createImageBitmap(blob) 82 | const canvas = await createCanvas(bitmap.width, bitmap.height, options.bgColor) 83 | const ctx = canvas.getContext('2d') 84 | ctx.drawImage(bitmap, 0, 0) 85 | return canvas 86 | } 87 | } 88 | 89 | /** 90 | * 메인쓰레드에 요청하여 캔버스 엘리먼트를 만든다. 91 | * @return {OffscreenCanvas} 92 | */ 93 | export async function createCanvas(width = 320, height = 240, bgColor = 'transparent') 94 | { 95 | const res = await sendMessageToMainThread(WORKER_MESSAGE.NEW_CANVAS, { 96 | width, 97 | height, 98 | }) 99 | const canvas = res.canvas 100 | const ctx = canvas.getContext('2d') 101 | ctx.fillStyle = bgColor 102 | ctx.fillRect(0, 0, width, height) 103 | return canvas 104 | } 105 | 106 | /** 107 | * 캔버스를 리사이즈한다. 108 | * @param {OffscreenCanvas} canvas 109 | * @param {object} options 110 | * @return {OffscreenCanvas} 111 | */ 112 | async function resize(canvas, options) 113 | { 114 | const size = getImageSize(canvas.width, canvas.height, options.width, options.height) 115 | const result = await resizeCanvas({ 116 | canvas, 117 | reSample: options.reSample, 118 | width: size.width, 119 | height: size.height, 120 | cx: 0, 121 | cy: 0, 122 | cw: canvas.width, 123 | ch: canvas.height, 124 | dx: 0, 125 | dy: 0, 126 | dw: size.width, 127 | dh: size.height, 128 | bgColor: options.bgColor, 129 | }) 130 | return result 131 | } 132 | 133 | /** 134 | * resize canvas 135 | * @param {Object} options 136 | * @return {Promise} 137 | */ 138 | export function resizeCanvas(options) 139 | { 140 | // assign options 141 | options = Object.assign({}, defaultResizeCanvasOptions, options) 142 | // set resampling count 143 | options.reSample = Math.min(4, options.reSample) 144 | options.reSample = Math.max(0, options.reSample) 145 | const reSamplingCount = Math.pow(2, options.reSample) 146 | return new Promise(async (resolve, reject) => { 147 | try 148 | { 149 | const canvas = await createCanvas( 150 | options.width * reSamplingCount, 151 | options.height * reSamplingCount, 152 | options.bgColor 153 | ) 154 | const ctx = canvas.getContext('2d') 155 | ctx.drawImage( 156 | options.canvas, 157 | options.cx, 158 | options.cy, 159 | options.cw, 160 | options.ch, 161 | options.dx * reSamplingCount, 162 | options.dy * reSamplingCount, 163 | options.dw * reSamplingCount, 164 | options.dh * reSamplingCount 165 | ) 166 | if (options.reSample > 0) 167 | { 168 | _resizeCanvas(options, options.reSample, canvas).then(resolve) 169 | } 170 | else 171 | { 172 | resolve(canvas) 173 | } 174 | } 175 | catch (e) 176 | { 177 | reject(e) 178 | } 179 | }) 180 | } 181 | /** 182 | * resize 183 | * @param {Object} options 184 | * @param {Number} count 185 | * @param {OffscreenCanvas} parentCanvas 186 | * @return {Promise} 187 | */ 188 | function _resizeCanvas(options, count, parentCanvas) 189 | { 190 | return new Promise(resolve => { 191 | async function func(count, parentCanvas) 192 | { 193 | const pow = Math.pow(2, count) 194 | const canvasForResize = await createCanvas( 195 | options.width * pow, 196 | options.height * pow, 197 | options.bgColor 198 | ) 199 | const ctx = canvasForResize.getContext('2d') 200 | ctx.drawImage( 201 | parentCanvas, 202 | 0, 203 | 0, 204 | parentCanvas.width * 0.5, 205 | parentCanvas.height * 0.5, 206 | ) 207 | if (count > 0) 208 | { 209 | func(count - 1, canvasForResize).then() 210 | } 211 | else 212 | { 213 | resolve(canvasForResize) 214 | } 215 | } 216 | func(count - 1, parentCanvas).then() 217 | }) 218 | } 219 | 220 | /** 221 | * sharpen 222 | * @param {OffscreenCanvas} canvas 223 | * @param {object} options 224 | */ 225 | async function sharpen(canvas, options) 226 | { 227 | const amount = (!isNaN(options?.sharpen)) ? options.sharpen : 0 228 | if (amount <= 0) return canvas 229 | const newCanvas = await createCanvas(canvas.width, canvas.height, options.bgColor) 230 | return filterSharpen(canvas, amount, newCanvas) 231 | } 232 | 233 | /** 234 | * Output data 235 | * @param {OffscreenCanvas} canvas 236 | * @param {object} options 237 | * @return {Promise} 238 | */ 239 | async function output(canvas, options) 240 | { 241 | let res 242 | switch (options.outputType) 243 | { 244 | case 'base64': 245 | res = await base64(canvas, options.format, options.quality) 246 | postMessage({ 247 | key: WORKER_MESSAGE.END, 248 | output: res, 249 | }) 250 | break 251 | case 'blob': 252 | res = await blob(canvas, options.format, options.quality) 253 | postMessage({ 254 | key: WORKER_MESSAGE.END, 255 | output: res, 256 | }) 257 | break 258 | case 'canvas': 259 | default: 260 | const bitmap = canvas.transferToImageBitmap() 261 | postMessage({ 262 | key: WORKER_MESSAGE.END, 263 | output: bitmap, 264 | }, [ bitmap ]) 265 | break 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'image-resize' { 2 | 3 | // types 4 | type typeOptions = { 5 | width?: number 6 | height?: number 7 | format?: 'png'|'jpg'|'webp' 8 | outputType?: 'base64'|'canvas'|'blob' 9 | quality?: number 10 | reSample?: number 11 | sharpen?: number 12 | bgColor?: string 13 | } 14 | type typeSource = string|File|Blob|HTMLCanvasElement 15 | type typeOutput = Promise 16 | 17 | // function 18 | export default function imageResize(src: typeSource, options?: typeOptions): typeOutput 19 | 20 | } 21 | -------------------------------------------------------------------------------- /vite.config/vite.docs.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite' 2 | 3 | // docs: https://vitejs.dev/config 4 | 5 | const config = defineConfig(({ mode }) => { 6 | const path = process.cwd() 7 | const env = loadEnv(mode, path) 8 | return { 9 | root: './src/demo', 10 | base: './', 11 | publicDir: path + '/public', 12 | server: { 13 | host: env.VITE_HOST, 14 | port: Number(env.VITE_PORT), 15 | open: env.VITE_OPEN_BROWSER === 'true', 16 | }, 17 | build: { 18 | outDir: path + '/docs', 19 | assetsDir: '', 20 | }, 21 | preview: { 22 | host: env.VITE_HOST, 23 | port: Number(env.VITE_PORT), 24 | }, 25 | } 26 | }) 27 | 28 | export default config 29 | -------------------------------------------------------------------------------- /vite.config/vite.libs.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | const projectName = 'imageResize' 4 | const config = defineConfig(() => { 5 | return { 6 | publicDir: false, 7 | base: './', 8 | build: { 9 | minify: true, 10 | outDir: 'libs', 11 | assetsDir: '', 12 | lib: { 13 | entry: 'src/image-resize/index.js', 14 | name: projectName, 15 | formats: [ 'es', 'umd' ], 16 | fileName: (format, a, b) => { 17 | return `${projectName}.${format}.js` 18 | }, 19 | }, 20 | }, 21 | } 22 | }) 23 | 24 | export default config 25 | --------------------------------------------------------------------------------