Initial commit.

This commit is contained in:
Zachary Hall 2023-11-17 12:30:24 -08:00
commit b412d4c279
15 changed files with 955 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
builddir/
testout/

113
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,113 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Generate automatic bitmap",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/builddir/graphicsconverter",
"args": [
"-in", "TEST.png",
"-out", "testout/TEST.B16"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"additionalSOLibSearchPath": "/home/catmeow/.cache/debuginfod_client/",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},
{
"description": "Enable debuginfod",
"text": "-gdb-set debuginfod enabled on",
"ignoreFailures": true
}
],
"preLaunchTask": "build"
},
{
"name": "Convert file to PNG",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/builddir/graphicsconverter",
"args": [
"-reverse",
"-in", "testout/PACK.16P.2B.B16",
"-out", "testout/PACK.16P.2B.PNG",
"-type", "t"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"additionalSOLibSearchPath": "/home/catmeow/.cache/debuginfod_client/",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},
{
"description": "Enable debuginfod",
"text": "-gdb-set debuginfod enabled on",
"ignoreFailures": true
}
],
"preLaunchTask": "build"
},
{
"name": "Load invalid file",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/builddir/graphicsconverter",
"args": [
"-in", "/dev/null/invalid",
"-out", "/dev/null",
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"additionalSOLibSearchPath": "/home/catmeow/.cache/debuginfod_client/",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},
{
"description": "Enable debuginfod",
"text": "-gdb-set debuginfod enabled on",
"ignoreFailures": true
}
],
"preLaunchTask": "build"
}
]
}

10
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
"C_Cpp.default.compileCommands": "builddir/compile_commands.json",
"mesonbuild.configureOnOpen": true,
"files.associations": {
"cstdint": "cpp",
"iosfwd": "cpp",
"*.ipp": "cpp",
"ios": "cpp"
}
}

15
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,15 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "meson",
"target": "graphicsconverter:executable",
"mode": "build",
"problemMatcher": [
"$meson-gcc"
],
"group": "build",
"label": "build"
}
]
}

10
LICENSE Normal file
View file

@ -0,0 +1,10 @@
Copyright 2023 Zachary Hall
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

BIN
PACK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# B16Converter
A converter to/from the B16/BMX bitmap format being developed for the Commander X16
## Building
This program requires ImageMagick, and Magick++ (which may come with ImageMagick)
Steps:
1. Clone the repo
2. cd into the repo directory
3. Run meson setup builddir
4. Run meson compile -C builddir
5. The binary is (Repo directory)/builddir/b16converter(.exe)
## Usage
Run the binary with the -in option specifying the input file and the -out option specifying the desired output file.
Use -reverse to convert B16 to a PC graphics format
Check the flags with -help
## Testing
Use the test.sh script from an environment with Bash available to automatically convert PACK.png and TEST.png to B16 at various resolutions and bitdepths, with and without dithering. It also converts back to PNG from the B16 files to test that functionality.
Once you have .b16 files, you can test it with my B16 viewer inside the X16 emulator.

BIN
TEST.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

330
bitmapx16.cpp Normal file
View file

@ -0,0 +1,330 @@
#include "bitmapx16.h"
#include <string.h>
#include <fstream>
#include <exception>
#define X16_IMG_START (512+32)
BitmapX16DebugFlags BitmapX16::debug = DebugNone;
float closeness_to_color(PaletteEntry a, PaletteEntry b) {
float closeness = ((float)((((float)a.r - (float)b.r) * (1 << 4)) + (((float)a.g - (float)b.g) * (1 << 8)) + ((float)a.b - (float)b.b)));
if (closeness < 0.0f) {
closeness = -closeness;
}
return closeness;
}
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() {
return bpp;
}
void BitmapX16::set_significant(uint8_t value) {
if (value >= (1 << bpp)) {
value = (1 << bpp) - 1;
}
significant_count = value;
quantize_colors = true;
}
uint8_t BitmapX16::get_significant() {
return significant_count;
}
size_t BitmapX16::get_width() {
return w;
}
size_t BitmapX16::get_height() {
return h;
}
void BitmapX16::enable_dithering(bool enabled) {
dither = enabled;
}
bool BitmapX16::dithering_enabled() {
return dither;
}
void BitmapX16::resize(size_t w, size_t h) {
printf("Resizing image to: (%lu, %lu)\n", w, h);
image->resize(Geometry(w, h));
}
void BitmapX16::queue_resize(size_t w, size_t h) {
tw = w;
th = h;
}
void BitmapX16::apply() {
if (tw != 0 && th != 0) {
resize(tw, th);
tw = 0;
th = 0;
}
if (bpp == 0) {
bpp = 8;
quantize_colors = true;
}
if (significant_count == 0) {
significant_count = (1 << bpp) - 1;
quantize_colors = true;
}
if (quantize_colors) {
image->quantizeColors(significant_count);
image->quantizeDither(dither);
if (dither) {
image->quantizeDitherMethod(Magick::FloydSteinbergDitherMethod);
}
image->quantize();
generate_palette();
}
}
uint8_t BitmapX16::extra_to_real_palette(uint8_t idx) {
return image_palette_count + idx;
}
void BitmapX16::write_x16(const char *filename) {
vector<uint8_t> buf;
size_t bufsize;
uint8_t pixels_per_byte;
vector<uint8_t> pixels;
size_t pixelCount;
pixels_per_byte = (8/bpp);
apply();
w = image->columns();
h = image->rows();
printf("Image size: (%lu, %lu)\n", w, h);
pixelCount = w * h * 3;
pixels.resize(pixelCount);
bufsize = 512+32+((w*h)/pixels_per_byte);
buf.resize(bufsize);
memset(buf.data(), 0, bufsize);
buf[0] = 0x42;
buf[1] = 0x4D;
buf[2] = 0x58;
buf[3] = 1; // Version
buf[4] = bpp;
switch (bpp) {
case 1:
buf[5] = 0;
break;
case 2:
buf[5] = 1;
break;
case 4:
buf[5] = 2;
break;
case 8:
buf[5] = 3;
break;
default:
printf("Error: Invalid bit depth.\n");
throw std::exception();
break;
}
buf[6] = w;
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[i] = 0; // Reserved bytes.
}
for (size_t i = 0; i < 256; i++) {
palette[i].write(buf.data() + (32+(i*2)));
}
for (size_t i = 0, x = 0, y = 0; i < (w * h); i++, x++) {
if (x >= w) {
x -= w;
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);
}
printf("Writing output file %s...\n", filename);
std::ofstream outfile(filename,std::ofstream::binary);
outfile.write((const char*)buf.data(), bufsize);
outfile.close();
loaded = true;
}
void BitmapX16::load_x16(const char *filename) {
vector<uint8_t> buf;
size_t bufsize = 0;
size_t bufpos = 0;
uint8_t pixels_per_byte;
vector<uint8_t> pixels;
bufsize = 3;
buf.resize(bufsize);
std::ifstream infile(filename, std::ifstream::binary);
infile.read((char*)buf.data() + bufpos, bufsize - bufpos);
bufpos += bufsize - bufpos;
uint8_t magic[3] = {0x42, 0x4D, 0x58};
for (uint8_t i = 0; i < 3; i++) {
if (buf[i] != magic[i]) {
printf("Error: Invalid magic bytes.\n");
throw std::exception();
}
}
bufsize += 10;
buf.resize(bufsize);
infile.read((char*)buf.data() + bufpos, bufsize - bufpos);
bufpos += bufsize - bufpos;
if (buf[3] > 1) {
printf("X16 bitmap version %u is unsupported!\n", buf[4]);
throw std::exception();
}
bpp = buf[4];
uint8_t vera_color_depth = buf[5]; // Ignore for now.
pixels_per_byte = (8 / bpp);
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;
buf.resize(bufsize);
infile.read((char*)buf.data() + bufpos, bufsize - bufpos);
bufpos += bufsize - bufpos;
for (size_t i = 0; i < 256; i++) {
palette[i] = 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);
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.
if (x >= w) {
x -= w;
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 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;
}
// Create the Magick++ image
image = new Image(w, h, "RGB", CharPixel, pixels.data());
// Clean up and set the loaded flag.
infile.close();
loaded = true;
}
void BitmapX16::write_pc(const char *filename) {
if (!loaded) {
printf("Error: Attempt to write unloaded file!\n");
throw std::exception();
}
image->write(filename);
}
void BitmapX16::load_pc(const char *filename) {
image = new Image(filename);
w = image->columns();
h = image->rows();
if (bpp == 0) set_bpp(8);
if (significant_count == 0) set_significant(1 << bpp);
apply();
loaded = true;
}
size_t BitmapX16::get_pixel_idx(size_t x, size_t y) {
return (y * w) + x;
}
size_t BitmapX16::get_byte_idx(size_t pixelidx) {
return pixelidx / (8/bpp);
}
uint8_t BitmapX16::get_inner_idx(size_t pixelidx) {
return pixelidx % (8/bpp);
}
uint8_t BitmapX16::get_bitmask() {
if (bitmask_bpp != bpp) {
bitmask = (1 << bpp) - 1;
}
return bitmask;
}
uint8_t BitmapX16::get_orable_pixel(uint8_t pixelinbyte, uint8_t color) {
return (color & get_bitmask()) << (bpp * ((8/bpp) - pixelinbyte - 1));
}
BitmapX16::BitmapX16() {
extra_palette_entries = vector<PaletteEntry>();
}
void BitmapX16::generate_palette() {
uint16_t max = (uint16_t)image->colorMapSize();
uint8_t color[3];
if (max > 256) max = 256;
if (bpp == 0) {
if (max <= 4) {
bpp = 2;
} else if (max <= 16) {
bpp = 4;
} else {
bpp = 8;
}
}
if (significant_count == 0) {
significant_count = max;
}
bitmask = (1 << bpp) - 1;
for (uint16_t i = 0; i < max; i++) {
ColorRGB map_color = image->colorMap(i);
palette[i] = PaletteEntry(map_color);
}
image_palette_count = max;
for (uint16_t i = max; i < 256; i++) {
if ((uint16_t)extra_palette_entries.size() > i - max) {
palette[i] = extra_palette_entries[i - max];
} else {
palette[i] = PaletteEntry();
}
}
return;
for (uint16_t i = 0; i < max; i++) {
printf("pallete[%u] = #%02x%02x%02x\n", i, palette[i].r, palette[i].g, palette[i].b);
}
}
uint8_t BitmapX16::add_palette_entry(PaletteEntry entry) {
extra_palette_entries.push_back(entry);
return (uint8_t)(extra_palette_entries.size() - 1);
}
uint8_t BitmapX16::color_to_palette_entry(const ColorRGB &rgb) {
PaletteEntry color(rgb);
float closeness = 100000.0f;
uint8_t output;
for (size_t i = 0; i < significant_count; i++) {
float possibility_closeness = closeness_to_color(palette[i], color);
//printf("Closeness: %f", possibility_closeness);
if (possibility_closeness < closeness) {
output = i;
closeness = possibility_closeness;
}
}
//PaletteEntry entry = palette[output];
//printf("Color: #%0x%0x%0x -> Palette entry#%0x%0x%0x, closeness: %f\n", color.r, color.g, color.b , entry.r, entry.g, entry.b, closeness);
return output;
}
BitmapX16::~BitmapX16() {
if (image != nullptr) {
delete image;
}
}
uint8_t BitmapX16::get_border_color() {
return border;
}
void BitmapX16::set_border_color(uint8_t idx) {
border = idx;
}

