view tools/64vw.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents cbac4912992c
children b1e392da8346
line wrap: on
line source

/*
 * 64vw - Displayer for various C64 graphics formats via libSDL
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2019 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "dmlib.h"
#include "dmargs.h"
#include "dmfile.h"
#include "libgfx.h"
#include "lib64gfx.h"
#include "lib64util.h"
#include <SDL.h>


#define SET_SKIP_AMOUNT 10


int     optVFlags = 0;
int     optScrWidth, optScrHeight;
int     optForcedFormat = -1;
BOOL    optInfoOnly  = FALSE,
        optProbeOnly = FALSE,
        optListOnly  = FALSE;
size_t  noptFilenames1 = 0, noptFilenames2 = 0;
char    **optFilenames = NULL;
char    *optCharROMFilename = NULL;
DMC64Palette *optC64Palette = NULL;
char    *optC64PaletteFile = NULL;

DMC64MemBlock setCharROM;


static const DMOptArg optList[] =
{
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 1, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 2,   0, "fs",         "Fullscreen", OPT_NONE },
    { 3, 'S', "scale",      "Scale image by factor (1-10)", OPT_ARGREQ },
    { 4, 'f', "format",     "Force input format (see --formats)", OPT_ARGREQ },
    { 5, 'F', "formats",    "List supported input formats", OPT_NONE },
    { 6, 'i', "info",       "Print information only (no display)", OPT_NONE },
    { 7, 'l', "list",       "Output list of files that were recognized (implies -i)", OPT_NONE },
    { 8, 'P', "probe",      "Probe only (do not attempt to decode the image)", OPT_NONE },
    { 9,   0, "char-rom",   "Set character ROM file to be used.", OPT_ARGREQ },
    {10, 'p', "palette" ,   "Set C64 palette to be used (see -p list).", OPT_ARGREQ },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


void dmSetScaleFactor(float factor)
{
    optScrWidth = (int) ((float) D64_SCR_WIDTH * factor * D64_SCR_PAR_XY);
    optScrHeight = (int) ((float) D64_SCR_HEIGHT * factor);
}


void argShowFormats()
{
    printf(
    "Available C64 bitmap formats (-f <frmt>):\n"
    " frmt | Type            | Description\n"
    "------+-----------------+-------------------------------------\n"
    );
    for (int i = 0; i < ndmC64ImageFormats; i++)
    {
        const DMC64ImageFormat *fmt = dmC64ImageFormatsSorted[i];
        char buf[64];
        printf("%-6s| %-15s | %s%s\n",
            fmt->fext,
            dmC64GetImageTypeString(buf, sizeof(buf), fmt->format->type, FALSE),
            fmt->name,
            fmt->flags & DM_FMT_BROKEN ? " [BROKEN]" : "");
    }
    printf("%d formats supported.\n", ndmC64ImageFormats);
}


void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName, "[options] <input image file>");
    dmArgsPrintHelp(stdout, optList, optListN, 0);
}


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

        case 1:
            dmVerbosity++;
            break;

        case 2:
            optVFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
            break;

        case 3:
            {
                float factor;
                if (sscanf(optArg, "%f", &factor) == 1)
                {
                    if (factor < 1 || factor >= 10)
                    {
                        dmErrorMsg("Invalid scale factor %1.0f, see help for valid values.\n", factor);
                        return FALSE;
                    }

                    dmSetScaleFactor(factor);
                }
                else
                {
                    dmErrorMsg("Invalid scale factor '%s'.\n", optArg);
                    return FALSE;
                }
            }
            break;

        case 4:
            optForcedFormat = -1;
            for (int i = 0; i < ndmC64ImageFormats; i++)
            {
                const DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
                if (fmt->fext != NULL &&
                    strcasecmp(optArg, fmt->fext) == 0)
                {
                    optForcedFormat = i;
                    break;
                }
            }

            if (optForcedFormat < 0)
            {
                dmErrorMsg("Invalid image format argument '%s'.\n", optArg);
                return FALSE;
            }
            break;

        case 5:
            argShowFormats();
            exit(0);
            break;

        case 7:
            optListOnly = TRUE;
            // Fallthrough

        case 6:
            if (dmVerbosity < 1)
                dmVerbosity = 1;
            optInfoOnly = TRUE;
            break;

        case 8:
            if (dmVerbosity < 1)
                dmVerbosity = 1;
            optProbeOnly = TRUE;
            break;

        case 9:
            optCharROMFilename = optArg;
            break;

        case 10:
            return argHandleC64PaletteOption(optArg, &optC64Palette, &optC64PaletteFile);

        default:
            dmErrorMsg("Unimplemented option argument '%s'.\n", currArg);
            return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile1(char *filename)
{
    (void) filename;

    noptFilenames1++;
    return TRUE;
}


BOOL argHandleFile2(char *filename)
{
    if (noptFilenames2 < noptFilenames1)
    {
        optFilenames[noptFilenames2++] = filename;
        return TRUE;
    }
    else
        return FALSE;
}


int dmReadC64Image(const char *filename, const DMC64ImageFormat *forced,
    const DMC64ImageFormat **fmt, DMC64Image **cimage)
{
    Uint8 *dataBuf = NULL;
    size_t dataSize;
    DMGrowBuf tmp;
    int ret;

    if ((ret = dmReadDataFile(NULL, filename, &dataBuf, &dataSize)) != DMERR_OK)
        goto exit;

    dmGrowBufConstCreateFrom(&tmp, dataBuf, dataSize);

    if (optProbeOnly)
        ret = dmC64ProbeBMP(&tmp, fmt) != DM_PROBE_SCORE_FALSE ? DMERR_OK : DMERR_NOT_SUPPORTED;
    else
        ret = dmC64DecodeBMP(cimage, &tmp, 0, 2, fmt, forced);

exit:
    dmFree(dataBuf);
    return ret;
}


int dmDecodeC64Image(DMC64Image *cimage, const DMC64ImageFormat *fmt,
    SDL_Surface *surf, const DMC64ImageConvSpec *spec)
{
    DMImage bmap;
    BOOL charDataSet;
    int res;

    memset(&bmap, 0, sizeof(bmap));
    bmap.size     = surf->pitch * surf->h;
    bmap.data     = surf->pixels;
    bmap.pitch    = surf->pitch;
    bmap.width    = surf->w;
    bmap.height   = surf->h;
    
    if ((res = dmPaletteCopy(&bmap.pal, spec->pal)) != DMERR_OK)
    {
        dmErrorMsg("Could not create copy of palette.\n");
        return res;
    }

    if (cimage->charData[0].data == NULL)
    {
        memcpy(&cimage->charData[0], &setCharROM, sizeof(DMC64MemBlock));
        charDataSet = TRUE;
    }
    else
        charDataSet = FALSE;

    if (fmt->format->convertFrom != NULL)
        res = fmt->format->convertFrom(&bmap, cimage, fmt, spec);
    else
        res = dmC64ConvertGenericBMP2Image(&bmap, cimage, fmt, spec);

    if (charDataSet)
        memset(&cimage->charData[0], 0, sizeof(DMC64MemBlock));

    SDL_SetPaletteColors(surf->format->palette, (SDL_Color *) bmap.pal->colors, 0, bmap.pal->ncolors);

    dmPaletteFree(bmap.pal);
    return res;
}

#if 0
        DIR *dirh;
        struct dirent *entry;

        if ((dirh = opendir(npath)) == NULL)
        {
            int err = th_get_error();
            THERR("Could not open directory '%s': %s\n",
                path, th_error_str(err));
            ret = FALSE;
            goto out;
        }

        while ((entry = readdir(dirh)) != NULL)
        if (entry->d_name[0] != '.')
        {
            if (!argHandleFileDir(npath, entry->d_name, pattern))
            {
                ret = FALSE;
                goto out;
            }
        }

        closedir(dirh);
#endif


int main(int argc, char *argv[])
{
    const DMC64ImageFormat *forced;
    DMC64ImageConvSpec optSpec;
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Texture *texture = NULL;
    SDL_Surface *surf = NULL;
    BOOL initSDL = FALSE, exitFlag, needRedraw;
    size_t currIndex, prevIndex;
    int res;

    // Initialize pre-requisites
    if ((res = dmLib64GFXInit()) != DMERR_OK)
    {
        dmErrorMsg("Could not initialize lib64gfx: %s\n",
            dmErrorStr(res));
        goto exit;
    }

    dmSetScaleFactor(2.0);
    memset(&optSpec, 0, sizeof(optSpec));
    memset(&setCharROM, 0, sizeof(setCharROM));

    dmInitProg("64vw", "Displayer for various C64 graphics formats", "0.4", NULL, NULL);

    // Parse arguments, round #1
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile1, OPTH_BAILOUT))
        exit(1);

    if (noptFilenames1 == 0)
    {
        dmErrorMsg("No input file(s) specified, perhaps you need some --help\n");
        goto exit;
    }

    // Allocate space for filename pointers
    if ((optFilenames = dmCalloc(noptFilenames1, sizeof(char *))) == NULL)
    {
        dmErrorMsg("Could not allocate memory for input file list.\n");
        goto exit;
    }

    // Assign the filename pointers
    if (!dmArgsProcess(argc, argv, optList, optListN,
        NULL, argHandleFile2, OPTH_BAILOUT | OPTH_ONLY_OTHER))
        goto exit;

    // Check for forced input format
    if (optForcedFormat >= 0)
    {
        forced = &dmC64ImageFormats[optForcedFormat];
        dmMsg(0, "Forced %s format image, type %d, %s\n",
            forced->name, forced->format->type, forced->fext);
    }
    else
        forced = NULL;

    // If we are simply displaying file information, no need to initialize SDL etc
    if (optInfoOnly || optProbeOnly)
    {
        for (size_t n = 0; n < noptFilenames2; n++)
        {
            char *filename = optFilenames[n];
            const DMC64ImageFormat *fmt = NULL;
            DMC64Image *cimage = NULL;

            if ((res = dmReadC64Image(filename, forced, &fmt, &cimage)) != DMERR_OK)
            {
                if (!optListOnly && res != DMERR_NOT_SUPPORTED)
                {
                    dmErrorMsg("Could not decode file '%s': %s\n",
                        filename, dmErrorStr(res));
                }
            }
            else
            if (optListOnly)
            {
                fprintf(stdout, "%s\n", filename);
            }
            else
            {
                fprintf(stdout, "\n%s\n", filename);
                dmC64ImageDump(stdout, cimage, fmt, "  ");
            }

            dmC64ImageFree(cimage);
        }
        goto exit;
    }

    if (optC64PaletteFile != NULL)
    {
        if ((res = dmHandleExternalPalette(optC64PaletteFile, &optSpec.pal)) != DMERR_OK)
            goto exit;
    }
    else
    {
        // No palette file specified, use internal palette
        if (optC64Palette == NULL)
            optC64Palette = &dmC64DefaultPalettes[0];
            
        dmMsg(1, "Using internal palette '%s' (%s).\n",
            optC64Palette->name, optC64Palette->desc);

        optSpec.cpal = optC64Palette;

        if ((res = dmC64PaletteFromC64Palette(&optSpec.pal, optC64Palette, FALSE)) != DMERR_OK)
        {
            dmErrorMsg("Could not setup palette: %s\n",
                dmErrorStr(res));
            goto exit;
        }
    }

    // Check character ROM filename
    if (optCharROMFilename == NULL)
        optCharROMFilename = DM_DEF_CHARGEN;

    // Attempt to read character ROM
    dmMsg(1, "Using character ROM file '%s'.\n",
        optCharROMFilename);

    if ((res = dmReadDataFile(NULL, optCharROMFilename,
        &setCharROM.data, &setCharROM.size)) != DMERR_OK)
    {
        dmErrorMsg("Could not read character ROM from '%s'.\n",
            optCharROMFilename);
    }

    // Initialize libSDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
    {
        dmErrorMsg("Could not initialize SDL: %s\n", SDL_GetError());
        goto exit;
    }
    initSDL = TRUE;

    // Open window
    if ((window = SDL_CreateWindow(dmProgName,
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        optScrWidth, optScrHeight,
        optVFlags | SDL_WINDOW_RESIZABLE
        //| SDL_WINDOW_HIDDEN
        )) == NULL)
    {
        dmErrorMsg("Can't create an SDL window: %s\n", SDL_GetError());
        goto exit;
    }

    if ((renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC)) == NULL)
    {
        dmErrorMsg("Can't create an SDL renderer: %s\n", SDL_GetError());
        goto exit;
    }

//    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");

    // Start main loop
    currIndex = 0;
    prevIndex = 1;
    needRedraw = TRUE;
    exitFlag = FALSE;
    while (!exitFlag)
    {
        SDL_Event event;
        while (SDL_PollEvent(&event))
        switch (event.type)
        {
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                    case SDLK_q:
                        exitFlag = TRUE;
                        break;

                    case SDLK_DOWN:
                    case SDLK_LEFT:
                        if (currIndex > 0)
                            currIndex--;
                        else
                            currIndex = 0;
                        break;

                    case SDLK_SPACE:
                    case SDLK_UP:
                    case SDLK_RIGHT:
                        if (currIndex < noptFilenames2 - 1)
                            currIndex++;
                        else
                            currIndex = noptFilenames2 - 1;
                        break;

                    case SDLK_PAGEDOWN:
                        if (currIndex > SET_SKIP_AMOUNT)
                            currIndex -= SET_SKIP_AMOUNT;
                        else
                            currIndex = 0;
                        break;

                    case SDLK_PAGEUP:
                        if (currIndex < noptFilenames2 - 1 - SET_SKIP_AMOUNT)
                            currIndex += SET_SKIP_AMOUNT;
                        else
                            currIndex = noptFilenames2 - 1;
                        break;

                    case SDLK_HOME:
                        currIndex = 0;
                        break;

                    case SDLK_END:
                        currIndex = noptFilenames2 - 1;
                        break;

                    case SDLK_f:
                        optVFlags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
                        if (SDL_SetWindowFullscreen(window, optVFlags) != 0)
                            goto exit;
                        break;

                    default:
                        break;
                }

                needRedraw = TRUE;
                break;

            case SDL_WINDOWEVENT:
                switch (event.window.event)
                {
                    case SDL_WINDOWEVENT_EXPOSED:
                        needRedraw = TRUE;
                        break;

                    case SDL_WINDOWEVENT_RESIZED:
                        optScrWidth  = event.window.data1;
                        optScrHeight = event.window.data2;

                        needRedraw = TRUE;
                        break;
                }
                break;

            case SDL_QUIT:
                goto exit;
        }

        if (currIndex != prevIndex)
        {
            char *filename = optFilenames[currIndex];
            const DMC64ImageFormat *fmt = NULL;
            DMC64Image *cimage = NULL;
            char *title = NULL;

            if (surf != NULL)
            {
                SDL_FreeSurface(surf);
                surf = NULL;
            }

            if ((res = dmReadC64Image(filename, forced, &fmt, &cimage)) != DMERR_OK)
            {
                if (res != DMERR_NOT_SUPPORTED)
                {
                    dmErrorMsg("Could not decode file '%s': %s\n",
                        filename, dmErrorStr(res));
                }
                goto fail;
            }

            if (fmt == NULL || cimage == NULL)
            {
                dmErrorMsg("Probing could not find any matching image format. Perhaps try forcing a format via -f.\n");
                goto fail;
            }

            // Create surface (we are lazy and ugly)
            if ((surf = SDL_CreateRGBSurfaceWithFormat(0,
                cimage->fmt->width, cimage->fmt->height,
                8, SDL_PIXELFORMAT_INDEX8)) == NULL)
            {
                dmC64ImageFree(cimage);
                dmErrorMsg("Could not allocate surface.\n");
                goto exit;
            }

            if (dmDecodeC64Image(cimage, fmt, surf, &optSpec) == DMERR_OK)
            {
                title = dm_strdup_printf("%s - [%d / %d] %s (%dx%d @ %s)",
                    dmProgName,
                    currIndex + 1,
                    noptFilenames2,
                    filename,
                    cimage->fmt->width, cimage->fmt->height,
                    fmt->name);

                if (dmVerbosity >= 1)
                {
                    fprintf(stdout, "\n%s\n", filename);
                    dmC64ImageDump(stdout, cimage, fmt, "  ");
                }
            }

fail:
            dmC64ImageFree(cimage);

            if (surf == NULL && (surf = SDL_CreateRGBSurfaceWithFormat(0,
                D64_SCR_WIDTH, D64_SCR_HEIGHT, 8, SDL_PIXELFORMAT_INDEX8)) == NULL)
            {
                dmErrorMsg("Could not allocate surface.\n");
                goto exit;
            }


            if (texture != NULL)
                SDL_DestroyTexture(texture);

            if ((texture = SDL_CreateTextureFromSurface(renderer, surf)) == NULL)
            {
                dmErrorMsg("Could not create texture from surface: %s\n", SDL_GetError());
                goto exit;
            }

            if (title == NULL)
            {
                title = dm_strdup_printf("%s - [%d / %d] %s",
                    dmProgName,
                    currIndex + 1,
                    noptFilenames2,
                    filename);
            }

            SDL_SetWindowTitle(window, title);
            dmFree(title);

            needRedraw = TRUE;
            prevIndex = currIndex;
        }

        if (needRedraw)
        {
            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, NULL);
            SDL_RenderPresent(renderer);
            needRedraw = FALSE;
        }

        SDL_Delay(50);
    }

exit:
    // Cleanup
    dmFree(optFilenames);
    dmC64MemBlockFree(&setCharROM);

    if (texture != NULL)
        SDL_DestroyTexture(texture);

    if (renderer != NULL)
        SDL_DestroyRenderer(renderer);

    if (window != NULL)
        SDL_DestroyWindow(window);

    if (surf != NULL)
        SDL_FreeSurface(surf);

    if (initSDL)
        SDL_Quit();

    dmPaletteFree(optSpec.pal);
    dmLib64GFXClose();

    return 0;
}