view tools/64vw.c @ 2576:812b16ee49db

I had been living under apparent false impression that "realfft.c" on which the FFT implementation in DMLIB was basically copied from was released in public domain at some point, but it could very well be that it never was. Correct license is (or seems to be) GNU GPL. Thus I removing the code from DMLIB, and profusely apologize to the author, Philip Van Baren. It was never my intention to distribute code based on his original work under a more liberal license than originally intended.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 11 Mar 2022 16:32:50 +0200
parents bb44c48cffac
children 9807ae37ad69
line wrap: on
line source

/*
 * 64vw - Displayer for various C64 graphics formats via libSDL
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2021 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;
const char *optCharROMFilename = NULL;
DMC64Palette *optC64Palette = NULL;
char    *optC64PaletteFile = NULL;

DMC64MemBlock setCharROM;


static const DMOptArg optList[] =
{
    {  0, '?', "help"            , "Show this help", OPT_NONE },
    {  1,   0, "license"         , "Print out this program's license agreement", OPT_NONE },
    {  2, 'v', "verbose"         , "Be more verbose", OPT_NONE },

    { 10,   0, "fs"              , "Fullscreen", OPT_NONE },
    { 12, 'S', "scale"           , "Scale image by factor (1-10)", OPT_ARGREQ },
    { 14, 'f', "format"          , "Force input format (see --formats)", OPT_ARGREQ },
    { 16, 'F', "formats"         , "List supported input formats", OPT_NONE },
    { 18, 'i', "info"            , "Print information only (no display)", OPT_NONE },
    { 20, 'l', "list"            , "List files that were recognized (implies -i)", OPT_NONE },
    { 22, 'P', "probe"           , "Probe only (do not attempt to decode the image)", OPT_NONE },
    { 24,   0, "char-rom"        , "Set character ROM file to be used.", OPT_ARGREQ },
    { 26, '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, 80 - 2);

    fprintf(stdout,
    "\n"
    "Keyboard controls in the viewer:\n"
    "--------------------------------\n"
    "  arrow keys    - next/previous file\n"
    "  space         - next file\n"
    "  home/end      - go to first/last file\n"
    "  page up/down  - go forward/backward %d files\n"
    "  esc / q       - quit\n"
    "  f             - toggle fullscreen\n"
    "\n"
    "Default character ROM file for this build is:\n"
    "  %s\n",
    SET_SKIP_AMOUNT,
    dmGetChargenROMPath()
    );
}


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

        case 1:
            dmPrintLicense(stdout);
            exit(0);
            break;

        case 2:
            dmVerbosity++;
            break;

        case 10:
            optVFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
            break;

        case 12:
            {
                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 14:
            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 16:
            argShowC64Formats(stdout, FALSE, TRUE);
            exit(0);
            break;

        case 20:
            // NOTICE! This fallthrough is intentional for -l option!
            // Take care if reordering the option indices.
            optListOnly = TRUE;
            // Fallthrough

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

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

        case 24:
            optCharROMFilename = optArg;
            break;

        case 26:
            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 out;

    dmGrowBufConstCreateFrom(&tmp, dataBuf, dataSize);

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

out:
    dmFree(dataBuf);
    return ret;
}


int dmConvertC64ImageToSDLSurface(DMImage **bimage, SDL_Surface **psurf, DMC64Image *cimage, const DMC64ImageConvSpec *spec)
{
    BOOL charDataSet = FALSE;
    int res;

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

    res = dmC64ConvertBMP2Image(bimage, cimage, spec);

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

    if (res == DMERR_OK)
    {
        *psurf = SDL_CreateRGBSurfaceWithFormatFrom(
            (*bimage)->data, (*bimage)->width, (*bimage)->height,
            8, (*bimage)->pitch, SDL_PIXELFORMAT_INDEX8);

        if (*psurf != NULL)
        {
            SDL_SetPaletteColors((*psurf)->format->palette,
                (SDL_Color *) (*bimage)->pal->colors, 0,
                (*bimage)->pal->ncolors);
        }
    }

    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;
    DMImage *bimage = NULL;
    BOOL initSDL = FALSE, exitFlag, needRedraw;
    size_t currIndex, prevIndex;
    int res = DMERR_OK;

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

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

    if (noptFilenames1 == 0)
    {
        argShowHelp();
        res = dmError(DMERR_INVALID_ARGS,
            "No input file(s) specified.\n");
        goto out;
    }

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

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

    // 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 out;
    }

    if (optC64PaletteFile != NULL)
    {
        if ((res = dmHandleExternalPalette(optC64PaletteFile, &optSpec.pal)) != DMERR_OK)
            goto out;

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

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

    // 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 out;
    }
    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 out;
    }

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

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

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

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

                    case SDLK_PAGEDOWN:
                        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 out;
                        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 out;
        }

        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 (bimage != NULL)
            {
                dmImageFree(bimage);
                bimage = 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 (dmConvertC64ImageToSDLSurface(&bimage, &surf, cimage, &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 out;
            }

            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 out;
            }

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

out:
    // 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();

    dmImageFree(bimage);
    dmPaletteFree(optSpec.pal);
    dmLib64GFXClose();

    return res;
}