1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-08 08:33:32 +00:00

working on pixel, palette editors

This commit is contained in:
Steven Hugg 2019-03-21 12:13:27 -04:00
parent 21bc4fd1e5
commit 318fa399a7
23 changed files with 374 additions and 160 deletions

View File

@ -460,6 +460,9 @@ div.asset_file {
div.asset_file_header {
font-weight: bold;
color: white;
background-color: #333;
border-radius: 8px;
padding-left: 1em;
}
div.asset_grid {
line-height:0;
@ -468,16 +471,28 @@ div.asset_grid {
div.asset_grid span {
white-space: nowrap;
}
div.asset_grid canvas {
.asset_cell {
padding: 0px;
border: 1px solid black;
}
div.asset_grid canvas:hover {
.asset_cell:hover {
border: 1px solid white;
}
.asset_editing {
border: 1px dotted white !important;
box-shadow: 0px 0px 1em rgba(255,255,255,1);
}
td.asset_editable {
padding:0.3em;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
}
div.asset_dual {
display: flex;
align-items: center;
display: flex;
align-items: center;
}
div.asset_dual table {
border-spacing: 10px;
border-collapse: separate;
}
div.asset_editor {
border-radius:16px;

View File

@ -97,6 +97,8 @@ TODO:
- better undo/diff for mistakes?
- ide bug/feature visualizer for sponsors
- optimization flags for sdcc (oldralloc)
- 'src is undefined' when committing old image editor
- editor: select palette for chr, select charmap for map (dependencies?)
WEB WORKER FORMAT

View File

@ -6,7 +6,7 @@
// link the pattern table into CHR ROM
//#link "chr_generic.s"
const char PALETTE[16] = {
const char PALETTE[16] = { /*{pal:"nes",layout:"nes"}*/
0x03,
0x11,0x30,0x27, 0,
0x1c,0x20,0x2c, 0,

View File

@ -726,7 +726,7 @@ void play_scene() {
rescue_scene();
}
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // ladders and pickups

View File

@ -2,9 +2,12 @@
.export _climbr_title_rle
_climbr_title_pal:
.byte $0f,$11,$25,$35,$0f,$01,$21,$30
.byte $0f,$06,$1c,$3c,$0f,$11,$28,$38
;;{pal:"nes",layout:"nes"};;
.byte $0F,$11,$25,$35,$0F,$01,$21,$30
.byte $0F,$06,$1C,$3C,$0F,$11,$28,$38
;;
_climbr_title_rle:
;;{w:32,h:30,bpp:8,comp:"rletag",map:"nesnt"};;
.byte $01,$00,$01,$10,$80,$01,$02,$00
.byte $80,$00,$80,$00,$01,$1f,$80,$80
.byte $00,$01,$07,$41,$4e,$00,$38,$42
@ -77,3 +80,4 @@ _climbr_title_rle:
.byte $02,$75,$00,$01,$02,$55,$01,$04
.byte $00,$01,$02,$05,$01,$03,$05,$01
.byte $00
;;

View File

@ -58,7 +58,7 @@ const unsigned char* const playerRunSeq[16] = {
playerRRun1, playerRRun2,
};
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // ladders and pickups

View File

@ -102,7 +102,7 @@ void scroll_demo() {
}
}
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // background 0

View File

@ -58,7 +58,7 @@ const unsigned char* const playerRunSeq[16] = {
playerRRun1, playerRRun2,
};
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // ladders and pickups

View File

@ -58,18 +58,18 @@ const unsigned char* const playerRunSeq[16] = {
playerRRun1, playerRRun2,
};
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // ladders and pickups
0x1c,0x20,0x2c, 0, // floor blocks
0x00,0x10,0x20, 0,
0x06,0x16,0x26, 0,
0x25,0x30,0x27,0x00, // ladders and pickups
0x1C,0x20,0x2C,0x00, // floor blocks
0x00,0x10,0x20,0x00,
0x06,0x16,0x26,0x00,
0x16,0x35,0x24, 0, // enemy sprites
0x00,0x37,0x25, 0, // rescue person
0x0d,0x2d,0x3a, 0,
0x0d,0x27,0x2a // player sprites
0x16,0x35,0x24,0x00, // enemy sprites
0x00,0x37,0x25,0x00, // rescue person
0x0D,0x2D,0x1A,0x00,
0x0D,0x27,0x2A // player sprites
};
// setup PPU and tables

View File

@ -58,7 +58,7 @@ const unsigned char* const playerRunSeq[16] = {
playerRRun1, playerRRun2,
};
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // ladders and pickups

View File

@ -25,7 +25,7 @@
//#define DEBUG_FRAMERATE
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x0f,
0x11,0x24,0x3c, 0,

View File

@ -251,7 +251,7 @@ AE(1,1,1,1),AE(1,1,1,1),AE(1,1,1,1),AE(1,1,1,1), AE(1,1,1,1),AE(1,1,1,1),AE(1,1,
};
// this is palette data
const unsigned char Palette_Table[16]={
const unsigned char Palette_Table[16]={ /*{pal:"nes",layout:"nes"}*/
0x02,
0x31,0x31,0x31,0x00,
0x34,0x34,0x34,0x00,

View File

@ -12,7 +12,7 @@
//#link "chr_generic.s"
const char PALETTE[32] = {
const char PALETTE[32] = { /*{pal:"nes",layout:"nes"}*/
0x03, // background color
0x11,0x30,0x27, 0, // ladders and pickups

View File

@ -664,6 +664,7 @@ DigitsBitmap ;;{w:8,h:5,count:10,brev:1};;
.byte $EE,$22,$22,$22,$22
.byte $EE,$AA,$EE,$AA,$EE
.byte $EE,$AA,$EE,$22,$EE
;;end
; Playfield bitmasks for all 40 brick columns
PFMaskTable

View File

@ -167,6 +167,7 @@ NUMBERS ;;{w:8,h:6,count:10,brev:1};;
.byte $EE,$22,$22,$22,$22,$00
.byte $EE,$AA,$EE,$AA,$EE,$00
.byte $EE,$AA,$EE,$22,$EE,$00
;;end
; Epilogue
org $fffc

View File

@ -151,6 +151,7 @@ DigitsBitmap ;;{w:8,h:5,count:10,brev:1};;
.byte $EE,$22,$22,$22,$22
.byte $EE,$AA,$EE,$AA,$EE
.byte $EE,$AA,$EE,$22,$EE
;;end
; Epilogue
org $fffc

View File

@ -114,6 +114,7 @@ NUMBERS ;;{w:8,h:6,count:10,brev:1};;
.byte $EE,$22,$22,$22,$22,$00
.byte $EE,$AA,$EE,$AA,$EE,$00
.byte $EE,$AA,$EE,$22,$EE,$00
;; end
; Epilogue
org $fffc

View File

@ -1,7 +1,9 @@
"use strict";
import { hex } from "../util";
import { CodeProject } from "../project";
import { hex, rgb2bgr, rle_unpack } from "../util";
import { ProjectWindows } from "../windows";
export type UintArray = number[] | Uint8Array | Uint16Array | Uint32Array; //{[i:number]:number};
export type PixelEditorImageFormat = {
w:number
@ -24,6 +26,8 @@ export type PixelEditorPaletteFormat = {
layout?:string
};
export type PixelEditorPaletteLayout = [string, number, number][];
type PixelEditorMessage = {
fmt : PixelEditorImageFormat
palfmt : PixelEditorPaletteFormat
@ -87,20 +91,13 @@ export function PixelEditor(parentDiv:HTMLElement,
updateImage();
function revrgb(x) {
var y = 0;
y |= ((x >> 0) & 0xff) << 16;
y |= ((x >> 8) & 0xff) << 8;
y |= ((x >> 16) & 0xff) << 0;
return y;
}
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(revrgb(rgb), 6);
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));
@ -278,7 +275,7 @@ export function parseHexWords(s:string) : number[] {
return arr;
}
export function replaceHexWords(s:string, words:number[]) : string {
export function replaceHexWords(s:string, words:UintArray) : string {
var result = "";
var m;
var li = 0;
@ -325,7 +322,7 @@ function remapBits(x:number, arr:number[]) : number {
return y;
}
function convertWordsToImages(words:number[] | Uint8Array, fmt:PixelEditorImageFormat) : Uint8Array[] {
function convertWordsToImages(words:UintArray, fmt:PixelEditorImageFormat) : Uint8Array[] {
var width = fmt.w;
var height = fmt.h;
var count = fmt.count || 1;
@ -402,7 +399,7 @@ function convertImagesToWords(images:Uint8Array[], fmt:PixelEditorImageFormat) :
}
// TODO
function convertPaletteBytes(arr:number[]|Uint8Array,r0,r1,g0,g1,b0,b1) : number[] {
function convertPaletteBytes(arr:UintArray,r0,r1,g0,g1,b0,b1) : number[] {
var result = [];
for (var i=0; i<arr.length; i++) {
var d = arr[i];
@ -428,7 +425,24 @@ export var currentPaletteStr : string;
export var currentPaletteFmt : PixelEditorPaletteFormat;
export var allthumbs;
function convertPaletteFormat(palbytes: number[]|Uint8Array, palfmt: PixelEditorPaletteFormat) : number[] {
export function getPaletteLength(palfmt: PixelEditorPaletteFormat) : number {
var pal = palfmt.pal;
if (typeof pal === 'number') {
var rr = Math.floor(Math.abs(pal/100) % 10);
var gg = Math.floor(Math.abs(pal/10) % 10);
var bb = Math.floor(Math.abs(pal) % 10);
return 1<<(rr+gg+bb);
} else {
var paltable = PREDEF_PALETTES[pal];
if (paltable) {
return paltable.length;
} else {
throw new Error("No palette named " + pal);
}
}
}
function convertPaletteFormat(palbytes:UintArray, palfmt: PixelEditorPaletteFormat) : number[] {
var pal = palfmt.pal;
var newpalette;
if (typeof pal === 'number') {
@ -436,14 +450,14 @@ function convertPaletteFormat(palbytes: number[]|Uint8Array, palfmt: PixelEditor
var gg = Math.floor(Math.abs(pal/10) % 10);
var bb = Math.floor(Math.abs(pal) % 10);
// TODO: n
if (currentPaletteFmt.pal >= 0)
if (pal >= 0)
newpalette = convertPaletteBytes(palbytes, 0, rr, rr, gg, rr+gg, bb);
else
newpalette = convertPaletteBytes(palbytes, rr+gg, bb, rr, gg, 0, rr);
} else {
var paltable = PREDEF_PALETTES[pal];
if (paltable) {
newpalette = new Uint32Array(palbytes).map((i) => { return paltable[i & (paltable.length-1)]; });
newpalette = new Uint32Array(palbytes).map((i) => { return paltable[i & (paltable.length-1)] | 0xff000000; });
} else {
throw new Error("No palette named " + pal);
}
@ -584,93 +598,91 @@ function pixelEditorKeypress(e) {
// TODO: reversed?
var PREDEF_PALETTES = {
'nes':[
0xFF7C7C7C ,0xFF0000FC ,0xFF0000BC ,0xFF4428BC ,0xFF940084 ,0xFFA80020 ,0xFFA81000 ,0xFF881400
,0xFF503000 ,0xFF007800 ,0xFF006800 ,0xFF005800 ,0xFF004058 ,0xFF000000 ,0xFF000000 ,0xFF000000
,0xFFBCBCBC ,0xFF0078F8 ,0xFF0058F8 ,0xFF6844FC ,0xFFD800CC ,0xFFE40058 ,0xFFF83800 ,0xFFE45C10
,0xFFAC7C00 ,0xFF00B800 ,0xFF00A800 ,0xFF00A844 ,0xFF008888 ,0xFF000000 ,0xFF000000 ,0xFF000000
,0xFFF8F8F8 ,0xFF3CBCFC ,0xFF6888FC ,0xFF9878F8 ,0xFFF878F8 ,0xFFF85898 ,0xFFF87858 ,0xFFFCA044
,0xFFF8B800 ,0xFFB8F818 ,0xFF58D854 ,0xFF58F898 ,0xFF00E8D8 ,0xFF787878 ,0xFF000000 ,0xFF000000
,0xFFFCFCFC ,0xFFA4E4FC ,0xFFB8B8F8 ,0xFFD8B8F8 ,0xFFF8B8F8 ,0xFFF8A4C0 ,0xFFF0D0B0 ,0xFFFCE0A8
,0xFFF8D878 ,0xFFD8F878 ,0xFFB8F8B8 ,0xFFB8F8D8 ,0xFF00FCFC ,0xFFF8D8F8 ,0xFF000000 ,0xFF000000
0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000,
0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000
]
};
var PREDEF_LAYOUTS = {
'nes_full':[
['Screen Color', 1],
['Background 1', 3], [null, 1],
['Background 2', 3], [null, 1],
['Background 3', 3], [null, 1],
['Background 4', 3], [null, 1],
['Sprite 1', 3], [null, 1],
['Sprite 2', 3], [null, 1],
['Sprite 3', 3], [null, 1],
['Sprite 4', 3]
var PREDEF_LAYOUTS : {[id:string]:PixelEditorPaletteLayout} = {
'nes':[
['Screen Color', 0x00, 1],
['Background 1', 0x01, 3],
['Background 2', 0x05, 3],
['Background 3', 0x09, 3],
['Background 4', 0x0d, 3],
['Sprite 1', 0x11, 3],
['Sprite 2', 0x15, 3],
['Sprite 3', 0x19, 3],
['Sprite 4', 0x1d, 3]
],
};
/////
export abstract class PixelNode {
left : PixelNode; // toward text editor
right : PixelNode; // toward pixel editor
// TODO: in/out(...) for each type?
input?
output?
export abstract class Node {
left : Node; // toward text editor
right : Node; // toward pixel editor
words? : UintArray; // file data
images? : Uint8Array[]; // array of indexed image data
rgbimgs? : Uint32Array[]; // array of rgba imgages
abstract updateLeft(); // update coming from right
abstract updateRight(); // update coming from left
refreshLeft() {
var p : PixelNode = this;
var p : Node = this;
while (p) {
p.updateLeft();
p = p.left;
}
}
refreshRight() {
var p : PixelNode = this;
var p : Node = this;
while (p) {
p.updateRight();
p = p.right;
}
}
addRight(node : PixelNode) {
addRight(node : Node) {
this.right = node;
node.left = this;
}
}
abstract class PixelCodeProjectDataNode extends PixelNode {
abstract class CodeProjectDataNode extends Node {
project : ProjectWindows;
fileid : string;
project : CodeProject;
words : UintArray;
}
export class PixelFileDataNode extends PixelCodeProjectDataNode {
output : Uint8Array;
export class FileDataNode extends CodeProjectDataNode {
constructor(fileid, data) {
constructor(project:ProjectWindows, fileid:string, data:Uint8Array) {
super();
this.project = project;
this.fileid = fileid;
this.output = data;
this.words = data;
}
updateLeft() {
if (this.project) {
this.project.updateFile(this.fileid, this.output);
this.project.updateFile(this.fileid, this.words as Uint8Array);
}
}
updateRight() {
}
}
export class PixelTextDataNode extends PixelCodeProjectDataNode {
export class TextDataNode extends CodeProjectDataNode {
text : string;
start : number;
end : number;
output : Uint8Array;
constructor(fileid, text, start, end) {
constructor(project:ProjectWindows, fileid:string, text:string, start:number, end:number) {
super();
this.project = project;
this.fileid = fileid;
this.text = text;
this.start = start;
@ -678,50 +690,84 @@ export class PixelTextDataNode extends PixelCodeProjectDataNode {
}
updateLeft() {
// TODO: reload editors?
var datastr = this.text.substring(this.start, this.end);
datastr = replaceHexWords(datastr, this.words);
this.text = this.text.substring(0, this.start) + datastr + this.text.substring(this.end);
if (this.project) {
this.project.updateFile(this.fileid, this.text);
//this.project.replaceTextRange(this.fileid, this.start, this.end, datastr);
}
}
updateRight() {
var datastr = this.text.substring(this.start, this.end);
datastr = convertToHexStatements(datastr); // TODO?
var words = parseHexWords(datastr);
this.output = new Uint8Array(words); // TODO: 16/32?
this.words = words; //new Uint8Array(words); // TODO: 16/32?
}
}
export class PixelMapper extends PixelNode {
export class Compressor extends Node {
words : UintArray;
updateLeft() {
// TODO
}
updateRight() {
this.words = rle_unpack(new Uint8Array(this.left.words));
}
}
export class Mapper extends Node {
fmt : PixelEditorImageFormat;
input : number[] | Uint8Array;
output : Uint8Array[];
words : UintArray;
images : Uint8Array[];
updateLeft() {
//TODO
this.input = convertImagesToWords(this.output, this.fmt);
this.images = this.right.images;
this.words = convertImagesToWords(this.images, this.fmt);
}
updateRight() {
// convert each word array to images
this.input = this.left.output;
this.output = convertWordsToImages(this.input, this.fmt);
this.words = this.left.words;
this.images = convertWordsToImages(this.words, this.fmt);
}
}
export class PixelPalettizer extends PixelNode {
class RGBAPalette {
palcols;
constructor(palcols : Uint32Array) {
this.palcols = palcols;
}
indexOf(rgba : number) : number {
return this.palcols.find(rgba);
}
}
input : Uint8Array[];
output : Uint32Array[];
export class Palettizer extends Node {
images : Uint8Array[];
rgbimgs : Uint32Array[];
palette : Uint32Array;
updateLeft() {
//TODO
this.rgbimgs = this.right.rgbimgs;
var pal = new RGBAPalette(this.palette);
this.images = this.rgbimgs.map( (im:Uint32Array) => {
var out = new Uint8Array(im.length);
for (var i=0; i<im.length; i++) {
out[i] = pal.indexOf(im[i]);
}
return out;
});
}
updateRight() {
this.images = this.left.images;
var mask = this.palette.length - 1; // must be power of 2
this.input = this.left.output;
// for each image, map bytes to RGB colors
this.output = this.input.map( (im:Uint8Array) => {
this.rgbimgs = this.images.map( (im:Uint8Array) => {
var out = new Uint32Array(im.length);
for (var i=0; i<im.length; i++) {
out[i] = this.palette[im[i] & mask];
@ -731,7 +777,7 @@ export class PixelPalettizer extends PixelNode {
}
}
function dedupPalette(cols : number[]) : Uint32Array {
function dedupPalette(cols : UintArray) : Uint32Array {
var dup = new Map();
var res = new Uint32Array(cols.length);
var ndups = 0;
@ -746,27 +792,35 @@ function dedupPalette(cols : number[]) : Uint32Array {
return res;
}
export class PixelPaletteFormatToRGB extends PixelNode {
export class PaletteFormatToRGB extends Node {
input : Uint8Array;
output : Uint32Array[];
words : UintArray;
rgbimgs : Uint32Array[];
palette : Uint32Array;
palfmt : PixelEditorPaletteFormat;
layout : PixelEditorPaletteLayout;
updateLeft() {
//TODO
}
updateRight() {
this.input = this.left.output;
this.palette = dedupPalette(convertPaletteFormat(this.input, this.palfmt));
this.output = [];
this.words = this.left.words;
this.palette = dedupPalette(convertPaletteFormat(this.words, this.palfmt));
this.layout = PREDEF_LAYOUTS[this.palfmt.layout];
this.rgbimgs = [];
this.palette.forEach( (rgba:number) => {
this.output.push(new Uint32Array([rgba]));
this.rgbimgs.push(new Uint32Array([rgba]));
});
}
getAllColors() {
var arr = [];
for (var i=0; i<getPaletteLength(this.palfmt); i++)
arr.push(i);
return convertPaletteFormat(arr, this.palfmt);
}
}
export class PixelViewer { // TODO: make PixelNode
export class Viewer { // TODO: make Node
width : number;
height : number;
@ -779,7 +833,7 @@ export class PixelViewer { // TODO: make PixelNode
this.pixdata = this.ctx.createImageData(this.width, this.height);
}
createWith(pv : PixelViewer) {
createWith(pv : Viewer) {
this.width = pv.width;
this.height = pv.height;
this.pixdata = pv.pixdata;
@ -805,3 +859,42 @@ export class PixelViewer { // TODO: make PixelNode
}
}
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);
var cscale = Math.max(2, Math.ceil(16/this.width)); // TODO
var imgsperline = this.width <= 8 ? 16 : 8; // TODO
var span = null;
this.rgbimgs.forEach((imdata, i) => {
var viewer = new Viewer();
viewer.width = this.width;
viewer.height = this.height;
viewer.recreate();
viewer.canvas.style.width = (viewer.width*cscale)+'px'; // TODO
viewer.updateImage(imdata);
$(viewer.canvas).addClass('asset_cell');
$(viewer.canvas).click((e) => {
onclick(i, viewer);
});
if (!span) {
span = $('<span/>');
agrid.append(span);
}
span.append(viewer.canvas);
var brk = (i % imgsperline) == imgsperline-1;
if (brk) {
agrid.append($("<br/>"));
span = null;
}
});
}
}
// TODO: scroll editors into view

View File

@ -144,6 +144,7 @@ const _JSNESPlatform = function(mainElement) {
else if (flags & KeyFlags.KeyUp)
nes.buttonUp(o.index+1, o.mask); // controller, button
});
//var s = ''; nes.ppu.palTable.curTable.forEach((rgb) => { s += "0x"+hex(rgb,6)+", "; }); console.log(s);
}
advance(novideo : boolean) {

View File

@ -423,3 +423,24 @@ export function clamp(minv:number, maxv:number, v:number) {
export function safeident(s : string) : string {
return s.replace(/\W+/g, "_");
}
export function rle_unpack(src : Uint8Array) : Uint8Array {
var i = 0;
var tag = src[i++];
var dest = [];
var data = tag;
while (i < src.length) {
var ch = src[i++];
if (ch == tag) {
var count = src[i++];
for (var j=0; j<count; j++)
dest.push(data);
if (count == 0)
break;
} else {
data = ch;
dest.push(data);
}
}
return new Uint8Array(dest);
}

View File

@ -2,13 +2,12 @@
import $ = require("jquery");
//import CodeMirror = require("codemirror");
import { CodeProject } from "./project";
import { SourceFile, WorkerError, Segment, FileData } from "./workertypes";
import { Platform, EmuState, ProfilerOutput, lookupSymbol } from "./baseplatform";
import { hex, lpad, rpad, safeident } from "./util";
import { hex, lpad, rpad, safeident, rgb2bgr } from "./util";
import { CodeAnalyzer } from "./analysis";
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
import { PixelEditorImageFormat, PixelViewer, PixelMapper, PixelPalettizer, PixelFileDataNode, PixelTextDataNode, PixelPaletteFormatToRGB, parseHexWords } from "./pixed/pixeleditor";
import * as pixed from "./pixed/pixeleditor";
export interface ProjectView {
createDiv(parent:HTMLElement, text:string) : HTMLElement;
@ -343,6 +342,11 @@ 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)
@ -955,6 +959,7 @@ export class ProfileView implements ProjectView {
export class AssetEditorView implements ProjectView {
maindiv : JQuery;
cureditordiv : JQuery;
cureditelem : JQuery;
createDiv(parent : HTMLElement) {
this.maindiv = $('<div class="vertical-scroll"/>');
@ -962,7 +967,7 @@ export class AssetEditorView implements ProjectView {
return this.maindiv[0];
}
setCurrentEditor(div : JQuery) {
setCurrentEditor(div : JQuery, editing : JQuery) {
if (this.cureditordiv != div) {
if (this.cureditordiv) {
this.cureditordiv.hide(250);
@ -973,6 +978,14 @@ export class AssetEditorView implements ProjectView {
this.cureditordiv.show(250);
}
}
if (this.cureditelem) {
this.cureditelem.removeClass('asset_editing');
this.cureditelem = null;
}
if (editing) {
this.cureditelem = editing;
this.cureditelem.addClass('asset_editing');
}
}
scanFileTextForAssets(id : string, data : string) {
@ -990,7 +1003,7 @@ export class AssetEditorView implements ProjectView {
} else {
end = data.indexOf(';', start); // C
}
console.log(id, start, end, m[1]);
//console.log(id, start, end, m[1]);
if (end > start) {
try {
var jsontxt = m[1].replace(/([A-Za-z]+):/g, '"$1":'); // fix lenient JSON
@ -1005,55 +1018,82 @@ export class AssetEditorView implements ProjectView {
return result;
}
addPixelEditorViews(filediv : JQuery, images : Uint32Array[], fmt : PixelEditorImageFormat) {
addPixelEditorViews(filediv:JQuery, images:Uint32Array[], fmt:pixed.PixelEditorImageFormat) {
var adual = $('<div class="asset_dual"/>'); // contains grid and editor
var agrid = $('<div class="asset_grid"/>'); // grid (or 1) of preview images
var aeditor = $('<div class="asset_editor"/>').hide(); // contains editor, when selected
adual.append(agrid, aeditor).appendTo(filediv);
var chooser = new pixed.ImageChooser();
chooser.rgbimgs = images;
chooser.width = fmt.w || 1;
chooser.height = fmt.h || 1;
chooser.recreate(adual, (index, viewer) => {
console.log("???",index);
var escale = Math.ceil(192/fmt.w);
var editview = new pixed.Viewer();
editview.createWith(viewer);
editview.updateImage(null);
editview.canvas.style.width = (viewer.width*escale)+'px'; // TODO
aeditor.empty().append(editview.canvas);
this.setCurrentEditor(aeditor, $(viewer.canvas));
});
adual.append(aeditor).appendTo(filediv);
}
addPaletteEditorViews(filediv:JQuery, words, palette, layout, allcolors, callback) {
var adual = $('<div class="asset_dual"/>').appendTo(filediv);
var aeditor = $('<div class="asset_editor"/>').hide(); // contains editor, when selected
// TODO: they need to update when refreshed from right
var imgsperline = fmt.w <= 8 ? 16 : 8; // TODO
// TODO?
var cscale = Math.ceil(16/fmt.w);
var escale = Math.ceil(192/fmt.w);
var i = 0;
var span = null;
images.forEach( (imdata) => {
var viewer = new PixelViewer();
viewer.width = fmt.w | 0;
viewer.height = fmt.h | 0;
viewer.recreate();
viewer.canvas.style.width = (viewer.width*cscale)+'px'; // TODO
viewer.updateImage(imdata); // TODO
$(viewer.canvas).click((e) => {
var editview = new PixelViewer();
editview.createWith(viewer);
editview.updateImage(null);
editview.canvas.style.width = (viewer.width*escale)+'px'; // TODO
aeditor.empty().append(editview.canvas);
this.setCurrentEditor(aeditor);
});
if (!span) {
span = $('<span/>');
agrid.append(span);
var allrgbimgs = [];
allcolors.forEach((rgba) => { allrgbimgs.push(new Uint32Array([rgba])); }); // array of array of 1 rgb color (for picker)
var atable = $('<table/>').appendTo(adual);
aeditor.appendTo(adual);
// make default layout if not exists
if (!layout) {
var imgsperline = palette.length > 32 ? 8 : 4;
var len = allcolors.length;
layout = [];
for (var i=0; i<len; i+=imgsperline) {
layout.push(["", i, Math.min(len-i,imgsperline)]);
}
span.append(viewer.canvas);
if (++i == imgsperline) {
agrid.append($("<br/>"));
span = null;
i = 0;
}
// iterate over each row of the layout
layout.forEach( ([name, start, len]) => {
if (start < palette.length) { // skip row if out of range
var arow = $('<tr/>').appendTo(atable);
$('<td/>').text(name).appendTo(arow);
var inds = [];
for (var k=start; k<start+len; k++)
inds.push(k);
inds.forEach( (i) => {
var val = words[i];
var rgb = palette[i];
var hexcol = '#'+hex(rgb2bgr(rgb),6);
var textcol = (rgb & 0x008000) ? 'black' : 'white';
var cell = $('<td/>').addClass('asset_cell asset_editable').text(hex(val,2)).css('background-color',hexcol).css('color',textcol).appendTo(arow);
cell.click((e) => {
var chooser = new pixed.ImageChooser();
chooser.rgbimgs = allrgbimgs;
chooser.width = 1;
chooser.height = 1;
chooser.recreate(aeditor, (index, newvalue) => {
callback(i, index);
});
this.setCurrentEditor(aeditor, cell);
});
});
}
});
}
addPixelEditor(filediv:JQuery, firstnode:PixelFileDataNode|PixelTextDataNode, fmt:PixelEditorImageFormat) {
addPixelEditor(filediv:JQuery, firstnode:pixed.Node, fmt:pixed.PixelEditorImageFormat) {
// data -> pixels
var mapper = new PixelMapper();
var mapper = new pixed.Mapper();
fmt.xform = 'scale(2)';
mapper.fmt = fmt;
// TODO: rotate node?
firstnode.addRight(mapper);
// pixels -> RGBA
var palizer = new PixelPalettizer();
var palizer = new pixed.Palettizer();
if (fmt.bpp*(fmt.np|1) == 1)
palizer.palette = new Uint32Array([0xff000000, 0xffffffff]);
else
@ -1061,48 +1101,64 @@ export class AssetEditorView implements ProjectView {
mapper.addRight(palizer);
// refresh
firstnode.refreshRight();
// add view objects (TODO)
this.addPixelEditorViews(filediv, palizer.output, fmt);
// add view objects
this.addPixelEditorViews(filediv, palizer.rgbimgs, fmt);
}
addPaletteEditor(filediv:JQuery, firstnode:PixelFileDataNode|PixelTextDataNode, palfmt?) {
addPaletteEditor(filediv:JQuery, firstnode:pixed.Node, palfmt?) {
// palette -> RGBA
var pal2rgb = new PixelPaletteFormatToRGB();
var pal2rgb = new pixed.PaletteFormatToRGB();
pal2rgb.palfmt = palfmt;
firstnode.addRight(pal2rgb);
firstnode.refreshRight();
// add view objects (TODO)
var imgfmt = {w:1,h:1};
this.addPixelEditorViews(filediv, pal2rgb.output, imgfmt);
// add view objects
// TODO: show which one is selected?
this.addPaletteEditorViews(filediv, firstnode.words,
pal2rgb.palette, pal2rgb.layout, pal2rgb.getAllColors(),
(index, newvalue) => {
console.log('set entry', index, '=', newvalue);
firstnode.words[index] = newvalue;
//firstnode.refreshRight();
firstnode.refreshLeft();
});
}
refreshAssetsInFile(fileid : string, data : FileData) {
refreshAssetsInFile(fileid : string, data : FileData) : number {
let nassets = 0;
let filediv = $('#'+this.getFileDivId(fileid)).empty();
// TODO
// TODO: check if open
if (fileid.endsWith('.chr') && data instanceof Uint8Array) {
// is this a NES CHR?
let node = new PixelFileDataNode(fileid, data);
let node = new pixed.FileDataNode(projectWindows, fileid, data);
const neschrfmt = {w:8,h:8,bpp:1,count:(data.length>>4),brev:true,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]}; // TODO
this.addPixelEditor(filediv, node, neschrfmt);
} else if (typeof data === 'string') {
let textfrags = this.scanFileTextForAssets(fileid, data);
for (let frag of textfrags) {
let node : pixed.Node = new pixed.TextDataNode(projectWindows, fileid, data, frag.start, frag.end);
if (frag.fmt.comp == 'rletag') {
node.addRight(new pixed.Compressor());
node.refreshRight(); // TODO
node = node.right;
console.log(node);
}
// is this a bitmap?
if (frag.fmt && frag.fmt.w > 0 && frag.fmt.h > 0) {
let node = new PixelTextDataNode(fileid, data, frag.start, frag.end);
this.addPixelEditor(filediv, node, frag.fmt);
nassets++;
}
// is this a palette?
else if (frag.fmt && frag.fmt.pal) {
let node = new PixelTextDataNode(fileid, data, frag.start, frag.end);
this.addPaletteEditor(filediv, node, frag.fmt);
nassets++;
}
else {
// TODO: other kinds of resources?
}
}
}
return nassets;
}
getFileDivId(id : string) {
@ -1117,8 +1173,13 @@ export class AssetEditorView implements ProjectView {
var header = $('<div class="asset_file_header"/>').text(id);
var body = $('<div/>').attr('id',divid);
var filediv = $('<div class="asset_file"/>').append(header, body).appendTo(this.maindiv);
this.refreshAssetsInFile(id, data);
// TODO: what if crash while parsing?
try {
var nassets = this.refreshAssetsInFile(id, data);
if (nassets == 0) filediv.hide();
} catch (e) {
console.log(e);
filediv.text(e+""); // TODO: error msg?
}
});
}

View File

@ -2,14 +2,14 @@
import $ = require("jquery");
import { CodeProject } from "./project";
import { WorkerError } from "./workertypes";
import { WorkerError, FileData } from "./workertypes";
import { ProjectView } from "./views";
type WindowCreateFunction = (id:string) => ProjectView;
export class ProjectWindows {
containerdiv:HTMLElement;
project:CodeProject;
containerdiv : HTMLElement;
project : CodeProject;
id2window : {[id:string]:ProjectView} = {};
id2createfn : {[id:string]:WindowCreateFunction} = {};
id2div : {[id:string]:HTMLElement} = {};
@ -103,4 +103,17 @@ export class ProjectWindows {
this.createOrShow(this.activeid);
}
}
updateFile(fileid : string, data : FileData) {
var wnd = this.id2window[fileid];
if (wnd && wnd.setText && typeof data === 'string') {
wnd.setText(data);
} else {
this.project.updateFile(fileid, data);
if (wnd) {
wnd.refresh(false);
}
}
}
};

2
tss

@ -1 +1 @@
Subproject commit 5b5ee67fc06956bc7dce51726e98812d2d897eaa
Subproject commit 61a1691a1de05dca3b694bf603db49ffbaf572cf