├── 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 | pixel 8 | 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 图像转像素图 8 | 9 | 10 | 11 | 12 |
13 | 43 |
44 |
45 | 46 |
47 |
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 = 'preview'; 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 = 'preview'; 173 | $('img-preview').innerHTML = img; 174 | render(); 175 | }; 176 | }, false); 177 | })(); 178 | 179 | 180 | 181 | 182 | 183 | 184 | --------------------------------------------------------------------------------