[
  {
    "path": ".gitattributes",
    "content": "*.html linguist-language=Javascript\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Frank Force\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ZzSprite\n\nA Tiny Sprite Generator by Frank Force\n\n[Based on a Dweet](https://www.dwitter.net/d/3078) by Firey Fly\n\n# [Live Demo](https://killedbyapixel.github.io/ZzSprite/)\n\n## Features\n\n- Generates a wide variety of symetric pixel art sprites\n- Click on a sprite to set that as the current seed\n- Sprite can be mutated in shape or color\n- Sprite sheets can be saved as pngs\n- Sprites can be coppied to the clipboard for easy pasting\n- Supports 4 color and monochrome color modes\n- ZzSprite function can be used to generate sprites at run time\n\n## Example sprites...\n![Example Image 1](/examples/example1.png)\n\n## Example mutations...\n![Example Image 2](/examples/example2.png)\n\n## Example animations...\n![Example Image 3](/examples/example3.png)\n\n## So many possibilities!\n![Example Image 4](/examples/example4.png)\n\n![Favicon](/favicon.png)\n"
  },
  {
    "path": "ZzSprite.js",
    "content": "// ZzSprite - Tiny Sprite Generator - Frank Force 2020 - MIT License\r\n\r\n'use strict';\r\n\r\n// ==ClosureCompiler==\r\n// @compilation_level ADVANCED_OPTIMIZATIONS\r\n// @language_out ECMASCRIPT_2019\r\n// @js_externs ZzSprite\r\n// ==/ClosureCompiler==\r\n\r\nfunction ZzSprite(context, x=0, y=0, seed=1, size=16, mode=0, mutateSeed=0, colorSeed=0)\r\n{\r\n    function Random(max=1, min=0)\r\n    {\r\n        randomSeed ^= randomSeed << 13;\r\n        randomSeed ^= randomSeed >>> 17;\r\n        return (Math.abs(randomSeed ^= randomSeed << 5) % 1e9 / 1e9)*(max-min) + min;\r\n    }\r\n\r\n    // random chance to flip drawing axis\r\n    let randomSeed = seed;\r\n    const flipAxis = Random() < .5;\r\n    const w =  flipAxis ? size-3 : size/2 - 1 |0;\r\n    const h = !flipAxis ? size-3 : size/2 - 1 |0;\r\n        \r\n    // apply mutations\r\n    randomSeed += mutateSeed + 1e8;\r\n    const spriteSize = size * Random(.9, .6);\r\n    const density = Random(1, .9);\r\n    const doubleCenter = Random() < .5;\r\n    const yBias = Random(.1, -.1);\r\n    const colorRand = (mode==1 ? .08 : .04);\r\n\r\n    // recenter\r\n    x += size/2 | 0;\r\n    y += 2 | 0;\r\n\r\n    function DrawSpriteInternal(x, y, outline)\r\n    {\r\n        // draw each pixel\r\n        randomSeed = seed;\r\n        const passCount = mode == 3 ? 3: 1\r\n        for(let pass=0; pass < passCount; ++pass)\r\n        for(let k=0; k < w*h; ++k)\r\n        {\r\n            const i = flipAxis  ? k/w|0 : k%w;\r\n            const j = !flipAxis ? k/w|0 : k%w;\r\n\r\n            // pick new random color using color seed\r\n            const saveSeed = randomSeed;\r\n            randomSeed += colorSeed + 1e9;\r\n            const r = Random(360)|0;\r\n            let newColor = `hsl(${ r },${ Random(200,0)|0 }%,${ Random(100,20)|0 }%)`;\r\n            if (outline || mode == 3)\r\n                newColor = '#000';\r\n            else if (mode == 1)\r\n                newColor = r%3? r%3==1 ? '#444' : '#999' : '#fff';\r\n            else if (mode == 2)\r\n                newColor = `#fff`;\r\n            if (!k || Random() < colorRand)\r\n                context.fillStyle = newColor;\r\n            randomSeed = saveSeed;\r\n\r\n            // check if pixel should be drawn\r\n            const isHole = Random() > density;\r\n            if (Random(spriteSize/2)**2 > i*i + (j-(1-2*yBias)*h/2)**2 && !isHole)\r\n            {\r\n                const o = !!outline;\r\n                context.fillRect(x+i-o-doubleCenter, y+j-o, 1+2*o, 1+2*o);\r\n                context.fillRect(x-i-o, y+j-o, 1+2*o, 1+2*o);\r\n            }\r\n        }\r\n    }\r\n\r\n    // outline then fill\r\n    if (mode != 3)\r\n        DrawSpriteInternal(x, y, 1);\r\n    DrawSpriteInternal(x, y);\r\n}"
  },
  {
    "path": "index.html",
    "content": "<style>\r\n*\r\n{\r\n    font-size:20px;\r\n    font-family:Arial;\r\n}\r\nbody\r\n{\r\n    background:#111;\r\n    color:#fff;\r\n    user-select:none;\r\n}\r\n.settingsContainer \r\n{\r\n  display:grid;\r\n  grid-template-columns: 130px 130px 130px 130px;\r\n  grid-gap:10px;\r\n  padding:10px;\r\n}\r\ncanvas\r\n{\r\n    background:#ccc;\r\n    image-rendering:-moz-crisp-edges;\r\n    image-rendering:pixelated;\r\n}\r\ndetails\r\n{\r\n    padding:10px;\r\n}\r\n.programTitle\r\n{\r\n    font-size:20px;\r\n    font-style:italic;\r\n    padding:4px;\r\n    text-align:center;\r\n}\r\n</style>\r\n<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"favicon.png\"/>\r\n<div style=display:flex;height:180px>\r\n<div id=settingsContainer class=settingsContainer></div>\r\n\r\n<div>\r\n<div id=programTitleDiv class=programTitle></div>\r\n<canvas id=previewCanvas style='flex-shrink:0;width:128px;height:128px;border:3px solid #000'></canvas>\r\n</div>\r\n<details style=display:none>\r\n<summary>Advanced</summary>\r\n<div class=settingsContainer id=advancedSettingsContainer>\r\n</div>\r\n</details>\r\n\r\n</div>\r\n<canvas id=canvas style=width:100%></canvas>\r\n<a id=link display:none></a>\r\n\r\n<script src=ZzSprite.js?1></script>\r\n<script>'use strict';\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n\r\nlet randomSeed = 0;\r\nfunction Random(max=1, min=0)\r\n{\r\n    randomSeed ^= randomSeed << 13;\r\n    randomSeed ^= randomSeed >>> 17;\r\n    return (Math.abs(randomSeed ^= randomSeed << 5) % 1e9 / 1e9)*(max-min) + min;\r\n}\r\n\r\nfunction GenerateRandomSeed() { return Math.random()*1e9|0; }\r\n\r\nfunction Update()\r\n{\r\n    // init canvas\r\n    const tileSize = settings.tileSize.Get();\r\n    const rows = settings.rows.Get();\r\n    const columns = settings.columns.Get();\r\n    canvas.height = rows*tileSize;\r\n    canvas.width = columns*tileSize;\r\n       \r\n    // render sprites\r\n    const displayMode = settings.displayMode.Get();\r\n    let seed = settings.seed.Get();\r\n    let colorSeed = settings.colorSeed.Get();\r\n    let mutateSeed = settings.mutateSeed.Get();\r\n    let animationSeed = GenerateRandomSeed();\r\n    let sheetSeed = settings.sheetSeed.Get();\r\n    for(let y = 0; y<rows; ++y)\r\n    for(let x = 0; x<columns; ++x)\r\n    {\r\n        if (x || y)\r\n        {\r\n            // randomize parameters\r\n            randomSeed = sheetSeed;\r\n            if (displayMode == 0)\r\n            {\r\n                seed = Random(1e9)|0;\r\n            }\r\n            else if (displayMode == 1)\r\n            {\r\n                mutateSeed = Random(1e9)|0;\r\n                colorSeed = Random(1e9)|0;\r\n            }\r\n            else if (displayMode == 2)\r\n            {\r\n                mutateSeed = Random(1e9)|0;\r\n            }\r\n            sheetSeed = randomSeed;\r\n        }\r\n    \r\n        savedParameters[x+y*columns] = {seed, mutateSeed, colorSeed};\r\n        ZzSprite(context, x*tileSize, y*tileSize, seed, settings.tileSize.Get(), settings.colorMode.Get(), mutateSeed, colorSeed);\r\n    }\r\n    \r\n    // draw preview sprite\r\n    const previewContext = previewCanvas.getContext('2d');\r\n    previewCanvas.width = tileSize;\r\n    previewCanvas.height = tileSize;\r\n    previewContext.fillStyle = '#fff';\r\n    previewContext.fillRect(0,0,2e3,2e3);\r\n    previewContext.drawImage(canvas, 0, 0);\r\n}\r\n\r\nclass Setting\r\n{\r\n    constructor(name, value, max=1, min=0, step=1, advanced=0)\r\n    {\r\n        const container = advanced ? advancedSettingsContainer : settingsContainer;\r\n        const nameElement = document.createElement('span');\r\n        nameElement.innerText = name;\r\n        container.appendChild(nameElement);\r\n        \r\n        const e = this.element = document.createElement('input');\r\n        container.appendChild(e);\r\n        \r\n        e.type = 'number';\r\n        e.value = this.default = value;\r\n        e.max = this.max = max;\r\n        e.min = this.min = min;\r\n        e.step = step;\r\n        \r\n        this.element.onchange= e=>\r\n        {\r\n            this.Set(step==1?this.element.value|0:this.element.value);\r\n            Update();\r\n        }\r\n    }\r\n        \r\n    SetDefault() { this.Set(this.default); }\r\n\r\n    Set(value)\r\n    { \r\n        value = value||0;\r\n        if (value < this.min)\r\n            value = this.min;\r\n        else if (value > this.max)\r\n            value = this.max;\r\n\r\n        this.element.value = value;\r\n    }\r\n    Get() { return parseFloat(this.element.value); }\r\n}\r\n\r\nclass SettingDropDown\r\n{\r\n    constructor(name, options, advanced = 0)\r\n    {\r\n        const container = advanced ? advancedSettingsContainer : settingsContainer;\r\n        const nameElement = document.createElement('span');\r\n        nameElement.innerText = name;\r\n        container.appendChild(nameElement);\r\n        \r\n        this.element = document.createElement('select');\r\n        container.appendChild(this.element);\r\n        options.map((o,i)=>\r\n        {\r\n            let e = document.createElement('option');\r\n            e.innerHTML = o;\r\n            e.value = i;\r\n            this.element.appendChild(e);\r\n        });\r\n        \r\n        this.element.onchange= e=> Update();\r\n    }\r\n    \r\n    SetDefault() { this.Set(0); }\r\n    Set(value) { this.element.selectedIndex = value; }\r\n    Get() { return this.element.options[this.element.selectedIndex].value; }\r\n}\r\n\r\nfunction BuildHTML()\r\n{\r\n    document.title = programTitle + ' - ' + programDescription;\r\n    programTitleDiv.innerHTML = programTitle + ' v' + programVersion;\r\n    canvas.title = 'Click to select seed';\r\n\r\n    settings.seed = new Setting('Seed', GenerateRandomSeed(), 1e9);\r\n    settings.tileSize = new Setting('Tile Size', 16, 32, 8, 1);\r\n    settings.displayMode =     new SettingDropDown('Display Mode', \r\n        ['Seeds','Mutations','Animations']);\r\n    settings.colorMode =       new SettingDropDown('Color Mode', \r\n        ['Full Color','4 Colors','2 Colors','1 Color']);\r\n    settings.rows =            new Setting('Rows', 8, 128, 1);\r\n    settings.columns =         new Setting('Columns', 16, 128, 1);\r\n    \r\n    // advanced\r\n    settings.colorSeed =       new Setting('Color Seed', 0, 1e9, 0, 1, 1);\r\n    settings.mutateSeed =      new Setting('Mutate Seed', 0, 1e9, 0, 1, 1);\r\n    settings.sheetSeed =       new Setting('Sheet Seed', GenerateRandomSeed(), 1e9, 0, 1, 1);\r\n    \r\n    let e;\r\n    /*e = settingsContainer.appendChild(document.createElement('button'));\r\n    e.innerHTML = 'Reset';\r\n    e.title = 'Reset all parameters.';\r\n    e.onclick = e => \r\n    { \r\n        // reset defaults\r\n        for(const setting in settings)\r\n        {\r\n            const s = settings[setting];\r\n            s.SetDefault && s.SetDefault();\r\n        }\r\n\r\n        settings.seed.Set(GenerateRandomSeed()); \r\n        settings.sheetSeed.Set(GenerateRandomSeed()); \r\n        Update(); \r\n    };*/\r\n    \r\n    e = settingsContainer.appendChild(document.createElement('button'));\r\n    e.innerHTML = 'Randomize';\r\n    e.title = 'Randomize sprite sheet.';\r\n    e.onclick = e => \r\n    { \r\n        settings.sheetSeed.Set(GenerateRandomSeed());\r\n        if (settings.displayMode.Get()==0)\r\n            settings.seed.Set(GenerateRandomSeed()); \r\n        Update(); \r\n    };\r\n    \r\n    e = settingsContainer.appendChild(document.createElement('button'));\r\n    e.innerHTML = 'Save Sheet';\r\n    e.title = 'Save png of entire sheet.';\r\n    e.onclick = e => link.click( \r\n            link.href = canvas.toDataURL('image/png'),\r\n            link.download = 'sprite_sheet');\r\n    \r\n    e = settingsContainer.appendChild(document.createElement('button'));\r\n    e.innerHTML = 'Save Sprite';\r\n    e.title = 'Save png of sprite.';\r\n    e.onclick = e => link.click( \r\n            link.href = previewCanvas.toDataURL('image/png'),\r\n            link.download = 'sprite');\r\n\r\n    e = settingsContainer.appendChild(document.createElement('button'));\r\n    e.innerHTML = 'Copy Sprite';\r\n    previewCanvas.title = e.title = 'Copy sprite to clipboard.';\r\n    previewCanvas.onclick = \r\n    e.onclick = e =>\r\n    {\r\n        try {\r\n          previewCanvas.toBlob(blob => \r\n            navigator.clipboard.write([new ClipboardItem({'image/png': blob})]));\r\n        } catch (e) {\r\n          alert('Could not copy!');\r\n        }\r\n    }\r\n}\r\n\r\ncanvas.onmousedown=e=>\r\n{\r\n    // get click tile\r\n    const rect = canvas.getBoundingClientRect();\r\n    const scaleX = canvas.width / rect.width;\r\n    const scaleY = canvas.height / rect.height;\r\n    const tileSize = settings.tileSize.Get();\r\n    const X = (e.x - rect.left)*scaleX/tileSize|0;\r\n    const Y = (e.y - rect.top)*scaleY/tileSize|0;\r\n    \r\n    // update settings\r\n    const columns = settings.columns.Get();\r\n    const parameters = savedParameters[X+Y*columns];\r\n    settings.seed.Set(parameters.seed,0);\r\n    settings.colorSeed.Set(parameters.colorSeed,0);\r\n    settings.mutateSeed.Set(parameters.mutateSeed,0);\r\n    Update();\r\n}\r\n\r\nconst programTitle = 'ZzSprite';\r\nconst programDescription = 'Tiny Sprite Generator';\r\nconst programVersion = '1.0';\r\nconst savedParameters = [];\r\nconst settings = {};\r\nconst context = canvas.getContext('2d');\r\nBuildHTML();\r\nUpdate();\r\n\r\n</script>\r\n<a href=\"https://github.com/KilledByAPixel/ZzSprite\" target=\"_blank\" class=\"github-corner\" aria-label=\"View source on GitHub\"><svg width=\"80\" height=\"80\" viewBox=\"0 0 250 250\" style=\"fill:#5AF; color:#222; position: absolute; top: 0; border: 0; right: 0;\" aria-hidden=\"true\"><path d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\"></path><path d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\" fill=\"currentColor\" style=\"transform-origin: 130px 106px;\" class=\"octo-arm\"></path><path d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\" fill=\"currentColor\" class=\"octo-body\"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>"
  }
]