Mercurial > hg > dmlib
view tools/64vw.c @ 2617:2f322910dec5
Comments.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 28 Nov 2023 19:20:53 +0200 |
parents | ff4c35d98267 |
children | 1b62395e2bb7 |
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 optSetWindowWidth, optSetWindowHeight; 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) { optSetWindowWidth = (int) ((float) D64_SCR_WIDTH * factor * D64_SCR_PAR_XY); optSetWindowHeight = (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 | ", filename); if (res == DMERR_OK || res == DMERR_NOT_SUPPORTED) { fprintf(stdout, "%s [%s]\n", fmt != NULL ? fmt->name : "UNKNOWN", fmt != NULL ? fmt->fext : "???"); } 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, optSetWindowWidth, optSetWindowHeight, 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 = optSetWindowWidth; currWindowHeight = optSetWindowHeight; 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, optSetWindowWidth, optSetWindowHeight); 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) { optSetWindowWidth = event.window.data1; optSetWindowHeight = 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; }