view tools/64vw.c @ 1576:e5be5235e7e1

Make home/end keys to go to first and last file respectively in 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 13 May 2018 09:49:03 +0300
parents 4643cd757c0b
children 1eb8c1dc81fd
line wrap: on
line source

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


#define SET_SKIP_AMOUNT 10


int    optVFlags = 0;
int    optScrWidth, optScrHeight;
int    optForcedFormat = -1;
BOOL   optProbeOnly = FALSE;
size_t noptFilenames1 = 0, noptFilenames2 = 0;
char   **optFilenames = NULL;


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 list below)", OPT_ARGREQ },
    { 5, 'p', "probe",      "Probe only (no display)", OPT_NONE },
};

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


void dmSetScaleFactor(float factor)
{
    optScrWidth = (int) ((float) C64_SCR_WIDTH * factor * C64_SCR_PAR_XY);
    optScrHeight = (int) ((float) C64_SCR_HEIGHT * factor);
}


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

    printf("\nAvailable bitmap formats:\n");
    for (int i = 0; i < ndmC64ImageFormats; i++)
    {
        const DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
        char buf[64];
        printf(" %-5s | %-15s | %s\n",
            fmt->fext,
            dmC64GetImageTypeString(buf, sizeof(buf), fmt->type, FALSE),
            fmt->name);
    }
}


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:
            if (dmVerbosity < 1)
                dmVerbosity = 1;
            optProbeOnly = TRUE;
            break;

        default:
            dmErrorMsg("Unknown option '%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;
    int ret;

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

    ret = dmC64DecodeBMP(cimage, dataBuf, dataSize, 0, 2, fmt, forced);

exit:
    dmFree(dataBuf);
    return ret;
}


int dmDecodeC64Image(const DMC64Image *cimage, const DMC64ImageFormat *fmt, SDL_Surface *surf)
{
    DMImage bmap;
    int ret;

    bmap.size = surf->pitch * surf->h;
    bmap.data = surf->pixels;
    bmap.pitch = surf->pitch;
    bmap.width = surf->w;
    bmap.height = surf->h;

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

    return ret;
}


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

    dmSetScaleFactor(2.0);

    dmInitProg("64vw", "Display some C64 bitmap graphics formats", "0.2", 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->type, forced->fext);
    }
    else
        forced = NULL;

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

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

            dmC64ImageFree(cimage);
        }
        goto exit;
    }

    // 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_LEFT:
                        if (currIndex > 0)
                            currIndex--;
                        else
                            currIndex = 0;
                        break;

                    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 ((ret = dmReadC64Image(filename, forced, &fmt, &cimage)) != DMERR_OK)
            {
                dmErrorMsg("Failed to decode bitmap data %d: %s\n", ret, dmErrorStr(ret));
                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->width, cimage->height, 8, SDL_PIXELFORMAT_INDEX8)) == NULL)
            {
                dmC64ImageFree(cimage);
                dmErrorMsg("Could not allocate surface.\n");
                goto exit;
            }

            if (dmDecodeC64Image(cimage, fmt, surf) == DMERR_OK)
            {
                title = dm_strdup_printf("%s - [%d / %d] %s (%dx%d @ %s)",
                    dmProgName,
                    currIndex + 1,
                    noptFilenames2,
                    filename,
                    cimage->width, cimage->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,
                C64_SCR_WIDTH, C64_SCR_HEIGHT, 8, SDL_PIXELFORMAT_INDEX8)) == NULL)
            {
                dmErrorMsg("Could not allocate surface.\n");
                goto exit;
            }

            SDL_SetPaletteColors(surf->format->palette, (SDL_Color *)dmDefaultC64Palette, 0, C64_NCOLORS);

            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 - %s [%d / %d]",
                    dmProgName, filename,
                    currIndex + 1, noptFilenames2);
            }

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

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

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

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

    if (initSDL)
        SDL_Quit();

    return 0;
}