137
bitmapx16.h Normal file
View file

@ -0,0 +1,137 @@
#pragma once
#include <vector>
#include <Magick++.h>
#include "palette.h"
using namespace Magick;
using std::vector;
enum BitmapX16DebugFlags {
DebugNone = 0,
DebugShowPalette = (1 << 0),
};
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.
vector<PaletteEntry> extra_palette_entries;
/// \brief The amount of colors used within the palette
uint8_t significant_count = 0;
/// \brief The beginning of the colors used.
uint8_t significant_start = 0;
/// \brief Imagemagick image to apply modifications to and read/write to an X16 bitmap
Image *image = nullptr;
/// \brief Set to true to queue color quantization. Set automatically when needed.
bool quantize_colors = false;
/// \brief Current width
size_t w = 0;
/// \brief Current height
size_t h = 0;
/// \brief Target width
size_t tw = 0;
/// \brief Target height
size_t th = 0;
/// \brief Whether or not to dither
bool dither = false;
/// \brief Whether or not the image has been loaded properly
bool loaded = false;
/// \brief The cache of the bitmask to use for pixels
uint8_t bitmask = 0;
/// \brief The bits per pixel the bitmap was created with.
uint8_t bitmask_bpp = 0;
/// \brief The palette entry corrosponding to the border color
uint8_t border = 0;
/// \brief The amount of palette entries the image itself is supposed to have.
uint8_t image_palette_count = 0;
/// \brief Generates a palette from the current image
void generate_palette();
/// \brief Actually resizes the image. User code should call queue_resize then apply
/// \param w The width to resize to
/// \param h The height to resize to
void resize(size_t w, size_t h);
/// \brief Gets the pixel index within this image based on X and Y values, as well as the width of the image
/// \param x The X value of the pixel
/// \param y The Y value of the pixel
/// \returns The pixel index within this image
size_t get_pixel_idx(size_t x, size_t y);
/// \brief Returns the index within a byte buffer containing raw VERA image data a pixel is located in based on the pixel index and bpp
/// \param pixelidx The index of the pixel
/// \returns The byte index
size_t get_byte_idx(size_t pixelidx);
/// \brief Returns the index of a pixel within a byte based on the index of the pixel.
/// \param pixelidx The index of the pixel
/// \returns The pixel index within a byte
uint8_t get_inner_idx(size_t pixelidx);
/// \brief Returns the correct bitmask for a pixel. May be calculated if the cache is invalid.
/// \returns A bitmask for any first pixel in a byte in the image. Can be shifted to get different pixels within a byte.
uint8_t get_bitmask();
/// \brief Gets a value that can be OR'ed to a byte in a buffer based on the pixel index, the BPP of the image, and the value to set.
/// \param inner_idx The index of the pixel within the byte
/// \param pixel The value of the pixel
/// \returns A value that can be OR'ed with a byte in a buffer that doesn't have a pixel at the specified index.
uint8_t get_orable_pixel(uint8_t inner_idx, uint8_t pixel);
/// \brief Converts a color to the nearest palette entry
/// \param rgb The color to convert
/// \returns The palette entry index
uint8_t color_to_palette_entry(const ColorRGB &rgb);
uint8_t extra_to_real_palette(uint8_t idx);
public:
/// \brief Sets the border color extra palette entry.
/// \param idx The index of the palette entry, must be an index into the extra palette entry list.
void set_border_color(uint8_t idx);
/// \brief Gets the current extra palette entry of the border color
/// \returns The border color palette entry from the extra palette entry list
uint8_t get_border_color();
/// \brief Adds an entry to the list of extra palette entries. Not guaranteed to continue existing after a new palette entry has been added.
/// \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 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);
/// \brief Returns the bits per pixel of the image
/// \returns The bits per pixel of the image, one of 1, 2, 4, or 8
uint8_t get_bpp();
/// \brief Sets the maximum amount of colors to be used.
/// \param value The maximum amount of colors to use.
void set_significant(uint8_t value);
/// \brief Returns the maximum amount of colors to be used
/// \returns The maximum amount of colors once written
uint8_t get_significant();
/// \brief Queues a resize operation. Call BitmapX16::apply() to apply
/// \param w The width to resize to
/// \param h The height to resize to
void queue_resize(size_t w, size_t h);
/// \brief Gets the width of the image
/// \returns The width of the image
size_t get_width();
/// \brief Gets the height of the image
/// \returns The height of the image
size_t get_height();
/// \brief Enables or disables dithering
/// \param enabled Pass true to enable, false to disable
void enable_dithering(bool enabled);
/// \brief Returns the status of the dithering flag
/// \returns The value of the dithering flag
bool dithering_enabled();
/// \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
/// \param filename The path to the file to write
void write_pc(const char *filename);
/// \brief Applies queued operations and wri the image to a Commander X16-compatible file
/// \param filename The path to the file to write
void write_x16(const char *filename);
/// \brief Loads a PC-compatible image file
/// \param filename The path to the file to load
void load_pc(const char *filename);
/// \brief Loads a Commander X16-compatible image file
/// \param filename The path to the file to load
void load_x16(const char *filename);
/// \brief The debug flags to use.
static BitmapX16DebugFlags debug;
/// \brief Constructs an unloaded X16-compatible bitmap image. Call load_pc or load_x16 before using any other functions.
BitmapX16();
~BitmapX16();
};

