support for extension mechanism

This commit is contained in:
Peli de Halleux 2017-09-29 09:51:01 -07:00
Родитель 71b167afd3
Коммит 22fd8f87a9
3 изменённых файлов: 249 добавлений и 181 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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>

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "neoanim",
"version": "0.1.0",
"version": "0.2.0",
"description": "A neopixel animation based on bitmaps",
"license": "MIT",
"dependencies": {