Skip to content

Instantly share code, notes, and snippets.

@irlabs
Last active August 31, 2020 07:58
Show Gist options
  • Save irlabs/eb841bfea1423e3a5ba56b649617145d to your computer and use it in GitHub Desktop.
Save irlabs/eb841bfea1423e3a5ba56b649617145d to your computer and use it in GitHub Desktop.
Snap color into fixed palette
<html>
<head>
</head>
<body>
<div class="wrap">
<div class="half">
<div class="colorPicker"></div>
</div>
<div class="half readout">
<h1>Snap into fixed palette</h1>
<p>The <strong>Color Fixed Palette</strong> will snap a given color into a predefined fixed palette of greys and colors.</p>
<h3>Example palette</h3>
<span class="title">Greys:</span>
<div id="showGreys"></div>
<span class="title">Colors:</span>
<div id="showColors"></div>
<h3>Demo</h3>
<p>
As demonstration pick a color from the color picker, this color will be snapped into a color from the palette.
</p>
<span class="title">Picked Color:</span>
<div id="values"></div>
<span class="title">Fixed Palette Color:</span>
<div id="forced"></div>
</div>
</div>
</body>
</html>
// Color Fixed Palette
// Usage: Initiate a ColorFixedPalette object with a palette,
// consisting of two sets: greys and colors.
//
// Then you can snap a given color into the palette by
// palette_instance .snap( input_color )
// This will return a color from the fixed palette.
//
// Both input and output colors are defined as hex strings
// (e.g. "#ff0000" )
//
// Snapping the color will first tries to see if the input
// color looks like a grey. If it's not a grey, if will
// return the closest matching color.
function ColorFixedPalette(greys, colors) {
this.greys = greys
this.colors = colors
this.snap = function(c) {
let hsv = tinycolor(c).toHsv()
//
// First force into the greys
// A simple algorithm, take the HSV color, and check the
// saturation below a specific threshold
if ((hsv.s < 0.15) && this.greys) {
let paletteValues = this.greys.map(g => tinycolor(g).toHsv().v)
let dist = paletteValues.map(v => Math.abs(hsv.v - v))
let index = dist.indexOf(Math.min(...dist))
return this.greys[index]
}
//
// If not grey, then find closest color
// calculate the distance to the each color in the preset
// and return the nearest.
// There is the `hueFactor` to account for the different scale
// and perceived distincting of the hue.
let hueFactor = 1.5
let paletteHSVs = this.colors.map(g => tinycolor(g).toHsv())
let dist = paletteHSVs.map(p => {
let hueDist = Math.min(
Math.abs(hsv.h - (p.h + 360)),
Math.abs(hsv.h - p.h),
Math.abs(hsv.h - (p.h - 360))
)
let distH = Math.pow((hueDist * hueFactor), 2)
let distS = Math.pow((Math.abs(hsv.s - p.s) * 100), 2)
let distV = Math.pow((Math.abs(hsv.v - p.v) * 100), 2)
return distH + distS + distV
})
let index = dist.indexOf(Math.min(...dist))
return this.colors[index]
}
}
// Example code
var greys = ["#000000", "#404040", "#808080", "#c0c0c0", "#ffffff"]
var colors = ["#f9ff8a", "#ff931f", "#ff4a21", "#c4000a", "#f5316f", "#c92e96", "#741594", "#2e1fff", "#3494e3", "#24c992", "#1da13c", "#85bd31"]
var fixedPalette = new ColorFixedPalette(greys, colors)
// We created a (iro.js) color picker instance for the example demo.
// https://iro.js.org/guide.html#getting-started
var colorPicker = new iro.ColorPicker(".colorPicker", {
// color picker options
// Option guide: https://iro.js.org/guide.html#color-picker-options
width: 280,
color: "rgb(255, 0, 0)",
borderWidth: 1,
borderColor: "#fff",
})
var values = document.getElementById("values")
// https://iro.js.org/guide.html#color-picker-events
colorPicker.on(["color:init", "color:change"], function(color){
//
// Grey Palette
showGreys.innerHTML = greys.map(s => '<span class="listSwatch" style="background-color: ' + s + '">&nbsp;&nbsp;</span>').join('\n')
//
showColors.innerHTML = colors.map(s => '<span class="listSwatch" style="background-color: ' + s + '">&nbsp;&nbsp;</span>').join('\n')
//
// Show the current color in different formats
// Using the selected color: https://iro.js.org/guide.html#selected-color-api
values.innerHTML = [
"hex: " + color.hexString + '<span class="swatch" style="background-color: '+ color.hexString + '">&nbsp;&nbsp;</span>',
// "rgb: " + color.rgbString,
// "hsl: " + color.hslString,
"hsv: " + tinycolor(color.hexString).toHsvString(),
].join("<br>")
//
// Test with color manipulation
let modCol = fixedPalette.snap(color.hexString)
forced.innerHTML = [
"hex: " + modCol + '<span class="swatch" style="background-color: '+ modCol + '">&nbsp;&nbsp;</span>',
"hsv: " + tinycolor(modCol).toHsvString(),
].join("<br>")
})
<script src="https://cdn.jsdelivr.net/npm/@jaames/iro/dist/iro.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.1/tinycolor.min.js"></script>
body {
color: #fff;
background: #171F30;
line-height: 150%;
}
.wrapper svg {
}
.wrap {
min-height: 100vh;
max-width: 840px;
margin: 0 auto;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.half {
width: 50%;
padding: 32px 0;
}
}
.title {
font-family: sans-serif;
line-height: 24px;
display: block;
padding: 8px 0;
}
.readout {
margin-top: 32px;
line-height: 180%;
}
#showGreys,
#showColors,
#forced,
#values {
font-family: monospace;
line-height: 150%;
}
.link {
margin-top: 16px;
a {
color: MediumSlateBlue;
}
}
span.listSwatch {
margin: 0px;
}
span.swatch {
margin-left: 10px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment