From: Lukas Krickl Date: Wed, 22 Mar 2023 05:26:43 +0000 (+0100) Subject: Added png2bin tool to make it easier to create maps and sprites X-Git-Url: https://git.krickl.dev/?a=commitdiff_plain;h=5fbc22efff623cbdac9ebcec5563de9f3aa0734a;p=turtlebay%2F.git Added png2bin tool to make it easier to create maps and sprites --- diff --git a/.gitignore b/.gitignore index e660fd9..0f76984 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ bin/ +tools/png2bin/bin +tools/png2bin/lib +tools/png2bin/obj +tools/png2bin/src/include/*.gch +tools/png2bin/src/*.txt +tools/png2bin/*.sprite diff --git a/main.asm b/main.asm index b5efd16..007a6ef 100644 --- a/main.asm +++ b/main.asm @@ -97,8 +97,10 @@ P0STARTX = $4D P0STARTY = $32 P1STARTX = 0 P1STARTY = 0 -M0HEIGHT = 8 +M0HEIGHT = 4 M0RESPAWNT = 255 +MAPCOUNT = 0 +OFFSETPERMAP = 6 ;=============================================================================== ; Define Start of Cartridge @@ -186,8 +188,8 @@ VerticalSync sta WSYNC sta VSYNC - lda #$30 ; - sta NUSIZ0 ; set missile0 to be 8x + lda #%00100000 ; + sta NUSIZ0 ; set missile0 to be 2x sta NUSIZ1 Sleep12 ; jsr here to sleep for 12 cycles rts @@ -671,6 +673,7 @@ NoP0PFCollision jmp Reset NoReset jsr ResetPPositions + jsr SetM0Pos NoP0P1Collision ; p0 and p1 did not collide! ; now we dio p0 m0 collision. m0 must be collected by turtle to advance @@ -797,6 +800,25 @@ NextMap adc Level ; score is 3 + level sta Score + + ; now we roll for next map + jsr Random + lda Rand8 + and #MAPCOUNT ; only allow MAPCOUNT for roll + tay + cpy #0 ; 0 does not require an offset + beq NextMapDone + + lda #OFFSETPERMAP + sta Temp + lda #0 + ; now add 6 for each number rolled +NextMapLoop + adc Temp + dey + bne NextMapLoop + sta CurrentMap +NextMapDone rts SetObjectColours @@ -1045,7 +1067,7 @@ Colours: .byte $C6 ; green - goes into COLUP0, color for player1 and missile0 .byte $86 ; blue - goes into COLUP1, color for player0 and missile1 .byte $46 ; red - goes into COLUPF, color for playfield and ball - .byte $00 ; black - goes into COLUBK, color for background + .byte $EE ; yellowish - goes into COLUBK, color for background .byte $0E ; white - goes into COLUP0, B&W for player0 and missile0 .byte $06 ; dark grey - goes into COLUP1, B&W for player1 and missile1 .byte $0A ; light grey - goes into COLUPF, B&W for playfield and ball diff --git a/tools/png2bin/example.png b/tools/png2bin/example.png new file mode 100644 index 0000000..1008b8d Binary files /dev/null and b/tools/png2bin/example.png differ diff --git a/tools/png2bin/example2.png b/tools/png2bin/example2.png new file mode 100644 index 0000000..a5e8952 Binary files /dev/null and b/tools/png2bin/example2.png differ diff --git a/tools/png2bin/makefile b/tools/png2bin/makefile new file mode 100644 index 0000000..de81d40 --- /dev/null +++ b/tools/png2bin/makefile @@ -0,0 +1,26 @@ +CC = g++ +CFLAGS = -g `libpng-config --cflags` +LIBS = `libpng-config --ldflags` + +LIBDIR = ./lib +IDIR = ./src/include +ODIR = ./obj +BINDIR = ./bin +SRCDIR = ./src + +_DEPS = imageconverter.h util.h colour.h +DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) + +_OBJ = main.o imageconverter.o util.o colour.o +OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) + +$(ODIR)/%.o: $(SRCDIR)/%.cpp $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +png2bin: $(OBJ) + $(CC) -o $(BINDIR)/$@ $^ $(LIBS) + +.PHONY: clean +clean: + rm $(ODIR)/*.o + rm $(BINDIR)/* diff --git a/tools/png2bin/readme.md b/tools/png2bin/readme.md new file mode 100644 index 0000000..ca654fd --- /dev/null +++ b/tools/png2bin/readme.md @@ -0,0 +1,3 @@ +This is a simple tool used to convert N by N sized images to byte arrays. +I used it to simplify making sprites and maps. +All images must be multiples of 8 in size! diff --git a/tools/png2bin/src/colour.cpp b/tools/png2bin/src/colour.cpp new file mode 100644 index 0000000..ee6e401 --- /dev/null +++ b/tools/png2bin/src/colour.cpp @@ -0,0 +1,13 @@ +#include +#include"include/colour.h" +namespace Colour { + // global colours + Modifier red(Colour::FG_RED); + Modifier def(Colour::FG_DEFAULT); + + // class definition + + Modifier::Modifier(Code pCode): code(pCode) { + + } +} diff --git a/tools/png2bin/src/imageconverter.cpp b/tools/png2bin/src/imageconverter.cpp new file mode 100644 index 0000000..3a38168 --- /dev/null +++ b/tools/png2bin/src/imageconverter.cpp @@ -0,0 +1,153 @@ +#include"include/imageconverter.h" +#include"include/colour.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace Colour; + +namespace Image { + ImageConverter::ImageConverter(Settings *settings) { + this->settings = settings; + try { + this->image = new png::image(settings->inputfile); + } catch(std::exception &e) { + std::cerr << red << "[FATAL] " << def << e.what() << std::endl; + exit(-1); + } + + if(settings->verbose) { + std::cout << "Creating array of size: (" << image->get_width() << "/" << image->get_height() << ")" << std::endl; + } + + /* + init converted array + this array contains every pixel -> access like this: + x * width + y + This simulates a 2d array with less of a memory mess + */ + this->converted = new unsigned int[image->get_width() * image->get_height()]; + if(!this->converted) { + std::cerr << red << "[FATAL] " << def + << "Unable to allocate enough memory for converted image!" << std::endl; + exit(-1); + } + } + + ImageConverter::~ImageConverter() { + // TODO error when freeing + if(this->converted) { + delete[] this->converted; + } + + if(this->image) { + delete this->image; + } + } + + void ImageConverter::convertPixels() { + if(settings->verbose) { + std::cout << "Converting pixels" << std::endl; + } + + for(int w = 0; w < this->image->get_width(); w++) { + for(int h = 0; h < this->image->get_height(); h++) { + png::rgb_pixel pixel = image->get_pixel(w, h); + + // converted pixel + int pixelConverted = 0; + + // write pixel into file if it is in pallette + + // convert colour to hex string just like the user input would specify + std::stringstream stream; + stream << std::hex << std::setw(2) << std::setfill('0') << (short)pixel.red; + std::string hexColour = "#" + stream.str(); + + // reset stream for next int + stream.str(""); + stream << std::hex << std::setw(2) << std::setfill('0') << (short)pixel.green; + hexColour = hexColour + stream.str(); + + stream.str(""); + stream << std::hex << std::setw(2) << std::setfill('0') << (short)pixel.blue; + hexColour = hexColour + stream.str(); + + bool colourFound = false; + for(int i = 0; i < settings->coloursLen; i++) { + std::string colourCheck = "#"; + stream.str(""); + stream << std::hex << std::setw(6) << settings->colours[i]; + colourCheck = colourCheck + stream.str(); + + // if colour is not in there default to 0 and output a warning + if(colourCheck == hexColour) { + if(settings->verbose) { + std::cout << "Pixel (" << w << "/" << h << ") " + << "Colour found for " << hexColour << ". Converted int: " << i << std::endl; + } + pixelConverted = i; + colourFound = true; + break; + } + } + + if(!colourFound) { + std::cerr << "Pixel (" << w << "/" << h << ") " + << red << "[WARNING] " << def << "Colour " << hexColour << " not found in list. Defaulting to 0 " + << std::endl; + } + + if(settings->verbose) { + std::cout << "Pixel (" << w << "/" << h << ") " + << std::setw(2) << std::setfill('0') + << (short)pixel.red << " " << std::setw(2) << std::setfill('0') << + (short)pixel.green << " " << std::setw(2) << std::setfill('0') + << (short)pixel.blue + << " " << hexColour << std::endl; + } + this->converted[w * image->get_width() + h] = pixelConverted; + if(settings->verbose) { + std::cout << "Set " << this->converted[w * image->get_width() + h] << "(" << + w * image->get_width() + h << "th element)\n\n"; + } + } + } + } + + void ImageConverter::writePixels() { + std::ofstream outfile; + + outfile.open(settings->outfile); + if(settings->outputBase == "detailed") { + if(settings->verbose) std::cout << "Outputting sprite in detailed mode!\n"; + + // outputting to file + unsigned short stepCounter = image->get_width() / 8; + for(int sc = 0; sc < stepCounter; sc++) { + // each of these loops will write 8 by 8 pixels at most so we loop N times to get all arrays + outfile << "; Sprite of " << settings->inputfile << " Part " << sc << "/" << stepCounter << std::endl; + for(int x = 0; x < image->get_width(); x++) { + outfile << ".byte "; + + for(int y = stepCounter * 8 - 8; y < 8 * stepCounter; y++) { + if(y < image->get_width()) { + outfile << converted[x * image->get_width() + y]; + } else { + outfile << 0; + } + } + outfile << std::endl; + } + outfile << std::endl; + } + + outfile << std::endl << std::endl; + } + outfile.close(); + } +} diff --git a/tools/png2bin/src/include/colour.h b/tools/png2bin/src/include/colour.h new file mode 100644 index 0000000..3954d97 --- /dev/null +++ b/tools/png2bin/src/include/colour.h @@ -0,0 +1,29 @@ +#ifndef COLOUR_H +#define COLOUR_H + +namespace Colour { + enum Code { + FG_RED = 31, + FG_GREEN = 32, + FG_BLUE = 34, + FG_DEFAULT = 39, + BG_RED = 41, + BG_GREEN = 42, + BG_BLUE = 44, + BG_DEFAULT = 49 + }; + + class Modifier { + Code code; + public: + Modifier(Code pCode); + friend std::ostream& operator<<(std::ostream& os, const Modifier& mod) { + return os << "\033[" << mod.code << "m"; + } + }; + + extern Modifier red; + extern Modifier def; +} + +#endif diff --git a/tools/png2bin/src/include/imageconverter.h b/tools/png2bin/src/include/imageconverter.h new file mode 100644 index 0000000..ea4ada9 --- /dev/null +++ b/tools/png2bin/src/include/imageconverter.h @@ -0,0 +1,27 @@ +#ifndef PNG_CONVERTER_H +#define PNG_CONVERTER_H + +#include +#include +#include"util.h" + +namespace Image { + class ImageConverter { + private: + png::image *image; + + unsigned int *converted; // converted image for each pixel + + Settings *settings; + public: + ImageConverter(Settings *settings); + + ~ImageConverter(); + + void convertPixels(); + + void writePixels(); + }; +} + +#endif diff --git a/tools/png2bin/src/include/util.h b/tools/png2bin/src/include/util.h new file mode 100644 index 0000000..dea3f7f --- /dev/null +++ b/tools/png2bin/src/include/util.h @@ -0,0 +1,64 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +// this struct holds program settings +struct Settings { + unsigned int minorVersion; + unsigned int majorVersion; + unsigned int patch; + std::string appname; + std::string dev; + std::string github; + std::string email; + + std::string appmode; // mode the app is in (sprite is the only one right now!) + std::string outputBase; + std::string inputfile; + std::string outfile; + uint32_t *colours; // pointer to as many colours are the user input + unsigned short coloursLen; + bool verbose; +}; + +extern Settings globalsettings; + +/* +This function inits the program and settings struct +*/ +void init(); + +/* +This function parses CLI arguments based on criteria +*/ +void parseArgs(int argc, char **argv); + +/* +This is just a helper function to make my life easier +*/ +unsigned int hexStringToInt(const std::string &hexstr); + +/* +This function determines if the arguments needed +are still covered by an array with size n +*/ +bool extraArgumentsNeeded(int needed, int argc, int index); + +/* +This function splits a string at a specific char and +returns an std::vector of all split elements +*/ +std::vector splitStr(const std::string &text, char sep); + +int setBit(int num, unsigned int bit); + +int unsetBit(int num, unsigned int bit); + +int readBit(int num, unsigned int bit); + +int toggleBit(int num, unsigned int bit); + +#endif diff --git a/tools/png2bin/src/main.cpp b/tools/png2bin/src/main.cpp new file mode 100644 index 0000000..df2cc5a --- /dev/null +++ b/tools/png2bin/src/main.cpp @@ -0,0 +1,16 @@ +#include +#include +#include +#include +#include"include/imageconverter.h" +#include"include/util.h" + +int main(int argc, char **argv) { + init(); + parseArgs(argc, argv); + + // start conversion + Image::ImageConverter converter(&globalsettings); + converter.convertPixels(); + converter.writePixels(); +} diff --git a/tools/png2bin/src/util.cpp b/tools/png2bin/src/util.cpp new file mode 100644 index 0000000..0720006 --- /dev/null +++ b/tools/png2bin/src/util.cpp @@ -0,0 +1,132 @@ +#include"include/util.h" +#include +#include +#include +#include +#include"include/colour.h" +#include + +Settings globalsettings; + +using namespace Colour; + +void init() { + // init settings struct with default values + globalsettings.appname = "png2bin"; + globalsettings.dev = "Lukas Krickl"; + globalsettings.email = "lukaskrickl@gmail.com"; + globalsettings.minorVersion = 0; + globalsettings.majorVersion = 0; + globalsettings.patch = 1; + globalsettings.github = "https://github.com/unlink2/png2bin"; + + // set default options + globalsettings.appmode = "sprite"; + globalsettings.outputBase = "detailed"; + globalsettings.colours = (uint32_t*)malloc(5 * sizeof(uint32_t)); + + // set default colour pallette + globalsettings.colours[0] = 0x000000; + globalsettings.colours[1] = 0xFFFFFF; + + globalsettings.coloursLen = 2; + + globalsettings.outfile = "./out.sprite"; + + globalsettings.verbose = false; +} + +void parseArgs(int argc, char **argv) { + if(argc <= 1) { + std::cout << globalsettings.appname << ": No arguments given! Use -h for help!\n"; + } + for(int index = 0; index < argc; index++) { + std::string arg = argv[index]; // convert to std::string for ease of use + + // simple version info + if(arg == "version" || arg == "-v") { + std::cout << globalsettings.appname << " Version: " << + globalsettings.majorVersion << "." << globalsettings.minorVersion << + "." << globalsettings.patch << ". Developed by: " << globalsettings.dev << " (" << + globalsettings.email << ") Github: " << globalsettings.github << std::endl; + } else if(arg == "-h") { + std::cout << globalsettings.appname << " help page. Use man " << globalsettings.appname << + " for more information!\n\n" << + "Arguments:\n" << + "-h\t(optional) Outputs this menu\n" << + "-v\t(optional) Outputs version information\n" << + "-f ./example.png\t(required) Set the file that is to be converted\n" << + "-o ./output\t(optional) Set the output file and directory\n" << + "-b detailed/compressed\t(optional) Set the base to output the data in (defaults to detailed)\n" << + "verbose\t(optional) Verbose mode."; + } else if(arg == "-f") { + if(extraArgumentsNeeded(1, argc, index)) { + index++; + globalsettings.inputfile = argv[index]; + } else { + std::cerr << red << "[FATAL]" << def << " Insufficent arguments: Use -h for syntax!" << std::endl; + exit(-1); + } + } else if(arg == "-o") { + if(extraArgumentsNeeded(1, argc, index)) { + index++; + globalsettings.outfile = argv[index]; + } else { + std::cerr << red << "[FATAL]" << def << " Insufficent arguments: Use -h for syntax!" << std::endl; + exit(-1); + } + } else if(arg == "-b") { + if(extraArgumentsNeeded(1, argc, index)) { + index++; + globalsettings.outputBase = argv[index]; + } else { + std::cerr << red << "[FATAL]" << def << " Insufficent arguments: Use -h for syntax!" << std::endl; + exit(-1); + } + } else if(arg == "verbose") { + globalsettings.verbose = true; + } + } +} + +unsigned int hexStringToInt(const std::string &hexstr) { + return std::stoul(hexstr, nullptr, 16); +} + +bool extraArgumentsNeeded(int needed, int argc, int index) { + if(index + needed >= argc) { + return false; + } + + return true; +} + +std::vector splitStr(const std::string &text, char sep) { + std::vector tokens; + std::size_t start = 0, end = 0; + while ((end = text.find(sep, start)) != std::string::npos) { + tokens.push_back(text.substr(start, end - start)); + start = end + 1; + } + tokens.push_back(text.substr(start)); + return tokens; +} + +int setBit(int num, unsigned int bit) { + return num | 1u << bit; +} + +int unsetBit(int num, unsigned int bit) { + return num & ~(1u << bit); +} + +int readBit(int num, unsigned int bit) { + int mask = 1 << bit; + int masked_n = num & mask; + int thebit = masked_n >> bit; + return thebit; +} + +int toggleBit(int num, unsigned int bit) { + return num ^ (1u << bit); +}