220
main.cpp Normal file
View file

@ -0,0 +1,220 @@
#include <Magick++.h>
#include <iostream>
#include <fstream>
#include <map>
#include "palette.h"
#include <string.h>
#include <vector>
#include <string>
#include <exception>
#include "bitmapx16.h"
using std::vector;
using std::map;
using std::stoi;
using namespace Magick;
void usage() {
printf("Usage: veraconvert: [options]\n");
printf("Options may be:\n");
printf("-in <input>\n");
printf("\tSets the input file.\n");
printf("-out <output>\n");
printf("\tSets the output file");
printf("-bpp <bpp>\n");
printf("\tSets the desired bits per pixel. May be 0 (default), 2, 4, or 8. 0: automatic.\n");
printf("-significant <color count>\n");
printf("\tSets the desired number of significant palette entries. Must be at least 0 and at most 256. 0 means automatic. Default: 0\n");
printf("-resize <width> <height>\n");
printf("\tResizes the image before converting.\n");
printf("-dither\n");
printf("\tEnables dithering of the output\n");
printf("-border <r> <g> <b>\n");
printf("\tIf possible, adds a border color with the specified RGB values which are in the range of 0-15.\n");
printf("-reverse\n");
printf("\tConverts to PC formats. Incompatible with -dither, -type, and -significant - they will be ignored.\n");
printf("-help\n");
printf("\tDisplays this help message.\n");
exit(1);
}
int main(int argc, char **argv) {
const char *input = NULL, *output = NULL;
size_t tw = 0, th = 0; // Target width & height;
uint8_t tbpp = 0;
uint16_t tcolorcount = 0;
bool dither = false;
bool reverse = false;
uint8_t br, bg, bb;
bool border_set = false;
InitializeMagick(*argv);
argc--;
argv++;
while (argc > 0) {
if (!strcmp(argv[0], "-in")) {
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
input = argv[0];
argc--;
argv++;
} else if (!strcmp(argv[0], "-out")) {
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
output = argv[0];
argc--;
argv++;
} else if (!strcmp(argv[0], "-bpp")) {
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
tbpp = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
switch (tbpp) {
case 0:
case 2:
case 4:
case 8:
break;
default:
usage();
}
argc--;
argv++;
} else if (!strcmp(argv[0], "-significant")) {
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
tcolorcount = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
if (tcolorcount > 256) {
usage();
}
argc--;
argv++;
} else if (!strcmp(argv[0], "-resize")) {
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
tw = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
th = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
argc--;
argv++;
} else if (!strcmp(argv[0], "-border")) {
argc--;
argv++;
border_set = true;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
br = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
bg = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
argc--;
argv++;
if (!argc || argv[0][0] == '-') {
usage();
}
try {
bb = stoi(argv[0]);
} catch (std::exception&) {
usage();
}
argc--;
argv++;
if (br > 15 || bg > 15 || bb > 15) {
printf("Border RGB values must be in the range of 0-15.\n");
usage();
}
} else if (!strcmp(argv[0], "-dither")) {
dither = true;
argc--;
argv++;
} else if (!strcmp(argv[0], "-reverse")) {
reverse = true;
argc--;
argv++;
} else if (!strcmp(argv[0], "-help")) {
usage();
} else {
printf("Error: Invalid command line argument.\n");
usage();
}
}
if (input == NULL || output == NULL) {
printf("Input and output must be specified!\n");
usage();
}
if (tbpp == 0) tbpp = 8;
if (tcolorcount == 0) tcolorcount = (1 << tbpp);
try {
BitmapX16 bitmap;
if (reverse) {
printf("Converting %s to a PC format...\n", input);
bitmap.load_x16(input);
} else {
printf("Using at most %u colors at %u bpp\n", tcolorcount, tbpp);
printf("Converting %s to BMX16...\n", input);
bitmap.load_pc(input);
}
if (tw != 0 && th != 0) {
bitmap.queue_resize(tw, th);
}
if (reverse) {
bitmap.write_pc(output);
} else {
bitmap.enable_dithering(dither);
bitmap.set_bpp(tbpp);
bitmap.set_significant(tcolorcount);
if (border_set) {
PaletteEntry entry(br, bg, bb);
bitmap.set_border_color(bitmap.add_palette_entry(entry));
}
bitmap.apply();
bitmap.write_x16(output);
}
} catch (std::exception &e) {
printf("Failed to convert image '%s'!\n", input);
}
return 0;
}

