raytracer-c/module_raytracer/canvas.c
2023-10-07 19:39:43 -04:00

321 lines
9.2 KiB
C

#include "canvas.h"
#include "exceptions.h"
#include <CException.h>
#include <assert.h>
#include <logger.h>
#include <math.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <utilities.h>
static CEXCEPTION_T exception;
CANVAS_Canvas *CANVAS_new(uint width, uint height) {
CANVAS_Canvas *canvas = (CANVAS_Canvas *)malloc(sizeof(CANVAS_Canvas));
if (!canvas)
Throw(E_MALLOC_FAILED);
CANVAS_init(canvas, width, height);
return canvas;
}
void CANVAS_init(CANVAS_Canvas *canvas, uint width, uint height) {
canvas->width = width;
canvas->height = height;
canvas->data = (TUPLES_Color *)malloc((size_t)width * height * sizeof(TUPLES_Color));
if (!canvas->data)
Throw(E_MALLOC_FAILED);
for (uint i = 0; i < width * height; i++) {
TUPLES_init_color(&canvas->data[i], 0, 0, 0);
}
}
CANVAS_Canvas *CANVAS_copy(const CANVAS_Canvas *canvas) {
assert(canvas);
CANVAS_Canvas *new_canvas = CANVAS_new(canvas->width, canvas->height);
for (uint i = 0; i < canvas->width * canvas->height; i++) {
TUPLES_copy(&new_canvas->data[i], &canvas->data[i]);
}
return new_canvas;
}
static uint calculate_index(const CANVAS_Canvas *canvas, uint x, uint y) {
assert(x < canvas->width);
assert(y < canvas->height);
return y * canvas->width + x;
}
TUPLES_Color *CANVAS_read_pixel(const CANVAS_Canvas *canvas, uint x, uint y) {
assert(canvas);
uint ndx = calculate_index(canvas, x, y);
return &canvas->data[ndx];
}
void CANVAS_write_pixel(CANVAS_Canvas *canvas, uint x, uint y, const TUPLES_Color *color) {
assert(canvas);
assert(color);
assert(x < canvas->width);
assert(y < canvas->height);
TUPLES_Color *dest = CANVAS_read_pixel(canvas, x, y);
TUPLES_copy(dest, color);
}
char *CANVAS_get_ppm_header_string(const CANVAS_Canvas *canvas) {
assert(canvas);
char *retval;
if (asprintf(&retval, "P3\n%u %u\n255\n", canvas->width, canvas->height) == -1)
Throw(E_MALLOC_FAILED);
return retval;
}
u_int8_t clampnscale_double(double d) {
if (d > 1)
d = 1;
if (d < 0)
d = 0;
return round(d * 255.0);
}
char *CANVAS_get_ppm_body_string(const CANVAS_Canvas *canvas) {
assert(canvas);
char *buffer = NULL;
for (uint j = 0; j < canvas->height; j++) {
for (uint k = 0; k < canvas->width; k++) {
TUPLES_Color *color = CANVAS_read_pixel(canvas, k, j);
u_int8_t red = clampnscale_double(color->red);
u_int8_t green = clampnscale_double(color->green);
u_int8_t blue = clampnscale_double(color->blue);
if (buffer) {
UTILITIES_sasprintf(buffer, "%s%u %u %u ", buffer, red, green, blue);
} else {
UTILITIES_sasprintf(buffer, "%u %u %u ", red, green, blue);
}
}
UTILITIES_sasprintf(buffer, "%s\n", buffer);
}
UTILITIES_sasprintf(buffer, "%s\n", buffer);
return buffer;
}
void CANVAS_write_body_to_file(const CANVAS_Canvas *canvas, FILE *file) {
assert(canvas);
assert(file);
for (uint j = 0; j < canvas->height; j++) {
for (uint k = 0; k < canvas->width; k++) {
TUPLES_Color *color = CANVAS_read_pixel(canvas, k, j);
u_int8_t red = clampnscale_double(color->red);
u_int8_t green = clampnscale_double(color->green);
u_int8_t blue = clampnscale_double(color->blue);
fprintf(file, "%u %u %u ", red, green, blue);
}
fprintf(file, "\n");
}
fprintf(file, "\n");
}
void CANVAS_write_to_file(const CANVAS_Canvas *canvas, const char *filename) {
FILE *file = fopen(filename, "wb");
if (!file)
Throw(E_FILE_FAILED);
char *header = CANVAS_get_ppm_header_string(canvas);
fprintf(file, "%s", header);
CANVAS_write_body_to_file(canvas, file);
fclose(file);
free(header);
}
void CANVAS_destroy(CANVAS_Canvas *canvas) {
assert(canvas);
assert(canvas->data);
free(canvas->data);
}
void CANVAS_delete(CANVAS_Canvas *canvas) {
assert(canvas);
CANVAS_destroy(canvas);
free(canvas);
}
struct parse_status {
FILE *file;
void (*next_step)(struct parse_status*);
unsigned int line_number;
CANVAS_Canvas *canvas;
unsigned int scale;
char *line;
size_t len;
};
static bool is_comment(struct parse_status *status) {
//TODO this is super lame - likely needs to be better, we'll see...
return (status->line[0] == '#');
}
static void parse_pixels(struct parse_status *status) {
ssize_t nread;
unsigned int current_x = 0;
unsigned int current_y = 0;
status->next_step = NULL;
long r = -1;
long g = -1;
long b = -1;
while ((nread = getline(&status->line, &status->len, status->file)) != -1) {
if (nread > 0) {
status->line_number++;
while (nread > 0 && is_comment(status)) {
nread = getline(&status->line, &status->len, status->file);
status->line_number++;
}
char *saveptr = NULL;
char *delim = " \n";
char* num_str = strtok_r(status->line, delim, &saveptr);
while (num_str) {
char *err = NULL;
long num = strtol(num_str, &err, 10);
if (num_str == err) {
LOGGER_log(LOGGER_ERROR, "Error parsing pixel string, line_number (%u), line(%s), err(%s)\n", status->line_number, status->line, err);
Throw(E_PARSE_FAILED);
}
if (r < 0) {
r = num;
} else if (g < 0) {
g = num;
} else {
b = num;
TUPLES_Color color;
TUPLES_init_color(&color, r/(double)status->scale, g/(double)status->scale, b/(double)status->scale);
CANVAS_write_pixel(status->canvas, current_x, current_y, &color);
if (++current_x == status->canvas->width) {
++current_y;
current_x = 0;
}
r = -1;
g = -1;
b = -1;
}
num_str = strtok_r(NULL, delim, &saveptr);
}
}
}
}
static void parse_scale(struct parse_status *status) {
ssize_t nread = getline(&status->line, &status->len, status->file);
status->next_step = NULL;
if (nread > 0) {
status->line_number++;
while (nread > 0 && is_comment(status)) {
nread = getline(&status->line, &status->len, status->file);
status->line_number++;
}
char *saveptr = NULL;
char *delim = " \n";
char* scale_string = strtok_r(status->line, delim, &saveptr);
char *err = NULL;
unsigned long scale = strtoul(scale_string, &err, 10);
if (scale_string == err) {
LOGGER_log(LOGGER_ERROR, "Error parsing scale string, line(%s), err(%s)\n", status->line, err);
Throw(E_PARSE_FAILED);
}
status->scale = scale;
status->next_step = &parse_pixels;
}
}
static void parse_dimensions(struct parse_status *status) {
ssize_t nread = getline(&status->line, &status->len, status->file);
status->next_step = NULL;
if (nread > 0) {
status->line_number++;
while (nread > 0 && is_comment(status)) {
nread = getline(&status->line, &status->len, status->file);
status->line_number++;
}
//Should be width<space>height
char *saveptr = NULL;
char *delim = " \n";
char* width_string = strtok_r(status->line, delim, &saveptr);
char* height_string = strtok_r(NULL, delim, &saveptr);
LOGGER_log(LOGGER_DEBUG, "Found width %s, height %s while parsing ppm\n", width_string, height_string);
char *err = NULL;
unsigned long width = strtoul(width_string, &err, 10);
if (width_string == err) {
LOGGER_log(LOGGER_ERROR, "Error parsing ppm width, line(%s), err(%s)\n", status->line, err);
Throw(E_PARSE_FAILED);
}
err = NULL;
unsigned long height = strtoul(height_string, &err, 10);
if (height_string == err) {
LOGGER_log(LOGGER_ERROR, "Error parsing ppm height, line(%s), err(%s)\n", status->line, err);
Throw(E_PARSE_FAILED);
}
status->canvas = CANVAS_new(width, height);
status->next_step = &parse_scale;
}
}
static void parse_magic_number(struct parse_status *status) {
ssize_t nread = getline(&status->line, &status->len, status->file);
status->next_step = NULL;
if (nread > 0) {
status->line_number++;
char *saveptr = NULL;
char *delim = " \n";
char* magic = strtok_r(status->line, delim, &saveptr);
if (strncmp(magic, "P3", nread) != 0) {
LOGGER_log(LOGGER_ERROR, "Error parsing PPM magic number: %s\n", status->line);
Throw(E_PARSE_FAILED);
return;
}
} else {
LOGGER_log(LOGGER_ERROR, "Error getting line while parsing PPM magic number\n");
Throw(E_PARSE_FAILED);
return;
}
status->next_step = &parse_dimensions;
}
CANVAS_Canvas *CANVAS_parse_stream(FILE *file) {
assert(file);
struct parse_status status = {
.line_number = 0,
.file = file,
.next_step = &parse_magic_number,
.canvas = NULL,
.len = 0,
.line = NULL
};
Try {
while (status.next_step) {
status.next_step(&status);
}
free(status.line);
}
Catch(exception) {
free(status.line);
Throw(exception);
}
return status.canvas;
}
CANVAS_Canvas *CANVAS_parse_file_by_name(char *filename) {
assert(filename);
FILE *objfile = fopen(filename, "r");
if (!objfile) {
char *cwd = getcwd(NULL, 0);
LOGGER_log(LOGGER_ERROR, "fopen failed to open (%s), cwd is (%s)\n", filename, cwd);
free(cwd);
Throw(E_FILE_FAILED);
} else {
LOGGER_log(LOGGER_INFO, "Opened file %s for parsing as ppm\n", filename);
}
CANVAS_Canvas *obj = CANVAS_parse_stream(objfile);
fclose(objfile);
return obj;
}