<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pixel → Lua Generator</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
background: #1e1e1e;
color: #cfcfcf;
font-family: Arial, sans-serif;
}
header, footer {
text-align: center;
padding: 1rem;
background: #2d2d2d;
}
main {
flex: 1;
padding: 1rem;
max-width: 700px;
margin: 0 auto;
}
input, button, textarea, select {
width: 100%;
margin-top: 0.5rem;
padding: 0.5rem;
background: #2d2d2d;
border: 1px solid #3c3c3c;
color: #cfcfcf;
font-size: 1rem;
box-sizing: border-box;
}
label { display: block; margin-top: 1rem; }
#original {
display: block;
margin: 1rem auto;
max-width: 100%;
border: 1px solid #3c3c3c;
}
#preview {
display: block;
margin: 1rem auto;
image-rendering: pixelated;
border: 1px solid #3c3c3c;
}
.slider-container {
margin-top: 1rem;
text-align: left;
display: none;
}
.slider-container span {
font-weight: bold;
}
#luaContainer {
margin-top: 1rem;
display: none;
}
textarea {
width: 100%;
height: 200px;
background: #1e1e1e;
color: #00ff90;
font-family: monospace;
font-size: 0.9rem;
border: 1px solid #333;
padding: 1rem;
resize: vertical;
line-height: 1.2;
}
footer {
font-size: 0.9rem;
}
#eyeDropBtn {
margin-top: 1rem;
cursor: pointer;
}
</style>
</head>
<body>
<header>
<h1>Pixel → Lua Exporter</h1>
</header>
<main>
<label for="fileInput">Choose pixel image (PNG/GIF):</label>
<input type="file" id="fileInput" accept="image/png, image/gif" />
<img id="original" src="" alt="Original Upload" style="display:none;" />
<div class="slider-container" id="sliderContainer">
<label for="pixelSize">Pixel Size: <span id="pixelSizeLabel">1</span></label>
<input type="range" id="pixelSize" min="1" max="50" value="1" />
</div>
<canvas id="preview" width="0" height="0"></canvas>
<!-- EyeDropper Color Picker -->
<button id="eyeDropBtn">Pick Ignore Color (Eyedropper)</button>
<div id="luaContainer">
<label for="luaCode">Generated Lua</label>
<textarea id="luaCode"></textarea>
</div>
<button id="downloadBtn" type="button" disabled>Export as .lua</button>
</main>
<footer>Made by Amar</footer>
<script>
const fileInput = document.getElementById('fileInput');
const originalImg = document.getElementById('original');
const pixelSize = document.getElementById('pixelSize');
const pixelSizeLabel = document.getElementById('pixelSizeLabel');
const sliderContainer = document.getElementById('sliderContainer');
const preview = document.getElementById('preview');
const ctx = preview.getContext('2d');
const eyeDropBtn = document.getElementById('eyeDropBtn');
const luaContainer = document.getElementById('luaContainer');
const luaCode = document.getElementById('luaCode');
const downloadBtn = document.getElementById('downloadBtn');
let img = new Image();
let imgName = 'image';
let origW = 0;
let origH = 0;
let loaded = false;
let ignoreHex = '000000';
// EyeDropper API
eyeDropBtn.addEventListener('click', async () => {
try {
const eyeDropper = new EyeDropper();
const { sRGBHex } = await eyeDropper.open();
ignoreHex = sRGBHex.slice(1); // Remove leading '#'
eyeDropBtn.textContent = `Color: ${sRGBHex}`;
updateLuaPreview();
} catch (err) {
console.error('EyeDropper canceled or not supported', err);
}
});
fileInput.addEventListener('change', e => {
const file = e.target.files[0];
if (!file) return;
imgName = file.name.replace(/\.[^.]+$/, '');
const url = URL.createObjectURL(file);
img.src = url;
originalImg.src = url;
originalImg.style.display = 'block';
img.onload = () => {
origW = img.width;
origH = img.height;
loaded = true;
sliderContainer.style.display = 'block';
drawPixelated();
updateLuaPreview();
luaContainer.style.display = 'block';
downloadBtn.disabled = false;
};
});
pixelSize.addEventListener('input', () => {
pixelSizeLabel.textContent = pixelSize.value;
if (loaded) {
drawPixelated();
updateLuaPreview();
}
});
function drawPixelated() {
const size = parseInt(pixelSize.value, 10);
const tmpW = Math.ceil(origW / size);
const tmpH = Math.ceil(origH / size);
const tmp = document.createElement('canvas');
tmp.width = tmpW;
tmp.height = tmpH;
const tctx = tmp.getContext('2d');
tctx.imageSmoothingEnabled = false;
tctx.drawImage(img, 0, 0, tmpW, tmpH);
preview.width = origW;
preview.height = origH;
ctx.imageSmoothingEnabled = false;
ctx.clearRect(0,0,origW,origH);
ctx.drawImage(tmp, 0, 0, tmpW, tmpH, 0, 0, origW, origH);
}
function rgbToHex(r, g, b) {
return ((1<<24)|(r<<16)|(g<<8)|b).toString(16).slice(1);
}
function buildLuaString() {
const size = parseInt(pixelSize.value, 10);
const tmpW = Math.ceil(origW / size);
const tmpH = Math.ceil(origH / size);
const tmp = document.createElement('canvas');
tmp.width = tmpW;
tmp.height = tmpH;
const tctx = tmp.getContext('2d');
tctx.imageSmoothingEnabled = false;
tctx.drawImage(img, 0, 0, tmpW, tmpH);
const data = tctx.getImageData(0, 0, tmpW, tmpH).data;
const rows = [];
for (let y = 0; y < tmpH; y++) {
const cols = [];
for (let x = 0; x < tmpW; x++) {
const i = (y*tmpW + x)*4;
const a = data[i+3];
const hex = a === 0
? 'ffffff'
: rgbToHex(data[i], data[i+1], data[i+2]);
cols.push(`"${hex}"`);
}
rows.push(' { ' + cols.join(', ') + ' },');
}
return [
'--[[',
`Title: ${imgName}`,
'Platform: (unknown)',
'url: ',
']]--',
'return {',
` title = "${imgName}",`,
` name = "${imgName}",`,
` width = ${tmpW},`,
` height= ${tmpH},`,
` ignore_colors = { "${ignoreHex}" },`,
' frames = {',
' {',
...rows,
' }',
' }',
'}'
].join('\n');
}
function updateLuaPreview() {
luaCode.value = buildLuaString();
}
downloadBtn.addEventListener('click', () => {
const content = luaCode.value;
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${imgName}.lua`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
</script>
</body>
</html>