working on pixel editor

This commit is contained in:
Steven Hugg 2017-05-04 11:54:56 -04:00
parent 15e6efd01d
commit 112cbeda1e
7 changed files with 470 additions and 43 deletions

View File

@ -48,13 +48,13 @@
left:0;
right:0;
background-color: #666;
overflow: hidden;;
overflow: hidden;
}
div.workspace {
background-color:#999;
#workspace {
background-color:#333;
width:50%;
}
div.editor {
width:50%;
line-height:1.25;
font-size:12pt;
}
@ -182,6 +182,8 @@ a.dropdown-toggle {
border-radius:6px 0 6px 6px;
}
.emubevel {
width:100%;
height:100%;
padding:4%;
background:#333;
}
@ -204,3 +206,13 @@ canvas.pixelated {
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
.palbtn {
width:2em;
height:2em;
border-style:none;
}
.selected {
border-width:2px;
border-color:white;
border-style:dotted;
}

View File

@ -7,6 +7,21 @@ body {
overflow: hidden !important;
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">
</head>
@ -61,6 +76,9 @@ body {
<button id="dbg_memory" type="submit" title="Show Memory" style="display:none"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span></button>
<button id="dbg_profile" type="submit" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button>
</span>
<span class="btn_group view_group" id="tools_bar">
<button id="dbg_bitmap" type="submit" title="Edit Bitmap"><span class="glyphicon glyphicon-camera" aria-hidden="true"></span></button>
</span>
<span id="best_in_firefox" style="display:none;font-size:12px;font-style:italic;float:right;color:#666">Note: Works best in Firefox</span>
</div>
<div id="notebook">
@ -99,39 +117,9 @@ body {
<a target="_new" href="https://www.amazon.com/Making-8-bit-Arcade-Games-C/dp/1545484759">
Making 8-bit Arcade Games in C</a>
</div>
<div id="welcomeModal" class="modal fade">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Welcome to 8bitworkshop!</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>What would you like to start programming?</p>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
<img data-dismiss="modal" class="img-responsive" data-platform="vcs" src="images/atarivcs.jpg" alt="Atari 2600 four-switch wood veneer version, dating from 1980-1982 (photo by Evan Amos)"/>
</div>
<div class="col-md-6">
<img data-dismiss="modal" class="img-responsive" data-platform="vicdual" src="images/arcadegames.jpg" alt="Galaxian arcade system board (photo by Dennis van Zuijlekom, CC BY-SA 2.0)"/>
</div>
</div>
<div class="row">
<div class="col-md-3 col-md-offset-2">
<button type="button" class="btn btn-secondary" data-platform="vcs" data-dismiss="modal">Atari VCS</button>
</div>
<div class="col-md-3 col-md-offset-2">
<button type="button" class="btn btn-secondary" data-platform="vicdual" data-dismiss="modal">Arcade Games</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="pixeditback" class="pixeditback" style="display:none">
<iframe id="pixeditframe" src="pixels.html">
</iframe>
</div>
<script src="jquery/jquery-2.2.3.min.js"></script>

71
pixels.html Normal file
View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>8bitworkshop Pixel Editor</title>
<style type="text/css" media="screen">
body {
overflow: hidden !important;
font-size: 11px;
}
.pixels {
}
</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 id="maineditor" class="emubevel">
</div>
</div>
<script src="jquery/jquery-2.2.3.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 src="src/util.js"></script>
<script src="src/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};
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,";
pixelEditorReceiveMessage({data:{fmt:fmt,bytestr:datastr,palfmt:{pal:332,n:16},palstr:paldatastr}});
}
//
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) {
postToParentWindow({close:true,save:true});
});
</script>
</body>
</html>

View File

@ -63,7 +63,6 @@ struct {
//
void main();
void _sdcc_heap_init(void); // for malloc()
// start routine @ 0x0
// set stack pointer, enable interrupts
@ -79,7 +78,6 @@ __asm
LDIR
__endasm;
_sdcc_heap_init();
main();
}

View File

@ -11,8 +11,6 @@ function noise() {
function __createCanvas(mainElement, width, height) {
// TODO
var fsElement = document.createElement('div');
fsElement.style.position = "relative";
fsElement.style.overflow = "hidden";
fsElement.classList.add("emubevel");
var canvas = document.createElement('canvas');

304
src/pixed/pixeleditor.js Normal file
View File

@ -0,0 +1,304 @@
"use strict";
function PixelEditor(parentDiv, width, height, palette, initialData) {
function createCanvas() {
var c = document.createElement('canvas');
c.width = width;
c.height = height;
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 fitCanvas() {
var w = $(parentDiv).width();
var h = $(parentDiv).height();
pixcanvas.style.height = Math.floor(h)+"px";
// TODO
}
this.resize = fitCanvas;
palette = new Uint32Array(palette);
var pixcanvas = createCanvas();
var ctx = pixcanvas.getContext('2d');
var pixdata = ctx.createImageData(pixcanvas.width, pixcanvas.height);
var pixints = new Uint32Array(pixdata.data.buffer);
for (var i=0; i<pixints.length; i++) {
pixints[i] = initialData ? palette[initialData[i]] : palette[0];
}
updateImage();
fitCanvas();
createPaletteButtons();
function revrgb(x) {
var y = 0;
y |= ((x >> 0) & 0xff) << 16;
y |= ((x >> 8) & 0xff) << 8;
y |= ((x >> 16) & 0xff) << 0;
return y;
}
function createPaletteButtons() {
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);
btn.click(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);
}
setCurrentColor(1);
}
function getPixelByOffset(ofs) {
var oldrgba = pixints[ofs];
for (var i=0; i<palette.length; i++) {
if (oldrgba == palette[i]) return i;
}
return 0;
}
function getPixel(x, y) {
var ofs = x+y*pixcanvas.width;
return getPixelByOffset(ofs);
}
function setPixel(x, y, col) {
var ofs = x+y*pixcanvas.width;
var oldrgba = pixints[ofs];
var rgba = palette[col];
if (oldrgba != rgba) {
pixints[ofs] = rgba;
updateImage();
}
}
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 curpalcol = -1;
setCurrentColor(1);
var dragcol = 1;
var dragging = false;
var pxls = $(pixcanvas);
pxls.mousedown(function(e) {
var pos = getPositionFromEvent(e);
dragcol = getPixel(pos.x, pos.y) == curpalcol ? 0 : curpalcol;
setPixel(pos.x, pos.y, curpalcol);
dragging = true;
})
.mousemove(function(e) {
var pos = getPositionFromEvent(e);
if (dragging) {
setPixel(pos.x, pos.y, dragcol);
}
})
.mouseup(function(e) {
var pos = getPositionFromEvent(e);
setPixel(pos.x, pos.y, dragcol);
dragging = false;
});
this.getImageColors = function() {
var pixcols = new Uint8Array(pixints.length);
for (var i=0; i<pixints.length; i++)
pixcols[i] = getPixelByOffset(i);
return pixcols;
}
}
function parseHexBytes(s) {
var arr = [];
var re = /0x([0-9a-f]+)/gi;
var m;
while (m = re.exec(s)) {
if (m[0].startsWith('0x'))
arr.push(parseInt(m[1],16));
else
arr.push(parseInt(m[1]));
}
return arr;
}
function replaceHexBytes(s, bytes) {
var result = "";
var re = /0x([0-9a-f]+)/gi;
var m;
var li = 0;
var i = 0;
while (m = re.exec(s)) {
result += s.slice(li, re.lastIndex - m[0].length);
li = re.lastIndex;
if (m[0].startsWith('0x'))
result += "0x" + hex(bytes[i++]);
else
result += bytes[i++].toString();
}
result += s.slice(li);
return result;
}
function convertBytesToImages(bytes, fmt) {
var count = fmt.count ? fmt.count : 1;
var width = fmt.w;
var height = fmt.h;
var bytesperline = fmt.sl ? fmt.sl : Math.ceil(fmt.w * fmt.bpp / 8);
//console.log(width,height,bytesperline);
var mask = (1 << fmt.bpp)-1;
var images = [];
var nplanes = fmt.np ? fmt.np : 1;
for (var n=0; n<count; n++) {
var imgdata = [];
for (var y=0; y<height; y++) {
var ofs = n*bytesperline*height + y*bytesperline;
var shift = 0;
for (var x=0; x<width; x++) {
var color = 0;
for (var p=0; p<nplanes; p++) {
var byte = bytes[ofs + p*(fmt.pofs|0)];
color |= ((fmt.brev ? byte>>(8-shift-fmt.bpp) : byte>>shift) & mask) << (p*fmt.bpp);
}
imgdata.push(color);
shift += fmt.bpp;
if (shift >= 8) {
ofs += 1;
shift = 0;
}
}
}
images.push(imgdata);
}
return images;
}
function convertImagesToBytes(images, fmt) {
var count = fmt.count ? fmt.count : 1;
var width = fmt.w;
var height = fmt.h;
var bytesperline = fmt.sl ? fmt.sl : Math.ceil(fmt.w * fmt.bpp / 8);
var mask = (1 << fmt.bpp)-1;
var nplanes = fmt.np ? fmt.np : 1;
var i = 0;
var bytes = new Uint8Array(bytesperline * height * nplanes * count);
for (var n=0; n<count; n++) {
var imgdata = images[n];
for (var y=0; y<height; y++) {
var ofs = n*bytesperline*height + y*bytesperline;
var shift = 0;
for (var x=0; x<width; x++) {
var color = imgdata[i++];
for (var p=0; p<nplanes; p++) {
bytes[ofs + p*(fmt.pofs|0)] |= fmt.brev ? (color << (8-shift-fmt.bpp)) : (color << shift);
/* TODO
var byte = bytes[ofs + p*(fmt.pofs|0)];
color |= ((fmt.brev ? byte>>(8-shift-fmt.bpp) : byte>>shift) & mask) << (p*fmt.bpp);
*/
}
shift += fmt.bpp;
if (shift >= 8) {
ofs += 1;
shift = 0;
}
}
}
}
return bytes;
}
function convertPaletteBytes(arr,r0,r1,g0,g1,b0,b1) {
var result = [];
for (var i=0; i<arr.length; i++) {
var d = arr[i];
var rgb = 0xff000000;
rgb |= ((d >> r0) & ((1<<r1)-1)) << (0+8-r1);
rgb |= ((d >> g0) & ((1<<g1)-1)) << (8+8-g1);
rgb |= ((d >> b0) & ((1<<b1)-1)) << (16+8-b1);
result.push(rgb);
}
return result;
}
var currentPixelEditor;
var parentSource;
var parentOrigin;
var allimages;
var currentFormat;
var currentByteStr;
var currentPaletteStr;
var currentPaletteFmt;
function pixelEditorReceiveMessage(e) {
console.log(e.data);
parentSource = e.source;
parentOrigin = e.origin;
currentFormat = e.data.fmt;
currentByteStr = e.data.bytestr;
currentPaletteFmt = e.data.palfmt;
currentPaletteStr = e.data.palstr;
var bytes = parseHexBytes(e.data.bytestr);
allimages = convertBytesToImages(bytes, e.data.fmt);
var palette = [0xff000000, 0xffffffff]; // TODO
if (currentPaletteStr) {
var palbytes = parseHexBytes(e.data.palstr);
var rr = Math.floor(Math.abs(currentPaletteFmt.pal/100) % 10);
var gg = Math.floor(Math.abs(currentPaletteFmt.pal/10) % 10);
var bb = Math.floor(Math.abs(currentPaletteFmt.pal) % 10);
if (currentPaletteFmt.pal >= 0)
palette = convertPaletteBytes(palbytes, 0, rr, rr, gg, rr+gg, bb);
else
palette = convertPaletteBytes(palbytes, rr+gg, bb, rr, gg, 0, rr);
}
currentPixelEditor = new PixelEditor(maineditor, e.data.fmt.w, e.data.fmt.h, palette, allimages[0]);
}
function postToParentWindow(data) {
if (data.save) {
allimages[0] = currentPixelEditor.getImageColors();
data.bytes = convertImagesToBytes(allimages, currentFormat);
data.bytestr = replaceHexBytes(currentByteStr, data.bytes);
}
parentSource.postMessage(data, "*");
}
function pixelEditorResize(e) {
if (currentPixelEditor) {
currentPixelEditor.resize();
}
}
function pixelEditorKeypress(e) {
if (!currentPixelEditor) return;
var c = e.charCode;
if (c >= 48 && c <= 57) {
currentPixelEditor.setCurrentColor(c-48);
} else if (c >= 97 && c <= 102) {
currentPixelEditor.setCurrentColor(c-97+10);
}
}

View File

@ -390,6 +390,7 @@ function invertMap(m) {
}
function setCompileOutput(data) {
// TODO: kills current selection
sourcefile = new SourceFile(data.lines);
if (data.asmlines) {
assemblyfile = new SourceFile(data.asmlines, data.intermediate.listing);
@ -1006,6 +1007,61 @@ function toggleProfileWindow() {
}
}
function handleWindowMessage(e) {
console.log("window message", e.data);
if (e.data.bytes) {
editor.replaceSelection(e.data.bytestr);
}
if (e.data.close) {
$("#pixeditback").hide(250);
}
}
function openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
$("#pixeditback").show(250);
pixeditframe.contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
}
function lookBackwardsForJSONComment(line) {
var re = /[/][*]([{].+[}])[*][/]/;
while (--line >= 0) {
var s = 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);
var start = {obj:obj, line:line, ch:s.indexOf(m[0])+m[0].length};
line--;
while (++line < editor.lineCount()) {
if (editor.getLine(line).indexOf(';') >= 0) {
var end = {line:line, ch:editor.getLine(line).length};
return {obj:obj, start:start, end:end};
}
}
}
}
}
function openBitmapEditorAtCursor() {
if ($("#pixeditback").is(":visible")) {
$("#pixeditback").hide(250);
return;
}
var data = lookBackwardsForJSONComment(getCurrentLine());
if (data && data.obj && data.obj.w>0 && data.obj.h>0 && data.obj.bpp>0) {
var paldata = lookBackwardsForJSONComment(data.start.line-1);
var palbytestr;
if (paldata) {
palbytestr = editor.getRange(paldata.start, paldata.end);
paldata = paldata.obj;
}
editor.setSelection(data.end, data.start);
openBitmapEditorWithParams(data.obj, editor.getSelection(), paldata, palbytestr);
} else {
alert("No bitmap spec found in format /*{w:,h:,bpp:,count:...}*/");
}
}
function setupDebugControls(){
$("#dbg_reset").click(resetAndDebug);
$("#dbg_pause").click(pause);
@ -1027,6 +1083,7 @@ function setupDebugControls(){
$("#dbg_disasm").click(toggleDisassembly).show();
}
$("#disassembly").hide();
$("#dbg_bitmap").click(openBitmapEditorAtCursor);
$(".dropdown-menu").collapse({toggle: false});
$("#item_new_file").click(_createNewFile);
$("#item_share_file").click(_shareFile);
@ -1076,7 +1133,6 @@ function showWelcomeMessage() {
tour.init();
setTimeout(function() { tour.start(); }, 2000);
}
if (qs['redir']) delete qs['redir'];
}
///////////////////////////////////////////////////
@ -1155,7 +1211,6 @@ function startPlatform() {
// try to load last file (redirect)
var lastid = localStorage.getItem("__lastid_"+platform_id) || localStorage.getItem("__lastid");
localStorage.removeItem("__lastid");
qs['redir'] = '1';
gotoPresetNamed(lastid || PRESETS[0].id);
return false;
}
@ -1186,6 +1241,7 @@ function loadSharedFile(sharekey) {
// start
function startUI(loadplatform) {
installErrorHandler();
window.addEventListener("message", handleWindowMessage, false);
// add default platform?
platform_id = qs['platform'] || localStorage.getItem("__lastplatform");
if (!platform_id) {