18
meson.build Normal file
View file

@ -0,0 +1,18 @@
project('graphicsconverter', 'cpp',
version : '0.1',
default_options : ['warning_level=3',
'cpp_std=c++14'])
deps = [
dependency('Magick++', version : '>=7.0')
]
srcs = [
'palette.cpp',
'bitmapx16.cpp',
'main.cpp'
]
exe = executable('b16converter', srcs,
install : true,
dependencies : deps)
test('basic', exe)

35
palette.cpp Normal file
View file

@ -0,0 +1,35 @@
#include "palette.h"
ColorRGB PaletteEntry::toColor() {
ColorRGB color;
color.red(((double)r) / 15.0);
color.green(((double)g) / 15.0);
color.blue(((double)b) / 15.0);
return color;
}
void PaletteEntry::write(uint8_t *ptr) {
ptr[0] = ((g & 0b1111) << 4) | (b & 0b1111);
ptr[1] = r;
}
PaletteEntry::PaletteEntry(uint8_t *ptr) {
b = (ptr[0] & 0b1111);
g = ((ptr[0] >> 4) & 0b1111);
r = ptr[1] & 0b1111;
}
PaletteEntry::PaletteEntry(uint8_t r, uint8_t g, uint8_t b) {
this->r = r;
this->g = g;
this->b = b;
}
PaletteEntry::PaletteEntry(const ColorRGB &rgb) {
r = (uint8_t)((rgb.red()) * 15);
g = (uint8_t)((rgb.green()) * 15);
b = (uint8_t)((rgb.blue()) * 15);
}
uint16_t PaletteEntry::hash() {
return ((r & 0b1111) << 8) | ((g & 0b1111) << 4) | (b & 0b1111);
}
PaletteEntry::PaletteEntry() {
r = 0;
g = 0;
b = 0;
}

