Skip to content

Instantly share code, notes, and snippets.

@cgsdev0
Created March 16, 2022 03:39
Show Gist options
  • Save cgsdev0/26affdfe65710379b8420153ae53e5d2 to your computer and use it in GitHub Desktop.
Save cgsdev0/26affdfe65710379b8420153ae53e5d2 to your computer and use it in GitHub Desktop.
slices a minecraft skin into multiple images; currently hosted here https://cgs.dev/minecraft_skin_exploder.html
<!doctype html>
<html>
<head>
<title>Minecraft Skin Exploder</title>
</head>
<script type="text/javascript">
const maskdata = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAZhJREFUeJztm71KA0EURjPaiJAmgitiYbWY3iewsNQm2FjZiZBewVLQUhDFTgR/QFL5HBaCxYattNIFkyYidmstCXcIN3DA/U6dO3P4uLOZ2UlCLcLC4VwZ+4zFx1EveOrLi6Zr/rCXmfNPeQb/DygAWoBGAdACNAqAFqAJ3u95L++L8+T06gAFQAvQKABagEYB0AI0Q2fl9Kww9wV5OzHP143VLde+ov/0YI5frKTm+Ek3H+v9Q+U7QAHQAjQhtuZjfF63J+Uykuzr2VUfeyZUvgMUAC1AowBoARoFQAvQKABagEYB0AI0Q/vkRr1lng36g465ty6bP777/GzGHP929socf/t7R+8DxkEB0AI0IbbmY/SWbiblMpK7t3tXfeyZUPkOUAC0AI0CoAVoFAAtQKMAaAEaBUAL0Aztk+utDfNsMOg8mnvr18K+v19O7Lu6tLD/H5An9u//dw/2zfrL45M/9ZXvAAVAC9CE2JqP8XLedQms16Zd9Wunm676yneAAqAFaBQALUCjAGgBGgVAC9AoAFqA5heamUiO3GpTvwAAAABJRU5ErkJggg==";
const old_colormap = {
"left_arm": "#e92826",
"right_arm": "#26e929",
"right_leg": "#e0e926",
"left_leg": "#2693e9",
"left_leg_outer": "#120d54",
"right_leg_outer": "#0d544e",
"right_arm_outer": "#687978",
"left_arm_outer": "#a50a9f",
"chest": "#123456",
"jacket": "#ff29f8",
"hat": "#ff29f8",
"head": "#187c13"
}
const colormap = {
"arms": [ "#e92826", "#26e929" ],
"legs": [ "#e0e926", "#2693e9" ],
"legs_outer": [ "#120d54", "#0d544e" ],
"arms_outer": [ "#687978", "#a50a9f" ],
"chest": "#123456",
"jacket": "#ff29f8",
"hat": "#ff29f8",
"head": "#187c13"
}
var uploaded_name = "unknown";
const componentToHex = (c) => {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
const color_comp = (r, g, b, hex) => {
const c = "#" +
componentToHex(r) +
componentToHex(g) +
componentToHex(b);
return c.toLowerCase() === hex.toLowerCase();
}
const clear_canvas = () => {
const canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
const create_canvas = (colormap, ...segments) => {
const img = document.querySelector("#mask");
const skin = document.querySelector("#preview");
const canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
if (!segments.some((seg) => color_comp(r,g,b,seg))) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
data[i + 3] = 255;
}
else {
data[i] = 255;
data[i + 1] = 255;
data[i + 2] = 255;
data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
var idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data32 = new Uint32Array(idata.data.buffer);
var i = 0, len = data32.length;
while(i < len) data32[i] = data32[i++] << 8;
ctx.putImageData(idata, 0, 0);
ctx.globalCompositeOperation = "source-in";
ctx.drawImage(skin, 0, 0);
ctx.globalCompositeOperation = "source-over";
}
//
function split_and_download() {
var colormap_edited = JSON.parse(document.querySelector("#mask_editor").value);
for(const [k, v] of Object.entries(colormap_edited)) {
if (Array.isArray(v)) {
create_canvas(colormap_edited, ...v);
} else {
create_canvas(colormap_edited, v);
}
var link = document.createElement('a');
link.setAttribute('download', uploaded_name + "_" + k + '.png');
link.setAttribute('href', document.querySelector("canvas").toDataURL("image/png").replace("image/png", "image/octet-stream"));
link.click();
}
clear_canvas();
}
window.addEventListener('load', () => {
document.querySelector('#upload').addEventListener('change', function() {
if (this.files && this.files[0]) {
var img = document.querySelector('#preview');
img.onload = () => {
URL.revokeObjectURL(img.src); // no longer needed, free memory
}
img.src = URL.createObjectURL(this.files[0]); // set src to blob url
const uploaded_name_arr = this.files[0].name.split(".");
if(uploaded_name_arr.length > 1) {
uploaded_name_arr.pop();
uploaded_name = uploaded_name_arr.join("");
}
else {
uploaded_name = uploaded_name_arr[0];
}
document.querySelector("#download").disabled = false;
}
});
document.querySelector('#upload_mask').addEventListener('change', function() {
if (this.files && this.files[0]) {
var img = document.querySelector('#mask');
img.onload = () => {
URL.revokeObjectURL(img.src); // no longer needed, free memory
}
img.src = URL.createObjectURL(this.files[0]); // set src to blob url
uploaded_name = this.files[0].name;
}
});
document.querySelector("#download").addEventListener('click', split_and_download)
document.querySelector("#download").disabled = true
document.querySelector("#mask_editor").value = JSON.stringify(colormap, null, 4)
function blobToBase64(img) {
var c = document.createElement('canvas');
c.height = img.naturalHeight;
c.width = img.naturalWidth;
var ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0, c.width, c.height);
var base64String = c.toDataURL();
return base64String;
}
// load settings
const mask_image = window.localStorage.getItem("mask_image");
const mask_settings = window.localStorage.getItem("mask_settings");
if (mask_settings) {
document.querySelector("#mask_editor").value = mask_settings
}
if (mask_image) {
document.querySelector("#mask").src = mask_image
}
document.querySelector("#save").addEventListener('click', async () => {
window.localStorage.setItem("mask_settings", document.querySelector("#mask_editor").value)
window.localStorage.setItem("mask_image", blobToBase64(document.querySelector("#mask")))
})
document.querySelector("#reset").addEventListener('click', () => {
document.querySelector("#mask_editor").value = JSON.stringify(colormap, null, 4)
document.querySelector("#mask").setAttribute("src", maskdata);
})
});
</script>
<h1>Minecraft Skin Exploder</h1>
<p>recommended: chrome &gt; settings &gt; advanced &gt; downloads and turn off "Ask where to save each file before downloading"</p>
<img id="preview" width="64" height="64" />
<input type="file" id="upload" />
<br />
<button type="submit" id="download">Download</button>
<br />
<canvas width="64" height="64"></canvas>
<br />
Current layer mask:
<img id="mask" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAZhJREFUeJztm71KA0EURjPaiJAmgitiYbWY3iewsNQm2FjZiZBewVLQUhDFTgR/QFL5HBaCxYattNIFkyYidmstCXcIN3DA/U6dO3P4uLOZ2UlCLcLC4VwZ+4zFx1EveOrLi6Zr/rCXmfNPeQb/DygAWoBGAdACNAqAFqAJ3u95L++L8+T06gAFQAvQKABagEYB0AI0Q2fl9Kww9wV5OzHP143VLde+ov/0YI5frKTm+Ek3H+v9Q+U7QAHQAjQhtuZjfF63J+Uykuzr2VUfeyZUvgMUAC1AowBoARoFQAvQKABagEYB0AI0Q/vkRr1lng36g465ty6bP777/GzGHP929socf/t7R+8DxkEB0AI0IbbmY/SWbiblMpK7t3tXfeyZUPkOUAC0AI0CoAVoFAAtQKMAaAEaBUAL0Aztk+utDfNsMOg8mnvr18K+v19O7Lu6tLD/H5An9u//dw/2zfrL45M/9ZXvAAVAC9CE2JqP8XLedQms16Zd9Wunm676yneAAqAFaBQALUCjAGgBGgVAC9AoAFqA5heamUiO3GpTvwAAAABJRU5ErkJggg==" />
<br />
Upload new mask:
<input type="file" id="upload_mask" />
<br>
<textarea rows="25" cols="50" id="mask_editor"></textarea>
<br>
<button type="submit" id="save">Save Mask + Settings</button>
<button type="submit" id="reset">Reset to defaults</button>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment