view tools/64vw.c @ 2634:f3c7115cbf85 default tip

Fix verbose build echos.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 29 Feb 2024 21:47:31 +0200
parents 1f0aecb1017c
children
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     optWindowFlags = 0;
int     optWindowWidth, optWindowHeight;
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"            , "One line per file list of detected format", 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)
{
    optWindowWidth = (int) ((float) D64_SCR_WIDTH * factor * D64_SCR_PAR_XY);
    optWindowHeight = (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:
            optWindowFlags |= 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 flagInitSDL = false, flagQuit, flagRedraw, flagResize;
    size_t currIndex, prevIndex;
    int currWindowWidth, currWindowHeight;
    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.5", 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;

            res = dmReadC64Image(filename, forced, &fmt, &cimage);
            if (optListOnly)
            {
                fprintf(stdout, "%s | %s | %s",
                    filename,
                    fmt != NULL ? fmt->name : "UNKNOWN",
                    fmt != NULL ? fmt->fext : "???");

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

            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;
    }
    flagInitSDL = true;

    // Create window
    if ((window = SDL_CreateWindow(dmProgName,
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        optWindowWidth, optWindowHeight,
        optWindowFlags | 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
    currWindowWidth = optWindowWidth;
    currWindowHeight = optWindowHeight;
    currIndex = 0;
    prevIndex = 1;
    flagRedraw = true;
    flagResize = true;
    flagQuit = false;
    while (!flagQuit)
    {
        SDL_Event event;
        while (SDL_PollEvent(&event))
        switch (event.type)
        {
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                    case SDLK_q:
                        flagQuit = 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:
                        // If we switch to/from fullscreen, set a flag so we do not
                        // use the fullscreen window size as new stored window size
                        flagResize = false;
                        optWindowFlags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;

                        if (SDL_SetWindowFullscreen(window, optWindowFlags) != 0)
                            goto out;

                        if ((optWindowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0)
                            SDL_SetWindowSize(window, optWindowWidth, optWindowHeight);
                        break;

                    default:
                        break;
                }

                flagRedraw = true;
                break;

            case SDL_WINDOWEVENT:
                switch (event.window.event)
                {
                    case SDL_WINDOWEVENT_EXPOSED:
                        flagRedraw = true;
                        break;

                    case SDL_WINDOWEVENT_RESIZED:
                        if (flagResize)
                        {
                            optWindowWidth  = event.window.data1;
                            optWindowHeight = event.window.data2;
                        }
                        currWindowWidth = event.window.data1;
                        currWindowHeight = event.window.data2;
                        flagResize = true;
                        flagRedraw = 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;

            // Delete previous surface if any
            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;
            }

            // Convert image to surface (we are lazy and ugly)
            if (dmConvertC64ImageToSDLSurface(&bimage, &surf, cimage, &optSpec) == DMERR_OK)
            {
                // Create title string
                title = dm_strdup_printf("%s - [%d / %d] %s (%dx%d @ %s)",
                    dmProgName,
                    currIndex + 1,
                    noptFilenames2,
                    filename,
                    cimage->fmt->width, cimage->fmt->height,
                    fmt->name);

                // Output some information to stdout if we are verbose
                if (dmVerbosity >= 1)
                {
                    fprintf(stdout, "\n%s\n", filename);
                    dmC64ImageDump(stdout, cimage, fmt, "  ");
                }
            }

fail:
            dmC64ImageFree(cimage);

            // Create or update surface and texture
            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;
            }

            // Create stub title string if we didn't manage to decode the image
            if (title == NULL)
            {
                title = dm_strdup_printf("%s - [%d / %d] %s",
                    dmProgName,
                    currIndex + 1,
                    noptFilenames2,
                    filename);
            }

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

            flagRedraw = true;
            prevIndex = currIndex;
        }

        if (flagRedraw)
        {
            // Calculate the image render size
            SDL_Rect dstRect;
            dstRect.w = (((float) currWindowHeight) * D64_SCR_FULL_WIDTH / D64_SCR_FULL_HEIGHT / D64_SCR_PAR_XY);
            dstRect.h = currWindowHeight;
            dstRect.x = (currWindowWidth - dstRect.w) / 2;
            dstRect.y = 0;

            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, &dstRect);
            SDL_RenderPresent(renderer);
            flagRedraw = false;
        }
        else
            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 (flagInitSDL)
        SDL_Quit();

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

    return res;
}