cube3D/MLX42/src/textures/mlx_xpm42.c
Etienne Rey-bethbeder bc5fc2fc59 Add MLX42 V2
2023-04-26 13:39:03 +02:00

209 lines
7.1 KiB
C

/* ************************************************************************** */
/* */
/* :::::::: */
/* mlx_xpm42.c :+: :+: */
/* +:+ */
/* By: W2Wizard <w2.wizzard@gmail.com> +#+ */
/* +#+ */
/* Created: 2021/12/28 03:42:29 by W2Wizard #+# #+# */
/* Updated: 2022/06/27 19:58:33 by lde-la-h ######## odam.nl */
/* */
/* ************************************************************************** */
#include "MLX42/MLX42_Int.h"
/**
* XPM is an obscure image format which can't seem to make up its mind
* whether it wants to be written in C code or not.
*
* https://en.wikipedia.org/wiki/X_PixMap
*
* This might anger some but instead I decided to write my own
* image format, very similar to XPM2, which seems to be the better
* option between the 3 versions. The only difference is in the
* header which carries the file type, width, height, color count
* and finally color type aka 'c' for RGBA8 or 'm' for monochrome
* output.
*
* The changes, in my opinion, very much simplify the XPM format
* into something literally anybody can use without much guessing
* as to what does what.
*
* Additionally with the C style format, the idea is that you simply include
* it directly into the compilation of the program (since it's just C).
*
* As convenient as this is, I just find it hideous especially the XPM3 variant.
* By sticking to the XPM style format, conversion should be very easy and
* straightforward to this format however.
*/
//= Private =//
/**
* Parses HEX color channel e.g: "0F"
*
* @param channel The 2 character string to parse.
* @return Int value of the channel.
*/
static uint8_t mlx_parse_hex_channel(char* channel)
{
char temp_chan[] = {channel[0], channel[1], '\0'};
return (strtol(temp_chan, NULL, 16));
}
/**
* Parses the XPM color value entry e.g: ".X #00FF00FF"
* into the color table while also verifying the format.
*
* @param xpm The XPM.
* @param line The line to parse.
* @param ctable The color hash table.
* @param s Size of the hash table
* @return True or false depending on if it sucessfully parsed the line.
*/
static bool mlx_insert_xpm_entry(xpm_t* xpm, char* line, uint32_t* ctable, size_t s)
{
// NOTE: uintptr because windows likes to complain...
// Verify the length of the Pixel string by checking backwards for the first
// occurence of a space and then check the distance by comparing with cpp.
if (((uintptr_t)strrchr(line, ' ') - (uintptr_t)line) != (uint64_t)xpm->cpp)
return (false);
if (!isspace(line[xpm->cpp]) || line[xpm->cpp + 1] != '#' || !isalnum(line[xpm->cpp + 2]))
return (false);
uint32_t color = 0;
size_t start_offset = xpm->cpp + 2;
color |= mlx_parse_hex_channel(line + start_offset) << 24;
color |= mlx_parse_hex_channel(line + start_offset + 2) << 16;
color |= mlx_parse_hex_channel(line + start_offset + 4) << 8;
color |= mlx_parse_hex_channel(line + start_offset + 6);
int32_t index = mlx_fnv_hash(line, xpm->cpp) % s;
ctable[index] = xpm->mode == 'm' ? mlx_rgba_to_mono(color) : color;
return (true);
}
/**
* Retrieves the pixel data line by line and then processes each pixel
* by hashing the characters and looking it up from the color table.
*
* @param xpm The XPM.
* @param file The filepath to the XPM42 file.
* @param ctable The color hash table.
* @param s Size of the hash table.
* @return True or false depending on if it sucessfully parsed the line.
*/
static bool mlx_read_data(xpm_t* xpm, FILE* file, uint32_t* ctable, size_t s)
{
size_t line_len;
char* line = NULL;
for (int64_t y_xpm = 0; y_xpm < xpm->texture.height; y_xpm++)
{
if (!mlx_getline(&line, &line_len, file))
return (free(line), false);
if (line[line_len - 1] == '\n')
line_len--;
if (line_len != xpm->texture.width * xpm->cpp)
return (free(line), false);
// NOTE: Copy pixel by pixel as we need to retrieve the hash table.
for (int64_t x_xpm = 0, x_line = 0; x_xpm < xpm->texture.width; x_xpm++, x_line += xpm->cpp)
{
uint8_t* pixelstart = &xpm->texture.pixels[(y_xpm * xpm->texture.width + x_xpm) * BPP];
mlx_draw_pixel(pixelstart, ctable[mlx_fnv_hash(&line[x_line], xpm->cpp) % s]);
}
}
free(line);
return (true);
}
/**
* For quick lookups we basically create a stack allocated lookup
* table with every ascii character in it. This should help avoid a O(n)
* case and give us a O(1) for very fast look ups.
*
* Downside is we still need to iterate over each pixel to solve its color.
* So I hope this makes it at least a bit faster.
*
* TODO: This buffer might be way to big! Do actual collision checks,
* for now just straight up raw dog this.
*/
static bool mlx_read_table(xpm_t* xpm, FILE* file)
{
char* line = NULL;
size_t line_len;
uint32_t ctable[UINT16_MAX] = {0};
for (int32_t i = 0; i < xpm->color_count; i++)
{
if (!mlx_getline(&line, &line_len, file))
return (free(line), false);
if (!mlx_insert_xpm_entry(xpm, line, ctable, (sizeof(ctable) / BPP)))
return (free(line), false);
}
free(line);
return (mlx_read_data(xpm, file, ctable, (sizeof(ctable) / BPP)));
}
/**
* Reads the XPM42 file header which usually consists of a
* file type declaration of "!XPM42" followed by the next line
* containing image information such as width, height, unique color
* count and finally the color mode. Which is either c for Color or
* m for Monochrome.
*/
static bool mlx_read_xpm_header(xpm_t* xpm, FILE *file)
{
int32_t flagc;
char buffer[64] = {0};
// Check file type dec...
if (!fgets(buffer, sizeof(buffer), file))
return (false);
if (strncmp(buffer, "!XPM42\n", sizeof(buffer)) != 0)
return (false);
// Get header info ...
if (!fgets(buffer, sizeof(buffer), file))
return (false);
flagc = sscanf(buffer, "%i %i %i %i %c\n", &xpm->texture.width, &xpm->texture.height, &xpm->color_count, &xpm->cpp, &xpm->mode);
if (flagc < 4 || xpm->texture.width > INT16_MAX || xpm->texture.height > INT16_MAX || \
!(xpm->mode == 'c' || xpm->mode == 'm') || xpm->cpp > 10)
return (false);
xpm->texture.bytes_per_pixel = BPP;
xpm->texture.pixels = calloc(xpm->texture.width * xpm->texture.height, sizeof(int32_t));
return (xpm->texture.pixels != NULL ? mlx_read_table(xpm, file) : false);
}
//= Public =//
xpm_t* mlx_load_xpm42(const char* path)
{
FILE* file;
xpm_t* xpm = NULL;
MLX_NONNULL(path);
if (!strstr(path, ".xpm42"))
return ((void*)mlx_error(MLX_INVEXT));
if (!(file = fopen(path, "r")))
return ((void*)mlx_error(MLX_INVFILE));
if (!(xpm = calloc(1, sizeof(xpm_t))))
return ((void*)mlx_error(MLX_MEMFAIL));
if (!mlx_read_xpm_header(xpm, file))
{
mlx_freen(2, xpm->texture.pixels, xpm);
mlx_error(MLX_INVXPM);
xpm = NULL;
}
fclose(file);
return (xpm);
}
void mlx_delete_xpm42(xpm_t* xpm)
{
MLX_NONNULL(xpm);
free(xpm->texture.pixels);
free(xpm);
}