8bitworkshop/src/pixed/pixeleditor.js

407 lines
10 KiB
JavaScript
Raw Normal View History

2017-05-04 15:54:56 +00:00
"use strict";
2017-05-04 23:25:41 +00:00
function PixelEditor(parentDiv, fmt, palette, initialData, thumbnails) {
var self = this;
var width = fmt.w;
var height = fmt.h;
function createCanvas(parent) {
2017-05-04 15:54:56 +00:00
var c = document.createElement('canvas');
c.width = width;
c.height = height;
2017-05-04 23:25:41 +00:00
if (fmt.xform) c.style.transform = fmt.xform;
2017-05-04 15:54:56 +00:00
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);
}
2017-05-04 23:25:41 +00:00
function updateThumbnails() {
if (!thumbnails) return;
for (var i=0; i<thumbnails.length; i++) {
thumbnails[i].copyImageFrom(self);
}
}
this.copyImageFrom = function(src) {
pixints.set(src.getImageData());
updateImage();
}
this.getImageData = function() { return pixints; }
2017-05-04 15:54:56 +00:00
function fitCanvas() {
var w = $(parentDiv).width();
var h = $(parentDiv).height();
pixcanvas.style.height = Math.floor(h)+"px";
// TODO
}
this.resize = fitCanvas;
var pixcanvas = createCanvas();
var ctx = pixcanvas.getContext('2d');
var pixdata = ctx.createImageData(width, height);
2017-05-04 15:54:56 +00:00
var pixints = new Uint32Array(pixdata.data.buffer);
for (var i=0; i<pixints.length; i++) {
pixints[i] = initialData ? palette[initialData[i]] : palette[0];
}
updateImage();
function revrgb(x) {
var y = 0;
y |= ((x >> 0) & 0xff) << 16;
y |= ((x >> 8) & 0xff) << 8;
y |= ((x >> 16) & 0xff) << 0;
return y;
}
2017-05-04 23:25:41 +00:00
this.createPaletteButtons = function() {
2017-05-04 15:54:56 +00:00
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);
2017-05-04 23:25:41 +00:00
btn.click(self.setCurrentColor.bind(this, i));
2017-05-04 15:54:56 +00:00
btn.attr('id', 'palcol_' + i);
btn.css('backgroundColor', color).text(i.toString(16));
if ((rgb & 0x808080) != 0x808080) { btn.css('color', 'white'); }
span.append(btn);
}
2017-05-04 23:25:41 +00:00
self.setCurrentColor(1);
2017-05-04 15:54:56 +00:00
}
function getPixelByOffset(ofs) {
2017-05-04 23:25:41 +00:00
var oldrgba = pixints[ofs] & 0xffffff;
2017-05-04 15:54:56 +00:00
for (var i=0; i<palette.length; i++) {
2017-05-04 23:25:41 +00:00
if (oldrgba == (palette[i] & 0xffffff)) return i;
2017-05-04 15:54:56 +00:00
}
return 0;
}
function getPixel(x, y) {
var ofs = x+y*width;
2017-05-04 15:54:56 +00:00
return getPixelByOffset(ofs);
}
function setPixel(x, y, col) {
if (x < 0 || x >= width || y < 0 || y >= height) return;
var ofs = x+y*width;
2017-05-04 15:54:56 +00:00
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;
}
2017-05-04 23:25:41 +00:00
///
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');
}
}
self.setCurrentColor = setCurrentColor;
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;
pixcanvas.setCapture();
2017-05-04 23:25:41 +00:00
})
.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;
updateThumbnails();
pixcanvas.releaseCapture();
2017-05-04 23:25:41 +00:00
});
}
2017-05-04 15:54:56 +00:00
}
2017-05-04 23:25:41 +00:00
/////////////////
var pixel_re = /([0#]?)([x$%])([0-9a-f]+)/gi;
2017-05-04 15:54:56 +00:00
function parseHexBytes(s) {
var arr = [];
var m;
while (m = pixel_re.exec(s)) {
var n;
if (m[2].startsWith('%'))
n = parseInt(m[3],2);
else if (m[2].startsWith('x') || m[2].startsWith('$'))
n = parseInt(m[3],16);
2017-05-04 15:54:56 +00:00
else
n = parseInt(m[3]);
arr.push(n);
2017-05-04 15:54:56 +00:00
}
return arr;
}
function replaceHexBytes(s, bytes) {
var result = "";
var m;
var li = 0;
var i = 0;
while (m = pixel_re.exec(s)) {
result += s.slice(li, pixel_re.lastIndex - m[0].length);
li = pixel_re.lastIndex;
if (m[2].startsWith('%'))
result += m[1] + "%" + bytes[i++].toString(2);
else if (m[2].startsWith('x'))
result += m[1] + "x" + hex(bytes[i++]);
else if (m[2].startsWith('$'))
result += m[1] + "$" + hex(bytes[i++]);
2017-05-04 15:54:56 +00:00
else
result += m[1] + bytes[i++].toString();
2017-05-04 15:54:56 +00:00
}
result += s.slice(li);
return result;
}
function remapBits(x, arr) {
if (!arr) return x;
var y = 0;
for (var i=0; i<arr.length; i++) {
var s = arr[i];
if (s < 0) {
s = -s-1;
y ^= 1 << s;
}
if (x & (1 << i)) {
y ^= 1 << s;
}
}
return y;
}
2017-05-04 15:54:56 +00:00
function convertBytesToImages(bytes, fmt) {
var width = fmt.w;
var height = fmt.h;
var count = fmt.count || 1;
var bpp = fmt.bpp || 1;
var nplanes = fmt.np || 1;
var bytesperline = fmt.sl || Math.ceil(width * bpp / 8);
2017-05-04 23:25:41 +00:00
var mask = (1 << bpp)-1;
2017-05-04 15:54:56 +00:00
var images = [];
for (var n=0; n<count; n++) {
var imgdata = [];
for (var y=0; y<height; y++) {
var ofs0 = n*bytesperline*height + y*bytesperline;
2017-05-04 15:54:56 +00:00
var shift = 0;
for (var x=0; x<width; x++) {
var color = 0;
var ofs = remapBits(ofs0, fmt.remap);
2017-05-04 15:54:56 +00:00
for (var p=0; p<nplanes; p++) {
var byte = bytes[ofs + p*(fmt.pofs|0)];
2017-05-04 23:25:41 +00:00
color |= ((fmt.brev ? byte>>(8-shift-bpp) : byte>>shift) & mask) << (p*bpp);
2017-05-04 15:54:56 +00:00
}
imgdata.push(color);
2017-05-04 23:25:41 +00:00
shift += bpp;
2017-05-04 15:54:56 +00:00
if (shift >= 8) {
ofs0 += 1;
2017-05-04 15:54:56 +00:00
shift = 0;
}
}
}
images.push(imgdata);
}
return images;
}
function convertImagesToBytes(images, fmt) {
var width = fmt.w;
var height = fmt.h;
var count = fmt.count || 1;
var bpp = fmt.bpp || 1;
var nplanes = fmt.np || 1;
var bytesperline = fmt.sl || Math.ceil(fmt.w * bpp / 8);
2017-05-04 23:25:41 +00:00
var mask = (1 << bpp)-1;
2017-05-04 15:54:56 +00:00
var bytes = new Uint8Array(bytesperline * height * nplanes * count);
for (var n=0; n<count; n++) {
var imgdata = images[n];
2017-05-04 23:25:41 +00:00
var i = 0;
2017-05-04 15:54:56 +00:00
for (var y=0; y<height; y++) {
var ofs0 = n*bytesperline*height + y*bytesperline;
2017-05-04 15:54:56 +00:00
var shift = 0;
for (var x=0; x<width; x++) {
var color = imgdata[i++];
var ofs = remapBits(ofs0, fmt.remap);
2017-05-04 15:54:56 +00:00
for (var p=0; p<nplanes; p++) {
2017-05-04 23:25:41 +00:00
var c = (color >> (p*bpp)) & mask;
bytes[ofs + p*(fmt.pofs|0)] |= (fmt.brev ? (c << (8-shift-bpp)) : (c << shift));
2017-05-04 15:54:56 +00:00
}
2017-05-04 23:25:41 +00:00
shift += bpp;
2017-05-04 15:54:56 +00:00
if (shift >= 8) {
ofs0 += 1;
2017-05-04 15:54:56 +00:00
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;
}
2017-05-10 02:43:42 +00:00
var palette;
var paletteSets;
var paletteSetIndex=0;
2017-05-04 15:54:56 +00:00
var currentPixelEditor;
var parentSource;
var parentOrigin;
var allimages;
var currentFormat;
var currentByteStr;
var currentPaletteStr;
var currentPaletteFmt;
2017-05-04 23:25:41 +00:00
var allthumbs;
2017-05-04 15:54:56 +00:00
function pixelEditorDecodeMessage(e) {
2017-05-04 15:54:56 +00:00
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);
2017-05-04 23:25:41 +00:00
palette = [0xff000000, 0xffffffff]; // TODO
2017-05-04 15:54:56 +00:00
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);
2017-05-04 23:25:41 +00:00
// TODO: n
2017-05-04 15:54:56 +00:00
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);
2017-05-10 02:43:42 +00:00
if (currentPaletteFmt.n) {
paletteSets = [];
for (var i=0; i<palette.length; i+=currentPaletteFmt.n) {
paletteSets.push(palette.slice(i, i+currentPaletteFmt.n));
}
palette = paletteSets[paletteSetIndex = 0];
// TODO: swap palettes
}
2017-05-04 23:25:41 +00:00
} else {
// TODO: default palette?
}
palette = new Uint32Array(palette);
}
function pixelEditorCreateThumbnails(e) {
2017-05-04 23:25:41 +00:00
// 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 = $("#thumbnaildiv").append("<div>");
}
allthumbs.push(createThumbnailForImage(parentdiv, i));
2017-05-04 15:54:56 +00:00
}
}
function pixelEditorReceiveMessage(e) {
pixelEditorDecodeMessage(e);
pixelEditorCreateThumbnails(e);
2017-05-04 23:25:41 +00:00
// create initial editor
createEditorForImage(0);
}
function createThumbnailForImage(parentdiv, i) {
var span = $('<span class="thumb">');
var thumb = new PixelEditor(span, currentFormat, palette, allimages[i]);
parentdiv.append(span);
span.click(function() { createEditorForImage(i) });
return thumb;
}
function createEditorForImage(i) {
currentPixelEditor = new PixelEditor(maineditor, currentFormat, palette, allimages[i], [allthumbs[i]]);
currentPixelEditor.resize();
currentPixelEditor.makeEditable();
currentPixelEditor.createPaletteButtons();
2017-05-04 15:54:56 +00:00
}
function postToParentWindow(data) {
if (data.save) {
2017-05-04 23:25:41 +00:00
var allimgs = [];
for (var i=0; i<allthumbs.length; i++) {
allimgs.push(allthumbs[i].getImageColors());
}
data.bytes = convertImagesToBytes(allimgs, currentFormat);
2017-05-04 15:54:56 +00:00
data.bytestr = replaceHexBytes(currentByteStr, data.bytes);
}
2017-05-04 23:25:41 +00:00
if (parentSource) parentSource.postMessage(data, "*");
return data;
2017-05-04 15:54:56 +00:00
}
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);
}
}