diff --git a/data/base/images/intfac.img b/data/base/images/intfac.img index ff0cf5306..a1aef55df 100644 --- a/data/base/images/intfac.img +++ b/data/base/images/intfac.img @@ -345,6 +345,12 @@ 4,231,102,23,13,0,0,"IMAGE VDP UP" 4,230,116,25,15,-1,-1,"IMAGE VDP HI" 2,49,99,4,5,0,0,"IMAGE GN STAR" +2,183,0,4,5,0,0,"IMAGE GN 15" +2,187,0,4,5,0,0,"IMAGE GN 14" +2,191,0,4,5,0,0,"IMAGE GN 13" +2,195,0,4,5,0,0,"IMAGE GN 12" +2,199,0,4,5,0,0,"IMAGE GN 11" +2,203,0,4,5,0,0,"IMAGE GN 10" 2,44,99,4,5,0,0,"IMAGE GN 9" 2,39,99,4,5,0,0,"IMAGE GN 8" 2,34,99,4,5,0,0,"IMAGE GN 7" @@ -353,7 +359,7 @@ 2,19,99,4,5,0,0,"IMAGE GN 4" 2,14,99,4,5,0,0,"IMAGE GN 3" 2,9,99,4,5,0,0,"IMAGE GN 2" -2,5,99,2,5,0,0,"IMAGE GN 1" +2,5,99,2,5,1,0,"IMAGE GN 1" 2,0,99,4,5,0,0,"IMAGE GN 0" 4,74,126,36,24,0,0,"IMAGE ORD CIRCLEUP" 4,111,126,36,24,0,0,"IMAGE ORD CIRCLEDOWN" @@ -466,12 +472,12 @@ 2,1,157,36,24,0,0,"IMAGE ORD FIREDES UP" 2,38,157,36,24,0,0,"IMAGE ORD FIREDES DOWN" 3,0,97,11,12,0,-9,"IMAGE ASCII64" -0,127,152,7,10,0,0,"IMAGE LFTTAB" -0,127,165,7,10,0,0,"IMAGE LFTTABD" -0,126,179,9,14,0,0,"IMAGE LFTTABH" -0,137,152,7,10,0,0,"IMAGE RGTTAB" -0,137,165,7,10,0,0,"IMAGE RGTTABD" -0,134,179,9,14,0,0,"IMAGE RGTTABH" +0,127,152,7,10,0,0,"IMAGE LFTTAB" +0,127,165,7,10,0,0,"IMAGE LFTTABD" +0,126,179,9,14,0,0,"IMAGE LFTTABH" +0,137,152,7,10,0,0,"IMAGE RGTTAB" +0,137,165,7,10,0,0,"IMAGE RGTTABD" +0,134,179,9,14,0,0,"IMAGE RGTTABH" 0,134,179,9,14,0,0,"IMAGE NADDA" 5,0,59,36,24,0,0,"IMAGE ORD PATROLUP" 5,37,59,36,24,0,0,"IMAGE ORD PATROLDOWN" diff --git a/data/base/images/intfac2.png b/data/base/images/intfac2.png index a1b34c05d..6bb92f7dd 100644 Binary files a/data/base/images/intfac2.png and b/data/base/images/intfac2.png differ diff --git a/src/intfac.h b/src/intfac.h index 3538404e3..10a11f838 100644 --- a/src/intfac.h +++ b/src/intfac.h @@ -372,6 +372,12 @@ enum { IMAGE_VDP_UP, IMAGE_VDP_HI, IMAGE_GN_STAR, + IMAGE_GN_15, + IMAGE_GN_14, + IMAGE_GN_13, + IMAGE_GN_12, + IMAGE_GN_11, + IMAGE_GN_10, IMAGE_GN_9, IMAGE_GN_8, IMAGE_GN_7, diff --git a/src/multiint.cpp b/src/multiint.cpp index bfd6e83c1..38b0ced0b 100644 --- a/src/multiint.cpp +++ b/src/multiint.cpp @@ -126,6 +126,8 @@ #define WZCOL_TERC3_GROUND_LOW pal_Colour(0x00, 0x1C, 0x0E) #define WZCOL_TERC3_GROUND_HIGH WZCOL_TERC3_CLIFF_HIGH +static const unsigned gnImage[] = {IMAGE_GN_0, IMAGE_GN_1, IMAGE_GN_2, IMAGE_GN_3, IMAGE_GN_4, IMAGE_GN_5, IMAGE_GN_6, IMAGE_GN_7, IMAGE_GN_8, IMAGE_GN_9, IMAGE_GN_10, IMAGE_GN_11, IMAGE_GN_12, IMAGE_GN_13, IMAGE_GN_14, IMAGE_GN_15}; + // //////////////////////////////////////////////////////////////////////////// // vars extern char MultiCustomMapsPath[PATH_MAX]; diff --git a/tools/image/image.cpp b/tools/image/image.cpp new file mode 100644 index 000000000..9fd476b9e --- /dev/null +++ b/tools/image/image.cpp @@ -0,0 +1,477 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void printUsage(char const *programName) +{ + printf("Usage:\n" + "\t%s split data/base/images/intfac\n" + "\t%s join data/base/images/intfac src/intfac.h\n" + "\t%s split data/base/images/frontend\n" + "\t%s join data/base/images/frontend src/frend.h\n" + "\t\t--no-crush: Don't crush modified png files.\n" + "\n" + "The header file argument is optional. After splitting data/base/images/X,\n" + "you may edit one or more of resulting data/base/images/X/*.png files, and\n" + "then join. After splitting, you may also add files. If adding a file\n" + "called image_my_file.png, you should add a dummy entry such as\n" + "3,0,0,0,0,0,0,\"image my file\"\n" + "which will result in the file being placed on page 3 if possible, or any\n" + "page with room, if there isn't room on page 3. The last two numbers give\n" + "the offset. The remaining numbers are ignored.\n" + "\n", programName, programName, programName, programName); +} + +bool shouldCrush = true; + +void crushPng(std::string const &pngFilename) +{ + if (!shouldCrush) + { + return; + } + /* + system(("pngcrush -o9 " + pngFilename + " TEMPORARY_FILE.png > /dev/null 2> /dev/null").c_str()); + system( "advpng -z4 TEMPORARY_FILE.png > /dev/null 2> /dev/null"); + system(("mv TEMPORARY_FILE.png " + pngFilename + " > /dev/null 2> /dev/null").c_str()); + */ + system(("optipng -o7 " + pngFilename + " > /dev/null 2> /dev/null").c_str()); + system(("advpng -z4 " + pngFilename + " > /dev/null 2> /dev/null").c_str()); +} + +char upper(char ch) +{ + if (ch >= 'a' && ch <= 'z') + { + return ch + 'A' - 'a'; + } + return ch; +} + +char lower(char ch) +{ + if (ch >= 'A' && ch <= 'Z') + { + return ch + 'a' - 'A'; + } + return ch; +} + + +std::string filter(std::string const &in, bool lowerCase = false) +{ + std::string out; + for (std::string::const_iterator i = in.begin(); i != in.end(); ++i) + { + char ch = '_'; + char up = upper(*i); + if ((up >= '0' && up <= '9') || + (up >= 'A' && up <= 'Z')) + { + ch = lowerCase? lower(up) : up; + } + out.push_back(ch); + } + + return out; +} + +static const unsigned DEFAULT_PAGEX = 256, DEFAULT_PAGEY = 256; + +struct ImgEntry +{ + ImgEntry() : tPage(0), tx(0), ty(0), sx(0), sy(0), ox(0), oy(0), dirty(true) {} + + //bool isValid() const { return tx < PAGEX && sx <= PAGEX - tx && ty < PAGEY && sy <= PAGEY - ty && tPage < 1000; } + + unsigned tPage; // Page index. + unsigned tx, ty; // Location of top left corner on page. + unsigned sx, sy; // Width and height. + int ox, oy; // Offset, used when blitting. + std::string name; // Name of image. Capital letters only. + + QImage data; + + bool dirty; // If true, tPage, tx and ty are arbitrary. +}; + +struct ImgPage +{ + QImage data; // Pixel data. + + bool dirty; // If true, image data has changed, and the page file needs saving. +}; + +struct ImgFile +{ + typedef std::vector Entries; + typedef std::vector Pages; + Entries entries; + Pages pages; +}; + +void arrangeEntryOnPage(ImgFile &img, ImgEntry &entry, unsigned page) +{ + unsigned PAGEX = img.pages[page].data.width(); + unsigned PAGEY = img.pages[page].data.height(); + if ((PAGEX > DEFAULT_PAGEX || + PAGEY > DEFAULT_PAGEY) && + !(entry.sx > DEFAULT_PAGEX || + entry.sy > DEFAULT_PAGEY)) + { +//printf("Skip page %u = (%u,%u), need (%u,%u)\n", page, PAGEX, PAGEY, entry.sx, entry.sy); + return; // This page is special... + } + + std::vector pageMap(PAGEX*PAGEY, false); + for (unsigned e = 0; e < img.entries.size(); ++e) + { + if (img.entries[e].tPage == page && !img.entries[e].dirty) + { + for (unsigned y = img.entries[e].ty; y < std::min(PAGEY, img.entries[e].ty + img.entries[e].sy); ++y) + for (unsigned x = img.entries[e].tx; x < std::min(PAGEX, img.entries[e].tx + img.entries[e].sx); ++x) + { + pageMap[x + y*PAGEX] = true; + } + } + } + std::vector cy(PAGEX, entry.sy - 1); + for (unsigned y = 0; y < PAGEY; ++y) + { + int cx = entry.sx - 1; + for (unsigned x = 0; x < PAGEX; ++x) + { + if (pageMap[x + y*PAGEX]) + { + cx = entry.sx; + } + pageMap[x + y*PAGEX] = --cx >= 0 || pageMap[x + y*PAGEX]; + + if (pageMap[x + y*PAGEX]) + { + cy[x] = entry.sy; + } + pageMap[x + y*PAGEX] = --cy[x] >= 0 || pageMap[x + y*PAGEX]; + } + } + for (unsigned y = 0; y != PAGEY; ++y) + for (unsigned x = 0; x != PAGEX; ++x) + { + if (!pageMap[x + y*PAGEX]) + { + entry.tPage = page; + entry.tx = x + 1 - entry.sx; + entry.ty = y + 1 - entry.sy; + entry.dirty = false; + img.pages[entry.tPage].dirty = true; + return; + } + } +} + +void arrangeEntry(ImgFile &img, ImgEntry &entry) +{ + if (entry.tPage < img.pages.size()) + { + arrangeEntryOnPage(img, entry, entry.tPage); + } + + for (unsigned page = 0; page < img.pages.size() && entry.dirty; ++page) + { + arrangeEntryOnPage(img, entry, page); + } + + if (!entry.dirty) + { + return; // Done. + } + + ImgPage newPage; + newPage.data = QImage(DEFAULT_PAGEX, DEFAULT_PAGEY, QImage::Format_ARGB32); + img.pages.push_back(newPage); + + arrangeEntryOnPage(img, entry, img.pages.size() - 1); + if (entry.dirty) + { + printf("Couldn't find room for entry on a blank page, entry (%u, %u) too big?\n", entry.sx, entry.sy); + exit(1); + } +} + +void arrangeEntries(ImgFile &img) +{ + std::vector, unsigned> > dirtyEntries; + for (unsigned i = 0; i < img.entries.size(); ++i) + { + if (img.entries[i].dirty) + { + int sx = img.entries[i].sx; + int sy = img.entries[i].sy; + dirtyEntries.push_back(std::make_pair(std::make_pair(-std::max(sx, sy), -std::min(sx, sy)), i)); + } + } + std::sort(dirtyEntries.begin(), dirtyEntries.end()); // Sort by width and then by height. + for (unsigned i = 0; i < dirtyEntries.size(); ++i) + { + arrangeEntry(img, img.entries[dirtyEntries[i].second]); + } +} + +void writeEntry(ImgFile &img, ImgEntry const &entry) +{ + QPainter paint(&img.pages[entry.tPage].data); + paint.setCompositionMode(QPainter::CompositionMode_Source); + paint.drawImage(entry.tx, entry.ty, entry.data); +} + +void writePages(ImgFile &img) +{ + for (unsigned i = 0; i < img.pages.size(); ++i) + { + //img.pages[i].data.fill(0x80FF0080); // Make empty areas pink semitransparent. + img.pages[i].data.fill(0x00FFFFFF); // Make empty areas white transparent. + } + for (unsigned i = 0; i < img.entries.size(); ++i) + { + writeEntry(img, img.entries[i]); + } +} + +ImgFile parseImgFile(std::string const &fName) +{ + ImgFile img; + + FILE *f = fopen((fName + ".img").c_str(), "rb"); + if (f == NULL) + { + printf("Couldn't read file \"%s\".\n", (fName + ".img").c_str()); + exit(1); + } + char buf[101]; + ImgEntry e; + while (fscanf(f, "%u,%u,%u,%u,%u,%d,%d,\"%100[0-9A-Z _]\"\n", &e.tPage, &e.tx, &e.ty, &e.sx, &e.sy, &e.ox, &e.oy, buf) == 8) + { + e.name = buf; + e.dirty = false; + img.entries.push_back(e); + + if (e.tPage > 100 || img.entries.size() > 100000) + { + printf("Invalid entry \"%s\" in file \"%s\".\n", buf, (fName + ".img").c_str()); + exit(1); + } + + if (e.tPage >= img.pages.size()) + { + img.pages.resize(e.tPage + 1); + } + } + if (!feof(f)) + { + printf("Invalid entry in file \"%s\".\n", (fName + ".img").c_str()); + exit(1); + } + fclose(f); + + for (unsigned i = 0; i < img.pages.size(); ++i) + { + char index[100]; + sprintf(index, "%u.png", i); + img.pages[i].data = QImage((fName + index).c_str()).convertToFormat(QImage::Format_ARGB32); + img.pages[i].dirty = false; + if (img.pages[i].data.isNull()) + { + printf("Error loading file \"%s\".\n", (fName + index).c_str()); + exit(1); + } + } + + for (unsigned i = 0; i < img.entries.size(); ++i) + { + ImgEntry &e = img.entries[i]; + e.data = img.pages[e.tPage].data.copy(e.tx, e.ty, e.sx, e.sy); + } + + return img; +} + +void writeImgFile(std::string const &fName, ImgFile const &img) +{ + FILE *f = fopen((fName + ".img").c_str(), "wb"); + if (f == NULL) + { + printf("Couldn't write file \"%s\".\n", (fName + ".img").c_str()); + exit(1); + } + + for (ImgFile::Entries::const_iterator i = img.entries.begin(); i != img.entries.end(); ++i) + { + fprintf(f, "%u,%u,%u,%u,%u,%d,%d,\"%s\"\n", i->tPage, i->tx, i->ty, i->sx, i->sy, i->ox, i->oy, i->name.c_str()); + } + + fclose(f); + + for (unsigned i = 0; i < img.pages.size(); ++i) + { + if (!img.pages[i].dirty) + { + continue; // Image not changed, no need to resave. + } + char index[100]; + sprintf(index, "%u.png", i); + std::string pageFilename = fName + index; + if (!img.pages[i].data.save(pageFilename.c_str())) + { + printf("Error saving file \"%s\".\n", pageFilename.c_str()); + exit(1); + } + crushPng(pageFilename); + } +} + +void readSplitFiles(std::string const &fName, ImgFile &img) +{ + std::string dir = fName + "/"; + for (ImgFile::Entries::iterator i = img.entries.begin(); i != img.entries.end(); ++i) + { + std::string png = dir + filter(i->name, true) + ".png"; + QImage newData = QImage(png.c_str()).convertToFormat(QImage::Format_ARGB32); + if (newData.isNull()) + { + printf("Error loading file \"%s\".\n", png.c_str()); + exit(1); + } + if (i->data == newData) + { + continue; // Nothing changed. + } + i->data = newData; + if (i->sx != (unsigned)i->data.width() || + i->sy != (unsigned)i->data.height()) + { + // Image size changed, mark it dirty, since it probably needs to be moved. + i->sx = i->data.width(); + i->sy = i->data.height(); + i->dirty = true; + } + else + { + // Image size did not change, so can just write it where it was. + img.pages[i->tPage].dirty = true; + } + } +} + +void writeSplitFiles(std::string const &fName, ImgFile const &img) +{ + std::string dir = fName + "/"; + mkdir(dir.c_str(), 0755); + + for (ImgFile::Entries::const_iterator i = img.entries.begin(); i != img.entries.end(); ++i) + { + std::string png = dir + filter(i->name, true) + ".png"; + i->data.save(png.c_str()); + crushPng(png); + } +} + +void writeHeaderFile(std::string const &fName, ImgFile const &img) +{ + FILE *f = fopen(fName.c_str(), "wb"); + if (f == NULL) + { + printf("Couldn't write file \"%s\".\n", fName.c_str()); + exit(1); + } + + fprintf(f, "// THIS IS AN AUTOGENERATED HEADER! DO NOT EDIT (since your changes would be lost, anyway)!\n" + "\n" + "#ifndef __INCLUDED_%s__\n" + "#define __INCLUDED_%s__\n" + "\n" + "enum\n" + "{\n", filter(fName).c_str(), filter(fName).c_str()); + for (ImgFile::Entries::const_iterator i = img.entries.begin(); i != img.entries.end(); ++i) + { + fprintf(f, "\t%s,\n", filter(i->name).c_str()); + } + fprintf(f, "};\n" + "\n" + "#endif //__INCLUDED_%s__\n", filter(fName).c_str()); + + fclose(f); +} + +int main(int argc, char **argv) +{ + char const *programName = argv[0]; + + if (argc >= 2 && std::string(argv[1]) == "--no-crush") + { + --argc; + ++argv; + shouldCrush = false; + } + + if (argc != 3 && argc != 4) + { + printUsage(programName); + return 1; + } + + std::string action = argv[1]; + std::string base = argv[2]; + std::string header; + if (argc == 4) + { + header = argv[3]; + } + + if (action == "split") + { + ImgFile img = parseImgFile(base); + + writeSplitFiles(base, img); + /*for (ImgFile::Entries::const_iterator i = img.entries.begin(); i != img.entries.end(); ++i) + { + printf("%u,%u,%u,%u,%u,%d,%d,\"%s\"\n", i->tPage, i->tx, i->ty, i->sx, i->sy, i->ox, i->oy, i->name.c_str()); + }*/ + } + else if (action == "join" || action == "repack") + { + ImgFile img = parseImgFile(base); + readSplitFiles(base, img); + + if (action == "repack") + { + for (unsigned i = 0; i < img.entries.size(); ++i) + { + img.entries[i].tPage = 0; + img.entries[i].dirty = true; + } + } + + arrangeEntries(img); + + writePages(img); + + writeImgFile(base, img); + if (!header.empty()) + { + writeHeaderFile(header, img); + } + } + else + { + printUsage(programName); + return 1; + } +}