integrated/replaced pixel editor, nes: updated presets
This commit is contained in:
parent
cc767eafd0
commit
36b8ed48a0
|
@ -257,7 +257,7 @@ canvas.pixelated {
|
|||
height:2em;
|
||||
border-style:none;
|
||||
}
|
||||
.palbtn .selected {
|
||||
.palbtn.selected {
|
||||
border-width:2px;
|
||||
border-color:white;
|
||||
border-style:dotted;
|
||||
|
|
20
index.html
20
index.html
|
@ -17,21 +17,6 @@ body {
|
|||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
}
|
||||
.pixeditback {
|
||||
position:absolute;
|
||||
z-index:100;
|
||||
width:100%;
|
||||
height:100%;
|
||||
padding:50px;
|
||||
border-width:4px;
|
||||
border-color:#333;
|
||||
border-style:solid;
|
||||
background-color:rgba(64, 64, 64, 0.5);
|
||||
}
|
||||
#pixeditframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="css/ui.css">
|
||||
<script>
|
||||
|
@ -166,7 +151,6 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
<button id="dbg_disasm" type="button" title="Show Disassembly" style="display:none"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></button>
|
||||
<button id="dbg_memory" type="button" title="Show Memory" style="display:none"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span></button>
|
||||
<button id="dbg_profile" type="button" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button>
|
||||
<button id="dbg_bitmap" type="button" title="Edit Bitmap"><span class="glyphicon glyphicon-camera" aria-hidden="true"></span></button>
|
||||
<button id="dbg_record" type="button" title="Start/Stop Replay Recording" style="display:none"><span class="glyphicon glyphicon-record" aria-hidden="true"></span></button>
|
||||
<button id="dbg_help" type="button" title="Help" style="display:none"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span></button>
|
||||
</span>
|
||||
|
@ -243,10 +227,6 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
<a target="_new" href="https://twitter.com/8bitworkshop" class="twitter-follow-button" data-show-count="false">Follow @8bitworkshop</a>-->
|
||||
<!--<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>-->
|
||||
<!--</div>-->
|
||||
<div id="pixeditback" class="pixeditback" style="display:none">
|
||||
<iframe id="pixeditframe" src="pixels.html">
|
||||
</iframe>
|
||||
</div>
|
||||
<div id="pleaseWaitModal" class="modal fade">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
|
|
113
pixels.html
113
pixels.html
|
@ -1,113 +0,0 @@
|
|||
<html lang="en">
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>8bitworkshop Pixel Editor</title>
|
||||
<style type="text/css" media="screen">
|
||||
body {
|
||||
overflow: hidden !important;
|
||||
font-size: 11px;
|
||||
}
|
||||
.thumbdiv {
|
||||
}
|
||||
.fcontainer
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
.fitem
|
||||
{
|
||||
background-color: #f3f2ef;
|
||||
border-radius: 3px;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="css/ui.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="controls_top">
|
||||
<span id="palette_group" class="palette_group">
|
||||
</span>
|
||||
<span style="float:right">
|
||||
<button id="btn_cancel" type="submit" title="Cancel">Discard <span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
||||
<button id="btn_saveandclose" type="submit" title="Save and Close">Save and Close <span class="glyphicon glyphicon-save" aria-hidden="true"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="notebook">
|
||||
<div style="width:60%;height:100%;position:absolute;left:0">
|
||||
<div id="maineditor" class="fcontainer">
|
||||
</div>
|
||||
</div>
|
||||
<div style="width:40%;height:100%;position:absolute;right:0">
|
||||
<div id="thumbnaildiv" class="fcontainer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="jquery/jquery-3.3.1.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
|
||||
<script src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="FileSaver.js/FileSaver.min.js"></script>
|
||||
|
||||
<script>
|
||||
var exports = {};
|
||||
function require(modname) {
|
||||
if (modname == 'jquery') return $;
|
||||
else if (modname.startsWith('.')) return exports;
|
||||
else { console.log("Unknown require()", modname); return exports; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="gen/util.js"></script>
|
||||
<script src="gen/pixed/pixeleditor.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
if (window.self === window.top) {
|
||||
/*
|
||||
var datastr = "{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, { 0x7e,0x81,0x95,0xb1,0xb1,0x95,0x81,0x7e }, { 0x7e,0xff,0xeb,0xcf,0xcf,0xeb,0xff,0x7e }, { 0x0e,0x1f,0x3f,0x7e,0x3f,0x1f,0x0e,0x00 }, { 0x08,0x1c,0x3e,0x7f,0x3e,0x1c,0x08,0x00 }, { 0x38,0x3a,0x9f,0xff,0x9f,0x3a,0x38,0x00 }, { 0x10,0x38,0xbc,0xff,0xbc,0x38,0x10,0x00 }, { 0x00,0x00,0x18,0x3c,0x3c,0x18,0x00,0x00 }, { 0xff,0xff,0xe7,0xc3,0xc3,0xe7,0xff,0xff }, { 0x00,0x3c,0x66,0x42,0x42,0x66,0x3c,0x00 }";
|
||||
var fmt = {w:8,h:8,bpp:1,count:8};
|
||||
var palette = [0xff000000, 0xffffffff];
|
||||
*/
|
||||
var paldatastr = " 0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x38, 0x70, 0x7f, 0xf8, ";
|
||||
var fmt = {w:14,h:16,bpp:4,brev:1,count:128};
|
||||
var datastr = "0x00,0x00,0xef,0xef,0xe0,0x00,0x00, 0x00,0xee,0xee,0xfe,0xee,0xe0,0x00, 0x0e,0xed,0xef,0xef,0xed,0xee,0x00, 0x0e,0xee,0xdd,0xdd,0xde,0xee,0x00, 0x0e,0xee,0xed,0xde,0xee,0xee,0x00, 0x00,0xee,0xee,0xde,0xee,0xe0,0x00, 0x00,0xee,0xee,0xde,0xee,0xe0,0x00, 0x00,0x00,0xed,0xdd,0xe0,0x00,0x0d, 0xdd,0xdd,0xee,0xee,0xed,0xdd,0xd0, 0x0d,0xee,0xee,0xee,0xee,0xee,0x00, 0x0e,0xe0,0xee,0xee,0xe0,0xee,0x00, 0x0e,0xe0,0xee,0xee,0xe0,0xee,0x00, 0x0e,0xe0,0xdd,0xdd,0xd0,0xde,0x00, 0x0d,0x00,0xee,0x0e,0xe0,0x0d,0x00, 0x00,0x00,0xed,0x0e,0xe0,0x00,0x00, 0x00,0x0d,0xdd,0x0d,0xdd,0x00,0x18,";
|
||||
/*
|
||||
var datastr = "$00,0xfe,0x82,0x82,0x82,0xfe,0xfe,0x00,0x00,0x00,0xfe,0xfe,0xc0,0x00,0x00,0x00,0x00,0xf2,0xf2,0x92,0x92,0x9e,0x9e,0x00,0x00,0xfe,0xfe,0x92,0x92,0x82,0x00,0x00,0x08,0xfe,0xfe,0x88,0x88,0xf8,0xf8,0x00,0x00,0x9e,0x9e,0x92,0x92,0xf2,0xf2,0x00,0x00,0x9e,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0xf0,0xf0,0x9e,0x9e,0x80,0x80,0x00,0x00,0xfe,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0xfe,0x92,0x92,0x92,0xf2,0xf0,0x00,0x00,0xfe,0xc8,0x88,0x88,0xfe,0xfe,0x00,0x00,0xee,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfc,0x86,0x82,0x82,0xfe,0xfe,0x00,0x00,0x82,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x80,0x90,0x90,0x90,0xfe,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xc8,0x88,0x88,0xfe,0xfe,0x00,0x00,0xee,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfc,0x86,0x82,0x82,0xfe,0xfe,0x00,0x00,0x82,0x92,0x92,0x92,0xfe,0xfe,0x00,0x80,0x90,0x90,0x90,0x90,0xfe,0xfe,0x00,0x00,0x9e,0x92,0x82,0x82,0xfe,0xfe,0x00,0xfe,0xfe,0x10,0x10,0x10,0xfe,0xfe,0x00,0x00,0x00,0xbe,0xbe,0x00,0x00,0x00,0x00,0xfc,0xfe,0x06,0x02,0x02,0x02,0x00,0x00,0x00,0x82,0x44,0x28,0x18,0xfe,0xfe,0x00,0x02,0x02,0x02,0x06,0xfe,0xfe,0x00,0x00,0xfe,0x40,0x20,0x18,0x20,0xfe,0xfe,0x00,0xfe,0x0c,0x08,0x10,0x20,0xfe,0xfe,0x00,0xfe,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfe,0x82,0x82,0x82,0xfe,0xfe,0x00,0x00,0x00,0xfe,0xfe,0xc0,0x00,0x00,0x00,0x00,0xf2,0xf2,0x92,0x92,0x9e,0x9e,0x00,0x00,0xfe,0xfe,0x92,0x92,0x82,0x00,0x00,0x08,0xfe,0xfe,0x88,0x88,0xf8,0xf8,0x00,0x00,0x9e,0x9e,0x92,0x92,0xf2,0xf2,0x00,0x00,0x9e,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0xf0,0xf0,0x9e,0x9e,0x80,0x80,0x00,0x00,0xfe,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0xfe,0x92,0x92,0x92,0xf2,0xf0,0x00,0x00,0xfe,0xc8,0x88,0x88,0xfe,0xfe,0x00,0x00,0xee,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfc,0x86,0x82,0x82,0xfe,0xfe,0x00,0x00,0x82,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x80,0x90,0x90,0x90,0xfe,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xc8,0x88,0x88,0xfe,0xfe,0x00,0x00,0xee,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfc,0x86,0x82,0x82,0xfe,0xfe,0x00,0x00,0x82,0x92,0x92,0x92,0xfe,0xfe,0x00,0x80,0x90,0x90,0x90,0x90,0xfe,0xfe,0x00,0x00,0x9e,0x92,0x82,0x82,0xfe,0xfe,0x00,0xfe,0xfe,0x10,0x10,0x10,0xfe,0xfe,0x00,0x00,0x00,0xbe,0xbe,0x00,0x00,0x00,0x00,0xfc,0xfe,0x06,0x02,0x02,0x02,0x00,0x00,0x00,0x82,0x44,0x28,0x18,0xfe,0xfe,0x00,0x02,0x02,0x02,0x06,0xfe,0xfe,0x00,0x00,0xfe,0x40,0x20,0x18,0x20,0xfe,0xfe,0x00,0xfe,0x0c,0x08,0x10,0x20,0xfe,0xfe,0x00,0xfe,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,";
|
||||
var fmt = {w:8,h:8,bpp:1,np:2,pofs:0x100,count:32,xform:"rotate(90deg) scaleX(-1)"};
|
||||
var paldatastr = "0x00,0x30,0x4f,0xff,0x00,0xf0,0xc0,0x7f, 0x00,0xc0,0x04,0x1f,0x00,0xd0,0xd0,0x0f, 0x00,0xc0,0xc0,0x0f,0x00,0x04,0x04,0x0f, 0x00,0xff,0x0f,0xf0,0x00,0x7f,0x0f,0xdf,";
|
||||
*/
|
||||
pixelEditorReceiveMessage({data:{fmt:fmt,bytestr:datastr,palfmt:{pal:332,n:16},palstr:paldatastr}});
|
||||
console.log(postToParentWindow({save:true}));
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
window.addEventListener("message", pixelEditorReceiveMessage, false);
|
||||
window.addEventListener("resize", pixelEditorResize, false);
|
||||
window.addEventListener("keypress", pixelEditorKeypress, false);
|
||||
|
||||
$("#btn_cancel").click(function(e) {
|
||||
if (confirm("Sure you want to cancel and discard?")) {
|
||||
postToParentWindow({close:true});
|
||||
}
|
||||
});
|
||||
$("#btn_saveandclose").click(function(e) {
|
||||
try {
|
||||
postToParentWindow({close:true,save:true});
|
||||
} catch (ex) {
|
||||
alert("Could not convert to bytes: " + ex);
|
||||
throw ex;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,16 +1,29 @@
|
|||
|
||||
#include "neslib.h"
|
||||
#include "nes.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define NES_MAPPER 2 // UxROM mapper
|
||||
#define NES_CHR_BANKS 0 // CHR RAM
|
||||
|
||||
void set_pixel(byte x, byte y, byte color) {
|
||||
// compute pattern table address
|
||||
word a = (x/8)*16 + ((y&63)/8)*(16*32) + (y&7);
|
||||
#define PPU_IS_ON() (PPU.mask & (MASK_BG|MASK_SPR))
|
||||
|
||||
void monobitmap_split() {
|
||||
// split screen at line 128
|
||||
split(0,0);
|
||||
PPU.control = PPU.control ^ 0x10; // bg bank 1
|
||||
}
|
||||
|
||||
void monobitmap_set_pixel(byte x, byte y, byte color) {
|
||||
byte b;
|
||||
// compute pattern table address
|
||||
word a = (x/8)*16 | ((y&63)/8)*(16*32) | (y&7);
|
||||
if (y & 64) a |= 8;
|
||||
if (y & 128) a |= 0x1000;
|
||||
// if PPU is active, wait for next frame
|
||||
if (PPU_IS_ON()) {
|
||||
ppu_wait_nmi();
|
||||
}
|
||||
// read old byte
|
||||
vram_adr(a);
|
||||
vram_read(&b, 1);
|
||||
|
@ -22,41 +35,58 @@ void set_pixel(byte x, byte y, byte color) {
|
|||
// write new byte
|
||||
vram_adr(a);
|
||||
vram_put(b);
|
||||
// if PPU is active, reset PPU addr and split screen
|
||||
if (PPU_IS_ON()) {
|
||||
vram_adr(0);
|
||||
monobitmap_split();
|
||||
}
|
||||
}
|
||||
|
||||
void monobitmap_draw_line(int x0, int y0, int x1, int y1, byte color) {
|
||||
int dx = abs(x1-x0);
|
||||
int sx = x0<x1 ? 1 : -1;
|
||||
int dy = abs(y1-y0);
|
||||
int sy = y0<y1 ? 1 : -1;
|
||||
int err = (dx>dy ? dx : -dy)>>1;
|
||||
int e2;
|
||||
for(;;) {
|
||||
monobitmap_set_pixel(x0, y0, color);
|
||||
if (x0==x1 && y0==y1) break;
|
||||
e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x0 += sx; }
|
||||
if (e2 < dy) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
// write values 0..255
|
||||
void vram_put_256inc() {
|
||||
void monobitmap_put_256inc() {
|
||||
word i;
|
||||
for (i=0; i<256; i++)
|
||||
vram_put(i);
|
||||
}
|
||||
|
||||
void vram_put_attrib() {
|
||||
void monobitmap_put_attrib() {
|
||||
vram_fill(0x00, 0x10); // first palette
|
||||
vram_fill(0x55, 0x10); // second palette
|
||||
}
|
||||
|
||||
void setup_monobitmap() {
|
||||
void monobitmap_clear() {
|
||||
// clear pattern table
|
||||
vram_adr(0x0);
|
||||
vram_fill(0x0, 0x2000);
|
||||
}
|
||||
|
||||
void monobitmap_setup() {
|
||||
monobitmap_clear();
|
||||
// setup nametable A and B
|
||||
vram_adr(NAMETABLE_A);
|
||||
vram_put_256inc();
|
||||
vram_put_256inc();
|
||||
vram_put_256inc();
|
||||
vram_put_256inc();
|
||||
vram_adr(NAMETABLE_B);
|
||||
vram_put_256inc();
|
||||
vram_put_256inc();
|
||||
vram_put_256inc();
|
||||
vram_put_256inc();
|
||||
monobitmap_put_256inc();
|
||||
monobitmap_put_256inc();
|
||||
monobitmap_put_256inc();
|
||||
monobitmap_put_256inc();
|
||||
vram_adr(NAMETABLE_A + 0x3c0);
|
||||
vram_put_attrib();
|
||||
vram_put_attrib();
|
||||
vram_adr(NAMETABLE_B + 0x3c0);
|
||||
vram_put_attrib();
|
||||
vram_put_attrib();
|
||||
monobitmap_put_attrib();
|
||||
monobitmap_put_attrib();
|
||||
bank_bg(0);
|
||||
// setup sprite 0
|
||||
oam_clear();
|
||||
|
@ -68,7 +98,7 @@ void setup_monobitmap() {
|
|||
vram_fill(0xff, 0x10);
|
||||
}
|
||||
|
||||
/*{pal:"nes"}*/
|
||||
/*{pal:"nes",layout:"nes"}*/
|
||||
const byte MONOBMP_PALETTE[16] = {
|
||||
0x03,
|
||||
0x30, 0x03, 0x30, 0x00,
|
||||
|
@ -77,29 +107,44 @@ const byte MONOBMP_PALETTE[16] = {
|
|||
0x03, 0x30, 0x30
|
||||
};
|
||||
|
||||
void demo() {
|
||||
void monobitmap_demo() {
|
||||
byte i;
|
||||
for (i=16; i<220; i++) {
|
||||
set_pixel(i,16,1);
|
||||
set_pixel(16,i,1);
|
||||
set_pixel(i,220,1);
|
||||
set_pixel(220,i,1);
|
||||
set_pixel(i,i,1);
|
||||
static const byte x1 = 16;
|
||||
static const byte y1 = 16;
|
||||
static const byte x2 = 240;
|
||||
static const byte y2 = 208;
|
||||
for (i=x1; i<=x2; i++) {
|
||||
monobitmap_set_pixel(i,y1,1);
|
||||
monobitmap_set_pixel(i,y2,1);
|
||||
}
|
||||
for (i=y1; i<=y2; i++) {
|
||||
monobitmap_set_pixel(x1,i,1);
|
||||
monobitmap_set_pixel(x2,i,1);
|
||||
}
|
||||
for (i=x1; i<x2; i+=16) {
|
||||
monobitmap_draw_line(x1,y1,i,y2,1);
|
||||
}
|
||||
for (i=y1; i<=y2; i+=16) {
|
||||
monobitmap_draw_line(x1,y1,x2,i,1);
|
||||
}
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
byte ctrl;
|
||||
setup_monobitmap();
|
||||
monobitmap_setup();
|
||||
pal_bg(MONOBMP_PALETTE);
|
||||
demo();
|
||||
ppu_on_all();//enable rendering
|
||||
monobitmap_demo();
|
||||
ppu_on_all();
|
||||
while (!pad_trigger(0)) {
|
||||
ppu_wait_nmi();
|
||||
monobitmap_split();
|
||||
}
|
||||
ppu_off();
|
||||
monobitmap_setup();
|
||||
ppu_on_all();
|
||||
monobitmap_demo();
|
||||
while(1) {
|
||||
ppu_wait_nmi();
|
||||
// split screen at line 128
|
||||
ctrl = PPU.control;
|
||||
split(0,0);
|
||||
PPU.control = ctrl ^ 0x10; // bg bank 1
|
||||
monobitmap_split();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,215 +56,6 @@ type PixelEditorMessage = {
|
|||
palstr : string
|
||||
};
|
||||
|
||||
export function PixelEditor(parentDiv:HTMLElement,
|
||||
fmt:PixelEditorImageFormat,
|
||||
palette:Uint32Array,
|
||||
initialData:Uint32Array,
|
||||
thumbnails?) {
|
||||
var width = fmt.w;
|
||||
var height = fmt.h;
|
||||
|
||||
function createCanvas() {
|
||||
var c = document.createElement('canvas');
|
||||
c.width = width;
|
||||
c.height = height;
|
||||
if (fmt.xform) c.style.transform = fmt.xform;
|
||||
c.classList.add("pixels");
|
||||
c.classList.add("pixelated");
|
||||
//canvas.tabIndex = "-1"; // Make it focusable
|
||||
$(parentDiv).empty().append(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
function updateImage() {
|
||||
ctx.putImageData(pixdata, 0, 0);
|
||||
}
|
||||
|
||||
function commit() {
|
||||
if (!thumbnails) return;
|
||||
for (var i=0; i<thumbnails.length; i++) {
|
||||
thumbnails[i].copyImageFrom(this);
|
||||
}
|
||||
initialData.set(this.getImageColors());
|
||||
}
|
||||
|
||||
this.copyImageFrom = function(src) {
|
||||
pixints.set(src.getImageData());
|
||||
updateImage();
|
||||
}
|
||||
|
||||
this.getImageData = function() { return pixints.slice(0); }
|
||||
|
||||
function fitCanvas() {
|
||||
pixcanvas.style.height = '50%'; // TODO?
|
||||
return;
|
||||
}
|
||||
this.resize = fitCanvas;
|
||||
|
||||
var pixcanvas = createCanvas();
|
||||
var ctx = pixcanvas.getContext('2d');
|
||||
var pixdata = ctx.createImageData(width, height);
|
||||
var pixints = new Uint32Array(pixdata.data.buffer);
|
||||
for (var i=0; i<pixints.length; i++) {
|
||||
pixints[i] = initialData ? palette[initialData[i]] : palette[0];
|
||||
}
|
||||
this.canvas = pixcanvas;
|
||||
|
||||
updateImage();
|
||||
|
||||
|
||||
this.createPaletteButtons = function() {
|
||||
var span = $("#palette_group").empty();
|
||||
for (var i=0; i<palette.length; i++) {
|
||||
var btn = $('<button class="palbtn">');
|
||||
var rgb = palette[i] & 0xffffff;
|
||||
var color = "#" + hex(rgb2bgr(rgb), 6);
|
||||
btn.click(this.setCurrentColor.bind(this, i));
|
||||
btn.attr('id', 'palcol_' + i);
|
||||
btn.css('backgroundColor', color).text(i.toString(16));
|
||||
if ((rgb & 0x808080) != 0x808080) { btn.css('color', 'white'); }
|
||||
span.append(btn);
|
||||
}
|
||||
this.setCurrentColor(1);
|
||||
}
|
||||
|
||||
function getPixelByOffset(ofs) {
|
||||
var oldrgba = pixints[ofs] & 0xffffff;
|
||||
for (var i=0; i<palette.length; i++) {
|
||||
if (oldrgba == (palette[i] & 0xffffff)) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getPixel(x, y) {
|
||||
var ofs = x+y*width;
|
||||
return getPixelByOffset(ofs);
|
||||
}
|
||||
|
||||
function setPixel(x, y, col) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) return;
|
||||
var ofs = x+y*width;
|
||||
var oldrgba = pixints[ofs];
|
||||
var rgba = palette[col];
|
||||
if (oldrgba != rgba) {
|
||||
pixints[ofs] = rgba;
|
||||
updateImage();
|
||||
}
|
||||
}
|
||||
|
||||
this.getImageColors = function() {
|
||||
var pixcols = new Uint8Array(pixints.length);
|
||||
for (var i=0; i<pixints.length; i++)
|
||||
pixcols[i] = getPixelByOffset(i);
|
||||
return pixcols;
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
this.makeEditable = function() {
|
||||
var curpalcol = -1;
|
||||
setCurrentColor(1);
|
||||
|
||||
function getPositionFromEvent(e) {
|
||||
var x = Math.floor(e.offsetX * width / pxls.width());
|
||||
var y = Math.floor(e.offsetY * height / pxls.height());
|
||||
return {x:x, y:y};
|
||||
}
|
||||
|
||||
function setCurrentColor(col) {
|
||||
if (curpalcol != col) {
|
||||
if (curpalcol >= 0)
|
||||
$("#palcol_" + curpalcol).removeClass('selected');
|
||||
curpalcol = col;
|
||||
$("#palcol_" + col).addClass('selected');
|
||||
}
|
||||
}
|
||||
this.setCurrentColor = setCurrentColor;
|
||||
|
||||
var dragcol = 1;
|
||||
var dragging = false;
|
||||
|
||||
var pxls = $(pixcanvas);
|
||||
pxls.mousedown( (e) => {
|
||||
var pos = getPositionFromEvent(e);
|
||||
dragcol = getPixel(pos.x, pos.y) == curpalcol ? 0 : curpalcol;
|
||||
setPixel(pos.x, pos.y, curpalcol);
|
||||
dragging = true;
|
||||
// TODO: pixcanvas.setCapture();
|
||||
})
|
||||
.mousemove( (e) => {
|
||||
var pos = getPositionFromEvent(e);
|
||||
if (dragging) {
|
||||
setPixel(pos.x, pos.y, dragcol);
|
||||
}
|
||||
})
|
||||
.mouseup( (e) => {
|
||||
var pos = getPositionFromEvent(e);
|
||||
setPixel(pos.x, pos.y, dragcol);
|
||||
dragging = false;
|
||||
commit();
|
||||
// TODO: pixcanvas.releaseCapture();
|
||||
});
|
||||
}
|
||||
|
||||
function setPixels(p) {
|
||||
var i = 0;
|
||||
for (var y=0; y<height; y++) {
|
||||
for (var x=0; x<width; x++) {
|
||||
setPixel(x, y, p[i++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.rotate = function(deg) {
|
||||
console.log("rotate " + deg);
|
||||
var s1 = Math.sin(deg * Math.PI / 180);
|
||||
var c1 = Math.cos(deg * Math.PI / 180);
|
||||
var p = this.getImageColors();
|
||||
var i = 0;
|
||||
for (var y=0; y<height; y++) {
|
||||
for (var x=0; x<width; x++) {
|
||||
var xx = x + 0.5 - width/2.0;
|
||||
var yy = y + 0.5 - height/2.0;
|
||||
var xx2 = xx*c1 - yy*s1 + width/2.0 - 0.5;
|
||||
var yy2 = yy*c1 + xx*s1 + height/2.0 - 0.5;
|
||||
var col = getPixel(Math.round(xx2), Math.round(yy2));
|
||||
p[i++] = col;
|
||||
}
|
||||
}
|
||||
setPixels(p);
|
||||
commit();
|
||||
}
|
||||
|
||||
this.flipy = function() {
|
||||
console.log("flipy");
|
||||
var p = this.getImageColors();
|
||||
var i = 0;
|
||||
for (var y=0; y<height; y++) {
|
||||
for (var x=0; x<width; x++) {
|
||||
var col = getPixel(x, height-1-y);
|
||||
p[i++] = col;
|
||||
}
|
||||
}
|
||||
setPixels(p);
|
||||
commit();
|
||||
}
|
||||
|
||||
this.flipx = function() {
|
||||
console.log("flipx");
|
||||
var p = this.getImageColors();
|
||||
var i = 0;
|
||||
for (var y=0; y<height; y++) {
|
||||
for (var x=0; x<width; x++) {
|
||||
var col = getPixel(width-1-x, y);
|
||||
p[i++] = col;
|
||||
}
|
||||
}
|
||||
setPixels(p);
|
||||
commit();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
var pixel_re = /([0#]?)([x$%]|\d'[bh])([0-9a-f]+)/gi;
|
||||
|
@ -435,19 +226,6 @@ function convertPaletteBytes(arr:UintArray,r0,r1,g0,g1,b0,b1) : number[] {
|
|||
return result;
|
||||
}
|
||||
|
||||
export var palette : Uint32Array;
|
||||
export var paletteSets;
|
||||
export var paletteSetIndex=0;
|
||||
export var currentPixelEditor;
|
||||
export var parentSource;
|
||||
export var parentOrigin;
|
||||
export var allimages;
|
||||
export var currentFormat : PixelEditorImageFormat;
|
||||
export var currentByteStr : string;
|
||||
export var currentPaletteStr : string;
|
||||
export var currentPaletteFmt : PixelEditorPaletteFormat;
|
||||
export var allthumbs;
|
||||
|
||||
export function getPaletteLength(palfmt: PixelEditorPaletteFormat) : number {
|
||||
var pal = palfmt.pal;
|
||||
if (typeof pal === 'number') {
|
||||
|
@ -488,136 +266,6 @@ function convertPaletteFormat(palbytes:UintArray, palfmt: PixelEditorPaletteForm
|
|||
return newpalette;
|
||||
}
|
||||
|
||||
export function pixelEditorDecodeMessage(e) {
|
||||
parentSource = e.source;
|
||||
parentOrigin = e.origin;
|
||||
let data : PixelEditorMessage = e.data;
|
||||
currentFormat = e.data.fmt;
|
||||
currentPaletteFmt = data.palfmt;
|
||||
currentPaletteStr = data.palstr;
|
||||
currentByteStr = convertToHexStatements(data.bytestr);
|
||||
var words = parseHexWords(currentByteStr);
|
||||
allimages = convertWordsToImages(words, data.fmt);
|
||||
var newpalette = [0xff000000, 0xffffffff]; // TODO
|
||||
if (currentPaletteStr) {
|
||||
var palbytes = parseHexWords(data.palstr);
|
||||
newpalette = convertPaletteFormat(palbytes, currentPaletteFmt) || newpalette;
|
||||
if (currentPaletteFmt.n) {
|
||||
paletteSets = [];
|
||||
for (var i=0; i<newpalette.length; i+=currentPaletteFmt.n) {
|
||||
paletteSets.push(newpalette.slice(i, i+currentPaletteFmt.n));
|
||||
}
|
||||
newpalette = paletteSets[paletteSetIndex = 0];
|
||||
// TODO: swap palettes
|
||||
}
|
||||
} else {
|
||||
var ncols = (currentFormat.bpp || 1) * (currentFormat.np || 1);
|
||||
switch (ncols) {
|
||||
case 2:
|
||||
newpalette = [0xff000000, 0xffff00ff, 0xffffff00, 0xffffffff];
|
||||
break;
|
||||
// TODO
|
||||
}
|
||||
// TODO: default palette?
|
||||
}
|
||||
palette = new Uint32Array(newpalette);
|
||||
}
|
||||
|
||||
function pixelEditorCreateThumbnails(e) {
|
||||
// create thumbnail for all images
|
||||
$("#thumbnaildiv").empty();
|
||||
var parentdiv;
|
||||
var count = e.data.fmt.count || 1;
|
||||
allthumbs = [];
|
||||
for (var i=0; i<count; i++) {
|
||||
if ((i & 15) == 0) {
|
||||
parentdiv = $('<div class="thumbdiv">').appendTo("#thumbnaildiv");
|
||||
}
|
||||
allthumbs.push(createThumbnailForImage(parentdiv, i));
|
||||
}
|
||||
}
|
||||
|
||||
function pixelEditorReceiveMessage(e) {
|
||||
pixelEditorDecodeMessage(e);
|
||||
pixelEditorCreateThumbnails(e);
|
||||
// create initial editor
|
||||
createEditorForImage(0);
|
||||
}
|
||||
|
||||
function createThumbnailForImage(parentdiv, i) {
|
||||
var span = $('<span class="thumb">');
|
||||
var thumb = new PixelEditor(span[0] as HTMLElement, currentFormat, palette, allimages[i]);
|
||||
// double size of canvas thumbnail
|
||||
thumb.canvas.style.height = currentFormat.h*2+"px";
|
||||
thumb.canvas.style.width = currentFormat.w*2+"px";
|
||||
parentdiv.append(span);
|
||||
span.click(() => { createEditorForImage(i) });
|
||||
return thumb;
|
||||
}
|
||||
|
||||
function createEditorForImage(i) {
|
||||
currentPixelEditor = new PixelEditor(document.getElementById('maineditor'), currentFormat, palette, allimages[i], [allthumbs[i]]);
|
||||
currentPixelEditor.resize();
|
||||
currentPixelEditor.makeEditable();
|
||||
currentPixelEditor.createPaletteButtons();
|
||||
}
|
||||
|
||||
function postToParentWindow(data) {
|
||||
if (data.save) {
|
||||
var allimgs = [];
|
||||
for (var i=0; i<allthumbs.length; i++) {
|
||||
allimgs.push(allthumbs[i].getImageColors());
|
||||
}
|
||||
data.bytes = convertImagesToWords(allimgs, currentFormat);
|
||||
data.bytestr = replaceHexWords(currentByteStr, data.bytes);
|
||||
}
|
||||
if (parentSource) parentSource.postMessage(data, "*");
|
||||
return data;
|
||||
}
|
||||
|
||||
function pixelEditorResize(e) {
|
||||
if (currentPixelEditor) {
|
||||
currentPixelEditor.resize();
|
||||
}
|
||||
}
|
||||
|
||||
function pixelEditorKeypress(e) {
|
||||
if (!currentPixelEditor) return;
|
||||
//console.log(e);
|
||||
var c = e.charCode;
|
||||
if (c >= 48 && c <= 57) {
|
||||
currentPixelEditor.setCurrentColor(c-48);
|
||||
} else if (c >= 97 && c <= 102) {
|
||||
currentPixelEditor.setCurrentColor(c-97+10);
|
||||
} else {
|
||||
switch (e.keyCode) {
|
||||
case 82: // 'R'
|
||||
currentPixelEditor.rotate(-90);
|
||||
break;
|
||||
case 114: // 'r'
|
||||
currentPixelEditor.rotate(90);
|
||||
break;
|
||||
case 84: // 'T'
|
||||
currentPixelEditor.rotate(-45);
|
||||
break;
|
||||
case 116: // 't'
|
||||
currentPixelEditor.rotate(45);
|
||||
break;
|
||||
}
|
||||
switch (e.charCode) {
|
||||
case 104:
|
||||
currentPixelEditor.flipx();
|
||||
break;
|
||||
|
||||
currentPixelEditor.flipy();
|
||||
break;
|
||||
default:
|
||||
console.log(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: illegal colors?
|
||||
var PREDEF_PALETTES = {
|
||||
'nes':[
|
||||
|
@ -655,10 +303,11 @@ export abstract class PixNode {
|
|||
words? : UintArray; // file data
|
||||
images? : Uint8Array[]; // array of indexed image data
|
||||
rgbimgs? : Uint32Array[]; // array of rgba imgages
|
||||
|
||||
palette? : Uint32Array; // array of rgba
|
||||
|
||||
abstract updateLeft(); // update coming from right
|
||||
abstract updateRight(); // update coming from left
|
||||
|
||||
|
||||
refreshLeft() {
|
||||
var p : PixNode = this;
|
||||
while (p) {
|
||||
|
@ -693,7 +342,7 @@ abstract class CodeProjectDataNode extends PixNode {
|
|||
}
|
||||
|
||||
export class FileDataNode extends CodeProjectDataNode {
|
||||
|
||||
|
||||
constructor(project:ProjectWindows, fileid:string, data:Uint8Array) {
|
||||
super();
|
||||
this.project = project;
|
||||
|
@ -702,6 +351,7 @@ export class FileDataNode extends CodeProjectDataNode {
|
|||
this.words = data;
|
||||
}
|
||||
updateLeft() {
|
||||
this.words = this.right.words;
|
||||
if (this.project) {
|
||||
this.project.updateFile(this.fileid, this.words as Uint8Array);
|
||||
}
|
||||
|
@ -723,8 +373,9 @@ export class TextDataNode extends CodeProjectDataNode {
|
|||
this.text = text;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
updateLeft() {
|
||||
this.words = this.right.words;
|
||||
// TODO: reload editors?
|
||||
var datastr = this.text.substring(this.start, this.end);
|
||||
datastr = replaceHexWords(datastr, this.words);
|
||||
|
@ -760,7 +411,11 @@ export class Mapper extends PixNode {
|
|||
fmt : PixelEditorImageFormat;
|
||||
words : UintArray;
|
||||
images : Uint8Array[];
|
||||
|
||||
|
||||
constructor(fmt) {
|
||||
super();
|
||||
this.fmt = fmt;
|
||||
}
|
||||
updateLeft() {
|
||||
this.images = this.right.images;
|
||||
this.words = convertImagesToWords(this.images, this.fmt);
|
||||
|
@ -778,7 +433,7 @@ class RGBAPalette {
|
|||
this.palcols = palcols;
|
||||
}
|
||||
indexOf(rgba : number) : number {
|
||||
return this.palcols.find(rgba);
|
||||
return this.palcols.indexOf(rgba);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -787,12 +442,12 @@ export class Palettizer extends PixNode {
|
|||
images : Uint8Array[];
|
||||
rgbimgs : Uint32Array[];
|
||||
palette : Uint32Array;
|
||||
|
||||
|
||||
ncolors : number;
|
||||
context : EditorContext;
|
||||
paloptions : SelectablePalette[];
|
||||
palindex : number = 0;
|
||||
|
||||
|
||||
// TODO: control to select palette for bitmaps
|
||||
|
||||
constructor(context:EditorContext, fmt:PixelEditorImageFormat) {
|
||||
|
@ -863,6 +518,10 @@ export class PaletteFormatToRGB extends PixNode {
|
|||
palfmt : PixelEditorPaletteFormat;
|
||||
layout : PixelEditorPaletteLayout;
|
||||
|
||||
constructor(palfmt) {
|
||||
super();
|
||||
this.palfmt = palfmt;
|
||||
}
|
||||
updateLeft() {
|
||||
//TODO
|
||||
}
|
||||
|
@ -889,7 +548,7 @@ export abstract class Compositor extends PixNode {
|
|||
images : Uint8Array[]; // output (1 image)
|
||||
width : number;
|
||||
height : number;
|
||||
|
||||
|
||||
context : EditorContext;
|
||||
tileoptions : SelectableTilemap[];
|
||||
tileindex : number = 0;
|
||||
|
@ -922,7 +581,7 @@ export class MetaspriteCompositor extends Compositor {
|
|||
}
|
||||
updateLeft() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
updateRight() {
|
||||
this.updateRefs();
|
||||
this.width = 16; // TODO
|
||||
|
@ -945,7 +604,7 @@ export class NESNametableConverter extends Compositor {
|
|||
}
|
||||
updateLeft() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
updateRight() {
|
||||
this.words = this.left.words;
|
||||
this.updateRefs();
|
||||
|
@ -986,51 +645,12 @@ export class NESNametableConverter extends Compositor {
|
|||
|
||||
///// UI CONTROLS
|
||||
|
||||
export class Viewer { // TODO: make PixNode
|
||||
|
||||
width : number;
|
||||
height : number;
|
||||
canvas : HTMLCanvasElement;
|
||||
ctx : CanvasRenderingContext2D;
|
||||
pixdata : ImageData;
|
||||
|
||||
recreate() {
|
||||
this.canvas = this.newCanvas();
|
||||
this.pixdata = this.ctx.createImageData(this.width, this.height);
|
||||
}
|
||||
|
||||
createWith(pv : Viewer) {
|
||||
this.width = pv.width;
|
||||
this.height = pv.height;
|
||||
this.pixdata = pv.pixdata;
|
||||
this.canvas = this.newCanvas();
|
||||
}
|
||||
|
||||
newCanvas() : HTMLCanvasElement {
|
||||
var c = document.createElement('canvas');
|
||||
c.width = this.width;
|
||||
c.height = this.height;
|
||||
//if (fmt.xform) c.style.transform = fmt.xform;
|
||||
c.classList.add("pixels");
|
||||
c.classList.add("pixelated");
|
||||
this.ctx = c.getContext('2d');
|
||||
return c;
|
||||
}
|
||||
|
||||
updateImage(imdata : Uint32Array) {
|
||||
if (imdata) {
|
||||
new Uint32Array(this.pixdata.data.buffer).set(imdata);
|
||||
}
|
||||
this.ctx.putImageData(this.pixdata, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageChooser {
|
||||
|
||||
rgbimgs : Uint32Array[];
|
||||
width : number;
|
||||
height : number;
|
||||
|
||||
|
||||
recreate(parentdiv:JQuery, onclick) {
|
||||
var agrid = $('<div class="asset_grid"/>'); // grid (or 1) of preview images
|
||||
parentdiv.empty().append(agrid);
|
||||
|
@ -1059,7 +679,7 @@ export class ImageChooser {
|
|||
span = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newDiv(parent?, cls? : string) {
|
||||
|
@ -1074,6 +694,7 @@ export class CharmapEditor extends PixNode {
|
|||
context;
|
||||
parentdiv;
|
||||
fmt;
|
||||
chooser;
|
||||
|
||||
constructor(context:EditorContext, parentdiv:JQuery, fmt:PixelEditorImageFormat) {
|
||||
super();
|
||||
|
@ -1081,27 +702,25 @@ export class CharmapEditor extends PixNode {
|
|||
this.parentdiv = parentdiv;
|
||||
this.fmt = fmt;
|
||||
}
|
||||
|
||||
updateLeft() { } // TODO
|
||||
|
||||
|
||||
updateLeft() {
|
||||
}
|
||||
|
||||
updateRight() {
|
||||
this.rgbimgs = this.left.rgbimgs;
|
||||
var adual = newDiv(this.parentdiv.empty(), "asset_dual"); // contains grid and editor
|
||||
var agrid = newDiv(adual);
|
||||
var aeditor = newDiv(adual, "asset_editor").hide(); // contains editor, when selected
|
||||
// add image chooser grid
|
||||
var chooser = new ImageChooser();
|
||||
var chooser = this.chooser = new ImageChooser();
|
||||
chooser.rgbimgs = this.rgbimgs;
|
||||
chooser.width = this.fmt.w || 1;
|
||||
chooser.height = this.fmt.h || 1;
|
||||
chooser.recreate(agrid, (index, viewer) => {
|
||||
var escale = Math.ceil(192 / this.fmt.w);
|
||||
var editview = new Viewer();
|
||||
editview.createWith(viewer);
|
||||
editview.updateImage(null);
|
||||
editview.canvas.style.width = (viewer.width*escale)+'px'; // TODO
|
||||
aeditor.empty().append(editview.canvas);
|
||||
var editview = this.createEditor(aeditor, viewer, escale);
|
||||
this.context.setCurrentEditor(aeditor, $(viewer.canvas));
|
||||
this.rgbimgs[index] = viewer.rgbdata;
|
||||
});
|
||||
// add palette selector
|
||||
// TODO: only view when editing?
|
||||
|
@ -1122,5 +741,191 @@ export class CharmapEditor extends PixNode {
|
|||
}
|
||||
}
|
||||
|
||||
createEditor(aeditor : JQuery, viewer : Viewer, escale : number) : PixEditor {
|
||||
var im = new PixEditor();
|
||||
im.createWith(viewer);
|
||||
im.updateImage();
|
||||
im.canvas.style.width = (viewer.width*escale)+'px'; // TODO
|
||||
im.makeEditable(this, aeditor, this.left.palette);
|
||||
return im;
|
||||
}
|
||||
}
|
||||
|
||||
export class Viewer {
|
||||
|
||||
width : number;
|
||||
height : number;
|
||||
canvas : HTMLCanvasElement;
|
||||
ctx : CanvasRenderingContext2D;
|
||||
imagedata : ImageData;
|
||||
rgbdata : Uint32Array;
|
||||
peerviewers : Viewer[];
|
||||
|
||||
recreate() {
|
||||
this.canvas = this.newCanvas();
|
||||
this.imagedata = this.ctx.createImageData(this.width, this.height);
|
||||
this.rgbdata = new Uint32Array(this.imagedata.data.buffer);
|
||||
this.peerviewers = [this];
|
||||
}
|
||||
|
||||
createWith(pv : Viewer) {
|
||||
this.width = pv.width;
|
||||
this.height = pv.height;
|
||||
this.imagedata = pv.imagedata;
|
||||
this.rgbdata = pv.rgbdata;
|
||||
this.canvas = this.newCanvas();
|
||||
this.peerviewers = [this, pv];
|
||||
}
|
||||
|
||||
newCanvas() : HTMLCanvasElement {
|
||||
var c = document.createElement('canvas');
|
||||
c.width = this.width;
|
||||
c.height = this.height;
|
||||
//if (fmt.xform) c.style.transform = fmt.xform;
|
||||
c.classList.add("pixels");
|
||||
c.classList.add("pixelated");
|
||||
this.ctx = c.getContext('2d');
|
||||
return c;
|
||||
}
|
||||
|
||||
updateImage(imdata? : Uint32Array) {
|
||||
if (imdata) {
|
||||
this.rgbdata.set(imdata);
|
||||
}
|
||||
for (let v of this.peerviewers) {
|
||||
v.ctx.putImageData(this.imagedata, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PixEditor extends Viewer {
|
||||
|
||||
left : PixNode;
|
||||
palette : Uint32Array;
|
||||
curpalcol : number = -1;
|
||||
currgba : number;
|
||||
palbtns : JQuery[];
|
||||
|
||||
getPositionFromEvent(e) {
|
||||
var x = Math.floor(e.offsetX * this.width / $(this.canvas).width());
|
||||
var y = Math.floor(e.offsetY * this.height / $(this.canvas).height());
|
||||
return {x:x, y:y};
|
||||
}
|
||||
|
||||
setPaletteColor(col: number) {
|
||||
col &= this.palette.length-1;
|
||||
if (this.curpalcol != col) {
|
||||
if (this.curpalcol >= 0)
|
||||
this.palbtns[this.curpalcol].removeClass('selected');
|
||||
this.curpalcol = col;
|
||||
this.currgba = this.palette[col & this.palette.length-1];
|
||||
this.palbtns[col].addClass('selected');
|
||||
}
|
||||
}
|
||||
|
||||
makeEditable(leftnode:PixNode, aeditor:JQuery, palette:Uint32Array) {
|
||||
this.left = leftnode;
|
||||
this.palette = palette;
|
||||
|
||||
var dragcol;
|
||||
var dragging = false;
|
||||
|
||||
var pxls = $(this.canvas);
|
||||
pxls.mousedown( (e) => {
|
||||
var pos = this.getPositionFromEvent(e);
|
||||
dragcol = this.getPixel(pos.x, pos.y) == this.currgba ? this.palette[0] : this.currgba;
|
||||
this.setPixel(pos.x, pos.y, this.currgba);
|
||||
dragging = true;
|
||||
// TODO: pixcanvas.setCapture();
|
||||
})
|
||||
.mousemove( (e) => {
|
||||
var pos = this.getPositionFromEvent(e);
|
||||
if (dragging) {
|
||||
this.setPixel(pos.x, pos.y, dragcol);
|
||||
}
|
||||
})
|
||||
.mouseup( (e) => {
|
||||
var pos = this.getPositionFromEvent(e);
|
||||
this.setPixel(pos.x, pos.y, dragcol);
|
||||
dragging = false;
|
||||
this.commit();
|
||||
// TODO: pixcanvas.releaseCapture();
|
||||
});
|
||||
|
||||
aeditor.empty();
|
||||
aeditor.append(this.canvas);
|
||||
aeditor.append(this.createPaletteButtons());
|
||||
this.setPaletteColor(1);
|
||||
}
|
||||
|
||||
getPixel(x, y) {
|
||||
var ofs = x+y*this.width;
|
||||
return this.rgbdata[ofs];
|
||||
}
|
||||
|
||||
setPixel(x, y, rgba) {
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
|
||||
var ofs = x+y*this.width;
|
||||
var oldrgba = this.rgbdata[ofs];
|
||||
if (oldrgba != rgba) {
|
||||
this.rgbdata[ofs] = rgba;
|
||||
this.updateImage();
|
||||
}
|
||||
}
|
||||
|
||||
createPaletteButtons() {
|
||||
this.palbtns = [];
|
||||
var span = $(document.createElement('div'));
|
||||
for (var i=0; i<this.palette.length; i++) {
|
||||
var btn = $(document.createElement('button')).addClass('palbtn');
|
||||
var rgb = this.palette[i] & 0xffffff;
|
||||
var color = "#" + hex(rgb2bgr(rgb), 6);
|
||||
btn.click(this.setPaletteColor.bind(this, i));
|
||||
btn.css('backgroundColor', color).text(i.toString(16));
|
||||
btn.css('color', (rgb & 0x008000) ? 'black' : 'white');
|
||||
span.append(btn);
|
||||
this.palbtns.push(btn);
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
commit() {
|
||||
this.left.refreshLeft();
|
||||
}
|
||||
|
||||
remapPixels(mapfn : (x:number,y:number) => number) {
|
||||
var i = 0;
|
||||
var pixels = new Uint32Array(this.rgbdata.length);
|
||||
for (var y=0; y<this.height; y++) {
|
||||
for (var x=0; x<this.width; x++) {
|
||||
pixels[i] = mapfn(x, y);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
this.rgbdata.set(pixels);
|
||||
this.commit();
|
||||
}
|
||||
|
||||
rotate(deg:number) {
|
||||
var s1 = Math.sin(deg * Math.PI / 180);
|
||||
var c1 = Math.cos(deg * Math.PI / 180);
|
||||
this.remapPixels((x,y) => {
|
||||
var xx = x + 0.5 - this.width/2.0;
|
||||
var yy = y + 0.5 - this.height/2.0;
|
||||
var xx2 = xx*c1 - yy*s1 + this.width/2.0 - 0.5;
|
||||
var yy2 = yy*c1 + xx*s1 + this.height/2.0 - 0.5;
|
||||
return this.getPixel(xx, yy);
|
||||
});
|
||||
}
|
||||
flipx() {
|
||||
this.remapPixels((x,y) => {
|
||||
return this.getPixel(this.width-1-x, y);
|
||||
});
|
||||
}
|
||||
flipy() {
|
||||
this.remapPixels((x,y) => {
|
||||
return this.getPixel(x, this.height-1-y);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -967,12 +967,6 @@ function _fastestFrameRate() {
|
|||
setFrameRateUI(60);
|
||||
}
|
||||
|
||||
function _openBitmapEditor() {
|
||||
var wnd = projectWindows.getActive();
|
||||
if (wnd && wnd.openBitmapEditorAtCursor)
|
||||
wnd.openBitmapEditorAtCursor();
|
||||
}
|
||||
|
||||
function traceTiming() {
|
||||
projectWindows.refresh(false);
|
||||
var wnd = projectWindows.getActive();
|
||||
|
@ -1112,7 +1106,6 @@ function setupDebugControls(){
|
|||
$("#dbg_timing").click(traceTiming).show();
|
||||
}
|
||||
$("#disassembly").hide();
|
||||
$("#dbg_bitmap").click(_openBitmapEditor);
|
||||
$(".dropdown-menu").collapse({toggle: false});
|
||||
$("#item_new_file").click(_createNewFile);
|
||||
$("#item_upload_file").click(_uploadNewFile);
|
||||
|
|
142
src/views.ts
142
src/views.ts
|
@ -21,7 +21,6 @@ export interface ProjectView {
|
|||
getCursorPC?() : number;
|
||||
getSourceFile?() : SourceFile;
|
||||
setGutterBytes?(line:number, s:string) : void;
|
||||
openBitmapEditorAtCursor?() : void;
|
||||
markErrors?(errors:WorkerError[]) : void;
|
||||
clearErrors?() : void;
|
||||
setTimingResult?(result:CodeAnalyzer) : void;
|
||||
|
@ -349,86 +348,12 @@ export class SourceEditor implements ProjectView {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
replaceSelection(start:number, end:number, text:string) {
|
||||
this.editor.setSelection(end, start);
|
||||
this.editor.replaceSelection(text);
|
||||
}
|
||||
|
||||
// bitmap editor (TODO: refactor)
|
||||
|
||||
openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
|
||||
|
||||
var handleWindowMessage = (e) => {
|
||||
//console.log("window message", e.data);
|
||||
if (e.data.bytes) {
|
||||
this.editor.replaceSelection(e.data.bytestr);
|
||||
}
|
||||
if (e.data.close) {
|
||||
$("#pixeditback").hide();
|
||||
}
|
||||
e.target.removeEventListener("message", handleWindowMessage);
|
||||
}
|
||||
|
||||
$("#pixeditback").show();
|
||||
window.addEventListener("message", handleWindowMessage, false); // TODO: remove listener
|
||||
window['pixeditframe'].contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
|
||||
}
|
||||
|
||||
lookBackwardsForJSONComment(line, req) {
|
||||
var re = /[/;][*;]([{].+[}])[*;][/;]/;
|
||||
while (--line >= 0) {
|
||||
var s = this.editor.getLine(line);
|
||||
var m = re.exec(s);
|
||||
if (m) {
|
||||
var jsontxt = m[1].replace(/([A-Za-z]+):/g, '"$1":'); // fix lenient JSON
|
||||
var obj = JSON.parse(jsontxt);
|
||||
if (obj[req]) {
|
||||
var start = {obj:obj, line:line, ch:s.indexOf(m[0])+m[0].length};
|
||||
var line0 = line;
|
||||
var pos0 = start.ch;
|
||||
line--;
|
||||
while (++line < this.editor.lineCount()) {
|
||||
var l = this.editor.getLine(line);
|
||||
var endsection;
|
||||
if (platform_id == 'verilog')
|
||||
endsection = l.indexOf('end') >= pos0;
|
||||
else if (s.startsWith(';;'))
|
||||
endsection = l.indexOf(';;') >= pos0;
|
||||
else
|
||||
endsection = l.indexOf(';') >= pos0;
|
||||
if (endsection) {
|
||||
var end = {line:line, ch:this.editor.getLine(line).length};
|
||||
return {obj:obj, start:start, end:end};
|
||||
}
|
||||
pos0 = 0;
|
||||
}
|
||||
line = line0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openBitmapEditorAtCursor() {
|
||||
if ($("#pixeditback").is(":visible")) {
|
||||
$("#pixeditback").hide(250);
|
||||
return;
|
||||
}
|
||||
var line = this.editor.getCursor().line + 1;
|
||||
var data = this.lookBackwardsForJSONComment(this.getCurrentLine(), 'w');
|
||||
if (data && data.obj && data.obj.w>0 && data.obj.h>0) {
|
||||
var paldata = this.lookBackwardsForJSONComment(data.start.line-1, 'pal');
|
||||
var palbytestr;
|
||||
if (paldata) {
|
||||
palbytestr = this.editor.getRange(paldata.start, paldata.end);
|
||||
paldata = paldata.obj;
|
||||
}
|
||||
this.editor.setSelection(data.end, data.start);
|
||||
this.openBitmapEditorWithParams(data.obj, this.editor.getSelection(), paldata, palbytestr);
|
||||
} else {
|
||||
alert("To edit graphics, move cursor to a constant array preceded by a comment in the format:\n\n/*{w:,h:,bpp:,count:...}*/\n\n(See code examples)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -679,7 +604,7 @@ export class MemoryView implements ProjectView {
|
|||
if (sym) s += ' ' + sym;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
readAddress(n : number) {
|
||||
return platform.readAddress(n);
|
||||
}
|
||||
|
@ -973,12 +898,12 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
this.maindiv = newDiv(parent, "vertical-scroll");
|
||||
return this.maindiv[0];
|
||||
}
|
||||
|
||||
|
||||
clearAssets() {
|
||||
this.rootnodes = [];
|
||||
this.deferrednodes = [];
|
||||
}
|
||||
|
||||
|
||||
registerAsset(type:string, node:pixed.PixNode, deferred:boolean) {
|
||||
this.rootnodes.push(node);
|
||||
if (deferred) {
|
||||
|
@ -987,7 +912,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
node.refreshRight();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getPalettes(matchlen : number) : pixed.SelectablePalette[] {
|
||||
var result = [];
|
||||
this.rootnodes.forEach((node) => {
|
||||
|
@ -1039,7 +964,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
setCurrentEditor(div : JQuery, editing : JQuery) {
|
||||
if (this.cureditordiv != div) {
|
||||
if (this.cureditordiv) {
|
||||
|
@ -1060,7 +985,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
this.cureditelem.addClass('asset_editing');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
scanFileTextForAssets(id : string, data : string) {
|
||||
// scan file for assets
|
||||
// /*{json}*/ or ;;{json};;
|
||||
|
@ -1111,7 +1036,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
addPaletteEditorViews(parentdiv:JQuery, words, palette, layout, allcolors, callback) {
|
||||
var adual = $('<div class="asset_dual"/>').appendTo(parentdiv);
|
||||
var aeditor = $('<div class="asset_editor"/>').hide(); // contains editor, when selected
|
||||
|
@ -1157,12 +1082,11 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addPixelEditor(parentdiv:JQuery, firstnode:pixed.PixNode, fmt:pixed.PixelEditorImageFormat) {
|
||||
// data -> pixels
|
||||
var mapper = new pixed.Mapper();
|
||||
fmt.xform = 'scale(2)';
|
||||
mapper.fmt = fmt;
|
||||
var mapper = new pixed.Mapper(fmt);
|
||||
// TODO: rotate node?
|
||||
firstnode.addRight(mapper);
|
||||
// pixels -> RGBA
|
||||
|
@ -1174,8 +1098,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
|
||||
addPaletteEditor(parentdiv:JQuery, firstnode:pixed.PixNode, palfmt?) {
|
||||
// palette -> RGBA
|
||||
var pal2rgb = new pixed.PaletteFormatToRGB();
|
||||
pal2rgb.palfmt = palfmt;
|
||||
var pal2rgb = new pixed.PaletteFormatToRGB(palfmt);
|
||||
firstnode.addRight(pal2rgb);
|
||||
// TODO: refresh twice?
|
||||
firstnode.refreshRight();
|
||||
|
@ -1190,7 +1113,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
firstnode.refreshLeft();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
refreshAssetsInFile(fileid : string, data : FileData) : number {
|
||||
let nassets = 0;
|
||||
let filediv = $('#'+this.getFileDivId(fileid)).empty();
|
||||
|
@ -1243,33 +1166,34 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
|||
}
|
||||
return nassets;
|
||||
}
|
||||
|
||||
|
||||
getFileDivId(id : string) {
|
||||
return '__asset__' + safeident(id);
|
||||
}
|
||||
|
||||
// TODO: recreate editors when refreshing
|
||||
refresh() {
|
||||
this.maindiv.empty();
|
||||
this.clearAssets();
|
||||
current_project.iterateFiles((id, data) => {
|
||||
var divid = this.getFileDivId(id);
|
||||
var filediv = newDiv(this.maindiv, 'asset_file');
|
||||
var header = newDiv(filediv, 'asset_file_header').text(id);
|
||||
var body = newDiv(filediv).attr('id',divid);
|
||||
try {
|
||||
var nassets = this.refreshAssetsInFile(id, data);
|
||||
if (nassets == 0) filediv.hide();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
filediv.text(e+""); // TODO: error msg?
|
||||
}
|
||||
});
|
||||
this.deferrednodes.forEach((node) => { node.refreshRight(); });
|
||||
this.deferrednodes = [];
|
||||
refresh(moveCursor : boolean) {
|
||||
if (moveCursor) {
|
||||
this.maindiv.empty();
|
||||
this.clearAssets();
|
||||
current_project.iterateFiles((id, data) => {
|
||||
var divid = this.getFileDivId(id);
|
||||
var filediv = newDiv(this.maindiv, 'asset_file');
|
||||
var header = newDiv(filediv, 'asset_file_header').text(id);
|
||||
var body = newDiv(filediv).attr('id',divid);
|
||||
try {
|
||||
var nassets = this.refreshAssetsInFile(id, data);
|
||||
if (nassets == 0) filediv.hide();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
filediv.text(e+""); // TODO: error msg?
|
||||
}
|
||||
});
|
||||
this.deferrednodes.forEach((node) => { node.refreshRight(); });
|
||||
this.deferrednodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: scroll editors into view
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,25 @@ var pixed = require("gen/pixed/pixeleditor.js");
|
|||
|
||||
describe('Pixel editor', function() {
|
||||
it('Should decode', function() {
|
||||
var paldatastr = " 0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x38, 0x70, 0x7f, 0xf8, ";
|
||||
|
||||
var fmt = {w:14,h:16,bpp:4,brev:1};
|
||||
var palfmt = {pal:332,n:16};
|
||||
|
||||
var paldatastr = " 0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x38, 0x70, 0x7f, 0xf8, ";
|
||||
var node4 = new pixed.TextDataNode(null, null, null, paldatastr, 0, paldatastr.length);
|
||||
var node5 = new pixed.PaletteFormatToRGB(palfmt);
|
||||
node4.addRight(node5);
|
||||
node4.refreshRight();
|
||||
|
||||
var datastr = "1,2, 0x00,0x00,0xef,0xef,0xe0,0x00,0x00, 0x00,0xee,0xee,0xfe,0xee,0xe0,0x00, 0x0e,0xed,0xef,0xef,0xed,0xee,0x00, 0x0e,0xee,0xdd,0xdd,0xde,0xee,0x00, 0x0e,0xee,0xed,0xde,0xee,0xee,0x00, 0x00,0xee,0xee,0xde,0xee,0xe0,0x00, 0x00,0xee,0xee,0xde,0xee,0xe0,0x00, 0x00,0x00,0xed,0xdd,0xe0,0x00,0x0d, 0xdd,0xdd,0xee,0xee,0xed,0xdd,0xd0, 0x0d,0xee,0xee,0xee,0xee,0xee,0x00, 0x0e,0xe0,0xee,0xee,0xe0,0xee,0x00, 0x0e,0xe0,0xee,0xee,0xe0,0xee,0x00, 0x0e,0xe0,0xdd,0xdd,0xd0,0xde,0x00, 0x0d,0x00,0xee,0x0e,0xe0,0x0d,0x00, 0x00,0x00,0xed,0x0e,0xe0,0x00,0x00, 0x00,0x0d,0xdd,0x0d,0xdd,0x00,0x18,";
|
||||
pixed.pixelEditorDecodeMessage({data:{fmt:fmt,bytestr:datastr,palfmt:{pal:332,n:16},palstr:paldatastr}});
|
||||
assert.deepEqual(pixed.palette, [0xff000000,
|
||||
var node1 = new pixed.TextDataNode(null, null, null, datastr, 0, datastr.length);
|
||||
var node2 = new pixed.Mapper(fmt);
|
||||
node1.addRight(node2);
|
||||
var node3 = new pixed.Palettizer(null, fmt);
|
||||
node2.addRight(node3);
|
||||
node1.refreshRight();
|
||||
|
||||
assert.deepEqual(node5.palette, [0xff000000,
|
||||
0xff000060,
|
||||
0xff006020,
|
||||
0xff404000,
|
||||
|
@ -29,7 +43,7 @@ describe('Pixel editor', function() {
|
|||
0xff40e0e0,
|
||||
0xffc0e000,
|
||||
]);
|
||||
assert.deepEqual(pixed.allimages, [[0,0,0,0,14,15,14,15,14,0,0,0,0,0,0,0,14,14,14,14,15,14,14,14,14,0,0,0,0,14,14,13,14,15,14,15,14,13,14,14,0,0,0,14,14,14,13,13,13,13,13,14,14,14,0,0,0,14,14,14,14,13,13,14,14,14,14,14,0,0,0,0,14,14,14,14,13,14,14,14,14,0,0,0,0,0,14,14,14,14,13,14,14,14,14,0,0,0,0,0,0,0,14,13,13,13,14,0,0,0,0,13,13,13,13,13,14,14,14,14,14,13,13,13,13,0,0,13,14,14,14,14,14,14,14,14,14,14,0,0,0,14,14,0,14,14,14,14,14,0,14,14,0,0,0,14,14,0,14,14,14,14,14,0,14,14,0,0,0,14,14,0,13,13,13,13,13,0,13,14,0,0,0,13,0,0,14,14,0,14,14,0,0,13,0,0,0,0,0,0,14,13,0,14,14,0,0,0,0,0,0,0,0,13,13,13,0,13,13,13,0,0,1,8]]);
|
||||
assert.deepEqual(node2.images, [[0,0,0,0,14,15,14,15,14,0,0,0,0,0,0,0,14,14,14,14,15,14,14,14,14,0,0,0,0,14,14,13,14,15,14,15,14,13,14,14,0,0,0,14,14,14,13,13,13,13,13,14,14,14,0,0,0,14,14,14,14,13,13,14,14,14,14,14,0,0,0,0,14,14,14,14,13,14,14,14,14,0,0,0,0,0,14,14,14,14,13,14,14,14,14,0,0,0,0,0,0,0,14,13,13,13,14,0,0,0,0,13,13,13,13,13,14,14,14,14,14,13,13,13,13,0,0,13,14,14,14,14,14,14,14,14,14,14,0,0,0,14,14,0,14,14,14,14,14,0,14,14,0,0,0,14,14,0,14,14,14,14,14,0,14,14,0,0,0,14,14,0,13,13,13,13,13,0,13,14,0,0,0,13,0,0,14,14,0,14,14,0,0,13,0,0,0,0,0,0,14,13,0,14,14,0,0,0,0,0,0,0,0,13,13,13,0,13,13,13,0,0,1,8]]);
|
||||
assert.equal(" 0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1F, 0x37, 0xE0, 0xA4, 0xFD, 0xFF, 0x38, 0x70, 0x7F, 0xF8, ",
|
||||
pixed.replaceHexWords(paldatastr, pixed.parseHexWords(paldatastr)));
|
||||
});
|
||||
|
|
2
tss
2
tss
|
@ -1 +1 @@
|
|||
Subproject commit 5b5ee67fc06956bc7dce51726e98812d2d897eaa
|
||||
Subproject commit 61a1691a1de05dca3b694bf603db49ffbaf572cf
|
Loading…
Reference in New Issue