17
palette.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
#include <Magick++.h>
using namespace Magick;
class PaletteEntry {
public:
uint8_t r;
uint8_t g;
uint8_t b;
uint16_t hash();
void write(uint8_t *ptr);
ColorRGB toColor();
PaletteEntry();
PaletteEntry(uint8_t *ptr);
PaletteEntry(uint8_t r, uint8_t g, uint8_t b);
PaletteEntry(const ColorRGB &color);
};

24
test.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
oldpwd="$(pwd)"
cd "$(dirname "$0")"
meson setup builddir
meson compile -C builddir || exit $?
images=("TEST.png" "PACK.png")
outdir="testout"
mkdir "$outdir"
for img in "${images[@]}"; do
for bpp in 2 4 8; do
for size in "8x8" "16x16" "32x32" "64x64" "320x240" "640x480"; do
width="$(echo -n "$size" | cut -dx -f1)"
height="$(echo -n "$size" | cut -dx -f2)"
name="$(echo -n "$img" | sed 's/\.png$//')"
name="$(printf "%s.%sP.%sB" "$name" "$width" "$bpp")"
./builddir/graphicsconverter -in "$img" -out "$outdir/$name.B16" -bpp "$bpp" -resize "$width" "$height" -border 15 0 15
./builddir/graphicsconverter -in "$img" -out "$outdir/$name.D.B16" -bpp "$bpp" -resize "$width" "$height" -dither -border 15 0 15
./builddir/graphicsconverter -reverse -in "$outdir/$name.B16" -out "$outdir/$name.PNG" -resize "$width" "$height"
./builddir/graphicsconverter -reverse -in "$outdir/$name.D.B16" -out "$outdir/$name.D.PNG" -resize "$width" "$height" -dither
done
done
done
cd "$oldpwd"