├── README.md
├── index.html
├── location.html
├── css
└── style.css
└── js
└── pixel.js
/README.md:
--------------------------------------------------------------------------------
1 | # Pixel
2 |
3 | Upload an image and convert it to a pixel image.
4 |
5 | [→ Try it!](https://enoyao.github.io/pixel)
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 图像转像素图
8 |
9 |
10 |
11 |
12 |
48 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/location.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
49 |
51 |
52 |
53 |
54 |
55 |
点击下面的按钮,获得对应信息:
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | /* reset */
4 | html,body,div,p,h1,h2,h3,h4,h5,h6,ul,li,ol,dl,dt,dd,span,img,a,form,input,select,label,textarea,button,legend,iframe,blockquote,pre,hr,figure,table,td,th,tr,caption,header,footer,section,article,aside,nav {margin: 0;padding: 0;}
5 | ul,ol {list-style: none;}
6 | a,button {cursor: pointer;}
7 | a {text-decoration: none;}
8 | fieldset,img {border: 0;}
9 | h1,h2,h3,h4,h5,h6 {font-size:100%}
10 | button,input,select,textarea {font-size:100%}
11 |
12 |
13 | html, body {
14 | width: 100%;
15 | height: 100%;
16 | font-family: Helvetica, Arial, "Microsoft YaHei", sans-serif;
17 | background-color: #fff;
18 | }
19 |
20 | .container {
21 | margin: 0 auto;
22 | width: 92%;
23 | min-height: 100%;
24 | min-width: 980px;
25 | }
26 |
27 | .sidebar {
28 | position: absolute;
29 | left: 20px;
30 | padding-left: 10px;
31 | padding-right: 10px;
32 | width: 300px;
33 | height: 100%;
34 | border-right: 1px solid #ccc;
35 | background-color: #fff;
36 | }
37 |
38 | .sidebar h1 {
39 | margin-top: 20px;
40 | margin-bottom: 30px;
41 | font-size: 32px;
42 | color: #666;
43 | }
44 |
45 | .sidebar .threshold {
46 | position: relative;
47 | margin-top: 40px;
48 | }
49 |
50 | .sidebar .threshold .threshold-checkbox {
51 | margin-bottom: 10px;
52 | }
53 |
54 | .sidebar .threshold .threshold-val {
55 | position: absolute;
56 | top: 0;
57 | right: 120px;
58 | font-size: 18px;
59 | color: #36b953;
60 | }
61 |
62 |
63 | .sidebar .mode {
64 | margin-top: 30px;
65 | }
66 |
67 | .sidebar .mode label {
68 | width: 50px;
69 | margin-right: 20px;
70 | }
71 |
72 | .sidebar .scale {
73 | margin-top: 20px;
74 | margin-bottom: 40px;
75 | }
76 |
77 | .sidebar .disable .btn-small {
78 | color: #ccc;
79 | cursor: default;
80 | }
81 |
82 | .sidebar .disable input[type="range"]::-webkit-slider-thumb {
83 | background-color: #ccc;
84 | }
85 |
86 | .sidebar .disable .threshold-val {
87 | color: #ccc;
88 | }
89 |
90 | .sidebar .disable .option + label::before {
91 | -webkit-box-shadow: 0 0 0 2px #ccc;
92 | box-shadow: 0 0 0 2px #ccc;
93 | background-color: #fff;
94 | cursor: default;
95 | }
96 |
97 | .sidebar .disable .option:checked + label::before {
98 | -webkit-box-shadow: 0 0 0 2px #ccc;
99 | box-shadow: 0 0 0 2px #ccc;
100 | background-color: #ccc;
101 | cursor: default;
102 | }
103 |
104 | .sidebar .disable label {
105 | color: #666;
106 | cursor: default;
107 | }
108 |
109 |
110 | #scale {
111 | -webkit-box-sizing: border-box;
112 | -moz-box-sizing: border-box;
113 | box-sizing: border-box;
114 | padding-left: 10px;
115 | width: 140px;
116 | border: 1px solid #ccc;
117 | -webkit-border-radius: 2px;
118 | border-radius: 2px;
119 | outline: none;
120 | color: #333;
121 | }
122 |
123 | .main {
124 | overflow: hidden;
125 | padding-top: 40px;
126 | margin-left: 320px;
127 | min-width: 500px;
128 | }
129 |
130 | #canvas {
131 | display: block;
132 | margin: 0 auto;
133 | max-width: 500px;
134 | }
135 |
136 | #img-upload {
137 | display: none;
138 | }
139 |
140 | #img-preview {
141 | margin-top: 20px;
142 | width: 300px;
143 | min-height: 200px;
144 | border: 1px solid #ccc;
145 | -webkit-border-radius: 6px;
146 | border-radius: 6px;
147 | line-height: 200px;
148 | text-align: center;
149 | color: #999;
150 | }
151 |
152 | #img-preview img {
153 | display: inline-block;
154 | max-width: 300px;
155 | max-height: 250px;
156 | vertical-align: middle;
157 | }
158 |
159 | .option {
160 | position: absolute;
161 | left: 0;
162 | visibility: hidden;
163 | }
164 |
165 | .option + label {
166 | display: inline-block;
167 | position: relative;
168 | left: 26px;
169 | cursor: pointer;
170 | }
171 | .option + label::before {
172 | content: '';
173 | position: absolute;
174 | left: -24px;
175 | top: 2px;
176 | width: 8px;
177 | height: 8px;
178 | border: 4px solid #fff;
179 | -webkit-box-shadow: 0 0 0 2px #36b953;
180 | box-shadow: 0 0 0 2px #36b953;
181 | -webkit-border-radius: 50%;
182 | border-radius: 50%;
183 | background-color: #fff;
184 | }
185 |
186 | .option:checked + label::before {
187 | content: '';
188 | position: absolute;
189 | left: -24px;
190 | top: 2px;
191 | width: 8px;
192 | height: 8px;
193 | border: 4px solid #fff;
194 | -webkit-box-shadow: 0 0 0 2px #36b953;
195 | box-shadow: 0 0 0 2px #36b953;
196 | -webkit-border-radius: 50%;
197 | border-radius: 50%;
198 | background-color: #36b953;
199 | }
200 |
201 | input[type="range"] {
202 | width: 230px;
203 | height: 2px;
204 | -webkit-border-radius: 6px;
205 | border-radius: 6px;
206 | outline: none;
207 | background-color: #b9c4bf;
208 | -webkit-appearance: none;
209 | }
210 |
211 | input[type="range"]::-webkit-slider-thumb {
212 | -webkit-appearance: none;
213 | height: 20px;
214 | width: 12px;
215 | background-color: rgba(42, 210, 110, .9);
216 | border: none;
217 | -webkit-border-radius: 2px;
218 | border-radius: 2px;
219 | -webkit-box-shadow: 0 0 1px rgba(42, 210, 110, .2);
220 | box-shadow: 0 0 1px rgba(42, 210, 110, .2);
221 | }
222 |
223 | label {
224 | color: #333;
225 | }
226 |
227 | .btn {
228 | display: block;
229 | padding: 6px;
230 | width: 80px;
231 | height: 20px;
232 | -webkit-border-radius: 4px;
233 | border-radius: 4px;
234 | font-size: 14px;
235 | text-align: center;
236 | text-shadow: 0 0 1px rgba(0, 0, 0, .2);
237 | color: #fff;
238 | background-color: #36b953;
239 | cursor: pointer;
240 | -webkit-transition: opacity .5s;
241 | transition: opacity .5s;
242 | }
243 |
244 | .btn:hover {
245 | opacity: .8;
246 | }
247 |
248 | .btn-small {
249 | display: inline-block;
250 | vertical-align: middle;
251 | padding: 0 6px;
252 | width: 24px;
253 | height: 18px;
254 | border: none;
255 | outline: none;
256 | line-height: 18px;
257 | font-size: 18px;
258 | text-align: center;
259 | text-shadow: 0 0 1px rgba(0, 0, 0, .2);
260 | color: #36b953;
261 | background-color: #fff;
262 | cursor: pointer;
263 | }
264 |
--------------------------------------------------------------------------------
/js/pixel.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var $ = function (id) {
3 | return id ? document.getElementById(id) : null;
4 | };
5 |
6 | // global
7 | var currentThreshold = 128,
8 | scale = 0.25,
9 | currentImage = '';
10 |
11 | var thresholdLevel = $('threshold-level'),
12 | thresholdVal = document.querySelector('.threshold-val'),
13 | mode = document.getElementsByName('mode'),
14 | add = $('add'),
15 | subtract = $('subtract'),
16 | scaleRatio = $('scale'),
17 | isThresholdOn = $('threshold-on'),
18 | modePanel = $('mode-panel'),
19 | modeBlack = $('mode-black'),
20 | modeColor = $('mode-color'),
21 | uploadBtn = $('img-upload'),
22 | downloadBtn = $('download');
23 |
24 | /**
25 | * [thresholdConvert 阈值处理]
26 | * @param {[type]} ctx [description]
27 | * @param {[type]} imageData [description]
28 | * @param {[type]} threshold [阈值]
29 | * @param {[type]} mode [模式:0:彩色,1:黑白]
30 | * @return {[type]} [description]
31 | */
32 | var thresholdConvert = function (ctx, imageData, threshold, mode) {
33 | var data = imageData.data;
34 | for (var i = 0; i < data.length; i += 4) {
35 | var red = data[i];
36 | var green = data[i + 1];
37 | var blue = data[i + 2];
38 | var alpha = data[i + 3];
39 |
40 | // 灰度计算公式
41 | var gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
42 | var color = gray >= threshold ? 255 : 0;
43 | data[i] = (mode == 0 && color == 0) ? red : color; // red
44 | data[i + 1] = (mode == 0 && color == 0) ? green : color; // green
45 | data[i + 2] = (mode == 0 && color == 0) ? blue : color; // blue
46 | data[i + 3] = alpha >= threshold ? 255 : 0; // 去掉透明
47 | }
48 | ctx.putImageData(imageData, 0, 0);
49 | };
50 |
51 | var render = function () {
52 | if (!currentImage) {
53 | alert('请先上传图片');
54 | return;
55 | }
56 | var canvasTemp = document.createElement('canvas');
57 | var context = canvasTemp.getContext('2d');
58 | var image = new Image();
59 | image.src = currentImage;
60 | image.onload = function () {
61 | canvasTemp.width = image.width * scale;
62 | canvasTemp.height = image.height * scale;
63 | // 缩小到 25%
64 | context.drawImage(image, 0, 0, image.width * scale, image.height * scale);
65 | var imageData = context.getImageData(0, 0, image.width * scale, image.height * scale);
66 | // 阈值处理
67 | isThresholdOn.checked && thresholdConvert(context, imageData, currentThreshold, getModeValue(mode));
68 | // canvas转图片
69 | var dataURL = canvasTemp.toDataURL();
70 | var canvas = $('canvas');
71 | var ctx = canvas.getContext('2d');
72 | var img = new Image();
73 | img.src = dataURL;
74 | img.onload = function () {
75 | canvas.width = img.width / scale;
76 | canvas.height = img.height / scale;
77 | // 反锯齿
78 | ctx.imageSmoothingEnabled = false;
79 | ctx.mozImageSmoothingEnabled = false;
80 | ctx.webkitImageSmoothingEnabled = false;
81 | ctx.msImageSmoothingEnabled = false;
82 | ctx.drawImage(img, 0, 0, img.width / scale, img.height / scale);
83 | download();
84 | };
85 | };
86 | };
87 |
88 | var toggleThreshold = function (checked) {
89 | var thresholdRange = $('threshold-range');
90 | if (checked) {
91 | thresholdLevel.disabled = false;
92 | add.disabled = false;
93 | subtract.disabled = false;
94 | modeBlack.disabled = false;
95 | modeColor.disabled = false;
96 | thresholdRange.classList.remove('disable');
97 | modePanel.classList.remove('disable');
98 | } else {
99 | thresholdLevel.disabled = true;
100 | add.disabled = true;
101 | subtract.disabled = true;
102 | modeBlack.disabled = true;
103 | modeColor.disabled = true;
104 | thresholdRange.classList.add('disable');
105 | modePanel.classList.add('disable');
106 | }
107 | };
108 |
109 | var getModeValue = function (ele) {
110 | for (var i = 0, len = ele.length; i < len; i++) {
111 | if (ele[i].checked) {
112 | return ele[i].value;
113 | }
114 | }
115 | };
116 |
117 | var download = function () {
118 | downloadBtn.download = 'pixel.png';
119 | let downloadPic = canvas.toDataURL();
120 | let img = '
';
121 | console.log($('img-download'));
122 | $('img-download').innerHTML = img;
123 | // canvas转图片
124 | // downloadBtn.href = canvas.toDataURL();
125 | };
126 |
127 | // events
128 | thresholdLevel.addEventListener('change', function () {
129 | currentThreshold = this.value;
130 | thresholdVal.innerHTML = currentThreshold;
131 | render();
132 | }, false);
133 |
134 | subtract.addEventListener('click', function () {
135 | currentThreshold = --thresholdLevel.value;
136 | thresholdVal.innerHTML = currentThreshold;
137 | render();
138 | }, false);
139 |
140 | add.addEventListener('click', function () {
141 | currentThreshold = ++thresholdLevel.value;
142 | thresholdVal.innerHTML = currentThreshold;
143 | render();
144 | }, false);
145 |
146 | scaleRatio.addEventListener('change', function () {
147 | scale = this.value;
148 | render();
149 | }, false);
150 |
151 | isThresholdOn.addEventListener('change', function () {
152 | toggleThreshold(this.checked);
153 | render();
154 | }, false);
155 |
156 | for (var i = 0, len = mode.length; i < len; i++) {
157 | mode[i].addEventListener('change', function () {
158 | render();
159 | }, false);
160 | }
161 |
162 | // upload
163 | uploadBtn.addEventListener('change', function (e) {
164 | var file = e.target.files[0];
165 | if (!file.type.match('image.*')) {
166 | return;
167 | }
168 | var reader = new FileReader();
169 | reader.readAsDataURL(file);
170 | reader.onload = function (arg) {
171 | currentImage = arg.target.result;
172 | var img = '
';
173 | $('img-preview').innerHTML = img;
174 | render();
175 | };
176 | }, false);
177 | })();
178 |
179 |
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------