Mercurial > hg > batmud > maputils
view src/map2ppm.c @ 2470:d0aad04c3e61
th-libs now uses stdbool.h if possible, so we need to rename all
BOOL/TRUE/FALSE to bool/true/false.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 07 Dec 2022 13:23:46 +0200 |
parents | eba783bef2ee |
children | a23fba3fa943 |
line wrap: on
line source
/* * Convert BatMUD ASCII map to PPM or PNG image file * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2006-2022 Tecnic Software productions (TNSP) */ #include "libmaputils.h" #include "th_args.h" #include "th_string.h" #ifdef HAVE_LIBPNG #include <png.h> #endif char *srcFilename = NULL, *dstFilename = NULL; bool optUseOldFormat = false, optInputIsDiff = false, optUseANSI = false, optCityFormat = false; int optScale = 1; #ifdef HAVE_LIBPNG int optPNGLevel = -1; #endif /* Arguments */ static const th_optarg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, { 2, 'q', "quiet", "Be quiet", OPT_NONE }, { 3, 'd', "input-diff", "Input is a diff", OPT_NONE }, { 4, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, { 5, 'o', "output", "Output filename", OPT_ARGREQ }, { 6, 'A', "ansi-colors", "Use ANSI colors", OPT_NONE }, { 7, 'c', "city-format", "Input is a city map", OPT_NONE }, { 8, 's', "scale", "Scale value (integer)", OPT_ARGREQ }, #ifdef HAVE_LIBPNG { 9, 'P', "png", "PNG format output (compression level 0-9)", OPT_ARGREQ }, #endif }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp(void) { th_print_banner(stdout, th_prog_name, "[options] <input mapfile>"); th_args_help(stdout, optList, optListN, 0, 80 - 2); } bool argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: th_verbosity++; break; case 2: th_verbosity = -1; break; case 3: optInputIsDiff = true; THMSG(2, "Input is a 'diff', handling it as such.\n"); break; case 4: optUseOldFormat = true; THMSG(2, "Input is using old map symbols/colors.\n"); break; case 5: dstFilename = optArg; THMSG(2, "Output file set to '%s'.\n", dstFilename); break; case 6: optUseANSI = true; THMSG(2, "Using ANSI colors.\n"); break; case 7: optCityFormat = true; THMSG(2, "Input is handled as a city map\n"); break; case 8: optScale = atoi(optArg); if (optScale < 1 || optScale > 50) { THERR("Invalid scale value %d, must be 1 < x < 50.\n", optScale); return false; } THMSG(2, "Output scaling set to %d.\n", optScale); break; #ifdef HAVE_LIBPNG case 9: optPNGLevel = atoi(optArg); if (optPNGLevel < 0 || optPNGLevel > 9) { THERR("Invalid PNG compression factor %d, must be 0 < x < 9.\n", optPNGLevel); return false; } THMSG(2, "Output format set to PNG, compression level %d.\n", optPNGLevel); break; #endif default: THERR("Unknown option '%s'.\n", currArg); return false; } return true; } bool argHandleFile(char *currArg) { if (!srcFilename) srcFilename = currArg; else { THERR("Too many input map files specified!\n"); return false; } return true; } int writeImageData(const MapBlock *map, void *cbdata, bool (*writeRowCB)(void *, const uint8_t *, const size_t), const int scale) { int res = 0; uint8_t *row = NULL; // Allocate memory for row buffer if ((row = th_malloc(map->width * 3 * scale + 16)) == NULL) { res = -16; goto done; } for (int y = 0; y < map->height; y++) { uint8_t *ptr = row; for (int x = 0; x < map->width; x++) { int qr, qg, qb, c; qr = 255; qg = qb = 0; c = (uint8_t) map->data[(y * map->scansize) + x]; if (optInputIsDiff) { if (c < nmapPieces) { if (optUseANSI) { qr = qg = qb = 255; } else { c = c & 63; qr = mapPieces[c].cr; qg = mapPieces[c].cg; qb = mapPieces[c].cb; } } } else if (optUseANSI) { if ((c = muGetMapPieceColor(c, optUseOldFormat, optCityFormat)) >= 0) { qr = mapColors[c].cr; qg = mapColors[c].cg; qb = mapColors[c].cb; } } else { if ((c = muGetMapPieceIndex(c, optUseOldFormat, optCityFormat)) >= 0) { qr = mapPieces[c].cr; qg = mapPieces[c].cg; qb = mapPieces[c].cb; } } for (int xscale = 0; xscale < scale; xscale++) { *ptr++ = qr; *ptr++ = qg; *ptr++ = qb; } } for (int yscale = 0; yscale < scale; yscale++) { if (!writeRowCB(cbdata, row, map->width * 3 * scale)) { res = -32; goto done; } } } done: th_free(row); return res; } bool writePPMRow(void *cbdata, const uint8_t *row, const size_t len) { return fwrite(row, sizeof(uint8_t), len, (FILE *) cbdata) == len; } int writePPMFile(FILE *outFile, const MapBlock *map, const int scale) { // Write header for 24-bit PPM fprintf(outFile, "P6\n%d %d\n255\n", map->width * scale, map->height * scale); // Write image data return writeImageData(map, (void *) outFile, writePPMRow, scale); } #ifdef HAVE_LIBPNG bool writePNGRow(void *cbdata, const uint8_t *row, const size_t len) { png_structp png_ptr = cbdata; (void) len; if (setjmp(png_jmpbuf(png_ptr))) return false; png_write_row(png_ptr, row); return true; } int writePNGFile(FILE *outFile, const MapBlock *map, const int scale) { int width, height; png_structp png_ptr; png_infop info_ptr; width = map->width * scale; height = map->height * scale; // Create PNG structures png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { THERR("PNG: png_create_write_struct() failed.\n"); return -1; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { THERR("PNG: png_create_info_struct(%p) failed.\n", (void *) png_ptr); return -2; } if (setjmp(png_jmpbuf(png_ptr))) { THERR("PNG: Error during PNG init_io().\n"); return -3; } png_init_io(png_ptr, outFile); // Write PNG header info if (setjmp(png_jmpbuf(png_ptr))) { THERR("PNG: Error during writing header.\n"); return -4; } png_set_IHDR(png_ptr, info_ptr, width, height, 8, // bits per component PNG_COLOR_TYPE_RGB, // 3 components, RGB PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // png_set_gAMA(png_ptr, info_ptr, 2.2); png_write_info(png_ptr, info_ptr); // Write compressed image data if (setjmp(png_jmpbuf(png_ptr))) { THERR("PNG: Error during writing image data.\n"); return -5; } writeImageData(map, (void *) png_ptr, writePNGRow, scale); // Write footer if (setjmp(png_jmpbuf(png_ptr))) { THERR("PNG: Error during writing image footer.\n"); return -6; } png_write_end(png_ptr, NULL); // Dellocate shit if (png_ptr && info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); return 0; } #endif /* Main program */ int main(int argc, char *argv[]) { FILE *outFile = NULL; MapBlock *map = NULL; int ret = 0; // Initialize th_init("map2ppm", "ASCII map to PPM/PNG image converter", "0.5", NULL, NULL); th_verbosity = 0; // Parse arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) exit(1); if (srcFilename == NULL) { THERR("Nothing to do. (try --help)\n"); exit(0); } // Read input file THMSG(1, "Reading map file '%s'\n", srcFilename); if ((map = mapBlockParseFile(srcFilename, optInputIsDiff)) == NULL) { THERR("Error reading map file '%s'!\n", srcFilename); goto out; } // Open output file if (dstFilename == NULL) outFile = stdout; else if ((outFile = fopen(dstFilename, "wb")) == NULL) { THERR("Error opening output file '%s'!\n", dstFilename); goto out; } THMSG(1, "Outputting image of %dx%d ...\n", map->width * optScale, map->height * optScale); #ifdef HAVE_LIBPNG if (optPNGLevel >= 0) ret = writePNGFile(outFile, map, optScale); else #endif ret = writePPMFile(outFile, map, optScale); if (ret != 0) THERR("Image write failed, code=%d\n", ret); else THMSG(1, "Done.\n"); out: if (outFile != NULL) fclose(outFile); mapBlockFree(map); return ret; }