Added png2bin tool to make it easier to create maps and sprites
authorLukas Krickl <lukas@krickl.dev>
Wed, 22 Mar 2023 05:26:43 +0000 (06:26 +0100)
committerLukas Krickl <lukas@krickl.dev>
Wed, 22 Mar 2023 05:26:43 +0000 (06:26 +0100)
13 files changed:
.gitignore
main.asm
tools/png2bin/example.png [new file with mode: 0644]
tools/png2bin/example2.png [new file with mode: 0644]
tools/png2bin/makefile [new file with mode: 0644]
tools/png2bin/readme.md [new file with mode: 0644]
tools/png2bin/src/colour.cpp [new file with mode: 0644]
tools/png2bin/src/imageconverter.cpp [new file with mode: 0644]
tools/png2bin/src/include/colour.h [new file with mode: 0644]
tools/png2bin/src/include/imageconverter.h [new file with mode: 0644]
tools/png2bin/src/include/util.h [new file with mode: 0644]
tools/png2bin/src/main.cpp [new file with mode: 0644]
tools/png2bin/src/util.cpp [new file with mode: 0644]

index e660fd93d3196215552065b1e63bf6a2f393ed86..0f76984db49c38df70f123d8059eef00bf25b8a8 100644 (file)
@@ -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
index b5efd1662d4f520486043ecc46ea1ba5e803fedf..007a6ef6f158460e8994d3f00054b3ee3728f3a4 100644 (file)
--- 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 (file)
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 (file)
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 (file)
index 0000000..de81d40
--- /dev/null
@@ -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 (file)
index 0000000..ca654fd
--- /dev/null
@@ -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 (file)
index 0000000..ee6e401
--- /dev/null
@@ -0,0 +1,13 @@
+#include <ostream>
+#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 (file)
index 0000000..3a38168
--- /dev/null
@@ -0,0 +1,153 @@
+#include"include/imageconverter.h"
+#include"include/colour.h"
+#include <exception>
+#include<string>
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <fstream>
+#include <bitset>
+
+using namespace Colour;
+
+namespace Image {
+  ImageConverter::ImageConverter(Settings *settings) {
+    this->settings = settings;
+    try {
+      this->image = new png::image<png::rgb_pixel>(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 (file)
index 0000000..3954d97
--- /dev/null
@@ -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 (file)
index 0000000..ea4ada9
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef PNG_CONVERTER_H
+#define PNG_CONVERTER_H
+
+#include<png++/png.hpp>
+#include<cstdint>
+#include"util.h"
+
+namespace Image {
+  class ImageConverter {
+  private:
+    png::image<png::rgb_pixel> *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 (file)
index 0000000..dea3f7f
--- /dev/null
@@ -0,0 +1,64 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <vector>
+#include<iostream>
+#include<cstdint>
+
+// 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<std::string> 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 (file)
index 0000000..df2cc5a
--- /dev/null
@@ -0,0 +1,16 @@
+#include<iostream>
+#include<stdlib.h>
+#include<stdio.h>
+#include<png++/png.hpp>
+#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 (file)
index 0000000..0720006
--- /dev/null
@@ -0,0 +1,132 @@
+#include"include/util.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include"include/colour.h"
+#include<string>
+
+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<std::string> splitStr(const std::string &text, char sep) {
+  std::vector<std::string> 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);
+}