├── 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 |
Filters
20 |
21 | Brightness
22 | Saturation
23 | Inversion
24 | Grayscale
25 |
26 |
27 |
28 |
Brightness
29 |
100%
30 |
31 |
32 |
33 |
34 |
35 |
Rotate & Flip
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Reset Filters
50 |
51 |
52 | Choose Image
53 | Save Image
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 | }
--------------------------------------------------------------------------------