Skip to content

Instantly share code, notes, and snippets.

@fabiogaluppo
Created July 5, 2024 22:05
Show Gist options
  • Save fabiogaluppo/d2f6bfebbf05e16005b9ff4bf9460910 to your computer and use it in GitHub Desktop.
Save fabiogaluppo/d2f6bfebbf05e16005b9ff4bf9460910 to your computer and use it in GitHub Desktop.
SDL PPM P3 Viewer
//Source code SDL PPM P3 Viewer por Fabio Galuppo
//C++ MasterClass - https://www.linkedin.com/company/cppmasterclass - https://www.youtube.com/@CPPMasterClass
//Fabio Galuppo - http://member.acm.org/~fabiogaluppo - [email protected]
//July 2024
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include "sdl_helpers.h"
#include "getopt.h"
struct point2d
{
int x, y;
};
struct rgb
{
int r, g, b;
};
static void set_pixel(SDL_Surface* surface, struct point2d pos, struct rgb color)
{
*((Uint32*)surface->pixels + pos.y * surface->w + pos.x) = (color.r << 16) + (color.g << 8) + color.b;
}
struct opt_args
{
char* input_path;
bool gray_mode;
};
static struct opt_args getopt_args(int argc, char** argv)
{
int ch;
bool gray_mode = false, normal_mode = false, usage = false;
char* input_path = NULL;
while ((ch = getopt(argc, argv, "ngi:")) != -1)
{
switch ((char)ch)
{
case 'n':
++optarg;
normal_mode = true;
break;
case 'g':
++optarg;
gray_mode = true;
break;
case 'i':
input_path = optarg;
break;
case '?':
default:
usage = true;
}
}
usage = usage || ((normal_mode && gray_mode) || input_path == NULL);
if (usage)
{
const char* msg = "sdl_ppm_p3_viewer -i <input_path> [-g|-n]\n"
"where\n"
" i: input file path (required)\n"
" g: gray image mode (optional)\n"
" n: normal image mode (optional)";
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Usage", msg, NULL);
exit(EXIT_FAILURE);
}
struct opt_args args = { input_path, gray_mode };
return args;
}
int main(int argc, char** argv)
{
static const int FPS = 15;
static const int FRAME_RATE_MS = 1000 / FPS;
static const Uint32 COLOR_BLACK = 0xFF000000;
static const Uint32 COLOR_RED = 0xFFFF0000;
static const Uint32 COLOR_YELLOW = 0xFFFFFF00;
static const Uint32 COLOR_GREEN = 0xFF00FF00;
static const Uint32 COLOR_PURPLE = 0xFF800080;
struct opt_args args = getopt_args(argc, argv);
char input_path[256];
strncpy(input_path, args.input_path, sizeof(input_path) / sizeof(input_path[0]));
FILE* ppm_p3 = fopen(input_path, "r");
if (ppm_p3 == NULL)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Open file", "The fopen function has failed", NULL);
exit(EXIT_FAILURE);
}
char buffer[16] = {0};
fgets(buffer, sizeof(buffer), ppm_p3);
fgets(buffer, sizeof(buffer), ppm_p3);
int width, height;
sscanf(buffer, "%d %d", &width, &height);
fgets(buffer, sizeof(buffer), ppm_p3);
int status = 0;
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
status = -1;
goto cleanup;
}
SDL_Window* window = SDL_CreateWindow
(
"PPM P3 Viewer",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
width, height, SDL_WINDOW_HIDDEN
);
if (window == NULL)
{
status = -1;
goto cleanup;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL)
{
status = -1;
goto cleanup;
}
SDL_Surface* surface = SDL_GetWindowSurface(window);
if (surface == NULL)
{
status = -1;
goto cleanup;
}
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
struct rgb color;
fgets(buffer, sizeof(buffer), ppm_p3);
sscanf(buffer, "%d %d %d", &color.r, &color.g, &color.b);
if (args.gray_mode)
{
//Luminosity Method formula per component = 0.3 * R + 0.59 * G + 0.11 * B
int grayscale = 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
color.r = grayscale;
color.g = grayscale;
color.b = grayscale;
}
struct point2d pos = { x, y };
set_pixel(surface, pos, color);
}
}
fclose(ppm_p3);
SDL_ShowWindow(window);
SDL_Event event;
while (true)
{
//Update
Uint64 start_ticks = SDL_GetTicks64();
if (SDL_PollEvent(&event) != 0)
{
if (event.type == SDL_QUIT)
break;
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
break;
}
int w, h;
SDL_GetWindowSize(window, &w, &h);
if (w != width || h != height)
{
SDL_HideWindow(window);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "SDL Window", "The window size has changed\nThis viewer is closing", NULL);
break;
}
//Render
display_flip(renderer, surface);
clock_tick(FRAME_RATE_MS, start_ticks);
}
cleanup:
//Cleanup resources
if (renderer)
SDL_DestroyRenderer(renderer);
if (window)
SDL_DestroyWindow(window);
SDL_Quit();
return status;
}
//Source code SDL PPM P3 Viewer por Fabio Galuppo
//C++ MasterClass - https://www.linkedin.com/company/cppmasterclass - https://www.youtube.com/@CPPMasterClass
//Fabio Galuppo - http://member.acm.org/~fabiogaluppo - [email protected]
//July 2024
//Adapted from getopt from FreeBSD.org
//Extract declaration from: https://svnweb.freebsd.org/base/stable/9/include/unistd.h?view=markup
//Extract definition from: https://svnweb.freebsd.org/base/stable/9/lib/libc/stdlib/getopt.c?view=markup
/*
* Copyright (c) 1987, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef GETOPT_H
#define GETOPT_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int opterr = 1;
static int optind = 1;
static int optopt;
static int optreset;
static char *optarg;
#define BADCH (int)'?'
#define BADARG (int)':'
#define EMSG ""
/*
* getopt --
* Parse argc/argv argument vector.
*/
static int getopt(int nargc, char* const nargv[], const char* ostr)
{
static char* place = EMSG; /* option letter processing */
char* oli; /* option letter list index */
if (optreset || *place == 0) { /* update scanning pointer */
optreset = 0;
place = nargv[optind];
if (optind >= nargc || *place++ != '-') {
/* Argument is absent or is not an option */
place = EMSG;
return (-1);
}
optopt = *place++;
if (optopt == '-' && *place == 0) {
++optind;
place = EMSG;
return (-1);
}
if (optopt == 0) {
/* Solitary '-', treat as a '-' option
if the program (eg su) is looking for it. */
place = EMSG;
if (strchr(ostr, '-') == NULL)
return (-1);
optopt = '-';
}
}
else
optopt = *place++;
/* See if option letter is one the caller wanted... */
if (optopt == ':' || (oli = (char*)(strchr(ostr, optopt))) == NULL) {
if (*place == 0)
++optind;
if (opterr && *ostr != ':')
(void) fprintf(stderr,
"%s: illegal option -- %c\n", "getopt",
optopt);
return (BADCH);
}
/* Does this option need an argument? */
if (oli[1] != ':') {
/* don't need argument */
optarg = NULL;
if (*place == 0)
++optind;
}
else {
/* Option-argument is either the rest of this argument or the
entire next argument. */
if (*place)
optarg = place;
else if (nargc > ++optind)
optarg = nargv[optind];
else {
/* option-argument absent */
place = EMSG;
if (*ostr == ':')
return (BADARG);
if (opterr)
(void) fprintf(stderr,
"%s: option requires an argument -- %c\n",
"getopt", optopt);
return (BADCH);
}
place = EMSG;
++optind;
}
return (optopt); /* return option letter */
}
#endif /* GETOPT_H */
//Source code SDL PPM P3 Viewer por Fabio Galuppo
//C++ MasterClass - https://www.linkedin.com/company/cppmasterclass - https://www.youtube.com/@CPPMasterClass
//Fabio Galuppo - http://member.acm.org/~fabiogaluppo - [email protected]
//July 2024
#ifndef SDL_HELPERS_H
#define SDL_HELPERS_H
#include <SDL.h>
static void display_flip(SDL_Renderer* renderer, SDL_Surface* surface)
{
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_DestroyTexture(texture);
SDL_RenderPresent(renderer);
}
static void clock_tick(int frame_rate_ms, Uint64 start_ticks)
{
Uint64 elapsed_ms = SDL_GetTicks64() - start_ticks;
if (elapsed_ms < frame_rate_ms)
SDL_Delay(frame_rate_ms - elapsed_ms);
}
static void screen_fill(SDL_Renderer* renderer, SDL_Surface* surface, Uint32 color)
{
SDL_RenderClear(renderer);
SDL_FillRect(surface, NULL, color);
}
#endif /* SDL_HELPERS_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment