view tools/64vw.c @ 2265:48b48251610a

Refactor how the image "mode/type" is handled. It is still not perfect for our purposes, but better now.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 17 Jun 2019 02:03:35 +0300
parents 2e656da1b10b
children 8ad08ab4975b
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 argShowHelp()
{
    dmPrintBanner(stdout, dmProgName, "[options] <input image file(s)>");
    dmArgsPrintHelp(stdout, optList, optListN, 0);

    fprintf(stdout,
    "\n"
    "Default character ROM file for this build is:\n"
    "%s\n",
    DM_DEF_CHARGEN
    );
}


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:
            argShowC64Formats(stdout, FALSE);
            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 dmConvertC64ImageToSDLSurface(DMC64Image *cimage, SDL_Surface *surf, const DMC64ImageConvSpec *spec)
{
    DMImage bmap;
    BOOL charDataSet, mixedPalette;
    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;

    mixedPalette  = (cimage->extraInfo[D64_EI_MODE] & D64_FMT_ILACE) &&
        cimage->extraInfo[D64_EI_ILACE_TYPE] == D64_ILACE_COLOR;

    if ((res = dmC64SetImagePalette(&bmap, spec, mixedPalette)) != 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 (cimage->fmt->convertFrom != NULL)
        res = cimage->fmt->convertFrom(&bmap, cimage, spec);
    else
        res = dmC64ConvertGenericBMP2Image(&bmap, cimage, 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;
}


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->mode, 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;

        if (optSpec.pal->ncolors < D64_NCOLORS)
        {
            dmErrorMsg("Palette does not have enough colors (%d < %d)\n",
                optSpec.pal->ncolors, D64_NCOLORS);
            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 + 1 < noptFilenames2)
                            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 + 1 + SET_SKIP_AMOUNT < noptFilenames2)
                            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 (dmConvertC64ImageToSDLSurface(cimage, 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;
}