Skip to content

Instantly share code, notes, and snippets.

@mchorse
Last active May 3, 2017 08:14
Show Gist options
  • Save mchorse/b567a0fbd0f9b8f80ed480c36cfea22b to your computer and use it in GitHub Desktop.
Save mchorse/b567a0fbd0f9b8f80ed480c36cfea22b to your computer and use it in GitHub Desktop.
Pixel skin editor (written on iPad)

Basic skin editor

Hello there. You're watching ESPN channel, but specifically iPad coding category... Whoa, sorry about that, he-he.

This is standard (Steve's) skin editor. It has really basic features:

  • Pencil
  • Eraser
  • Clear canvas
  • Importing skins
  • Previewing skin
  • Saving skin (hold finger on the image below preview until it shows the dialog)
  • Scale and translate canvas with move tool (it's for iPad, duh)
  • RGB and CSS color picker
  • 4x4 grid

It's made for making skins on the go, and probably works on other devices. No mouse support (deal with it 😎).

Since previous versions few bugs were fixed, and the general layout was also changed a little bit.

By the way, this skin editor written on iPad.

data:text/html,<!doctype%20html><html>%20%20<head>%20%20%20%20<meta%20charset="utf-8">%20%20%20%20<title>Skin%20Editor</title>%20%20%20%20<meta%20name="viewport"%20content="initial-scale=1.0,%20maximum-scale=1.0,%20minimum-scale=1.0,%20user-scalable=no,%20width=device-width"><style>*{%20%20box-sizing:%20border-box;%20%20margin:%200px;%20%20padding:%200px;}img{%20%20image-rendering:%20optimizeSpeed;%20%20image-rendering:%20-webkit-optimize-contrast;}html,body{%20%20background-color:%20#eee;%20%20font:%20400%2014px%20Helvetica%20Neue;%20%20overflow:%20hidden;}.hidden{%20%20display:%20none;}/*%20Color%20bar%20*/.colors{%20%20position:%20fixed;%20%20top:%200px;%20%20right:%200px;%20%20%20%20box-shadow:%200px%201px%204px%20rgba(0,%200,%200,%200.425);%20%20border-radius:%200px%200px%200px%208px;%20%20overflow:%20hidden;}.color-result{%20%20float:%20left;%20%20width:%2060px;%20%20height:%2036px;}#hide{%20%20background-color:%20red;%20%20border:%200px;%20%20color:%20white;%20%20font-size:%2014px;%20%20font-weight:%20bold;%20%20%20%20float:%20left;%20%20height:%2036px;%20%20padding:%200px%2024px;}.color-input{%20%20border:%200px%20solid%20#eee;%20%20border-radius:%200px;%20%20font-size:%2014px;%20%20%20%20padding:%206px%206px;%20%20height:%2036px;%20%20width:%20176px;}/*%20Tool%20bar%20*/.color{%20%20background-color:%20#000;%20%20border-bottom:%203px%20solid%20#fff;%20%20%20%20float:%20left;}.bar{%20%20border-radius:%200px%200px%208px%200px;%20%20box-shadow:%200px%201px%204px%20rgba(0,%200,%200,%200.425);%20%20overflow:%20hidden;%20%20%20%20float:%20left;}.second{%20%20border-radius:%200px%200px%208px%208px;%20%20%20%20margin-left:%202em;}.bar:after{%20%20clear:%20both;%20%20content:%20"";%20%20display:%20block;%20%20height:%200px;}.toolbar{%20%20position:%20fixed;%20%20top:%200px;%20%20left:%200px;%20%20right:%200px;}.toolbar%20.color{%20%20padding:%208px%2012px;}.pencil{%20%20color:%20#000;%20%20background-color:%20#ff0;%20%20border-color:%20#ff0;}.eraser{%20%20background-color:%20#eee;%20%20border-color:%20#eee;}.picker{%20%20color:%20#fff;%20%20background-color:%20#444;%20%20border-color:%20#444;}.move{%20%20color:%20#fff;%20%20background-color:%20deeppink;%20%20border-color:%20deeppink;}.import{%20%20color:%20#fff;%20%20background-color:%20#08f;%20%20border-color:%20#08f;}.reset{%20%20color:%20#fff;%20%20background-color:%20#e11;%20%20border-color:%20#e11;}.toolbar%20.bar:not(.second)%20:not(.selected){%20%20border-color:%20#fff;}/*%20Canvas%20*/#canvas{%20%20position:%20absolute;%20%20top:%2050%;%20%20left:%2050%;%20%20background-color:%20#fff;%20%20border:%201px%20solid%20#ddd;%20%20margin-left:%20-320px;%20%20margin-top:%20-160px;}#preview{%20%20position:%20fixed;%20%20bottom:%200px;%20%20left:%200px;%20%20%20%20background-color:%20rgba(0,%200,%200,%200.2);%20%20height:%20192px;%20%20width:%20204px;}#avatar{%20%20position:%20fixed;%20%20bottom:%20200px;%20%20%20%20background-color:%20rgba(0,%200,%200,%200.2);%20%20height:%2064px;%20%20width:%20128px;}#red,#green,#blue{%20%20position:%20absolute;%20%20right:%208px;}#red{%20%20top:%2044px;%20%20border-radius:%208px%208px%200px%200px;}#green{%20%20top:%2074px;}#blue{%20%20top:%20104px;%20%20border-radius:%200px%200px%208px%208px;}#file{%20%20visibility:%20hidden;}</style>%20%20</head>%20%20<body><canvas%20id="canvas"%20width="640"%20height="320"></canvas><img%20id="preview"><img%20id="avatar"><div%20class="colors">%20%20<button%20id="hide">X</button>%20%20<div%20class="color-result"></div>%20%20<input%20class="color-input"%20placeholder="Hex%20or%20named%20color"%20type="color"></div><canvas%20id="red"%20width="220"%20height="30"></canvas><canvas%20id="green"%20width="220"%20height="30"></canvas><canvas%20id="blue"%20width="220"%20height="30"></canvas><div%20class="toolbar">%20%20<div%20class="bar">%20%20%20%20<div%20class="color%20pencil"%20data-tool="pencil">Pencil</div>%20%20%20%20<div%20class="color%20eraser"%20data-tool="eraser">Eraser</div>%20%20%20%20<div%20class="color%20picker"%20data-tool="picker">Picker</div>%20%20%20%20<div%20class="color%20move"%20data-tool="move">Move</div>%20%20</div>%20%20%20%20<div%20class="bar%20second">%20%20%20%20<div%20class="color%20import">Import</div>%20%20%20%20<div%20class="color%20reset">Reset</div>%20%20</div>%20%20%20%20<input%20id="file"%20type="file"></div><script>var%20transparent%20=%20[0,%200,%200,%200];transparent.rgb%20=%20to_color(transparent);/*%20Utils%20*/function%20to_a%20(list)%20{%20%20return%20Array.prototype.slice.call(list);}function%20eStack%20(e)%20{%20%20var%20stack%20=%20e.stack.split("\n").map(function%20(str)%20{%20%20%20%20return%20str.substr(0,%20str.indexOf("@"))%20+%20"@localhost";%20%20});%20%20%20%20return%20stack.join("\n");}/*%20DOM%20functions%20*/function%20$%20(s,%20c)%20{%20%20return%20(c%20||%20document).querySelector(s);}function%20$$%20(s,%20c)%20{%20%20return%20to_a((c%20||%20document).querySelectorAll(s));}function%20on%20(n,%20e,%20f)%20{%20%20n.addEventListener(e,%20f);}/*%20Canvas%20class%20*/function%20Canvas%20(w,%20h)%20{%20%20this.w%20=%20w;%20%20this.h%20=%20h;%20%20this.reset();}Canvas.prototype%20=%20{%20%20reset:%20function%20()%20{%20%20%20%20this.map%20=%20[];%20%20%20%20%20%20for%20(var%20x%20=%200;%20x%20<%20this.w;%20x%20++)%20{%20%20%20%20%20%20this.map[x]%20=%20[];%20%20%20%20%20%20%20%20%20%20for%20(var%20y%20=%200;%20y%20<%20this.h;%20y%20++)%20{%20%20%20%20%20%20%20%20this.map[x][y]%20=%20transparent;%20%20%20%20%20%20}%20%20%20%20}%20%20},%20%20%20%20scale:%20function%20(w,%20h)%20{%20%20%20%20return%20Math.floor(Math.min(w%20/%20this.w,%20h%20/%20this.h))%20||%201%20%20},%20%20%20draw:%20function%20(x,%20y,%20color)%20{%20%20%20%20color.rgb%20=%20to_color(color);%20%20%20%20this.map[x][y]%20=%20color;%20%20},%20%20%20%20render:%20function%20(ctx,%20w,%20h)%20{%20%20%20%20var%20scale%20=%20this.scale(w,%20h);%20%20%20%20%20%20%20%20for%20(var%20x%20=%200;%20x%20<%20this.w;%20x%20++)%20{%20%20%20%20%20%20for%20(var%20y%20=%200;%20y%20<%20this.h;%20y%20++)%20{%20%20%20%20%20%20%20%20this.renderPixel(ctx,%20x,%20y,%20scale);%20%20%20%20%20%20}%20%20%20%20}%20%20},%20%20%20%20renderPixel:%20function%20(ctx,%20x,%20y,%20scale,%20x2,%20y2)%20{%20%20%20%20ctx.fillStyle%20=%20this.map[x][y].rgb;%20%20%20%20%20%20%20%20x2%20=%20x2%20===%20undefined%20?%20x%20:%20x2;%20%20%20%20y2%20=%20y2%20===%20undefined%20?%20y%20:%20y2;%20%20%20%20ctx.clearRect(x2%20*%20scale,%20y2%20*%20scale,%20scale,%20scale);%20%20%20%20ctx.fillRect(x2%20*%20scale,%20y2%20*%20scale,%20scale,%20scale);%20%20},%20%20%20%20fromImageData:%20function%20(img)%20{%20%20%20%20for%20(var%20i%20=%200,%20c%20=%20img.data.length;%20i%20<%20c;%20i%20+=%204)%20{%20%20%20%20%20%20var%20x%20=%20(i%20/%204)%20%%20img.width,%20%20%20%20%20%20%20%20y%20=%20Math.floor((i%20/%204)%20/%20img.width);%20%20%20%20%20%20var%20r%20=%20img.data[i],%20%20%20%20%20%20%20%20g%20=%20img.data[i%20+%201],%20%20%20%20%20%20%20%20b%20=%20img.data[i%20+%202],%20%20%20%20%20%20%20%20a%20=%20img.data[i%20+%203];%20%20%20%20%20%20this.draw(x,%20y,%20[r,%20g,%20b,%20a]);%20%20%20%20}%20%20}};function%20to_color%20(color)%20{%20%20var%20r%20=%20color[0]%20>>%200,%20%20%20%20g%20=%20color[1]%20>>%200,%20%20%20%20b%20=%20color[2]%20>>%200,%20%20%20%20a%20=%20color[3]%20>>%200;%20%20%20%20return%20"rgba("%20+%20r%20+%20","%20+%20g%20+%20","%20+%20b%20+%20","%20+%20a%20+%20")";}function%20Slide%20(picker,%20canvas,%20index)%20{%20%20this.picker%20=%20picker;%20%20this.ctx%20=%20canvas.getContext("2d");%20%20this.index%20=%20index;%20%20%20%20this.initEvents(canvas);%20%20this.generateGradient();}Slide.prototype%20=%20{%20%20initEvents:%20function%20(canvas)%20{%20%20%20%20var%20self%20=%20this,%20%20%20%20%20%20callback%20=%20function%20(e)%20{%20self.click(e,%20canvas);%20};%20%20%20%20%20%20%20%20on(canvas,%20"touchstart",%20callback);%20%20%20%20on(canvas,%20"touchmove",%20callback);%20%20%20%20this.canvas%20=%20canvas;%20%20},%20%20%20%20click:%20function%20(e,%20canvas)%20{%20%20%20%20var%20value%20=%20e.touches[0].pageX%20-%20canvas.offsetLeft;%20%20%20%20%20%20%20%20value%20=%20(15%20+%20value)%20/%20(canvas.width%20-%2030);%20%20%20%20%20%20%20%20this.picker.setComponent(value%20*%20255,%20this.index);%20%20},%20%20%20%20generateGradient:%20function%20()%20{%20%20%20%20var%20color%20=%20this.picker.color.slice();%20%20%20%20%20%20%20%20color[this.index]%20=%200;%20%20%20%20this.gradient%20=%20this.ctx.createLinearGradient(0,%200,%20this.canvas.width,%200);%20%20%20%20this.gradient.addColorStop(0,%20to_color(color));%20%20%20%20color[this.index]%20=%20255;%20%20%20%20this.gradient.addColorStop(1,%20to_color(color));%20%20},%20%20%20%20render:%20function%20()%20{%20%20%20%20var%20w%20=%20this.canvas.width,%20%20%20%20%20%20h%20=%20this.canvas.height;%20%20%20%20var%20ctx%20=%20this.ctx,%20%20%20%20%20%20value%20=%20this.picker.color[this.index];%20%20%20%20%20%20%20%20var%20x%20=%2015%20+%20(value%20/%20255)%20*%20(w%20-%2030);%20%20%20%20%20%20%20%20ctx.fillStyle%20=%20this.gradient;%20%20%20%20ctx.fillRect(0,%200,%20w,%20h);%20%20%20%20%20%20%20%20ctx.strokeStyle%20=%20"rgba(255,255,255,0.5)";%20%20%20%20ctx.lineWidth%20=%201.6;%20%20%20%20ctx.beginPath();%20%20%20%20ctx.arc(x,%2015,%2012,%200,%20Math.PI%20*%202);%20%20%20%20ctx.stroke();%20%20}};function%20Picker%20(red,%20green,%20blue)%20{%20%20this.color%20=%20[0,%200,%200,%201];%20%20%20%20this.red%20=%20new%20Slide(this,%20red,%200);%20%20this.green%20=%20new%20Slide(this,%20green,%201);%20%20this.blue%20=%20new%20Slide(this,%20blue,%202);}Picker.prototype%20=%20{%20%20render:%20function%20()%20{%20%20%20%20this.red.render();%20%20%20%20this.green.render();%20%20%20%20this.blue.render();%20%20},%20%20%20%20setColor:%20function%20(r,%20g,%20b)%20{%20%20%20%20this.color%20=%20arguments.length%20==%203%20?%20[r,%20g,%20b,%201]%20:%20r;%20%20%20%20%20%20%20%20input.value%20=%20to_color(this.color);%20%20%20%20color.style.backgroundColor%20=%20to_color(this.color);%20%20%20%20this.red.generateGradient();%20%20%20%20this.green.generateGradient();%20%20%20%20this.blue.generateGradient();%20%20%20%20%20%20%20%20this.render();%20%20},%20%20%20%20setComponent:%20function%20(value,%20index)%20{%20%20%20%20this.color[index]%20=%20value%20>%20255%20?%20255%20:%20(value%20<%200%20?%200%20:%20value);%20%20%20%20this.setColor(this.color.slice());%20%20}};/*%20States%20*/var%20app%20=%20{%20%20picker:%20new%20Picker($("#red"),%20$("#green"),%20$("#blue")),%20%20tools:%20{%20%20%20%20pencil:%20function%20(x,%20y)%20{%20%20%20%20%20%20app.canvas.draw(x,%20y,%20app.picker.color);%20%20%20%20},%20%20%20%20eraser:%20function%20(x,%20y)%20{%20%20%20%20%20%20app.canvas.draw(x,%20y,%20transparent);%20%20%20%20},%20%20%20%20picker:%20function%20(x,%20y)%20{%20%20%20%20%20%20setColor(app.canvas.map[x][y].rgb,%20true);%20%20%20%20}%20%20},%20%20tool:%20"pencil",%20%20canvas:%20new%20Canvas(64,%2032)};/*%20HTML%20*/var%20canvas%20=%20$("#canvas"),%20%20preview%20=%20$("#preview"),%20%20avatar%20=%20$("#avatar"),%20%20toolbar%20=%20$$("[data-tool]"),%20%20color%20=%20$(".color-result"),%20%20input%20=%20$(".color-input"),%20%20importFile%20=%20$("#file"),%20%20hide%20=%20$("#hide");var%20parseCanvas%20=%20document.createElement("canvas");parseCanvas.width%20=%20parseCanvas.height%20=%201;/*%20Bootstrap%20*/var%20prevent%20=%20function%20(e)%20{%20%20e.preventDefault();};on(document,%20"touchmove",%20prevent);on(canvas,%20"touchend",%20prevent);var%20sx,%20sy,%20%20%20%20omx,%20omy;on(canvas,%20"touchstart",%20function%20(e)%20{%20%20if%20(app.tool%20!=%20"move")%20return%20drawPixel(e);%20%20%20%20sx%20=%20e.touches[0].pageX;%20%20sy%20=%20e.touches[0].pageY;%20%20%20%20omx%20=%20parseInt(canvas.style.marginLeft)%20||%200;%20%20omy%20=%20parseInt(canvas.style.marginTop)%20||%200;});on(canvas,%20"touchmove",%20function%20(e)%20{%20%20if%20(app.tool%20!=%20"move")%20return%20drawPixel(e);%20%20%20%20var%20ox%20=%20sx%20-%20e.touches[0].pageX,%20%20%20%20oy%20=%20sy%20-%20e.touches[0].pageY;%20%20%20%20canvas.style.marginLeft%20=%20(omx%20-%20ox)%20+%20"px";%20%20canvas.style.marginTop%20=%20(omy%20-%20oy)%20+%20"px";});var%20sw,%20sh;on(document,%20"gesturestart",%20function%20(e)%20{%20%20prevent(e);%20%20sw%20=%20canvas.width;%20%20sh%20=%20canvas.height;});on(document,%20"gesturechange",%20function%20(e)%20{%20%20if%20(app.tool%20!=%20"move")%20return;%20%20%20%20var%20w%20=%20Math.floor(sw%20*%20e.scale),%20%20%20%20h%20=%20Math.floor(sh%20*%20e.scale);%20%20%20%20w%20-=%20w%20%%2064;%20%20h%20-=%20h%20%%2032;%20%20%20%20canvas.width%20=%20w;%20%20canvas.height%20=%20h;});on(document,%20"gestureend",%20function%20()%20{%20%20drawCanvas(canvas,%20true);});on(input,%20"change",%20function%20()%20{%20%20setColor(this.value);});on(importFile,%20"change",%20function%20()%20{%20%20var%20reader%20=%20new%20FileReader();%20%20%20%20reader.onload%20=%20function%20(e)%20{%20%20%20%20var%20image%20=%20new%20Image();%20%20%20%20%20%20%20%20image.onload%20=%20function%20()%20{%20%20%20%20%20%20pcanvas.width%20=%2064;%20%20%20%20%20%20pcanvas.height%20=%2032;%20%20%20%20%20%20%20%20%20%20%20%20var%20ctx%20=%20pcanvas.getContext("2d");%20%20%20%20%20%20%20%20%20%20%20%20ctx.drawImage(image,%200,%200);%20%20%20%20%20%20app.canvas.fromImageData(ctx.getImageData(0,%200,%2064,%2032));%20%20%20%20%20%20drawCanvas(canvas,%20true);%20%20%20%20};%20%20%20%20%20%20%20%20image.src%20=%20e.target.result;%20%20};%20%20%20%20reader.readAsDataURL(this.files[0]);});on($(".import"),%20"click",%20function%20()%20{%20%20importFile.click();});on($(".reset"),%20"click",%20function%20()%20{%20%20if%20(!confirm("Are%20you%20sure%20you%20want%20to%20reset?"))%20return;%20%20%20%20app.canvas.reset();%20%20drawCanvas(canvas,%20true);});on(hide,%20"click",%20function%20()%20{%20%20app.picker.red.canvas.classList.toggle("hidden");%20%20app.picker.green.canvas.classList.toggle("hidden");%20%20app.picker.blue.canvas.classList.toggle("hidden");});toolbar.forEach(function%20(node)%20{%20%20on(node,%20"click",%20function%20()%20{%20%20%20%20setTool(node);%20%20});});setTool($(".pencil"));setColor("#000",%20true);drawCanvas(canvas,%20true);app.picker.render();setInterval(updatePreview,%20700);canvas.style.marginLeft%20=%20"-320px";canvas.style.marginTop%20=%20"-160px";/*%20Functions%20*/function%20setTool%20(node)%20{%20%20app.tool%20=%20node.dataset.tool;%20%20%20%20toolbar.forEach(function%20(tool)%20{%20%20%20%20tool.classList.toggle("selected",%20tool.dataset.tool%20==%20app.tool);%20%20});}function%20setColor%20(clr,%20reset)%20{%20%20if%20(clr%20=%20parseColor(clr))%20{%20%20%20%20app.picker.setColor(clr);%20%20%20%20if%20(reset)%20input.value%20=%20to_color(clr);%20%20}}function%20drawCanvas%20(canvas,%20grid)%20{%20%20app.canvas.render(canvas.getContext("2d"),%20canvas.width,%20canvas.height);%20%20%20%20if%20(grid)%20drawGrid(canvas.getContext("2d"),%20canvas.width,%20canvas.height);}function%20drawGrid(ctx,%20w,%20h)%20{%20%20var%20scale%20=%20app.canvas.scale(w,%20h);%20%20if%20(scale%20<%203)%20return;%20%20%20%20ctx.fillStyle%20=%20"#eee";%20%20for%20(var%20i%20=%200;%20i%20<%2064;%20i%20+=%204)%20ctx.fillRect(i%20*%20scale,%200,%201,%2032%20*%20scale);%20%20for%20(var%20i%20=%200;%20i%20<%2032;%20i%20+=%204)%20ctx.fillRect(0,%20i%20*%20scale,%2064%20*%20scale,%201);}function%20drawPixel%20(e)%20{%20%20var%20x%20=%20e.touches[0].pageX%20-%20canvas.offsetLeft,%20%20%20%20y%20=%20e.touches[0].pageY%20-%20canvas.offsetTop,%20%20%20%20w%20=%20canvas.width,%20%20%20%20h%20=%20canvas.height;%20%20%20%20var%20scale%20=%20app.canvas.scale(w,%20h),%20%20%20%20nx%20=%20Math.floor(x%20/%20scale),%20%20%20%20ny%20=%20Math.floor(y%20/%20scale);%20%20%20%20if%20(nx%20<%200%20||%20nx%20>=%20app.canvas.w%20||%20ny%20<%200%20||%20ny%20>=%20app.canvas.h)%20return;%20%20%20%20app.tools[app.tool](nx,%20ny);%20%20app.canvas.renderPixel(canvas.getContext("2d"),%20nx,%20ny,%20scale);}var%20pcanvas%20=%20document.createElement("canvas");function%20updatePreview%20()%20{%20%20pcanvas.width%20=%2064;%20%20pcanvas.height%20=%2032;%20%20%20%20drawCanvas(pcanvas);%20%20avatar.src%20=%20pcanvas.toDataURL("image/png");%20%20%20%20var%20scale%20=%206;%20%20pcanvas.width%20=%20204;%20%20pcanvas.height%20=%20192;%20%20%20%20/*%20Front%20head%20*/%20%20drawRegion(pcanvas,%208,%208,%208,%208,%204,%200,%20scale);%20%20%20%20/*%20Arms%20*/%20%20drawRegion(pcanvas,%2044,%2020,%204,%2012,%200,%208,%20scale);%20%20drawRegion(pcanvas,%2044,%2020,%204,%2012,%2012,%208,%20scale,%20true);%20%20%20%20/*%20Body%20*/%20%20drawRegion(pcanvas,%2020,%2020,%208,%2012,%204,%208,%20scale);%20%20/*%20Legs%20*/%20%20drawRegion(pcanvas,%204,%2020,%204,%2012,%204,%2020,%20scale);%20%20drawRegion(pcanvas,%204,%2020,%204,%2012,%208,%2020,%20scale,%20true);%20%20%20%20/*%20Back%20head%20*/%20%20drawRegion(pcanvas,%2024,%208,%208,%208,%2022,%200,%20scale,%20true);%20%20%20%20/*%20Arms%20*/%20%20drawRegion(pcanvas,%2052,%2020,%204,%2012,%2018,%208,%20scale,%20true);%20%20drawRegion(pcanvas,%2052,%2020,%204,%2012,%2030,%208,%20scale);%20%20%20%20/*%20Body%20*/%20%20drawRegion(pcanvas,%2032,%2020,%208,%2012,%2022,%208,%20scale,%20true);%20%20/*%20Legs%20*/%20%20drawRegion(pcanvas,%2012,%2020,%204,%2012,%2022,%2020,%20scale,%20true);%20%20drawRegion(pcanvas,%2012,%2020,%204,%2012,%2026,%2020,%20scale);%20%20%20%20preview.src%20=%20pcanvas.toDataURL("image/png");}function%20drawRegion(to,%20x,%20y,%20w,%20h,%20x2,%20y2,%20s,%20flip)%20{%20%20var%20ctx%20=%20to.getContext("2d");%20%20%20%20for%20(var%20i%20=%200;%20i%20<%20w;%20i%20++)%20{%20%20%20%20for%20(var%20j%20=%200;%20j%20<%20h;%20j%20++)%20{%20%20%20%20%20%20app.canvas.renderPixel(ctx,%20i%20+%20x,%20j%20+%20y,%20s,%20(flip%20?%20w%20-%20i%20-%201%20:%20i)%20+%20x2,%20j%20+%20y2);%20%20%20%20}%20%20}}function%20parseColor%20(color)%20{%20%20var%20ctx%20=%20parseCanvas.getContext("2d");%20%20%20%20ctx.fillStyle%20=%20color;%20%20ctx.fillRect(0,%200,%201,%201);%20%20%20%20var%20d%20=%20ctx.getImageData(0,%200,%201,%201).data;%20%20%20%20return%20[d[0],%20d[1],%20d[2],%20d[3]%20/%20255];}</script>%20%20</body></html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Skin Editor</title>
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, width=device-width">
<style>
*
{
box-sizing: border-box;
margin: 0px;
padding: 0px;
}
img
{
image-rendering: optimizeSpeed;
image-rendering: -webkit-optimize-contrast;
}
html,
body
{
background-color: #eee;
font: 400 14px Helvetica Neue;
overflow: hidden;
}
.hidden
{
display: none;
}
/* Color bar */
.colors
{
position: fixed;
top: 0px;
right: 0px;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.425);
border-radius: 0px 0px 0px 8px;
overflow: hidden;
}
.color-result
{
float: left;
width: 60px;
height: 36px;
}
#hide
{
background-color: red;
border: 0px;
color: white;
font-size: 14px;
font-weight: bold;
float: left;
height: 36px;
padding: 0px 24px;
}
.color-input
{
border: 0px solid #eee;
border-radius: 0px;
font-size: 14px;
padding: 6px 6px;
height: 36px;
width: 176px;
}
/* Tool bar */
.color
{
background-color: #000;
border-bottom: 3px solid #fff;
float: left;
}
.bar
{
border-radius: 0px 0px 8px 0px;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.425);
overflow: hidden;
float: left;
}
.second
{
border-radius: 0px 0px 8px 8px;
margin-left: 2em;
}
.bar:after
{
clear: both;
content: "";
display: block;
height: 0px;
}
.toolbar
{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
}
.toolbar .color
{
padding: 8px 12px;
}
.pencil
{
color: #000;
background-color: #ff0;
border-color: #ff0;
}
.eraser
{
background-color: #eee;
border-color: #eee;
}
.picker
{
color: #fff;
background-color: #444;
border-color: #444;
}
.move
{
color: #fff;
background-color: deeppink;
border-color: deeppink;
}
.import
{
color: #fff;
background-color: #08f;
border-color: #08f;
}
.reset
{
color: #fff;
background-color: #e11;
border-color: #e11;
}
.toolbar .bar:not(.second) :not(.selected)
{
border-color: #fff;
}
/* Canvas */
#canvas
{
position: absolute;
top: 50%;
left: 50%;
background-color: #fff;
border: 1px solid #ddd;
margin-left: -320px;
margin-top: -160px;
}
#preview
{
position: fixed;
bottom: 0px;
left: 0px;
background-color: rgba(0, 0, 0, 0.2);
height: 192px;
width: 204px;
}
#avatar
{
position: fixed;
bottom: 200px;
background-color: rgba(0, 0, 0, 0.2);
height: 64px;
width: 128px;
}
#red,
#green,
#blue
{
position: absolute;
right: 8px;
}
#red
{
top: 44px;
border-radius: 8px 8px 0px 0px;
}
#green
{
top: 74px;
}
#blue
{
top: 104px;
border-radius: 0px 0px 8px 8px;
}
#file
{
visibility: hidden;
}
</style>
</head>
<body>
<canvas id="canvas" width="640" height="320"></canvas>
<img id="preview">
<img id="avatar">
<div class="colors">
<button id="hide">X</button>
<div class="color-result"></div>
<input class="color-input" placeholder="Hex or named color" type="color">
</div>
<canvas id="red" width="220" height="30"></canvas>
<canvas id="green" width="220" height="30"></canvas>
<canvas id="blue" width="220" height="30"></canvas>
<div class="toolbar">
<div class="bar">
<div class="color pencil" data-tool="pencil">Pencil</div>
<div class="color eraser" data-tool="eraser">Eraser</div>
<div class="color picker" data-tool="picker">Picker</div>
<div class="color move" data-tool="move">Move</div>
</div>
<div class="bar second">
<div class="color import">Import</div>
<div class="color reset">Reset</div>
</div>
<input id="file" type="file">
</div>
<script>var transparent = [0, 0, 0, 0];
transparent.rgb = to_color(transparent);
/* Utils */
function to_a (list) {
return Array.prototype.slice.call(list);
}
function eStack (e) {
var stack = e.stack.split("\n").map(function (str) {
return str.substr(0, str.indexOf("@")) + "@localhost";
});
return stack.join("\n");
}
/* DOM functions */
function $ (s, c) {
return (c || document).querySelector(s);
}
function $$ (s, c) {
return to_a((c || document).querySelectorAll(s));
}
function on (n, e, f) {
n.addEventListener(e, f);
}
/* Canvas class */
function Canvas (w, h) {
this.w = w;
this.h = h;
this.reset();
}
Canvas.prototype = {
reset: function () {
this.map = [];
for (var x = 0; x < this.w; x ++) {
this.map[x] = [];
for (var y = 0; y < this.h; y ++) {
this.map[x][y] = transparent;
}
}
},
scale: function (w, h) {
return Math.floor(Math.min(w / this.w, h / this.h)) || 1
},
draw: function (x, y, color) {
color.rgb = to_color(color);
this.map[x][y] = color;
},
render: function (ctx, w, h) {
var scale = this.scale(w, h);
for (var x = 0; x < this.w; x ++) {
for (var y = 0; y < this.h; y ++) {
this.renderPixel(ctx, x, y, scale);
}
}
},
renderPixel: function (ctx, x, y, scale, x2, y2) {
ctx.fillStyle = this.map[x][y].rgb;
x2 = x2 === undefined ? x : x2;
y2 = y2 === undefined ? y : y2;
ctx.clearRect(x2 * scale, y2 * scale, scale, scale);
ctx.fillRect(x2 * scale, y2 * scale, scale, scale);
},
fromImageData: function (img) {
for (var i = 0, c = img.data.length; i < c; i += 4) {
var x = (i / 4) % img.width,
y = Math.floor((i / 4) / img.width);
var r = img.data[i],
g = img.data[i + 1],
b = img.data[i + 2],
a = img.data[i + 3];
this.draw(x, y, [r, g, b, a]);
}
}
};
function to_color (color) {
var r = color[0] >> 0,
g = color[1] >> 0,
b = color[2] >> 0,
a = color[3] >> 0;
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
function Slide (picker, canvas, index) {
this.picker = picker;
this.ctx = canvas.getContext("2d");
this.index = index;
this.initEvents(canvas);
this.generateGradient();
}
Slide.prototype = {
initEvents: function (canvas) {
var self = this,
callback = function (e) { self.click(e, canvas); };
on(canvas, "touchstart", callback);
on(canvas, "touchmove", callback);
this.canvas = canvas;
},
click: function (e, canvas) {
var value = e.touches[0].pageX - canvas.offsetLeft;
value = (15 + value) / (canvas.width - 30);
this.picker.setComponent(value * 255, this.index);
},
generateGradient: function () {
var color = this.picker.color.slice();
color[this.index] = 0;
this.gradient = this.ctx.createLinearGradient(0, 0, this.canvas.width, 0);
this.gradient.addColorStop(0, to_color(color));
color[this.index] = 255;
this.gradient.addColorStop(1, to_color(color));
},
render: function () {
var w = this.canvas.width,
h = this.canvas.height;
var ctx = this.ctx,
value = this.picker.color[this.index];
var x = 15 + (value / 255) * (w - 30);
ctx.fillStyle = this.gradient;
ctx.fillRect(0, 0, w, h);
ctx.strokeStyle = "rgba(255,255,255,0.5)";
ctx.lineWidth = 1.6;
ctx.beginPath();
ctx.arc(x, 15, 12, 0, Math.PI * 2);
ctx.stroke();
}
};
function Picker (red, green, blue) {
this.color = [0, 0, 0, 1];
this.red = new Slide(this, red, 0);
this.green = new Slide(this, green, 1);
this.blue = new Slide(this, blue, 2);
}
Picker.prototype = {
render: function () {
this.red.render();
this.green.render();
this.blue.render();
},
setColor: function (r, g, b) {
this.color = arguments.length == 3 ? [r, g, b, 1] : r;
input.value = to_color(this.color);
color.style.backgroundColor = to_color(this.color);
this.red.generateGradient();
this.green.generateGradient();
this.blue.generateGradient();
this.render();
},
setComponent: function (value, index) {
this.color[index] = value > 255 ? 255 : (value < 0 ? 0 : value);
this.setColor(this.color.slice());
}
};
/* States */
var app = {
picker: new Picker($("#red"), $("#green"), $("#blue")),
tools: {
pencil: function (x, y) {
app.canvas.draw(x, y, app.picker.color);
},
eraser: function (x, y) {
app.canvas.draw(x, y, transparent);
},
picker: function (x, y) {
setColor(app.canvas.map[x][y].rgb, true);
}
},
tool: "pencil",
canvas: new Canvas(64, 32)
};
/* HTML */
var canvas = $("#canvas"),
preview = $("#preview"),
avatar = $("#avatar"),
toolbar = $$("[data-tool]"),
color = $(".color-result"),
input = $(".color-input"),
importFile = $("#file"),
hide = $("#hide");
var parseCanvas = document.createElement("canvas");
parseCanvas.width = parseCanvas.height = 1;
/* Bootstrap */
var prevent = function (e) {
e.preventDefault();
};
on(document, "touchmove", prevent);
on(canvas, "touchend", prevent);
var sx, sy,
omx, omy;
on(canvas, "touchstart", function (e) {
if (app.tool != "move") return drawPixel(e);
sx = e.touches[0].pageX;
sy = e.touches[0].pageY;
omx = parseInt(canvas.style.marginLeft) || 0;
omy = parseInt(canvas.style.marginTop) || 0;
});
on(canvas, "touchmove", function (e) {
if (app.tool != "move") return drawPixel(e);
var ox = sx - e.touches[0].pageX,
oy = sy - e.touches[0].pageY;
canvas.style.marginLeft = (omx - ox) + "px";
canvas.style.marginTop = (omy - oy) + "px";
});
var sw, sh;
on(document, "gesturestart", function (e) {
prevent(e);
sw = canvas.width;
sh = canvas.height;
});
on(document, "gesturechange", function (e) {
if (app.tool != "move") return;
var w = Math.floor(sw * e.scale),
h = Math.floor(sh * e.scale);
w -= w % 64;
h -= h % 32;
canvas.width = w;
canvas.height = h;
});
on(document, "gestureend", function () {
drawCanvas(canvas, true);
});
on(input, "change", function () {
setColor(this.value);
});
on(importFile, "change", function () {
var reader = new FileReader();
reader.onload = function (e) {
var image = new Image();
image.onload = function () {
pcanvas.width = 64;
pcanvas.height = 32;
var ctx = pcanvas.getContext("2d");
ctx.drawImage(image, 0, 0);
app.canvas.fromImageData(ctx.getImageData(0, 0, 64, 32));
drawCanvas(canvas, true);
};
image.src = e.target.result;
};
reader.readAsDataURL(this.files[0]);
});
on($(".import"), "click", function () {
importFile.click();
});
on($(".reset"), "click", function () {
if (!confirm("Are you sure you want to reset?")) return;
app.canvas.reset();
drawCanvas(canvas, true);
});
on(hide, "click", function () {
app.picker.red.canvas.classList.toggle("hidden");
app.picker.green.canvas.classList.toggle("hidden");
app.picker.blue.canvas.classList.toggle("hidden");
});
toolbar.forEach(function (node) {
on(node, "click", function () {
setTool(node);
});
});
setTool($(".pencil"));
setColor("#000", true);
drawCanvas(canvas, true);
app.picker.render();
setInterval(updatePreview, 700);
canvas.style.marginLeft = "-320px";
canvas.style.marginTop = "-160px";
/* Functions */
function setTool (node) {
app.tool = node.dataset.tool;
toolbar.forEach(function (tool) {
tool.classList.toggle("selected", tool.dataset.tool == app.tool);
});
}
function setColor (clr, reset) {
if (clr = parseColor(clr)) {
app.picker.setColor(clr);
if (reset) input.value = to_color(clr);
}
}
function drawCanvas (canvas, grid) {
app.canvas.render(canvas.getContext("2d"), canvas.width, canvas.height);
if (grid) drawGrid(canvas.getContext("2d"), canvas.width, canvas.height);
}
function drawGrid(ctx, w, h) {
var scale = app.canvas.scale(w, h);
if (scale < 3) return;
ctx.fillStyle = "#eee";
for (var i = 0; i < 64; i += 4) ctx.fillRect(i * scale, 0, 1, 32 * scale);
for (var i = 0; i < 32; i += 4) ctx.fillRect(0, i * scale, 64 * scale, 1);
}
function drawPixel (e) {
var x = e.touches[0].pageX - canvas.offsetLeft,
y = e.touches[0].pageY - canvas.offsetTop,
w = canvas.width,
h = canvas.height;
var scale = app.canvas.scale(w, h),
nx = Math.floor(x / scale),
ny = Math.floor(y / scale);
if (nx < 0 || nx >= app.canvas.w || ny < 0 || ny >= app.canvas.h) return;
app.tools[app.tool](nx, ny);
app.canvas.renderPixel(canvas.getContext("2d"), nx, ny, scale);
}
var pcanvas = document.createElement("canvas");
function updatePreview () {
pcanvas.width = 64;
pcanvas.height = 32;
drawCanvas(pcanvas);
avatar.src = pcanvas.toDataURL("image/png");
var scale = 6;
pcanvas.width = 204;
pcanvas.height = 192;
/* Front head */
drawRegion(pcanvas, 8, 8, 8, 8, 4, 0, scale);
/* Arms */
drawRegion(pcanvas, 44, 20, 4, 12, 0, 8, scale);
drawRegion(pcanvas, 44, 20, 4, 12, 12, 8, scale, true);
/* Body */
drawRegion(pcanvas, 20, 20, 8, 12, 4, 8, scale);
/* Legs */
drawRegion(pcanvas, 4, 20, 4, 12, 4, 20, scale);
drawRegion(pcanvas, 4, 20, 4, 12, 8, 20, scale, true);
/* Back head */
drawRegion(pcanvas, 24, 8, 8, 8, 22, 0, scale, true);
/* Arms */
drawRegion(pcanvas, 52, 20, 4, 12, 18, 8, scale, true);
drawRegion(pcanvas, 52, 20, 4, 12, 30, 8, scale);
/* Body */
drawRegion(pcanvas, 32, 20, 8, 12, 22, 8, scale, true);
/* Legs */
drawRegion(pcanvas, 12, 20, 4, 12, 22, 20, scale, true);
drawRegion(pcanvas, 12, 20, 4, 12, 26, 20, scale);
preview.src = pcanvas.toDataURL("image/png");
}
function drawRegion(to, x, y, w, h, x2, y2, s, flip) {
var ctx = to.getContext("2d");
for (var i = 0; i < w; i ++) {
for (var j = 0; j < h; j ++) {
app.canvas.renderPixel(ctx, i + x, j + y, s, (flip ? w - i - 1 : i) + x2, j + y2);
}
}
}
function parseColor (color) {
var ctx = parseCanvas.getContext("2d");
ctx.fillStyle = color;
ctx.fillRect(0, 0, 1, 1);
var d = ctx.getImageData(0, 0, 1, 1).data;
return [d[0], d[1], d[2], d[3] / 255];
}</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment