diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fb29e10 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lzsa"] + path = lzsa + url = https://github.com/emmanuel-marty/lzsa.git diff --git a/bitmapx16.cpp b/bitmapx16.cpp index 850fe04..cadb013 100644 --- a/bitmapx16.cpp +++ b/bitmapx16.cpp @@ -1,4 +1,5 @@ #include "bitmapx16.h" +#include #include #include #include @@ -16,9 +17,6 @@ float BitmapX16::closeness_to_color(PaletteEntry a, PaletteEntry b) { void BitmapX16::set_bpp(uint8_t bpp) { this->bpp = bpp; quantize_colors = true; - if (get_significant() >= (1 << bpp)) { - set_significant((1 << bpp) - 1); - } } uint8_t BitmapX16::get_bpp() const { return bpp; @@ -81,6 +79,8 @@ void BitmapX16::write_x16(const char *filename) { size_t bufsize; uint8_t pixels_per_byte; vector pixels; + vector outpixels; + size_t outpixelsize; size_t pixelCount; pixels_per_byte = (8/bpp); apply(); @@ -89,8 +89,10 @@ void BitmapX16::write_x16(const char *filename) { printf("Image size: (%lu, %lu)\n", w, h); pixelCount = w * h * 3; pixels.resize(pixelCount); - bufsize = 512+32+((w*h)/pixels_per_byte); + bufsize = palette_entries.size()*2+32; buf.resize(bufsize); + outpixelsize = ((w*h)/pixels_per_byte); + outpixels.resize(outpixelsize); memset(buf.data(), 0, bufsize); buf[0] = 0x42; buf[1] = 0x4D; @@ -120,14 +122,19 @@ void BitmapX16::write_x16(const char *filename) { buf[7] = w >> 8; buf[8] = h; buf[9] = h >> 8; - buf[10] = extra_to_real_palette(border); - buf[11] = significant_count; - buf[12] = significant_start; - for (size_t i = 13; i < 32; i++) { + buf[10] = palette_entries.size(); + buf[11] = significant_start; + uint16_t image_start = 32+(2*palette_entries.size())+1; + buf[12] = image_start; + buf[13] = image_start>>8; + buf[14] = compress ? 255 : 0; + --image_start; + for (size_t i = 15; i < 31; i++) { buf[i] = 0; // Reserved bytes. } - for (size_t i = 0; i < 256; i++) { - palette[i].write(buf.data() + (32+(i*2))); + buf[31] = extra_to_real_palette(border); + for (size_t i = 0; i < palette_entries.size(); i++) { + palette_entries[i].write(buf.data() + (32+(i*2))); } for (size_t i = 0, x = 0, y = 0; i < (w * h); i++, x++) { if (x >= w) { @@ -135,11 +142,23 @@ void BitmapX16::write_x16(const char *filename) { y += 1; } ColorRGB px = image->pixelColor(x, y); - size_t imagestart = (32+512); size_t pixelIdx = get_pixel_idx(x, y); size_t imagebyteidx = get_byte_idx(pixelIdx); uint8_t pixelinbyte = get_inner_idx(pixelIdx); - buf[imagestart + imagebyteidx] |= (color_to_palette_entry(px) & get_bitmask()) << (bpp * pixelinbyte); + outpixels[imagebyteidx] |= (color_to_palette_entry(px) & get_bitmask()) << (bpp * pixelinbyte); + } + bufsize += outpixelsize; + buf.resize(bufsize); + if (compress) { + size_t compressed_size = lzsa_compress_inmem(outpixels.data(), buf.data() + image_start, outpixelsize, bufsize - image_start, LZSA_FLAG_RAW_BLOCK, 1, 2); + if (compressed_size == (size_t)-1) { + printf("Error compressing file\n"); + throw std::exception(); + } + bufsize -= outpixelsize - compressed_size; + buf.resize(bufsize); + } else { + memcpy(buf.data() + image_start, outpixels.data(), outpixelsize); } printf("Writing output file %s...\n", filename); std::ofstream outfile(filename,std::ofstream::binary); @@ -151,7 +170,11 @@ void BitmapX16::load_x16(const char *filename) { vector buf; size_t bufsize = 0; size_t bufpos = 0; + uint8_t palette_used = 0; uint8_t pixels_per_byte; + uint16_t image_start = 0; + bool compressed = false; + vector decompression_buffer; vector pixels; bufsize = 3; buf.resize(bufsize); @@ -173,7 +196,7 @@ void BitmapX16::load_x16(const char *filename) { throw std::exception(); } } - bufsize += 10; + bufsize += 12; buf.resize(bufsize); infile.read((char*)buf.data() + bufpos, bufsize - bufpos); bufpos += bufsize - bufpos; @@ -187,24 +210,37 @@ void BitmapX16::load_x16(const char *filename) { w = buf[6] | (buf[7] << 8); h = buf[8] | (buf[9] << 8); printf("Image size: (%lu, %lu)\n", w, h); - border = buf[10]; - significant_count = buf[11]; - significant_start = buf[12]; - bufsize += 19; - bufsize += 512; - bufsize += (w * h)/pixels_per_byte; + palette_used = buf[10]; + significant_start = buf[11]; + significant_count = palette_used; + image_palette_count = 0; + image_start = buf[12] | (buf[13] << 8); + if ((int8_t)buf[14] == -1) { + compressed = true; + } + --image_start; + bufsize = std::filesystem::file_size(filename); buf.resize(bufsize); infile.read((char*)buf.data() + bufpos, bufsize - bufpos); bufpos += bufsize - bufpos; - - for (size_t i = 0; i < 256; i++) { - palette[(uint8_t)(i-significant_start)] = PaletteEntry(buf.data() + (32+(i*2))); + border = buf[31]; + palette_entries.clear(); + for (size_t i = 0; i < palette_used; i++) { + palette_entries.push_back(PaletteEntry(buf.data() + (32+(i*2)))); } - // Border is always an extra palette entry. - extra_palette_entries.push_back(palette[border]); - border = extra_palette_entries.size() - 1; // Get pixel vector for later use as image data. pixels.resize(w * h * 3); + decompression_buffer.resize(w*h/pixels_per_byte); + if (compressed) { + int version; + size_t bytes = lzsa_decompress_inmem(buf.data() + image_start, decompression_buffer.data(), bufsize - image_start, decompression_buffer.size(), LZSA_FLAG_RAW_BLOCK, &version); + if (bytes == (size_t)-1) { + printf("Error decompressing file!\n"); + throw std::exception(); + } + } else { + memcpy(decompression_buffer.data(), buf.data() + image_start, decompression_buffer.size()); + } size_t outpixelidx = 0; for (size_t i = 0, x = 0, y = 0; i < (w * h); i++, x++) { // Make sure Y is incremented when necessary. @@ -213,17 +249,19 @@ void BitmapX16::load_x16(const char *filename) { y += 1; } // Get the required data. - size_t imagestart = (32+512); size_t pixelIdx = get_pixel_idx(x, y); size_t imagebyteidx = get_byte_idx(pixelIdx); uint8_t pixelinbyte = get_inner_idx(pixelIdx); - uint8_t paletteidx = (buf[imagestart + imagebyteidx] >> (pixelinbyte * bpp)) & (get_bitmask()); - PaletteEntry entry = palette[paletteidx]; + uint8_t paletteidx = (decompression_buffer[imagebyteidx] >> (pixelinbyte * bpp)) & (get_bitmask()); + PaletteEntry entry = palette_entries[paletteidx]; uint8_t r = entry.r << 4, g = entry.g << 4, b = entry.b << 4; // Add the pixel data to the pixels array. pixels[outpixelidx++] = r; pixels[outpixelidx++] = g; pixels[outpixelidx++] = b; + if (paletteidx > image_palette_count+significant_start) { + image_palette_count = paletteidx-significant_start; + } } // Create the Magick++ image image = new Image(w, h, "RGB", CharPixel, pixels.data()); @@ -238,13 +276,6 @@ void BitmapX16::write_pc(const char *filename) { } image->write(filename); } -PaletteEntry BitmapX16::get_palette_entry(uint8_t idx, bool extra) const { - if (extra) { - return extra_palette_entries.at(idx); - } else { - return palette[idx]; - } -} void BitmapX16::load_pc(const char *filename) { image = new Image(filename); w = image->columns(); @@ -274,7 +305,7 @@ uint8_t BitmapX16::get_orable_pixel(uint8_t pixelinbyte, uint8_t color) { } BitmapX16::BitmapX16() { - extra_palette_entries = vector(); + palette_entries = vector(); } void BitmapX16::generate_palette() { uint16_t max = (uint16_t)image->colorMapSize(); @@ -296,27 +327,22 @@ void BitmapX16::generate_palette() { significant_count = max; } bitmask = (1 << bpp) - 1; - for (uint16_t i = 0; i < significant_start; i++) { - palette[i] = PaletteEntry(); - } - for (uint16_t i = significant_start; i < max+significant_start; i++) { - ColorRGB map_color = image->colorMap(i-significant_start); - palette[i] = PaletteEntry(map_color); - } image_palette_count = max; - for (uint16_t i = max+significant_start; i < 256; i++) { - if ((uint16_t)extra_palette_entries.size() > i - (max+significant_start)) { - palette[i] = extra_palette_entries[i - (max+significant_start)]; - } else { - palette[i] = PaletteEntry(); - } + palette_entries.clear(); + for (uint16_t i = 0; i < image_palette_count; i++) { + ColorRGB map_color = image->colorMap(i); + palette_entries.push_back(PaletteEntry(map_color)); } + for (uint16_t i = 0; i < extra_palette_entries.size(); i++) { + palette_entries.push_back(extra_palette_entries[i]); + } + significant_count = palette_entries.size(); if (debug & DebugShowPalette) { - for (size_t i = 0; i < 256; i++) { - uint8_t significant_end = significant_start+significant_count; + for (size_t i = 0; i < palette_entries.size(); i++) { + uint8_t significant_end = significant_start+image_palette_count; bool significant = i >= significant_start && i < significant_end; - bool extra = i >= significant_end && i < significant_end+extra_palette_entries.size(); - printf("palette[%02x] = %s %s\n", (uint16_t)i, palette[i].to_string().c_str(), significant ? "(Significant)" : extra ? "(Extra)" : ""); + bool extra = i >= image_palette_count && i < image_palette_count+extra_palette_entries.size(); + printf("palette[%02x] = %s %s\n", (uint16_t)i, palette_entries[i].to_string().c_str(), significant ? "(Significant)" : extra ? "(Extra)" : ""); } } } @@ -331,8 +357,8 @@ uint8_t BitmapX16::color_to_palette_entry(const ColorRGB &rgb) { if (debug & DebugShowCloseness) { printf("Closest color for %s: ", color.to_string().c_str()); } - for (size_t i = significant_start; i < significant_count+significant_start; i++) { - float possibility_closeness = closeness_to_color(palette[i], color); + for (size_t i = 0; i < image_palette_count; i++) { + float possibility_closeness = closeness_to_color(palette_entries[i], color); //printf("Closeness: %f", possibility_closeness); if (possibility_closeness < closeness) { output = i; @@ -340,7 +366,7 @@ uint8_t BitmapX16::color_to_palette_entry(const ColorRGB &rgb) { } } if (debug & DebugShowCloseness) { - PaletteEntry output_entry = palette[output]; + PaletteEntry output_entry = palette_entries[output]; printf("%s\n", output_entry.to_string().c_str()); } //PaletteEntry entry = palette[output]; @@ -369,4 +395,10 @@ uint8_t BitmapX16::get_border_color() const { } void BitmapX16::set_border_color(uint8_t idx) { border = idx; +} +void BitmapX16::enable_compression(bool enabled) { + compress = enabled; +} +bool BitmapX16::compression_enabled() const { + return compress; } \ No newline at end of file diff --git a/bitmapx16.h b/bitmapx16.h index 70b3a43..f010f30 100644 --- a/bitmapx16.h +++ b/bitmapx16.h @@ -12,9 +12,9 @@ enum BitmapX16DebugFlags : int { class BitmapX16 { /// \brief Bits per pixel of the image uint8_t bpp = 0; - /// \brief The palette entries used within this image - PaletteEntry palette[256]; - /// \brief Any extra palette entries to add. + /// \brief The list of palette entries. + vector palette_entries; + /// \brief A copy of the extra palette entries. vector extra_palette_entries; /// \brief The amount of colors used within the palette uint8_t significant_count = 0; @@ -24,6 +24,8 @@ class BitmapX16 { Image *image = nullptr; /// \brief Set to true to queue color quantization. Set automatically when needed. bool quantize_colors = false; + /// \brief Enables LZSA compression + bool compress = false; /// \brief Current width size_t w = 0; /// \brief Current height @@ -88,11 +90,6 @@ class BitmapX16 { /// \param entry The new entry to add /// \returns The palette index within the extra palette entries of the new entry uint8_t add_palette_entry(PaletteEntry entry); - /// \brief Obtains a palette entry within the image - /// \param id The index of the palette entry - /// \param extra Set to true for an extra palette entry, false for a normal palette entry. - /// \returns The palette entry requested. - PaletteEntry get_palette_entry(uint8_t id, bool extra) const; /// \brief Sets the bits per pixel of the image /// \param bpp The bits per pixel of the image, one of 1, 2, 4, or 8 void set_bpp(uint8_t bpp); @@ -124,6 +121,12 @@ class BitmapX16 { /// \brief Returns the status of the dithering flag /// \returns The value of the dithering flag bool dithering_enabled() const; + /// \brief Enables or disables LZSA compression + /// \param enabled Pass true to enable, false to disable + void enable_compression(bool enabled); + /// \brief Returns the status of the LZSA compression flag + /// \returns The value of the compression flag. + bool compression_enabled() const; /// \brief Applies queued operations to the internal representation of the image void apply(); /// \brief Applies queued operations and writes the image to a PC-compatible file diff --git a/lzsa b/lzsa new file mode 160000 index 0000000..15ee2df --- /dev/null +++ b/lzsa @@ -0,0 +1 @@ +Subproject commit 15ee2dfe118eeb8f7683ca44f64821c3a61ca1e5 diff --git a/main.cpp b/main.cpp index 93d8c16..d41e359 100644 --- a/main.cpp +++ b/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "bitmapx16.h" using std::vector; using std::map; @@ -35,6 +36,8 @@ void usage() { printf("\tEnables debugging flags. May contain any number of single-letter debugging flags.\n"); printf("\tFlags: p = show palette entry, c = show palette closeness lookups\n"); printf("\tUse an ! before a flag to disable it.\n"); + printf("-compress\n"); + printf("\tCompresses the image with LZSA compression\n"); printf("-help\n"); printf("\tDisplays this help message.\n"); exit(1); @@ -47,6 +50,7 @@ int main(int argc, char **argv) { const char *error_msg_part = "load the image"; bool dither = false; bool reverse = false; + bool compress = false; uint8_t br, bg, bb; bool border_set = false; InitializeMagick(*argv); @@ -180,6 +184,10 @@ int main(int argc, char **argv) { reverse = true; argc--; argv++; + } else if (!strcmp(argv[0], "-compress")) { + compress = true; + argc--; + argv++; } else if (!strcmp(argv[0], "-help")) { usage(); } else if (!strcmp(argv[0], "-debug")) { @@ -224,15 +232,15 @@ int main(int argc, char **argv) { if (tcolorcount == 0) tcolorcount = (1 << tbpp); try { BitmapX16 bitmap; + bitmap.enable_compression(compress); if (reverse) { printf("Loading X16 bitmap file '%s' to be convertedto a PC format...\n", input); bitmap.load_x16(input); printf("Image has %u significant colors starting at %u and is %u bpp\n", bitmap.get_significant(), bitmap.get_significant_start(), bitmap.get_bpp()); - PaletteEntry border = bitmap.get_palette_entry(bitmap.get_border_color(), true); - printf("Border color: %s\n", border.to_string().c_str()); } else { printf("Loading PC image file '%s' to be converted to the X16 bitmap format...\n", input); printf("Using at most %u colors (excluding border color) at %u bpp\n", tcolorcount, tbpp); + if (bitmap.compression_enabled()) printf("Compression enabled\n"); bitmap.load_pc(input); } if (tw != 0 && th != 0) { diff --git a/meson.build b/meson.build index f09f4c5..3cef0ad 100644 --- a/meson.build +++ b/meson.build @@ -1,19 +1,44 @@ -project('graphicsconverter', 'cpp', +project('graphicsconverter', ['cpp', 'c'], version : '0.1', default_options : ['warning_level=3', 'cpp_std=c++17', 'default_library=static']) deps = [ - dependency('Magick++', version : '>=6.9.11') + dependency('Magick++', version : '>=6.9.11'), + dependency('openmp') ] srcs = [ 'palette.cpp', 'bitmapx16.cpp', - 'main.cpp' + 'main.cpp', + 'lzsa/src/dictionary.c', + 'lzsa/src/expand_block_v1.c', + 'lzsa/src/expand_block_v2.c', + 'lzsa/src/expand_context.c', + 'lzsa/src/expand_inmem.c', + 'lzsa/src/expand_streaming.c', + 'lzsa/src/frame.c', + 'lzsa/src/matchfinder.c', + 'lzsa/src/shrink_block_v1.c', + 'lzsa/src/shrink_block_v2.c', + 'lzsa/src/shrink_context.c', + 'lzsa/src/shrink_inmem.c', + 'lzsa/src/shrink_streaming.c', + 'lzsa/src/stream.c', + 'lzsa/src/libdivsufsort/lib/divsufsort_utils.c', + 'lzsa/src/libdivsufsort/lib/divsufsort.c', + 'lzsa/src/libdivsufsort/lib/sssort.c', + 'lzsa/src/libdivsufsort/lib/trsort.c' +] +incdirs = [ + 'lzsa/src', + 'lzsa/src/libdivsufsort/include' ] exe = executable('b16converter', srcs, install : true, + include_directories: incdirs, + c_args: ['-DHAVE_CONFIG_H=1','-D__STDC_LIMIT_MACROS','-D__STDC_CONSTANT_MACROS','-D__STDC_FORMAT_MACROS'], dependencies : deps) test('basic', exe) diff --git a/test.sh b/test.sh index 393e74d..5747dd9 100755 --- a/test.sh +++ b/test.sh @@ -31,11 +31,12 @@ resize=() enable_defaults=1 dither=1 reverse=1 +enable_compression=1 enable_reverse=1 enable_dither=1 debug_flags="" outdir="testout" -OPTIONS=$(getopt -o "b:hp:i:r:no:d:" --long "help,use-program:,input-image:,output-bpp:,resize:,no-defaults,output-dir:,debug:,no-reverse,no-dither" -- "$@") +OPTIONS=$(getopt -o "b:hp:i:r:no:d:" --long "help,use-program:,input-image:,output-bpp:,resize:,no-defaults,output-dir:,debug:,no-reverse,no-dither,no-compress" -- "$@") if [ $? != 0 ]; then echo "Getopt error." usage @@ -79,6 +80,10 @@ while [ -n "$1" ]; do enable_dither=0 shift ;; + --no-compress) + enable_compression=0 + shift + ;; -d|--debug) debug_flags="${debug_flags}$2" shift 2 @@ -110,20 +115,30 @@ run() { for img in "${images[@]}"; do for bpp in "${bpps[@]}"; do for size in "${resize[@]}"; do - width="$(echo -n "$size" | cut -dx -f1)" - height="$(echo -n "$size" | cut -dx -f2)" - name="$(basename "$img" | sed 's/\.png$//')" - name="$(printf "%s.%sP.%sB" "$name" "$width" "$bpp")" - run "$converter" -in "$img" -out "$outdir/$name.BMX" -bpp "$bpp" -resize "$width" "$height" -border 15 0 15 -debug "$debug_flags" - if [ $enable_dither -ne 0 ]; then - run "$converter" -in "$img" -out "$outdir/$name.D.BMX" -bpp "$bpp" -resize "$width" "$height" -dither -border 15 0 15 -debug "$debug_flags" - fi - if [ $enable_reverse -ne 0 ]; then - run "$converter" -reverse -in "$outdir/$name.BMX" -out "$outdir/$name.PNG" -resize "$width" "$height" -debug "$debug_flags" - if [ $enable_dither -ne 0 ]; then - run "$converter" -reverse -in "$outdir/$name.D.BMX" -out "$outdir/$name.D.PNG" -resize "$width" "$height" -dither -debug "$debug_flags" + for compressflag in -compress ""; do + width="$(echo -n "$size" | cut -dx -f1)" + height="$(echo -n "$size" | cut -dx -f2)" + name="$(basename "$img" | sed 's/\.png$//')" + name="$(printf "%s.%sP.%sB" "$name" "$width" "$bpp")" + extraflags=() + if [ -n "$compressflag" ]; then + if [ $enable_compression -eq 0 ]; then + continue + fi + extraflags+=( "$compressflag" ) + name+=".C" fi - fi + run "$converter" "${extraflags[@]}" -in "$img" -out "$outdir/$name.BMX" -bpp "$bpp" -resize "$width" "$height" -border 15 0 15 -debug "$debug_flags" + if [ $enable_dither -ne 0 ]; then + run "$converter" "${extraflags[@]}" -in "$img" -out "$outdir/$name.D.BMX" -bpp "$bpp" -resize "$width" "$height" -dither -border 15 0 15 -debug "$debug_flags" + fi + if [ $enable_reverse -ne 0 ]; then + run "$converter" "${extraflags[@]}" -reverse -in "$outdir/$name.BMX" -out "$outdir/$name.PNG" -resize "$width" "$height" -debug "$debug_flags" + if [ $enable_dither -ne 0 ]; then + run "$converter" "${extraflags[@]}" -reverse -in "$outdir/$name.D.BMX" -out "$outdir/$name.D.PNG" -resize "$width" "$height" -dither -debug "$debug_flags" + fi + fi + done done done done