/*
This file is part of Retro Graphics Toolkit
Retro Graphics Toolkit is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.
Retro Graphics Toolkit is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Retro Graphics Toolkit. If not, see .
Copyright Sega16 (or whatever you wish to call me) (2012-2017)
*/
#include
#include "macros.h"
#include "savepng.h"
#include "dither.h"
#include "callback_chunk.h"
#include "callbacksprites.h"
#include "undo.h"
#include "image.h"
#include "classpalettebar.h"
#include "class_global.h"
#include "gui.h"
#include "palette.h"
#include "errorMsg.h"
void pickExtAttrsCB(Fl_Widget*, void*) {
if (!currentProject->containsData(pjHaveMap)) {
currentProject->haveMessage(pjHaveMap);
return;
}
currentProject->tms->maps[currentProject->curPlane].pickExtAttrs();
window->damage(FL_DAMAGE_USER1);
}
void setTmapOffsetCB(Fl_Widget*o, void*) {
Fl_Int_Input*i = (Fl_Int_Input*)o;
currentProject->tms->maps[currentProject->curPlane].offset = atoi(i->value());
}
void tileDPicker(Fl_Widget*, void*) {
Fl_Window *win;
Fl_Progress *progress;
mkProgress(&win, &progress);
currentProject->tms->maps[currentProject->curPlane].pickRowDelta(true, progress);
win->remove(progress);// remove progress bar from window
delete (progress); // deallocate it
delete win;
window->damage(FL_DAMAGE_USER1);
}
void resizeBlocksCB(Fl_Widget*o, void*) {
int32_t wtmp, htmp;
wtmp = SafeTxtInput(window->map_w);
htmp = SafeTxtInput(window->map_h);
currentProject->tms->maps[currentProject->curPlane].resizeBlocks(wtmp, htmp);
window->redraw();
}
void blocksAmtCB(Fl_Widget*o, void*) {
Fl_Int_Input*i = (Fl_Int_Input*)o;
int amtTmp = SafeTxtInput(i);
pushTilemapBlocksAmt(amtTmp);
currentProject->tms->maps[currentProject->curPlane].blockAmt(amtTmp);
window->redraw();
}
void toggleBlocksCB(Fl_Widget*o, void*) {
Fl_Check_Button* b = (Fl_Check_Button*)o;
bool Toggle = b->value() ? true : false;
currentProject->tms->maps[currentProject->curPlane].toggleBlocks(Toggle);
if (!Toggle) {
currentProject->Chunk->useBlocks = false;
window->useBlocksChunkCBtn->value(0);
}
window->redraw();
}
void FixOutOfRangeCB(Fl_Widget*, void*) {
//use current attributes
pushTilemapAll(false);
for (int y = 0; y < currentProject->tms->maps[currentProject->curPlane].mapSizeHA; ++y) {
for (int x = 0; x < currentProject->tms->maps[currentProject->curPlane].mapSizeW; ++x) {
if (currentProject->tms->maps[currentProject->curPlane].get_tile(x, y) >= currentProject->tileC->amount())
currentProject->tms->maps[currentProject->curPlane].set_tile_full(x, y, window->getCurrentTileCurrentTab(), palBar.selRow[2], G_hflip[0], G_vflip[0], G_highlow_p[0]);
}
}
window->damage(FL_DAMAGE_USER1);
}
void callback_resize_map(Fl_Widget* o, void*) {
int32_t w, h;
w = SafeTxtInput(window->map_w);
h = SafeTxtInput(window->map_h);
pushTilemapResize(w, h);
currentProject->tms->maps[currentProject->curPlane].resize_tile_map(w, h);
window->redraw();
}
void set_grid(Fl_Widget*, void*) {
/*this function will only be trigger when the check button is pressed
so we just need to invert the bool using xor to avoid if statements*/
show_grid ^= true;
window->redraw();//redraw to reflect the updated statues of the grid
}
void set_grid_placer(Fl_Widget*, void*) {
show_grid_placer ^= true;
window->redraw();//redraw to reflect the updated statues of the grid
}
void save_tilemap_as_image(Fl_Widget*, void*) {
if (!currentProject->containsData(pjHaveMap | pjHaveTiles)) {
currentProject->haveMessage(pjHaveMap | pjHaveTiles);
return;
}
std::string the_file;
if (currentProject->containsData(pjHaveTiles | pjHaveMap)) {
if (loadOrSaveFile(the_file, "Save PNG as", true)) {
uint32_t w = currentProject->tms->maps[currentProject->curPlane].mapSizeW * currentProject->tileC->width();
uint32_t h = currentProject->tms->maps[currentProject->curPlane].mapSizeHA * currentProject->tileC->height();
uint8_t * image = (uint8_t*)malloc(w * h);
uint8_t * imageold = image;
if (!image)
show_malloc_error(w * h)
for (unsigned y = 0; y < h; y += currentProject->tileC->height()) {
for (unsigned x = 0; x < w; x += currentProject->tileC->width()) {
unsigned tCur = currentProject->tms->maps[currentProject->curPlane].get_tile(x / currentProject->tileC->width(), y / currentProject->tileC->height());
unsigned off = currentProject->tms->maps[currentProject->curPlane].getPalRow(x / currentProject->tileC->width(), y / currentProject->tileC->height()) * currentProject->pal->perRow;
for (unsigned yy = 0; yy < currentProject->tileC->height(); ++yy) {
for (unsigned xx = 0; xx < currentProject->tileC->width(); ++xx)
image[x + xx + ((y + yy)*w)] = currentProject->tileC->getPixel(tCur, xx, yy) + off;
}
}
}
savePNG(the_file.c_str(), w, h, (void*)imageold, currentProject->pal->rgbPal, currentProject->pal->colorCnt);
free(imageold);
}
}
}
void save_tilemap_as_colspace(Fl_Widget*, void*) {
if (!currentProject->containsData(pjHaveMap | pjHaveTiles)) {
currentProject->haveMessage(pjHaveMap | pjHaveTiles);
return;
}
std::string the_file;
if (loadOrSaveFile(the_file, "Save png as", true) == true) {
uint32_t w = currentProject->tms->maps[currentProject->curPlane].mapSizeW * 8;
uint32_t h = currentProject->tms->maps[currentProject->curPlane].mapSizeHA * 8;
uint8_t * image = (uint8_t*)malloc(w * h * 3);
currentProject->tms->maps[currentProject->curPlane].truecolor_to_image(image, -1, false);
ditherImage(image, w, h, false, true);
savePNG(the_file.c_str(), w, h, (void*)image);
free(image);
}
}
void load_tile_map(Fl_Widget*, void*) {
if (!currentProject->containsData(pjHaveMap)) {
currentProject->haveMessage(pjHaveMap);
return;
}
pushTilemapAll(false);
if (unlikely(!currentProject->tms->maps[currentProject->curPlane].loadFromFile()))
alertWrap("Error: Cannot load file");
}
void save_map(Fl_Widget*, void*) {
if (!currentProject->containsData(pjHaveMap)) {
currentProject->haveMessage(pjHaveMap);
return;
}
if (unlikely(!currentProject->tms->maps[currentProject->curPlane].saveToFile()))
alertWrap("Error: can not save file\nTry making sure that you have permission to save the file here");
}
void fill_tile_map_with_tile(Fl_Widget*, void*) {
pushTilemapAll(false);
if (mode_editor != tile_place) {
alertWrap("To prevent accidental modification to the tile map be in plane editing mode");
return;
}
if (fl_ask("This will erase the entire tilemap and fill it with the currently selected tile\nAre you sure you want to do this?")) {
for (uint32_t y = 0; y < currentProject->tms->maps[currentProject->curPlane].mapSizeHA; ++y) {
for (uint32_t x = 0; x < currentProject->tms->maps[currentProject->curPlane].mapSizeW; ++x)
currentProject->tms->maps[currentProject->curPlane].set_tile_full(x, y, window->getCurrentTileCurrentTab(), palBar.selRow[2], G_hflip[0], G_vflip[0], G_highlow_p[0]);
}
window->damage(FL_DAMAGE_USER1);
}
}
void dither_tilemap_as_imageCB(Fl_Widget*, void*) {
//normally this program dithers all tiles individually this is not always desirable
//to fix this I created this function It converts the tilemap to image and dithers all tiles
//so first create ram for image
unsigned method;
method = fl_choice("How would you like this tilemap dithered?", "Dither each palette row separately", "Dither entire image at once", "Cancel");
if (method == 2)
return;
pushTilesAll(tTypeTile);
currentProject->tms->maps[currentProject->curPlane].ditherAsImage(method);
Fl::check();
window->redraw();
}
void load_image_to_tilemap_project_ptr(struct Project* cProject, const char*fname, bool over, bool tilesonly, bool append, unsigned curPlane, bool undo) {
// TODO: undo will only work correctly when cProject == currentProject
Fl_Shared_Image*loaded_image = Fl_Shared_Image::get(fname);
if (!loaded_image) {
fl_alert("Error loading image");
return;
}
unsigned tilebitw, tilebith;
tilebitw = cProject->tileC->width();
tilebith = cProject->tileC->height();
if ((cProject->subSystem & NES2x2) && (cProject->gameSystem == NES)) {
tilebitw *= 2;
tilebith *= 2;
}
uint32_t w, h;
w = loaded_image->w();
h = loaded_image->h();
uint32_t w8, h8;
uint32_t wt, ht;
int wr, hr;
wr = w % tilebitw;
hr = h % tilebith;
w8 = w / cProject->tileC->width();
h8 = h / cProject->tileC->height();
if (wr)
++w8;
if (hr)
++h8;
if ((cProject->gameSystem == NES) && (cProject->subSystem & NES2x2)) {
if (w8 & 1)
++w8;
if (h8 & 1)
++h8;
}
if (over) {
if ((w8 != cProject->tms->maps[curPlane].mapSizeW) || (h8 != cProject->tms->maps[curPlane].mapSizeHA)) {
alertWrap("When importing over tilemap width and height must be the same");
loaded_image->release();
return;
}
}
wt = w8 * cProject->tileC->width();
ht = h8 * cProject->tileC->height();
if (wr || hr)
messageWrap("When width and/or height is not a multiple of %d,%d the image will be centered.\nThe width of this image is %d and the height is %d", tilebitw, tilebith, w, h);
//start by copying the data
uint8_t * imgptr = (uint8_t *)loaded_image->data()[0];
//now we can convert to tiles
unsigned depth = loaded_image->d();
printf("image width: %u image height: %u depth: %u\n", w, h, depth);
if (unlikely(depth != 3 && depth != 4 && depth != 1)) {
alertWrap("Please use color depth of 1,3 or 4\nYou Used %d", loaded_image->d());
loaded_image->release();
return;
} else
printf("Image depth %d\n", loaded_image->d());
if (undo)
pushTilesAll(tTypeBoth);
unsigned appendoff;
if (!over) {
if (append)
appendoff = cProject->tileC->amount();
else
appendoff = 0;
cProject->tileC->resizeAmt(w8 * h8 + appendoff);
updateTileSelectAmt();
} else
appendoff = 0;
unsigned center[3];
center[0] = (wt - w) / 2;
center[1] = (ht - h) / 2;
center[2] = wt - w - center[0];
uint8_t*palMap;
bool grayscale;
unsigned remap[256];
if (depth == 1) {
unsigned numcol;
grayscale = handle1byteImg(loaded_image, remap, &numcol);
if (!grayscale) {
palMap = (uint8_t*)loaded_image->data()[1];
imgptr = (uint8_t*)loaded_image->data()[2];
if (fl_ask("Overwrite palette? This can be undone if you change your mind.")) {
if (undo)
pushPaletteAll();
for (unsigned i = 0; i < std::min(cProject->pal->colorCnt + cProject->pal->colorCntalt, numcol); ++i)
cProject->pal->rgbToEntry(palMap[i * 4 + 1], palMap[i * 4 + 2], palMap[i * 4 + 3], i);
}
}
}
for (uint32_t y = 0, tcnt = 0; y < ht; ++y) {
if (y % cProject->tileC->height())
tcnt -= wt / cProject->tileC->width();
if ((!((y < center[1]) || (y >= (h + center[1])))) && (depth == 1) && (!grayscale))
imgptr = (uint8_t*)loaded_image->data()[y + 2 - center[1]];
for (uint32_t x = 0; x < wt; x += cProject->tileC->width(), ++tcnt) {
uint32_t ctile;
if (over) {
ctile = cProject->tms->maps[curPlane].get_tile(x / cProject->tileC->width(), y / cProject->tileC->height());
//See if ctile is allocated
if (ctile >= cProject->tileC->amount()) {
//tile on map but not a tile associated with it
imgptr += cProject->tileC->width() * depth;
continue;
}
} else
ctile = tcnt;
ctile += appendoff;
uint8_t*ttile = cProject->tileC->truetDat.data() + ((ctile * cProject->tileC->tcSize) + ((y % cProject->tileC->height()) * cProject->tileC->width() * 4));
//First take care of border
unsigned line = cProject->tileC->width();
if ((y < center[1]) || (y >= (h + center[1])))
memset(ttile, 0, line * 4);
else {
if (x < center[0]) {
memset(ttile, 0, center[0] * 4);
line -= center[0];
ttile += center[0] * 4;
} else if (x >= (wt - cProject->tileC->width()))
line -= center[2];
switch (depth) {
case 1:
for (unsigned xx = 0; xx < line; ++xx) {
if (grayscale) {
*ttile++ = *imgptr;
*ttile++ = *imgptr;
*ttile++ = *imgptr++;
*ttile++ = 255;
} else {
if (*imgptr == ' ') {
memset(ttile, 0, 4);
ttile += 4;
++imgptr;
} else {
unsigned p = (*imgptr++);
*ttile++ = palMap[remap[p] + 1];
*ttile++ = palMap[remap[p] + 2];
*ttile++ = palMap[remap[p] + 3];
*ttile++ = 255;
}
}
}
break;
case 3:
for (unsigned xx = 0; xx < line; ++xx) {
*ttile++ = *imgptr++;
*ttile++ = *imgptr++;
*ttile++ = *imgptr++;
*ttile++ = 255;
}
break;
case 4:
memcpy(ttile, imgptr, line * 4);
imgptr += line * 4;
ttile += line * 4;
break;
}
if (x >= (wt - cProject->tileC->width()))
memset(ttile, 0, center[2] * 4);
}
}
}
loaded_image->release();
if ((!over) && (!tilesonly)) {
if (undo)
pushTilemapAll(false);
cProject->tms->maps[curPlane].resize_tile_map(w8, h8);
uint32_t tilecounter = appendoff;
for (uint32_t y = 0; y < h8; ++y) {
for (uint32_t x = 0; x < w8; ++x) {
cProject->tms->maps[curPlane].set_tile_full(x, y, tilecounter, 0, false, false, false);
++tilecounter;
}
}
}
if (window)
window->redraw();
}
void load_image_to_tilemap(const char*fname, bool over, bool tilesonly, bool append) {
load_image_to_tilemap_project_ptr(currentProject, fname, over, tilesonly, append, currentProject->curPlane, true);
}
void load_image_to_tilemapCB(Fl_Widget*, void*o) {
if (!currentProject->containsData(pjHaveTiles | pjHaveMap)) {
currentProject->haveMessage(pjHaveTiles | pjHaveMap);
return;
}
bool over = (uintptr_t)o & 1;
bool tilesonly = (uintptr_t)o >> 1;
bool append;
if (over)
append = false;
else
append = fl_choice("Append tiles or overwrite starting at 0?", "Overwrite", "Append", 0);
std::string fname;
if (loadOrSaveFile(fname, "Select an image"))
load_image_to_tilemap(fname.c_str(), over, tilesonly, append);
}
void set_prioCB(Fl_Widget*, void*o) {
unsigned off = (uintptr_t)o;
G_highlow_p[off] ^= true;
if ((tileEditModeChunk_G) && (off == 1)) {
pushChunkEdit(currentChunk, editChunk_G[0], editChunk_G[1]);
currentProject->Chunk->setPrio(currentChunk, editChunk_G[0], editChunk_G[1], G_highlow_p[off]);
} else if (tileEditModePlace_G && (off == 0)) {
pushTilemapEdit(selTileE_G[0], selTileE_G[1]);
currentProject->tms->maps[currentProject->curPlane].set_prio(selTileE_G[0], selTileE_G[1], G_highlow_p[off]);
}
window->redraw();
}
void set_hflipCB(Fl_Widget*, void*o) {
unsigned off = (uintptr_t)o;
G_hflip[off] ^= true;
if ((tileEditModeChunk_G) && (off == 1)) {
pushChunkEdit(currentChunk, editChunk_G[0], editChunk_G[1]);
currentProject->Chunk->setHflip(currentChunk, editChunk_G[0], editChunk_G[1], G_hflip[off]);
} else if (tileEditModePlace_G && (off == 0)) {
pushTilemapEdit(selTileE_G[0], selTileE_G[1]);
currentProject->tms->maps[currentProject->curPlane].set_hflip(selTileE_G[0], selTileE_G[1], G_hflip[off]);
}
window->redraw();
}
void set_vflipCB(Fl_Widget*, void*o) {
unsigned off = (uintptr_t)o;
G_vflip[off] ^= true;
if ((tileEditModeChunk_G) && (off == 1)) {
pushChunkEdit(currentChunk, editChunk_G[0], editChunk_G[1]);
currentProject->Chunk->setVflip(currentChunk, editChunk_G[0], editChunk_G[1], G_vflip[off]);
} else if (tileEditModePlace_G && (off == 0)) {
pushTilemapEdit(selTileE_G[0], selTileE_G[1]);
currentProject->tms->maps[currentProject->curPlane].set_vflip(selTileE_G[0], selTileE_G[1], G_vflip[off]);
}
window->redraw();
}
void update_map_scroll_x(Fl_Widget*, void*) {
map_scroll_pos_x = window->map_x_scroll->value();
window->redraw();
}
void update_map_scroll_y(Fl_Widget*, void*) {
map_scroll_pos_y = window->map_y_scroll->value();
window->redraw();
}
void update_map_size(Fl_Widget*, void*) {
currentProject->tms->maps[currentProject->curPlane].ScrollUpdate();
window->redraw();
}
void tilemap_remove_callback(Fl_Widget*, void*) {
char * str_ptr;
str_ptr = (char *)fl_input("Enter Tile");
if (!str_ptr)
return;
if (!verify_str_number_only(str_ptr))
return;
int32_t tile = atoi(str_ptr);
if (unlikely(tile < 0)) {
fl_alert("You must enter a number greater than or equal to 0 however you entered %d\n", tile);
return;
}
if (tile)
currentProject->tms->maps[currentProject->curPlane].sub_tile_map(tile, tile - 1, false, false);
else
currentProject->tms->maps[currentProject->curPlane].sub_tile_map(0, 0, false, false);
window->damage(FL_DAMAGE_USER1);
}
void shadow_highligh_findout(Fl_Widget*, void*) {
if (unlikely(currentProject->gameSystem != segaGenesis)) {
fl_alert("Only the Sega Genesis/Mega Drive supports shadow highlight mode\n");
return;
}
unsigned type = fl_choice("How will it be determined if the tile is shadowed or not?", "Tile brightness", "Delta", 0);
//this function will see if 3 or less pixels are above 125 and if so set priority to low or set priority to high if bright tile
unsigned x, y;
uint32_t xx;
if (type == 0) {
for (y = 0; y < currentProject->tms->maps[currentProject->curPlane].mapSizeHA; ++y) {
for (x = 0; x < currentProject->tms->maps[currentProject->curPlane].mapSizeW; ++x) {
uint32_t cur_tile = currentProject->tms->maps[currentProject->curPlane].get_tile(x, y);
uint8_t over = 0;
for (xx = cur_tile * 256; xx < cur_tile * 256 + 256; xx += 4) {
if ((currentProject->tileC->truetDat[xx] > 130) || (currentProject->tileC->truetDat[xx + 1] > 130) || (currentProject->tileC->truetDat[xx + 2] > 130))
over++;
}
if (over > 4)
currentProject->tms->maps[currentProject->curPlane].set_prio(x, y, true); //normal
else
currentProject->tms->maps[currentProject->curPlane].set_prio(x, y, false); //shadowed
}
}
} else {
uint8_t temp[256];
for (y = 0; y < currentProject->tms->maps[currentProject->curPlane].mapSizeHA; ++y) {
for (x = 0; x < currentProject->tms->maps[currentProject->curPlane].mapSizeW; ++x) {
uint32_t cur_tile = currentProject->tms->maps[currentProject->curPlane].get_tile(x, y);
uint32_t errorSh = 0, errorNorm = 0;
uint8_t * ptrorgin = ¤tProject->tileC->truetDat[(cur_tile * currentProject->tileC->tcSize)];
set_palette_type_force(0);//normal
currentProject->tileC->truecolor_to_tile(currentProject->tms->maps[currentProject->curPlane].getPalRow(x, y), cur_tile, false);
currentProject->tileC->tileToTrueCol(¤tProject->tileC->tDat[(cur_tile * currentProject->tileC->tileSize)], temp, currentProject->tms->maps[currentProject->curPlane].getPalRow(x, y));
for (xx = 0; xx < 256; xx += 4) {
errorNorm += abs(temp[xx] - ptrorgin[xx]);
errorNorm += abs(temp[xx + 1] - ptrorgin[xx + 1]);
errorNorm += abs(temp[xx + 2] - ptrorgin[xx + 2]);
}
set_palette_type_force(8);//shadow
currentProject->tileC->truecolor_to_tile(currentProject->tms->maps[currentProject->curPlane].getPalRow(x, y), cur_tile, false);
currentProject->tileC->tileToTrueCol(¤tProject->tileC->tDat[(cur_tile * currentProject->tileC->tileSize)], temp, currentProject->tms->maps[currentProject->curPlane].getPalRow(x, y));
for (xx = 0; xx < 256; xx += 4) {
errorSh += abs(temp[xx] - ptrorgin[xx]);
errorSh += abs(temp[xx + 1] - ptrorgin[xx + 1]);
errorSh += abs(temp[xx + 2] - ptrorgin[xx + 2]);
}
if (errorSh < errorNorm)
currentProject->tms->maps[currentProject->curPlane].set_prio(x, y, false); //shadowed
else
currentProject->tms->maps[currentProject->curPlane].set_prio(x, y, true); //normal
}
}
set_palette_type();//0 normal 8 shadow 16 highlight
}
window->redraw();
}