view map2ppm.c @ 1771:72adabd8e746

More cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 27 Oct 2017 05:10:08 +0300
parents cc59f80b0e78
children 79dd960610cb
line wrap: on
line source

/*
 * Convert BatMUD ASCII map to PPM or PNG image file
 * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen)
 * (C) Copyright 2006-2015 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,
        *destFilename = 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);
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 1:
        th_verbosityLevel++;
        break;

    case 2:
        th_verbosityLevel = -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:
        destFilename = optArg;
        THMSG(2, "Output file set to '%s'.\n", destFilename);
        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(MapBlock *map, void *cbdata, BOOL (*writeRowCB)(void *, uint8_t *, size_t), 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].r;
                        qg = mapPieces[c].g;
                        qb = mapPieces[c].b;
                    }
                }
            }
            else
            if (optUseANSI)
            {
                if ((c = muGetMapPieceColor(c, optUseOldFormat, optCityFormat)) >= 0)
                {
                    qr = mapColors[c].r;
                    qg = mapColors[c].g;
                    qb = mapColors[c].b;
                }
            }
            else
            {
                if ((c = muGetMapPieceIndex(c, optUseOldFormat, optCityFormat)) >= 0)
                {
                    qr = mapPieces[c].r;
                    qg = mapPieces[c].g;
                    qb = mapPieces[c].b;
                }
            }

            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, uint8_t *row, size_t len)
{
    return fwrite(row, sizeof(uint8_t), len, (FILE *) cbdata) == len;
}


int writePPMFile(FILE *outFile, MapBlock *map, 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, uint8_t *row, 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, MapBlock *map, 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", 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;
    MapBlock *map;
    int ret;

    // Initialize
    th_init("map2ppm", "ASCII map to PPM/PNG image converter", "0.5", NULL, NULL);
    th_verbosityLevel = 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);
        exit(1);
    }

    // Open output file
    if (destFilename == NULL)
        outFile = stdout;
    else
    if ((outFile = fopen(destFilename, "wb")) == NULL)
    {
        THERR("Error opening output file '%s'!\n",
            destFilename);
        mapBlockFree(map);
        exit(1);
    }

    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");

    fclose(outFile);

    mapBlockFree(map);

    return ret;
}