support for extension mechanism
This commit is contained in:
Родитель
71b167afd3
Коммит
22fd8f87a9
|
@ -17,7 +17,10 @@ light.animationSheet(null, 50)
|
|||
|
||||
## Animation editor
|
||||
|
||||
Go to https://microsoft.github.io/pxt-neoanim/ and copy or drag your animation into the page.
|
||||
From the https://makecode.adafruit.com editor,
|
||||
* click on **Add Package**
|
||||
* add **neo-anim**
|
||||
* go to the **Lights** category and click on **Animation Editor**
|
||||
|
||||
## Buffer format
|
||||
|
||||
|
|
423
index.html
423
index.html
|
@ -1,194 +1,259 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" integrity="sha256-5+W3JHnvGYIJkVxUBsw+jBi9+pOlu9enPX3vZapXj5M="
|
||||
crossorigin="anonymous" />
|
||||
</head>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="pusher">
|
||||
<div class="ui vertical stripe segment">
|
||||
<div class="ui container">
|
||||
<h1>NeoPixel Animation for Microsoft MakeCode</h1>
|
||||
<p>
|
||||
<a href="https://learn.adafruit.com/circuit-playground-neoanim-using-bitmaps-to-animate-neopixels/from-pixels-to-neopixels">Paint your NeoPixel animations</a> and convert them to <a href="https://makecode.adafruit.com">Microsoft MakeCode</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui vertical stripe segment">
|
||||
<div class="ui container">
|
||||
<div class="ui info message">
|
||||
<strong>Drag/Drop or Copy/paste your animation image in this page.</strong> Make sure it is 10 pixel
|
||||
high and uses less than 256 color tones.
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" integrity="sha256-5+W3JHnvGYIJkVxUBsw+jBi9+pOlu9enPX3vZapXj5M="
|
||||
crossorigin="anonymous" />
|
||||
</head>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="pusher">
|
||||
<div class="ui vertical stripe segment">
|
||||
<div class="ui container">
|
||||
<h1>NeoPixel Animation for Microsoft MakeCode</h1>
|
||||
<p>
|
||||
<a href="https://learn.adafruit.com/circuit-playground-neoanim-using-bitmaps-to-animate-neopixels/from-pixels-to-neopixels">Paint your NeoPixel animations</a> and convert them to <a href="https://makecode.adafruit.com">Microsoft MakeCode</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="preview">
|
||||
<div class="ui vertical stripe segment">
|
||||
<div class="ui container">
|
||||
<div class="ui info message">
|
||||
<strong>Drag/Drop or Copy/paste your animation image in this page.</strong> Make sure it is 10 pixel
|
||||
high and uses less than 256 color tones.
|
||||
</div>
|
||||
<div id="preview">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="outputext" class="ui vertical stripe segment" style="display:none;">
|
||||
<div class="ui container">
|
||||
<p>
|
||||
Go to <a href="https://makecode.adafruit.com">makecode.adafruit.com</a>, add the <code>pxt-neoanim</code> package, copy the code below to the JavaScript editor in MakeCode. Enjoy!
|
||||
</p>
|
||||
<div class="ui small fluid icon input">
|
||||
<pre id="output"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="outputext" class="ui vertical stripe segment" style="display:none;">
|
||||
<div class="ui container">
|
||||
<p>
|
||||
Go to <a href="https://makecode.adafruit.com">makecode.adafruit.com</a>,
|
||||
add the <code>pxt-neoanim</code> package,
|
||||
copy the code below to the JavaScript editor in MakeCode. Enjoy!
|
||||
</p>
|
||||
<div class="ui small fluid icon input">
|
||||
<pre id="output"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function selectOutput() {
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(output);
|
||||
var sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
function color(data, i) {
|
||||
var c = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]; // ignore alpha
|
||||
var alpha = data[i + 3];
|
||||
if (!alpha || c == 0xffffff) c = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
function importBitmap(f) {
|
||||
outputext.style.display = 'none;'
|
||||
preview.innerText = 'loading file...';
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onerror = function () {
|
||||
preview.innerText = 'ooops, could not read file';
|
||||
}
|
||||
reader.onload = function (ev) {
|
||||
preview.innerText = 'loading image...';
|
||||
|
||||
var img = document.createElement('img'); // HTMLImageElement
|
||||
img.onerror = function () {
|
||||
preview.innerText = "oops, can't load that PNG file"
|
||||
|
||||
<script>
|
||||
var extId = window.location.hash.substr(1);
|
||||
var hosted = false;
|
||||
var idToType = {};
|
||||
|
||||
function receiveMessage(ev) {
|
||||
var msg = ev.data;
|
||||
var action = idToType[resp.id];
|
||||
if (action) {
|
||||
console.debug('neoanim received ' + action);
|
||||
switch (action) {
|
||||
case "extinit":
|
||||
hosted = false;
|
||||
console.log('host connection completed')
|
||||
// read back the code
|
||||
sendRequest("extreadcode");
|
||||
break;
|
||||
case "extreadcode":
|
||||
var dataUri = msg.data.body && msg.data.body.json ? JSON.parse(msg.data.body.json) : undefined;
|
||||
if (dataUri) {
|
||||
console.log('importing previous data')
|
||||
importImage(dataUri);
|
||||
}
|
||||
break;
|
||||
}
|
||||
delete idToType[msg.id];
|
||||
}
|
||||
}
|
||||
img.onload = function () {
|
||||
var w = img.width;
|
||||
var h = img.height;
|
||||
|
||||
var cvs = document.createElement('canvas');
|
||||
var ctx = cvs.getContext("2d");
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
ctx.drawImage(img, 0, 0, w, h, 0, 0, w, h);
|
||||
var imgData = ctx.getImageData(0, 0, w, h)
|
||||
|
||||
// quantize palette
|
||||
var data = imgData.data;
|
||||
var quant = {}; // TODO: use proper algo here
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
var c = color(data, i);
|
||||
quant[c] = (quant[c] || 0) + 1;
|
||||
|
||||
function mkRequest(action) {
|
||||
var id = Math.random().toString();
|
||||
idToType[id] = action;
|
||||
return {
|
||||
type: "pxtpkgext",
|
||||
action: action,
|
||||
extId: extId,
|
||||
response: true,
|
||||
id: id
|
||||
}
|
||||
var palette = Object.keys(quant);
|
||||
if (palette.length > 0xff) {
|
||||
preview.innerText = 'too many colors... save your png to a 8bit palette';
|
||||
return;
|
||||
}
|
||||
|
||||
function sendRequest(action, body) {
|
||||
var msg = mkRequest(action);
|
||||
msg.body = body;
|
||||
window.parent.postMessage(msg, "*");
|
||||
}
|
||||
|
||||
function selectOutput() {
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(output);
|
||||
var sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
function color(data, i) {
|
||||
var c = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]; // ignore alpha
|
||||
var alpha = data[i + 3];
|
||||
if (!alpha || c == 0xffffff) c = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
function importImage(dataUri) {
|
||||
var img = document.createElement('img'); // HTMLImageElement
|
||||
img.onerror = function () {
|
||||
preview.innerText = "oops, can't load that PNG file"
|
||||
}
|
||||
// reverse lookup
|
||||
for (var i = 0; i < palette.length; ++i)
|
||||
quant[palette[i]] = i;
|
||||
|
||||
// start building buffer
|
||||
var out = [0x2e, 0x0a, 0x21, 0x88, 0, 0];
|
||||
// magic number 0..3
|
||||
// palette
|
||||
out[4] = palette.length;
|
||||
var k = 6;
|
||||
for (var i = 0; i < palette.length; ++i) {
|
||||
out[k++] = (palette[i] >> 16) & 0xff;
|
||||
out[k++] = (palette[i] >> 8) & 0xff;
|
||||
out[k++] = (palette[i]) & 0xff;
|
||||
}
|
||||
// indexed colors
|
||||
for (var col = 0; col < w; ++col) {
|
||||
for (var row = 0; row < h; ++row) {
|
||||
var pix = (row * w + col) * 4;
|
||||
var c = color(data, pix);
|
||||
out[k++] = quant[c];
|
||||
img.onload = function () {
|
||||
var w = img.width;
|
||||
var h = img.height;
|
||||
|
||||
if (w > 2048) {
|
||||
preview.innerText = 'Too many columns, maximum 2048';
|
||||
return;
|
||||
}
|
||||
|
||||
if (h > 512) {
|
||||
preview.innerText = 'Too many rows, maximum 512';
|
||||
return;
|
||||
}
|
||||
|
||||
var cvs = document.createElement('canvas');
|
||||
var ctx = cvs.getContext("2d");
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
ctx.drawImage(img, 0, 0, w, h, 0, 0, w, h);
|
||||
var imgData = ctx.getImageData(0, 0, w, h)
|
||||
|
||||
// quantize palette
|
||||
var data = imgData.data;
|
||||
var quant = {}; // TODO: use proper algo here
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
var c = color(data, i);
|
||||
quant[c] = (quant[c] || 0) + 1;
|
||||
}
|
||||
var palette = Object.keys(quant);
|
||||
if (palette.length > 0xff) {
|
||||
preview.innerText = 'too many colors... save your png to a 8bit palette';
|
||||
return;
|
||||
}
|
||||
// reverse lookup
|
||||
for (var i = 0; i < palette.length; ++i)
|
||||
quant[palette[i]] = i;
|
||||
|
||||
// start building buffer
|
||||
var out = [0x2e, 0x0a, 0x21, 0x88, 0, 0];
|
||||
// magic number 0..3
|
||||
// palette
|
||||
out[4] = palette.length;
|
||||
var k = 6;
|
||||
for (var i = 0; i < palette.length; ++i) {
|
||||
out[k++] = (palette[i] >> 16) & 0xff;
|
||||
out[k++] = (palette[i] >> 8) & 0xff;
|
||||
out[k++] = (palette[i]) & 0xff;
|
||||
}
|
||||
// indexed colors
|
||||
for (var col = 0; col < w; ++col) {
|
||||
for (var row = 0; row < h; ++row) {
|
||||
var pix = (row * w + col) * 4;
|
||||
var c = color(data, pix);
|
||||
out[k++] = quant[c];
|
||||
}
|
||||
}
|
||||
|
||||
// generate TS
|
||||
var ts =
|
||||
`const animation = light.animationSheet(hex \`${out.map(function (c, i) { return ('0' + c.toString(16)).slice(-2) + (i % 80 == 79 ? '\n' : ''); }).join('')}\`, 50);
|
||||
loops.forever(() => {
|
||||
light.pixels.showAnimationFrame(animation);
|
||||
})
|
||||
`
|
||||
if (hosted) {
|
||||
preview.innerText = '';
|
||||
output.innerText = ts;
|
||||
outputext.style.display = 'block';
|
||||
selectOutput();
|
||||
} else {
|
||||
sendRequest("extwritecode", {
|
||||
code: ts,
|
||||
json: JSON.stringify(dataUri)
|
||||
})
|
||||
preview.innerText = 'Animation saved...';
|
||||
}
|
||||
}
|
||||
|
||||
// generate TS
|
||||
var ts =
|
||||
`const animation = light.animationSheet(hex \`${out.map(function (c, i) { return ('0' + c.toString(16)).slice(-2) + (i % 80 == 79 ? '\n' : ''); }).join('')}\`, 50);
|
||||
loops.forever(() => {
|
||||
light.pixels.showAnimationFrame(animation);
|
||||
})
|
||||
`
|
||||
|
||||
output.innerText = ts;
|
||||
outputext.style.display = 'block';
|
||||
preview.innerText = '';
|
||||
|
||||
selectOutput();
|
||||
img.src = dataUri;
|
||||
}
|
||||
img.src = reader.result;
|
||||
}
|
||||
reader.readAsDataURL(f);
|
||||
}
|
||||
|
||||
document.onreadystatechange = function (er) {
|
||||
if (document.readyState != "complete") return;
|
||||
document.body.addEventListener('paste', function (e) {
|
||||
if (e.clipboardData) {
|
||||
// has file?
|
||||
let files = e.clipboardData.files
|
||||
if (files && files.length > 0) {
|
||||
e.stopPropagation(); // Stops some browsers from redirecting.
|
||||
e.preventDefault();
|
||||
importBitmap(files[0]);
|
||||
|
||||
function importBitmap(f) {
|
||||
outputext.style.display = 'none;'
|
||||
preview.innerText = 'loading file...';
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onerror = function () {
|
||||
preview.innerText = 'ooops, could not read file';
|
||||
}
|
||||
// has item?
|
||||
else if (e.clipboardData.items && e.clipboardData.items.length > 0) {
|
||||
let f = e.clipboardData.items[0].getAsFile()
|
||||
if (f) {
|
||||
reader.onload = function (ev) {
|
||||
preview.innerText = 'loading image...';
|
||||
importImage(reader.result);
|
||||
}
|
||||
reader.readAsDataURL(f);
|
||||
}
|
||||
|
||||
document.onreadystatechange = function (er) {
|
||||
if (document.readyState != "complete") return;
|
||||
|
||||
sendRequest("extinit")
|
||||
document.body.addEventListener('paste', function (e) {
|
||||
if (e.clipboardData) {
|
||||
// has file?
|
||||
let files = e.clipboardData.files
|
||||
if (files && files.length > 0) {
|
||||
e.stopPropagation(); // Stops some browsers from redirecting.
|
||||
e.preventDefault();
|
||||
importBitmap(files[0]);
|
||||
}
|
||||
// has item?
|
||||
else if (e.clipboardData.items && e.clipboardData.items.length > 0) {
|
||||
let f = e.clipboardData.items[0].getAsFile()
|
||||
if (f) {
|
||||
e.stopPropagation(); // Stops some browsers from redirecting.
|
||||
e.preventDefault();
|
||||
importBitmap(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
document.body.addEventListener('dragover', function (e) {
|
||||
let types = e.dataTransfer.types;
|
||||
let found = false;
|
||||
for (let i = 0; i < types.length; ++i)
|
||||
if (types[i] == "Files")
|
||||
found = true;
|
||||
if (found) {
|
||||
if (e.preventDefault) e.preventDefault(); // Necessary. Allows us to drop.
|
||||
e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, false);
|
||||
document.body.addEventListener('drop', function (e) {
|
||||
var files = e.dataTransfer.files;
|
||||
if (files && files.length > 0) {
|
||||
e.stopPropagation(); // Stops some browsers from redirecting.
|
||||
e.preventDefault();
|
||||
importBitmap(f)
|
||||
importBitmap(files[0]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, false);
|
||||
document.body.addEventListener('dragend', function (e) {
|
||||
return false;
|
||||
}, false);
|
||||
|
||||
output.onclick = function () { selectOutput(); }
|
||||
}
|
||||
})
|
||||
document.body.addEventListener('dragover', function (e) {
|
||||
let types = e.dataTransfer.types;
|
||||
let found = false;
|
||||
for (let i = 0; i < types.length; ++i)
|
||||
if (types[i] == "Files")
|
||||
found = true;
|
||||
if (found) {
|
||||
if (e.preventDefault) e.preventDefault(); // Necessary. Allows us to drop.
|
||||
e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, false);
|
||||
document.body.addEventListener('drop', function (e) {
|
||||
var files = e.dataTransfer.files;
|
||||
if (files && files.length > 0) {
|
||||
e.stopPropagation(); // Stops some browsers from redirecting.
|
||||
e.preventDefault();
|
||||
importBitmap(files[0]);
|
||||
}
|
||||
return false;
|
||||
}, false);
|
||||
document.body.addEventListener('dragend', function (e) {
|
||||
return false;
|
||||
}, false);
|
||||
|
||||
output.onclick = function () { selectOutput(); }
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
2
pxt.json
2
pxt.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "neoanim",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "A neopixel animation based on bitmaps",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
Загрузка…
Ссылка в новой задаче