Files
GnW_MiSTer/tools/generator.html
Pierre Cornier 74677862e7 wip
2022-06-06 16:25:46 +02:00

532 lines
19 KiB
HTML

<html>
<script
src="https://code.jquery.com/jquery-3.5.0.min.js"
integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ="
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/js/all.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/rgbquant@1.1.2/src/rgbquant.min.js"></script>
<body>
<div class="container">
<div class="row">
<div class="col-sm mt-3">
<canvas id="cvs" width="720" height="480"></canvas>
<div id="svg" style="display: none"></div>
</div>
</div>
<div class="row">
<div class="col-sm mt-3">
<label>mask expansion <i class="fa-solid fa-expand"></i></label>
<div class="badge">
<input id="maskexp" type="text" value="10" size="2">
</div>
<input type="checkbox" onchange="javascript:dither=!dither;refresh()" id="VGA">
<label for="dither">Dither</label>
<div class="badge">
x:<input type="text" onchange="trans(sel, 0, this.value)" id="xc" size="2" value="0">
y:<input type="text" onchange="trans(sel, 1, this.value)" id="yc" size="2" value="0">
w:<input type="text" onchange="trans(sel, 2, this.value)" id="xs" size="2" value="0">
h:<input type="text" onchange="trans(sel, 3, this.value)" id="ys" size="2" value="0">
</div>
</div>
</div>
<div class="mb-3">
<div class="badge badge-pill badge-warning">
<i class="fa-solid fa-layer-group"></i>
<select onchange="selectLayer(this.value)">
<option value="0">BG1</option>
<option value="1">BG2</option>
<option value="2">BG3</option>
<option value="3">SVG1</option>
<option value="4">SVG2</option>
</select>
<label id="upbtn" role="button" class="m-0" for="upload"><i class="fa-solid fa-upload"></i></label>
<input id="upload" type="file" onchange="upload()" style="display:none">
</div>
<div class="badge badge-pill badge-warning">
<i class="fa-solid fa-arrows-alt"></i>
<button onmousedown="startTransform(sel, 0, -1)" onmouseup="stopTransform()"><i class="fa-solid fa-long-arrow-alt-left"></i></button>
<button onmousedown="startTransform(sel, 0, 1)" onmouseup="stopTransform()"><i class="fa-solid fa-long-arrow-alt-right"></i></button>
<button onmousedown="startTransform(sel, 1, -1)" onmouseup="stopTransform()"><i class="fa-solid fa-long-arrow-alt-up"></i></button>
<button onmousedown="startTransform(sel, 1, 1)" onmouseup="stopTransform()"><i class="fa-solid fa-long-arrow-alt-down"></i></button>
</div>
<div class="badge badge-pill badge-warning">
<i class="fa-solid fa-arrows-alt-h"></i>
<button onmousedown="startTransform(sel, 2, 1)" onmouseup="stopTransform()"><i class="fa-solid fa-plus"></i></button>
<button onmousedown="startTransform(sel, 2, -1)" onmouseup="stopTransform()"><i class="fa-solid fa-minus"></i></button>
</div>
<div class="badge badge-pill badge-warning">
<i class="fa-solid fa-arrows-alt-v"></i>
<button onmousedown="startTransform(sel, 3, 1)" onmouseup="stopTransform()"><i class="fa-solid fa-plus"></i></button>
<button onmousedown="startTransform(sel, 3, -1)" onmouseup="stopTransform()"><i class="fa-solid fa-minus"></i></button>
</div>
<div class="badge badge-pill badge-warning" style="padding: 6px">
<label id="LROM" role="button" class="m-0" for="ROM">
<i class="fa-solid fa-upload"></i>
<span class="pl-1">no rom binary</span>
</label>
<input id="ROM" type="file" onchange="importROM()" style="display:none">
</div>
<div class="badge badge-pill badge-warning" style="padding: 6px" role="button" onclick="exportBIN()">
<i class="fa-solid fa-save"></i> export
</div>
</div>
<div class="row">
<div class="col-sm">
<label>MCU</label>
<select id="mcuid">
<option value="0" disabled>SM-5A</option>
<option value="1" selected>SM-510</option>
<option value="2" disabled>SM-511</option>
<option value="3" disabled>SM-530</option>
<option value="4" disabled>SM-590</option>
</select>
<label>Time location $</label>
<input id="timeloc" type="text" value="14" size="2">
</div>
</div>
<div class="row" id="keys">
<div class="col">
<div class="row">
<div class="col-sm"></div>
<div class="col-sm">K1</div>
<div class="col-sm">K2</div>
<div class="col-sm">K3</div>
<div class="col-sm">K4</div>
</div>
<div class="row bg-light">
<div class="col-sm">S1</div>
<div class="col-sm"><select id="k0"></select></div>
<div class="col-sm"><select id="k1"></select></div>
<div class="col-sm"><select id="k2"></select></div>
<div class="col-sm"><select id="k3"></select></div>
</div>
<div class="row">
<div class="col-sm">S2</div>
<div class="col-sm"><select id="k4"></select></div>
<div class="col-sm"><select id="k5"></select></div>
<div class="col-sm"><select id="k6"></select></div>
<div class="col-sm"><select id="k7"></select></div>
</div>
<div class="row bg-light">
<div class="col-sm">S3</div>
<div class="col-sm"><select id="k8"></select></div>
<div class="col-sm"><select id="k9"></select></div>
<div class="col-sm"><select id="k10"></select></div>
<div class="col-sm"><select id="k11"></select></div>
</div>
<div class="row">
<div class="col-sm">S4</div>
<div class="col-sm"><select id="k12"></select></div>
<div class="col-sm"><select id="k13"></select></div>
<div class="col-sm"><select id="k14"></select></div>
<div class="col-sm"><select id="k15"></select></div>
</div>
<div class="row bg-light">
<div class="col-sm">S5</div>
<div class="col-sm"><select id="k16"></select></div>
<div class="col-sm"><select id="k17"></select></div>
<div class="col-sm"><select id="k18"></select></div>
<div class="col-sm"><select id="k19"></select></div>
</div>
<div class="row">
<div class="col-sm">S6</div>
<div class="col-sm"><select id="k20"></select></div>
<div class="col-sm"><select id="k21"></select></div>
<div class="col-sm"><select id="k22"></select></div>
<div class="col-sm"><select id="k23"></select></div>
</div>
</div>
</div>
<div class="container w-100" id="config">
<div class="row">
<div class="badge">
<button onclick="saveconf()"><i class="fa-solid fa-file-arrow-down" title="refresh config"></i></button>
<button onclick="loadconf()"><i class="fa-solid fa-file-arrow-up" title="import config"></i></button>
</div>
</div>
<div class="row">
<textarea style="width: 100%; height: 80px; font-size: 12px"></textarea>
</div>
</div>
<div class="clearfix"></div>
</div>
<br>
</body>
<script>
const width = 720
const height = 480
let DOMURL = window.URL || window.webkitURL || window
let canvas = document.getElementById('cvs')
let ctx = canvas.getContext('2d')
let ROM
let BGImg = []
let SVGImg = []
let AASVGImg = []
let tfs = [
[0, 0, width, height], [0, 0, width, height],
[0, 0, width, height], [0, 0, width, height],
[0, 0, width, height]
]
let sel = 0
let dither = false
let options = [
'right', 'left', 'down', 'up',
'jump', 'time', 'game A', 'game B',
'alarm', 'action 1', 'action 2', 'action 3'
]
$('#keys select').each((i, e) => {
for(k in options) {
var opt = $('<option>')
$(e).append(opt.val(k).text(options[k]))
}
$(e).append(opt.val(15).text('none'))
$(e).val(15) // none
})
refresh()
function selectLayer(v) {
sel = v
refresh()
}
function upload() {
switch (true) {
case sel < 3: importBG(); break;
case sel >= 3: importSVG(); break;
}
}
function importBG() {
let BG = document.getElementById('upload').files[0]
if (BG.type.match(/.+\/svg/)) {
alert('Please use a SVG channel')
document.getElementById('upload').value = ''
return;
}
if (BG) loadBGFile(BG)
}
function importSVG() {
let SVG = document.getElementById('upload').files[0]
if (!SVG.type.match(/.+\/svg/)) {
alert('Please use a BG channel')
document.getElementById('upload').value = ''
return;
}
if (SVG) loadSVGFile(SVG)
}
function importROM() {
let RF = document.getElementById('ROM').files[0]
if (RF) loadROMFile(RF)
}
function loadBGFile(BG) {
let reader = new FileReader()
reader.readAsDataURL(BG)
reader.onload = BGLoaded
}
function loadSVGFile(SVG) {
let reader = new FileReader()
reader.readAsText(SVG, "UTF-8")
reader.onload = SVGLoaded
}
function loadROMFile(RF) {
let reader = new FileReader()
reader.readAsArrayBuffer(RF)
reader.onload = evt => {
ROM = evt.target.result
document.querySelector('#LROM span').innerHTML = 'rom: ' + RF.name
}
}
function BGLoaded(evt) {
if (!BGImg[sel]) BGImg[sel] = new Image()
BGImg[sel].src = evt.target.result
BGImg[sel].onload = function() {
refresh()
}
}
function drawBG() {
BGImg.forEach((img, i) => {
ctx.drawImage(img, tfs[i][0], tfs[i][1], tfs[i][2], tfs[i][3])
})
return dither ? doRGBQuant() : false;
}
function drawSVG() {
ctx.imageSmoothingEnabled = false;
SVGImg.forEach((img, i) => {
ctx.drawImage(img, tfs[i][0], tfs[i][1], tfs[i][2], tfs[i][3])
})
}
function drawAASVG(pal) {
ctx.imageSmoothingEnabled = false;
AASVGImg.forEach((img, i) => {
ctx.drawImage(img, tfs[i][0], tfs[i][1], tfs[i][2], tfs[i][3])
})
return doRGBQuant(pal)
}
function drawHelpers() {
ctx.strokeStyle = 'red'
ctx.beginPath()
ctx.moveTo(width/2, 0)
ctx.lineTo(width/2, height)
ctx.stroke()
ctx.moveTo(0, height/2)
ctx.lineTo(width, height/2)
ctx.stroke()
}
function refresh(helpers) {
var h = helpers || 1
ctx.fillStyle = "#000"
ctx.fillRect(0, 0, width, height)
drawBG()
drawSVG()
if (h) drawHelpers()
$('#xc').val(tfs[sel][0])
$('#yc').val(tfs[sel][1])
$('#xs').val(tfs[sel][2])
$('#ys').val(tfs[sel][3])
}
function doRGBQuant(pal) {
var opts = pal ? { palette: pal } : {};
var q = new RgbQuant(opts)
q.sample(canvas)
var pal = q.palette(true, false)
var out = q.reduce(canvas, 2)
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
let data = imageData.data
let j = 0
for (var i = 0; i < data.length; i += 4) {
data[i ] = pal[out[j]][0]
data[i+1] = pal[out[j]][1]
data[i+2] = pal[out[j]][2]
j++
}
ctx.putImageData(imageData, 0, 0);
return { pal: pal, img: out }
}
function doRGB332() {
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
let data = imageData.data
for (var i = 0; i < data.length; i += 4) {
data[i ] = data[i ] & 0b11100000
data[i+1] = data[i+1] & 0b11100000
data[i+2] = data[i+2] & 0b11000000
}
ctx.putImageData(imageData, 0, 0);
}
function trans(i, j, n) {
tfs[i][j] = n
refresh()
}
let transform
function startTransform(i, j, n) {
transform = setInterval(_ => {
var nv = tfs[i][j] + n
trans(i, j, nv)
}, 50)
}
function stopTransform() {
clearInterval(transform)
}
function SVGLoaded(evt) {
$('#svg').html(evt.target.result)
$('#svg svg').attr('width', width).removeAttr('height')
// should be the big white rectangle, pls check
// can we use it for automatic segment positioning?
$('svg #layer2').hide()
let mask_expansion = $("#maskexp").val()
let data = $('#svg').html()
let svg = new Blob([data], {type: 'image/svg+xml'})
let url = DOMURL.createObjectURL(svg)
if (!AASVGImg[sel]) AASVGImg[sel] = new Image()
AASVGImg[sel].src = url
let elements = $('#layer1 path,#layer1 g')
elements.each((k, e) => {
let title = $(e).find('title')
if (title.length) {
let id = title.text().split('.')
let col = ((id[0] & 0b11) << 6) | ((id[1] & 0b1111) << 2) | (id[2] & 0b11)
let style = `fill-opacity:1;fill:#00${('00'+(col).toString(16)).substr(-2)}00;`
style += `stroke:#00${('00'+(col).toString(16)).substr(-2)}00;`
style += `stroke-width: ${mask_expansion}px`
console.log(`found ${title.text()}, converted to ${col.toString(16)}`)
if ($(e).prop('tagName') == 'g') {
$(e).find('path').each((i, e) => $(e).attr('style', style))
$(e).find('polygon').each((i, e) => $(e).attr('style', style))
}
else {
$(e).attr('style', style)
}
}
})
$('svg path').each((k, e) => {
$(e).attr('shape-rendering', 'crispEdges')
})
data = $('#svg').html()
svg = new Blob([data], {type: 'image/svg+xml'})
url = DOMURL.createObjectURL(svg)
if (!SVGImg[sel]) SVGImg[sel] = new Image()
SVGImg[sel].src = url
SVGImg[sel].onload = function() {
refresh()
}
}
function saveconf() {
let keys = []
for (var i = 0; i < 23; i+=2) {
keys.push($('#k' + i).val())
keys.push($('#k' + (i+1)).val())
}
let conf = {
tfs: tfs,
mcuid: $('#mcuid').val(),
timeloc: $('#timeloc').val(),
keys: keys
}
let confstr = JSON.stringify(conf)
$('#config textarea').val(window.btoa(confstr))
}
function loadconf() {
let conf = JSON.parse(window.atob($('#config textarea').val()))
for (var i = 0; i < 23; i+=2) {
$('#k' + i).val(conf.keys[i])
$('#k' + (i+1)).val(conf.keys[i+1])
}
$('#mcuid').val(conf.mcuid)
$('#timeloc').val(conf.timeloc)
tfs = conf.tfs
refresh()
}
function exportBIN() {
if (!ROM) return
// mcu id (1)
// config size (1)
// joystick config (12)
// image size (32)
// mask+img data (XxYx2)
// palette (256x3)
// rom binary
let mcu_size = 1
let config_size = 1
let config = 13
let img_size = width*height*3-1
let buffer_size = mcu_size + config_size + config + 32 + img_size + 256*3-1 + ROM.byteLength;
let buffer = new Uint8Array(buffer_size)
let ptr = 0
// 1 byte for mcu
buffer[ptr++] = $('#mcuid').val()
// config size
buffer[ptr++] = config
// first config byte is time location
buffer[ptr++] = $("#timeloc").val()
// 12 config bytes for joystick
for (var i = 0; i < 23; i+=2) {
var id1 = '#k' + i
var id2 = '#k' + (i+1)
buffer[ptr++] = parseInt($(id1).val()) << 4 | parseInt($(id2).val())
}
// img size
for (var i = 24; i >= 0; i -= 8) {
buffer[ptr++] = (img_size >> i) & 0xff
}
// mask
ctx.fillStyle = "#ffffff"
ctx.fillRect(0, 0, width, height)
drawSVG()
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
let maskData = imageData.data
let mask = []
for (var i = 0; i < maskData.length; i += 4) {
mask.push(maskData[i+1]) // green channel
}
dither = true
$('#VGA').prop('checked', dither)
ctx.fillStyle = "#000"
ctx.fillRect(0, 0, width, height)
let qdat = drawBG()
let segs = drawAASVG(qdat.pal)
for (var i = 0; i < qdat.img.length; i++) {
buffer[ptr++] = mask[i]
buffer[ptr++] = segs.img[i]
buffer[ptr++] = qdat.img[i]
}
for (var i = 0; i < qdat.pal.length; i++) {
buffer[ptr++] = qdat.pal[i][0]
buffer[ptr++] = qdat.pal[i][1]
buffer[ptr++] = qdat.pal[i][2]
}
buffer.set(new Uint8Array(ROM), ptr)
let binary = new Blob([buffer], {type: 'application/octet-binary'})
let url = DOMURL.createObjectURL(binary)
var a = document.createElement('a')
document.body.appendChild(a)
a.style = 'display: none'
a.href = url
a.download = 'download.bin'
a.click()
window.URL.revokeObjectURL(url)
refresh()
}
</script>
</html>