Hi guys,
I've started to code an HTML5+Javascript app to transform a frame into several sprites and then converting the lot to to C data code.
Something similar to what imagenesis already does but getting focus a litte bit more on dealing with multi-sprite characters.
It has being made firstly for personal use but I've had in mind to share it as one tool that can help anyone using SGDK and even make it part of SGDK tools if I could polish it enough (and Stef aproves it).
As a coder I'm quite shitty (I'm not a coder) and because I can't put all the time I'd like into this I've decided to share it in its actual state so maybe I can get some help with it.
At the moment it is "firefox friendly only" because I couldn't find a way to avoid antialiasing when scalling the image on other browsers.
Here's the line I'm talking about:
// Disable antialiasing (Firefox only)
in_ctx.mozImageSmoothingEnabled = false;
http://beatemup1985.byethost7.com/splitter.htm
Feel free to grab the html code and play around.
TODO:
(on drawStoredSprite() function)
- Show or hide the spritebox when an item is selected (done) or unselected (ToDo)
(on removeSpriteBox() function)
- Remove sprite form "sprites" array
- Sort sprite listbox when an item is removed
(on createSpriteBox() function)
- Fix where tiles are placed into tileset canvas. From "sprite box": grab the first two tiles from the "spritebox" left column, add them to "tileset canvas" one on top of another (first and secon tiles on the left column), next two tiles from first column on "spritebox" go to the second column into "tileset canvas", and so on ...
- Fix the C code output.
--- Colors taken from original image must be converted from RGBA to a 4bit value (it is almost done, still a bit "faulty")
--- Code is not properly written
--- I'd like to add a new textarea to fill output for frame sprites initialization. (Easy one but it is not done either)
Example:
bigben01[0].x = 0; // Relative x pos on "frame space"
bigben01[0].y = 1; // Relative y pos on "frame space"
bigben01[0].width = 4; //
bigben01[0].height = 4; //
(somewhere else)
- (Maybe) Show a grid over the frame, wich can be hiden/shown using a checkbox.
... and... I think that's all I would like to achive (well, appart from a nicer looking genesis-like gui, but this is an unnecesary extra).
I'll keep working on it but if someone else is faster than me...
Thanks guys for your time and once again, to Steff for his cool SGDK.
Here's the HTML code so far:
Code: Select all
<html>
<head>
<title>Megadrive Frame Splitter</title>
</head>
<body onLoad="init()" >
<!-- FRAME canvas -->
<div style="position: absolute; left: 4px; top: 4px;">
<h3>Frame</h3>
<canvas id="canvas-in" width="320", height="480"></canvas>
<form><input type="file" value="gfxframe" /></form>
</div>
<!-- TILESET column: canvas and options -->
<div style="position: absolute; left: 360px; top: 4px;">
<h3>Tileset</h3>
<canvas id="canvas-out" width="320", height="240"></canvas>
<form>
<br />Tile counter:
<input type="text" id="num_tiles" value="0" size="4"/><br />
<div>
<br/>Zoom:
<input type="radio" name="zoomfactor" value=4 onClick="zoomFrame(3);">3X
<input type="radio" name="zoomfactor" value=4 onClick="zoomFrame(4);">4X
<input type="radio" name="zoomfactor" value=5 onClick="zoomFrame(5);"checked>5X
<input type="radio" name="zoomfactor" value=6 onClick="zoomFrame(6);">6X
</div>
<br />Current sprite size (tiles):
<br /><input type="text" id="sprite_size" value="0" size="6"/><br />
</form>
</div>
<!-- SPRITELIST column -->
<div style="position: absolute; left: 690px; top: 4px;">
<h3>Sprites</h3>
<form>
<select name="spritelist" size="12" onChange="drawStoredSprite(this.form.spritelist)">
<br />Sprite counter:
<input type="text" id="num_sprites" value=0 size="2"/><br />
<input type="button" value="New" onClick="createSpriteBox(this.form.spritelist)"/>
<input type="button" value="Remove" onClick="removeSpriteBox(this.form.spritelist)"/>
</select>
</form>
</div>
<!-- ACTUAL FRAME canvas, non visible, using for extracting tiles -->
<div style="position: absolute; left: -360px; top: 4px;">
<h3>Actual frame</h3>
<canvas id="canvas-original" width="160", height="320"></canvas>
</div>
<!-- TEXTAREA: output TILESET data -->
<div style="position: absolute; left: 4px; top: 640px;">
<textarea cols="120" rows="24" id="txtTileset" readonly=""></textarea>
</div>
<script type="text/javascript">
//------------------------------------------------------
// GLOBAL ONLOAD THINGIES
//------------------------------------------------------
var ZOOM_FACTOR = 5;
var LAST_SPRITE = 0;
var LAST_TILE = 0;
var new_sprite_x = 0;
var new_sprite_y = 0;
var new_sprite_w = 0;
var new_sprite_h = 0;
// Canvas data:
var canvas_in = document.getElementById("canvas-in"); // frame
var canvas_out = document.getElementById("canvas-out"); // tileset
var canvas_original = document.getElementById("canvas-original"); // Non scaled frame
var canvas_width = canvas_in.width;
var canvas_height = canvas_in.height;
var in_ctx = canvas_in.getContext("2d");
var out_ctx = canvas_out.getContext("2d");
var original_ctx = canvas_original.getContext("2d");
var TILESET_ROW = canvas_out.width / 8; //Number of tiles in a "canvas-out" row
// Rectangle drawinf stuff
rect = {},
drag = false; // mouse dragging
// Array where sprite data (coords and size) will be stored
var sprites = [];
// Disable antialiasing (Firefox only)
in_ctx.mozImageSmoothingEnabled = false;
// Frame image loading stuff
var img = new Image();
img.onload = function(){ zoomFrame(5); original_ctx.drawImage(img,0,0,img.width,img.height); };
img.src = "bigben01.png"; // PROVISIONAL
//Get filename (no extension) form image.src into trim_imgsrc
var str = img.src;
var trim_imgsrc = str.split('\\').pop().split('/').pop().slice(0,-4);
// Clear or initialize previous content of all text items
document.getElementById("txtTileset").value = "";
document.getElementById("sprite_size").value = "(0, 0)";
document.getElementById("num_tiles").value = 0;
document.getElementById("num_sprites").value = 0;
//------------------------------------------------------
// INIT (IN-CANVAS)
//------------------------------------------------------
function init() {
canvas_in.addEventListener('mousedown', mouseDown, false);
canvas_in.addEventListener('mouseup', mouseUp, false);
canvas_in.addEventListener('mousemove', mouseMove, false);
}
//------------------------------------------------------
// MOUSEDOWN (IN-CANVAS)
//------------------------------------------------------
function mouseDown(e) {
rect.startX = e.pageX - this.offsetLeft;
rect.startY = e.pageY - this.offsetTop;
drag = true;
}
//------------------------------------------------------
// MOUSEUP (IN-CANVAS)
//------------------------------------------------------
function mouseUp() {
drag = false;
}
//------------------------------------------------------
// MOUSEMOVE (IN-CANVAS)
//------------------------------------------------------
function mouseMove(e) {
if (drag) {
rect.w = ((e.pageX - this.offsetLeft) - rect.startX);
rect.h = ((e.pageY - this.offsetTop) - rect.startY);
//Adjust position and coords to nearest 8mult
new_sprite_x = Math.floor(rect.startX/(8*ZOOM_FACTOR))*8*ZOOM_FACTOR;
new_sprite_y = Math.floor(rect.startY/(8*ZOOM_FACTOR))*8*ZOOM_FACTOR;
new_sprite_w = Math.ceil(rect.w/(8*ZOOM_FACTOR))*8*ZOOM_FACTOR;
new_sprite_h = Math.ceil(rect.h/(8*ZOOM_FACTOR))*8*ZOOM_FACTOR;
//Limit the sprite size
if(new_sprite_w > (64*ZOOM_FACTOR)) new_sprite_w = 64*ZOOM_FACTOR;
if(new_sprite_h > (64*ZOOM_FACTOR)) new_sprite_h = 64*ZOOM_FACTOR;
//Inform about new sprite size
document.getElementById("sprite_size").value = '(' + (new_sprite_w/(8*ZOOM_FACTOR)) + ', ' + (new_sprite_h/(8*ZOOM_FACTOR)) + ')';
//Reload upscaled frame
in_ctx.drawImage(img,0,0,img.width*ZOOM_FACTOR,img.height*ZOOM_FACTOR);
//And finally draw the rectangle box
draw();
}
}
//------------------------------------------------------
// ZOOMFRAME
//------------------------------------------------------
function zoomFrame(factor)
{
ZOOM_FACTOR = factor;
//Clearing the canvas
in_ctx.fillStyle = "rgba(255, 255, 255, 255)";
in_ctx.fillRect (0, 0, canvas_width, canvas_height);
//Drawing the upscaled image
in_ctx.drawImage(img,0,0,img.width*ZOOM_FACTOR,img.height*ZOOM_FACTOR);
}
//------------------------------------------------------
// DRAW
//------------------------------------------------------
function draw()
{
in_ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
in_ctx.fillRect(new_sprite_x, new_sprite_y, new_sprite_w, new_sprite_h);
}
//------------------------------------------------------
// DRAWSTOREDSPRITE
//------------------------------------------------------
function drawStoredSprite(theSel) {
var i = theSel.options[theSel.selectedIndex].value;
//sprites[i].selected = 1;
//Get a random color
var color = Math.floor(Math.random()*4);
switch(color){
case 0: in_ctx.fillStyle = "rgba(200, 200, 200, 0.5)"; break;
case 1: in_ctx.fillStyle = "rgba(200, 0, 200, 0.5)"; break;
case 2: in_ctx.fillStyle = "rgba(200, 200, 0, 0.5)"; break;
case 3: in_ctx.fillStyle = "rgba(120, 120, 120, 0.5)"; break;
}//end switch
in_ctx.strokeStyle = "#f00";
in_ctx.lineWidth = 2;
in_ctx.strokeRect(sprites[i].x*ZOOM_FACTOR*8, sprites[i].y*ZOOM_FACTOR*8, sprites[i].w*ZOOM_FACTOR*8, sprites[i].h*ZOOM_FACTOR*8);
in_ctx.fillRect(sprites[i].x*ZOOM_FACTOR*8, sprites[i].y*ZOOM_FACTOR*8, sprites[i].w*ZOOM_FACTOR*8, sprites[i].h*ZOOM_FACTOR*8);
}
//------------------------------------------------------
// CREATESPRITEBOX
//------------------------------------------------------
function createSpriteBox(theSel)
{
// Local data _______________________________
var i = 0;
var ii = 0;
var r, g, b;
var h, v;
var tile;
var tileset_h = 0;
var tileset_v = 0;
var fake_last_tile = LAST_TILE;
var color; // integer value sum (concat) of r+g+b
var palette = new Array(); // 16 colors values
var palcol = 0; // index for "palette" array
var pixelcounter = 0;
var tilecounter = 0;
var spritecounter = 0;
// Process __________________________________
// Initialize palette array
for(i=0; i<16; i++) palette[i] = -1;
//Save relative position and size (in tile units)
sprites[LAST_SPRITE] = new Object();
sprites[LAST_SPRITE].x = new_sprite_x / (8 * ZOOM_FACTOR);
sprites[LAST_SPRITE].y = new_sprite_y / (8 * ZOOM_FACTOR);
sprites[LAST_SPRITE].w = new_sprite_w / (8 * ZOOM_FACTOR);
sprites[LAST_SPRITE].h = new_sprite_h / (8 * ZOOM_FACTOR);
//sprites[LAST_SPRITE].selected = -1;
//Add a new item to the sprite listbox ....................................
if (theSel.length == 0) {
var newOpt1 = new Option(trim_imgsrc+'['+LAST_SPRITE+']'+ ', X=' + sprites[LAST_SPRITE].x + ', Y=' + sprites[LAST_SPRITE].y + ', W=' + sprites[LAST_SPRITE].w + ', H=' + sprites[LAST_SPRITE].h, LAST_SPRITE);
theSel.options[0] = newOpt1;
theSel.selectedIndex = 0;
} else if (theSel.selectedIndex != -1) {
var selText = new Array();
var selValues = new Array();
var selIsSel = new Array();
var newCount = -1;
var newSelected = -1;
var i;
for(i=0; i<theSel.length; i++)
{
newCount++;
selText[newCount] = theSel.options[i].text;
selValues[newCount] = theSel.options[i].value;
selIsSel[newCount] = theSel.options[i].selected;
if (newCount == theSel.selectedIndex) {
newCount++;
selText[newCount] = trim_imgsrc+'['+LAST_SPRITE+']'+ ', X=' + sprites[LAST_SPRITE].x + ', Y=' + sprites[LAST_SPRITE].y + ', W=' + sprites[LAST_SPRITE].w + ', H=' + sprites[LAST_SPRITE].h;
selValues[newCount] = LAST_SPRITE;
selIsSel[newCount] = false;
newSelected = newCount - 1;
}
}
for(i=0; i<=newCount; i++)
{
var newOpt = new Option(selText[i], selValues[i]);
theSel.options[i] = newOpt;
theSel.options[i].selected = selIsSel[i];
}
}
//Add the tiles to tile canvas and data to textarea ................................
for(h=0; h<(sprites[LAST_SPRITE].w*8); h+=8) // sprite tiles loops (Hor & Vert)
{
for(v=0; v<(sprites[LAST_SPRITE].h*8); v+=8)
{
//Load fake_last_tile (for every two tiles downside placement) (DOESN'T WORK YET :S)
if(LAST_TILE % 2 == 0)
fake_last_tile = Math.ceil(LAST_TILE/2);
else
fake_last_tile = TILESET_ROW + Math.floor(LAST_TILE/2);
//Load tile area into "tile" buffer
var tile = original_ctx.getImageData((sprites[LAST_SPRITE].x*8)+h, (sprites[LAST_SPRITE].y*8)+v, 8, 8);
//Write into tile_canvas
tileset_v = Math.floor(fake_last_tile/TILESET_ROW)*8;
tileset_h = (fake_last_tile % TILESET_ROW)*8;
out_ctx.putImageData(tile, tileset_h, tileset_v);
// Write sprite header into textarea
if(tilecounter % (sprites[LAST_SPRITE].w * sprites[LAST_SPRITE].h) == 0)document.getElementById("txtTileset").value += "}\n" + "u32 " + trim_imgsrc +"[" + LAST_SPRITE + "][" + (sprites[LAST_SPRITE].w * sprites[LAST_SPRITE].h) + "] = {\n ";
//Loop through pixel data and write data to textarea .......................
for(i=0; i<256; i+=4)
{
//Save pixel RGB values
r = tile.data[i];
g = tile.data[i+1];
b = tile.data[i+2];
//Store RGB value as an integer
color = parseInt(r.toString() + g.toString() + b.toString());
//If not already saved we store the color value to palette
while((palette[palcol]!=-1) && (palcol<15)) palcol++;
if((palcol<15) && (palette[palcol]!==color)) palette[palcol] = color;
// Make 8 consecutive pixels into an u32 value
document.getElementById("txtTileset").value += "0x";
for(ii=0; ii<8; ii++)
{
document.getElementById("txtTileset").value += palcol.toString(16);
i++;
pixelcounter++;
}
document.getElementById("txtTileset").value += ", ";
if(pixelcounter % 64 == 0) //Check for TILE CHANGE (64 pixels = 1 tile)
{
tilecounter++;
LAST_TILE++;
document.getElementById("txtTileset").value += "// TILE " + Math.floor(tilecounter) + "\n ";
}
if(tilecounter == sprites[LAST_SPRITE].w * sprites[LAST_SPRITE].h) //Check for SPRITE CHANGE
{
tilecounter = 0; // reset
spritecounter++;
LAST_SPRITE++; //Update sprite counter
}
}//end for(tile pixel data)
}//end for
}//end for
//Update the infobox
document.getElementById("num_tiles").value = LAST_TILE;
document.getElementById("num_sprites").value = LAST_SPRITE;
}
//------------------------------------------------------
// REMOVESPRITEBOX
//------------------------------------------------------
function removeSpriteBox(theSel)
{
var selIndex = theSel.selectedIndex;
if (selIndex != -1) {
for(i=theSel.length-1; i>=0; i--)
{
if(theSel.options[i].selected)
{
theSel.options[i] = null;
}
}
if (theSel.length > 0) {
theSel.selectedIndex = selIndex == 0 ? 0 : selIndex - 1;
}
}
LAST_SPRITE--;
//Update the infobox
document.getElementById("num_sprites").value = LAST_SPRITE;
}
</script>
</body>
</html>