<div id="box">Drop an image in this window</div>
body, html {
height: 100%;
}
body {
-webkit-align-items: center;
align-items: center;
background: #222;
color: #fff;
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
justify-content: center;
margin: 0;
transition: background-color 200ms;
}
#box {
background: rgba(255, 255, 255, .06);
border-radius: 20px;
font-family: sans-serif;
font-size: 4vh;
max-height: 80%;
overflow: auto;
padding: 30px;
text-shadow: 0 1px 1px rgba(0, 0, 0, .5);
text-transform: uppercase;
}
#box > div {
-webkit-align-items: center;
align-items: center;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: row;
flex-direction: row;
font-family: monospace;
font-size: 14px;
margin-bottom: 8px;
text-transform: none;
}
#box > p {
margin: 0 0 .5em;
}
#box > p button {
background: rgba(0, 0, 0, .2);
border: 0;
border-radius: 4px;
color: #fff;
font-size: 14px;
margin-right: 6px;
padding: 6px 10px;
}
#box > p button:hover {
background: rgba(0, 0, 0, .5);
}
#box > p button.selected {
color: #0f0;
font-weight: bold;
}
.color {
border: #fff solid 1px;
border-radius: 9999px;
box-shadow: 0 2px 2px rgba(0, 0, 0, .5);
}
.drop-it-like-its-hot {
background: #0a0;
}
.working-on-it {
background: #888;
}
.oh-crap {
background: #a00;
}
const body = document.body;
const box = document.getElementById('box');
function bytes(hex) {
const bytes = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16));
}
return bytes;
}
function hex(...bytes) {
return bytes.reduce((hex, byte) => hex + (byte < 16 ? '0' : '') + byte.toString(16), '');
}
const adapters = {
android: (r, g, b) => {
return `Color.rgb(${r}, ${g}, ${b})`
},
hex: (r, g, b) => `#${hex(r, g, b)}`,
swift: (r, g, b) => {
[r, g, b] = [r, g, b].map(v => (v / 255).toPrecision(3));
return `UIColor(red: ${r}, green: ${g}, blue: ${b}, alpha: 1)`
}
};
class App {
displayError(error) {
box.textContent = error;
body.classList.remove('working-on-it');
body.classList.add('oh-crap');
setTimeout(() => {
body.classList.remove('oh-crap');
if (box.textContent == error) {
box.textContent = 'Drop an image in this window';
}
}, 2000);
}
dragenter() {
body.classList.add('drop-it-like-its-hot');
}
dragleave() {
body.classList.remove('drop-it-like-its-hot');
}
dragover(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
body.classList.add('drop-it-like-its-hot');
}
drop(event) {
event.preventDefault();
this.dragleave();
const firstImage = Array.prototype.filter.call(event.dataTransfer.files, file => file.type.match(/^image\//))[0];
if (!firstImage) {
this.displayError('Could not load image');
return;
}
box.textContent = 'Please wait...';
body.classList.add('working-on-it');
setTimeout(() => {
this.getCanvasFromFile(firstImage)
.then(canvas => this.extractColors(canvas))
.then(colors => {
this.renderPalette(colors);
})
.then(() => body.classList.remove('working-on-it'))
.catch(() => {
this.displayError('Could not load image');
});
}, 500);
}
extractColors(canvas) {
const palette = Object.create(null);
const context = canvas.getContext('2d');
const data = context.getImageData(0, 0, canvas.width, canvas.height).data;
for (let i = 0; i < data.length; i += 4) {
let color = hex(data[i], data[i + 1], data[i + 2]);
palette[color] = (palette[color] || 0) + 1;
}
const numPixels = canvas.width * canvas.height;
return Object.keys(palette)
.map(color => ({color, count: palette[color], fraction: palette[color] / numPixels}))
.sort((a, b) => b.count - a.count)
.slice(0, 100);
}
getCanvasFromFile(file) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onerror = reject;
image.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
resolve(canvas);
};
image.src = URL.createObjectURL(file);
});
}
handleEvent(event) {
return this[event.type](event);
}
renderPalette(colors, adapter = null) {
const fragment = document.createDocumentFragment();
const p = document.createElement('p');
for (let option in adapters) {
if (!adapter) adapter = option;
let button = document.createElement('button');
button.textContent = option;
button.onclick = () => this.renderPalette(colors, option);
if (adapter == option) {
button.className = 'selected';
}
p.appendChild(button);
}
fragment.appendChild(p);
colors.forEach(({color, fraction}) => {
const size = Math.ceil(Math.min(.3 + fraction * 20, 1) * 32);
const div = document.createElement('div');
div.innerHTML = `<div class="color" style="background: #${color};width:${size}px;height:${size}px;margin:0 ${32-size/2}px;" title="${(fraction * 100).toPrecision(3)}%"></div> ${adapters[adapter](...bytes(color))}`;
fragment.appendChild(div)
});
box.innerHTML = '';
box.appendChild(fragment);
}
}
const app = new App();
['dragover', 'dragenter', 'dragleave', 'drop'].forEach(t => body.addEventListener(t, app));
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.