Created
December 23, 2020 14:55
-
-
Save Meshiest/1106f0ce85bf1982f3306417a54ce53d to your computer and use it in GitHub Desktop.
generate brickadia gradient avatars with ease
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<style> | |
#palette { | |
display: inline-flex; | |
width: auto; | |
border: 1px solid black; | |
} | |
#palette > div { | |
width: 30px; | |
height: 30px; | |
} | |
</style> | |
<p> | |
<input id="startColor" value="#ffffff"> | |
<input id="endColor" value="#000000"> | |
</p> | |
<p> | |
<div id="palette"></div> | |
</p> | |
<input id="filename" value="avatar"><button id="downloadBtn">download</button> | |
<a id="download" style="display: none"></a> | |
<script> | |
const avatar = { | |
"formatVersion": "1", | |
"presetVersion": "1", | |
"type": "Avatar", | |
"data": | |
{ | |
"parts": [ | |
{ | |
"partDescriptor": "PPD_Default_Pelvis", | |
"decalDescriptor": "", | |
"treeIndex": 0, | |
"colors": [ | |
{ | |
"b": 255, | |
"g": 255, | |
"r": 255, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Skeleton_Torso", | |
"decalDescriptor": "PDD_Torso_Suit", | |
"treeIndex": 1, | |
"colors": [ | |
{ | |
"b": 193, | |
"g": 193, | |
"r": 193, | |
"a": 255 | |
}, | |
{ | |
"b": 73, | |
"g": 73, | |
"r": 73, | |
"a": 255 | |
}, | |
{ | |
"b": 130, | |
"g": 130, | |
"r": 130, | |
"a": 255 | |
}, | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 130, | |
"g": 130, | |
"r": 130, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 193, | |
"g": 193, | |
"r": 193, | |
"a": 255 | |
}, | |
{ | |
"b": 255, | |
"g": 255, | |
"r": 255, | |
"a": 255 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Default_Head", | |
"decalDescriptor": "PDD_Generic_Blank", | |
"treeIndex": 2, | |
"colors": [ | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Skeleton_Arm", | |
"decalDescriptor": "", | |
"treeIndex": 4, | |
"colors": [ | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Cyborg_Wrist", | |
"decalDescriptor": "", | |
"treeIndex": 5, | |
"colors": [ | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 255, | |
"g": 255, | |
"r": 255, | |
"a": 255 | |
}, | |
{ | |
"b": 193, | |
"g": 193, | |
"r": 193, | |
"a": 255 | |
}, | |
{ | |
"b": 130, | |
"g": 130, | |
"r": 130, | |
"a": 255 | |
}, | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Cyborg_Hand", | |
"decalDescriptor": "", | |
"treeIndex": 6, | |
"colors": [ | |
{ | |
"b": 255, | |
"g": 255, | |
"r": 255, | |
"a": 255 | |
}, | |
{ | |
"b": 193, | |
"g": 193, | |
"r": 193, | |
"a": 255 | |
}, | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Skeleton_Arm", | |
"decalDescriptor": "", | |
"treeIndex": 7, | |
"colors": [ | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Cyborg_Wrist", | |
"decalDescriptor": "", | |
"treeIndex": 8, | |
"colors": [ | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 255, | |
"g": 255, | |
"r": 255, | |
"a": 255 | |
}, | |
{ | |
"b": 193, | |
"g": 193, | |
"r": 193, | |
"a": 255 | |
}, | |
{ | |
"b": 130, | |
"g": 130, | |
"r": 130, | |
"a": 255 | |
}, | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Cyborg_Hand", | |
"decalDescriptor": "", | |
"treeIndex": 9, | |
"colors": [ | |
{ | |
"b": 255, | |
"g": 255, | |
"r": 255, | |
"a": 255 | |
}, | |
{ | |
"b": 193, | |
"g": 193, | |
"r": 193, | |
"a": 255 | |
}, | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Striped_Leg", | |
"decalDescriptor": "", | |
"treeIndex": 10, | |
"colors": [ | |
{ | |
"b": 73, | |
"g": 73, | |
"r": 73, | |
"a": 255 | |
}, | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 130, | |
"g": 130, | |
"r": 130, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Striped_Boot", | |
"decalDescriptor": "", | |
"treeIndex": 11, | |
"colors": [ | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 255 | |
}, | |
{ | |
"b": 73, | |
"g": 73, | |
"r": 73, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Striped_Leg", | |
"decalDescriptor": "", | |
"treeIndex": 12, | |
"colors": [ | |
{ | |
"b": 73, | |
"g": 73, | |
"r": 73, | |
"a": 255 | |
}, | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 103, | |
"g": 103, | |
"r": 103, | |
"a": 255 | |
}, | |
{ | |
"b": 130, | |
"g": 130, | |
"r": 130, | |
"a": 255 | |
}, | |
{ | |
"b": 160, | |
"g": 160, | |
"r": 160, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
}, | |
{ | |
"partDescriptor": "PPD_Striped_Boot", | |
"decalDescriptor": "", | |
"treeIndex": 13, | |
"colors": [ | |
{ | |
"b": 42, | |
"g": 42, | |
"r": 42, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 255 | |
}, | |
{ | |
"b": 73, | |
"g": 73, | |
"r": 73, | |
"a": 255 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
}, | |
{ | |
"b": 0, | |
"g": 0, | |
"r": 0, | |
"a": 0 | |
} | |
] | |
} | |
] | |
} | |
}; | |
// in the future, it may be viable to order all the colors based on colorset order | |
// so people can generate gradients for any avatar | |
const ordered = Array.from(new Set( | |
avatar.data.parts | |
.flatMap(p => p.colors) | |
.filter(c => c.a) | |
.map(c => c.b) | |
.sort((a, b) => a - b))); | |
let colors = []; | |
const $ = document.querySelector.bind(document); | |
// regenerate the palette | |
function recolor() { | |
const isHex = s => !!s.match(/^#[0-9a-f]{6}$/i); | |
const start = $('#startColor').value; | |
const end = $('#endColor').value; | |
if (!isHex(start) || !isHex(end)) return; | |
const palette = $('#palette'); | |
// empty the palette | |
while (palette.firstChild) { | |
palette.removeChild(palette.firstChild); | |
} | |
colors = []; | |
// generate new colors in the palette | |
for (let i = 0; i < ordered.length; i++) { | |
// generate new color in gradient | |
const color = lerpColor(start, end, i/(ordered.length-1)); | |
colors.push(color); | |
// load palette elements | |
const el = document.createElement('div'); | |
el.style.backgroundColor = color; | |
palette.appendChild(el); | |
} | |
} | |
// interoplate between two colors | |
function lerpColor(a, b, amount) { | |
// TODO: maybe change interp method + allow curve selection | |
var ah = ah = +a.replace('#', '0x'), | |
ar = ah >> 16, ag = ah >> 8 & 0xff, ab = ah & 0xff, | |
bh = +b.replace('#', '0x'), | |
br = bh >> 16, bg = bh >> 8 & 0xff, bb = bh & 0xff, | |
rr = ar + amount * (br - ar), | |
rg = ag + amount * (bg - ag), | |
rb = ab + amount * (bb - ab); | |
return '#' + ((1 << 24) + (rr << 16) + (rg << 8) + rb | 0).toString(16).slice(1); | |
} | |
function hexToRgba(hex) { | |
const h = +hex.replace('#', '0x'); | |
const r = h >> 16, g = h >> 8 & 0xff, b = h & 0xff; | |
return { r, g, b, a: 255 }; | |
} | |
// save the avatar to a file | |
function saveAvatar() { | |
const a = $('#download'); | |
// deep clone the original avatar | |
const cloned = JSON.parse(JSON.stringify(avatar)); | |
// color each of the parts (based on the blue index in ordered) | |
for (const part of cloned.data.parts) { | |
for (const color of part.colors) { | |
if (color.a === 0) continue; | |
const newColor = hexToRgba(colors[ordered.indexOf(color.b)]); | |
Object.assign(color, newColor); | |
} | |
} | |
// write to object url and save | |
const json = JSON.stringify(cloned, 0, 2); | |
const blob = new Blob([json], {type: "octet/stream"}); | |
const url = window.URL.createObjectURL(blob); | |
a.href = url; | |
a.download = $('#filename').value + '.bp'; | |
a.click(); | |
window.URL.revokeObjectURL(url); | |
} | |
recolor(); | |
$('#startColor').addEventListener('keyup', recolor); | |
$('#endColor').addEventListener('keyup', recolor); | |
$('#downloadBtn').addEventListener('click', saveAvatar); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment