├── License ├── index.html ├── placholder.png ├── script.js └── style.css /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniel Nasr 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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Image Editor 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Easy Image Editor

16 |
17 |
18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |

Brightness

29 |

100%

30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | Preview 46 |
47 |
48 |
49 | 50 |
51 | 52 | 53 | 54 |
55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /placholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imdanieldev/ImageEditor/ae83598e5768b2be48d22b5b7e631d3eabf4d8c1/placholder.png -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const 2 | fileInput = document.querySelector(".file-input"), 3 | filterOptions = document.querySelectorAll(".filter button"), 4 | filterSlider = document.querySelector(".slider input"), 5 | filterName = document.querySelector(".filter-info .name"), 6 | filterValue = document.querySelector(".filter-info .value"), 7 | rotateOptions = document.querySelectorAll(".rotate button"), 8 | previewImage = document.querySelector(".preview-img img"), 9 | resetFilterBtn = document.querySelector(".reset-filter"), 10 | saveImageBtn = document.querySelector(".save-img"), 11 | chooseImageBtn = document.querySelector(".choose-img"); 12 | 13 | let 14 | brightness = 100, 15 | saturation = 100, 16 | inversion = 0, 17 | grayscale = 0; 18 | let 19 | rotate = 0, 20 | flipHorizontal = 1, 21 | flipVertical = 1; 22 | 23 | 24 | const applyFilters = ()=>{ 25 | previewImage.style.transform = `rotate(${rotate}deg) scale(${flipHorizontal}, ${flipVertical})`; 26 | previewImage.style.filter = `brightness(${brightness}%) saturate(${saturation}%) invert(${inversion}%) grayscale(${grayscale}%)`; 27 | } 28 | 29 | 30 | const loadImage = () => { 31 | let file = fileInput.files[0]; 32 | if(!file) return; 33 | previewImage.src = URL.createObjectURL(file); 34 | previewImage.addEventListener("load", ()=>{ 35 | resetFilterBtn.click(); 36 | document.querySelector(".container").classList.remove("disable"); 37 | }); 38 | } 39 | 40 | filterOptions.forEach(option=>{ 41 | option.addEventListener("click", ()=>{ 42 | document.querySelector(".filter .active").classList.remove("active"); 43 | option.classList.add("active"); 44 | filterName.innerText = option.innerText; 45 | 46 | if(option.id === "brightness"){ 47 | filterSlider.max = "200"; 48 | filterSlider.value = brightness; 49 | filterValue.innerText = `${brightness}%`; 50 | }else if(option.id === "saturation"){ 51 | filterSlider.max = "200"; 52 | filterSlider.value = saturation; 53 | filterValue.innerText = `${saturation}%`; 54 | }else if(option.id === "inversion"){ 55 | filterSlider.max = "100"; 56 | filterSlider.value = inversion; 57 | filterValue.innerText = `${inversion}%`; 58 | }else{ 59 | filterSlider.max = "100"; 60 | filterSlider.value = grayscale; 61 | filterValue.innerText = `${grayscale}%`; 62 | } 63 | }); 64 | }); 65 | 66 | const updateFilter = () => { 67 | filterValue.innerText = `${filterSlider.value}%`; 68 | const selectedFilter = document.querySelector(".filter .active"); 69 | 70 | if(selectedFilter.id === "brightness"){ 71 | brightness = filterSlider.value; 72 | }else if(selectedFilter.id === "saturation"){ 73 | saturation = filterSlider.value; 74 | }else if(selectedFilter.id === "inversion"){ 75 | inversion = filterSlider.value; 76 | }else{ 77 | grayscale = filterSlider.value; 78 | } 79 | applyFilters(); 80 | } 81 | 82 | rotateOptions.forEach(option =>{ 83 | option.addEventListener("click", ()=>{ 84 | if(option.id === "left"){ 85 | rotate -= 90; 86 | }else if(option.id === "right"){ 87 | rotate += 90; 88 | }else if(option.id === "horizontal"){ 89 | flipHorizontal = flipHorizontal === 1 ? -1 : 1; 90 | }else{ 91 | flipVertical = flipVertical === 1 ? -1 : 1; 92 | } 93 | applyFilters(); 94 | }); 95 | }); 96 | 97 | const resetFilter = () => { 98 | brightness = 100; 99 | saturation = 100; 100 | inversion = 0; 101 | grayscale = 0; 102 | rotate = 0; 103 | flipHorizontal = 1; 104 | flipVertical = 1; 105 | filterOptions[0].click(); 106 | applyFilters(); 107 | } 108 | 109 | const saveImage = () => { 110 | const canvas = document.createElement("canvas"); 111 | const ctx = canvas.getContext("2d"); 112 | canvas.width = previewImage.naturalWidth; 113 | canvas.height = previewImage.naturalHeight; 114 | 115 | ctx.filter = `brightness(${brightness}%) saturate(${saturation}%) invert(${inversion}%) grayscale(${grayscale}%)`; 116 | ctx.translate(canvas.width / 2, canvas.height / 2); 117 | if(rotate !== 0){ 118 | ctx.rotate(rotate * Math.PI / 180); 119 | } 120 | ctx.scale(flipHorizontal, flipVertical); 121 | ctx.drawImage(previewImage, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); 122 | 123 | const link = document.createElement("a"); 124 | link.download = "Image-Edited.png"; 125 | link.href = canvas.toDataURL(); 126 | link.click(); 127 | } 128 | 129 | fileInput.addEventListener("change", loadImage); 130 | filterSlider.addEventListener("input", updateFilter); 131 | resetFilterBtn.addEventListener("click", resetFilter); 132 | saveImageBtn.addEventListener("click", saveImage); 133 | chooseImageBtn.addEventListener("click", () => fileInput.click()); -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); 2 | 3 | *{ 4 | padding: 0; 5 | margin: 0; 6 | box-sizing: border-box; 7 | font-family: 'Poppins', sans-serif; 8 | } 9 | body{ 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | min-height: 100vh; 14 | background: #E3F2FD; 15 | } 16 | .filter-info{ 17 | display: flex; 18 | justify-content: space-between; 19 | } 20 | .container{ 21 | width: 850px; 22 | background: #fff; 23 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); 24 | border-radius: 0.5rem; 25 | padding: 30px 35px 35px; 26 | } 27 | .container.disable :where(.editor-panel, .reset-filter, .save-img){ 28 | opacity: 0.6; 29 | pointer-events: none; 30 | } 31 | .container h2{ 32 | font-size: 22px; 33 | font-weight: 500; 34 | } 35 | .container .wrapper{ 36 | display: flex; 37 | margin: 20px 0; 38 | min-height: 335px; 39 | } 40 | .container .wrapper .editor-panel{ 41 | width: 280px; 42 | border-radius: 5px; 43 | padding: 15px 20px; 44 | border: 1px solid #ccc; 45 | } 46 | .container .wrapper .editor-panel .title{ 47 | display: block; 48 | font-size: 16px; 49 | margin-bottom: 12px; 50 | } 51 | .container .wrapper .editor-panel .options,.container .controls{ 52 | display: flex; 53 | flex-wrap: wrap; 54 | justify-content: space-between; 55 | } 56 | .container .wrapper .editor-panel button{ 57 | height: 40px; 58 | font-size: 14px; 59 | color: #6C757D; 60 | margin-bottom: 8px; 61 | border: 1px solid #aaa; 62 | border-radius: 3px; 63 | background: #fff; 64 | 65 | } 66 | .container .wrapper .editor-panel .filter button{ 67 | width: calc(100% / 2 - 4px); 68 | cursor: pointer; 69 | } 70 | .container .wrapper .editor-panel .filter button.active{ 71 | background: #5372F0; 72 | color: #fff; 73 | border-color: #5372F0; 74 | } 75 | .container .wrapper .editor-panel .filter button.active:hover{ 76 | background: #4070F0; 77 | color: #fff; 78 | border-color: #4070F0; 79 | } 80 | .container .wrapper .editor-panel .filter button:hover{ 81 | background: #f5f5f5; 82 | } 83 | .container .wrapper .editor-panel .rotate{ 84 | margin-top: 17px; 85 | } 86 | .container .wrapper .editor-panel .rotate button{ 87 | width: calc(100% / 4 - 3px); 88 | } 89 | .container .wrapper .editor-panel .slider{ 90 | margin-top: 12px; 91 | } 92 | .container .wrapper .editor-panel .slider input{ 93 | width: 100%; 94 | height: 5px; 95 | accent-color: #5372F0; 96 | } 97 | .container .wrapper .editor-panel .rotate button{ 98 | cursor: pointer; 99 | } 100 | .container .wrapper .editor-panel .rotate button:nth-child(3), 101 | .container .wrapper .editor-panel .rotate button:nth-child(4){ 102 | font-size: 18px; 103 | } 104 | .container .wrapper .preview-img{ 105 | flex-grow: 1; 106 | display: flex; 107 | align-items: center; 108 | justify-content: center; 109 | margin-left: 20px; 110 | overflow: hidden; 111 | } 112 | .container .wrapper .preview-img img{ 113 | max-width: 490px; 114 | max-height: 335px; 115 | width: 100%; 116 | height: 100%; 117 | object-fit: contain; 118 | } 119 | .container .controls button{ 120 | padding: 11px 20px; 121 | font-size: 14px; 122 | cursor: pointer; 123 | color: #fff; 124 | border-radius: 3px; 125 | background: #fff; 126 | text-transform: uppercase; 127 | } 128 | .container .controls .reset-filter{ 129 | color: #6C757D; 130 | border: 1px solid #6C757D; 131 | } 132 | .container .controls .row .choose-img{ 133 | background: #6C757D; 134 | border: 1px solid #6C757D; 135 | } 136 | .container .controls .row .save-img{ 137 | margin-left: 5px; 138 | background: #5372F0; 139 | border: 1px solid #5372F0; 140 | } 141 | @media screen and (max-width: 760px){ 142 | .container{ 143 | padding: 25px; 144 | } 145 | .container .wrapper{ 146 | flex-wrap: wrap-reverse; 147 | } 148 | .container .wrapper .editor-panel{ 149 | width: 100%; 150 | } 151 | .container .wrapper .preview-img{ 152 | width: 100%; 153 | margin: 0 0 15px; 154 | } 155 | } 156 | @media screen and (max-width: 500px){ 157 | .container .controls button{ 158 | width: 100%; 159 | margin-bottom: 10px; 160 | } 161 | .container .controls .row{ 162 | width: 100% 163 | } 164 | .container .controls .row .save-img{ 165 | margin-left: 0px; 166 | } 167 | } --------------------------------------------------------------------------------