#include "hvif-light.h" #include #include enum { STYLE_TYPE_SOLID_COLOR = 1, STYLE_TYPE_GRADIENT = 2, STYLE_TYPE_SOLID_COLOR_NO_ALPHA = 3, STYLE_TYPE_SOLID_GRAY = 4, STYLE_TYPE_SOLID_GRAY_NO_ALPHA = 5, SHAPE_TYPE_PATH_SOURCE = 10, TRANSFORMER_TYPE_AFFINE = 20, TRANSFORMER_TYPE_CONTOUR = 21, TRANSFORMER_TYPE_PERSPECTIVE = 22, TRANSFORMER_TYPE_STROKE = 23, }; enum gradients_type { GRADIENT_LINEAR = 0, GRADIENT_CIRCULAR, GRADIENT_DIAMOND, GRADIENT_CONIC, GRADIENT_XY, GRADIENT_SQRT_XY, HVIF_GRADIENT_TYPE_MAX }; enum { GRADIENT_FLAG_TRANSFORM = 1 << 1, GRADIENT_FLAG_NO_ALPHA = 1 << 2, GRADIENT_FLAG_16_BIT_COLORS = 1 << 3, /* not yet used */ GRADIENT_FLAG_GRAYS = 1 << 4, }; enum { PATH_FLAG_CLOSED = 1 << 1, PATH_FLAG_USES_COMMANDS = 1 << 2, PATH_FLAG_NO_CURVES = 1 << 3, }; enum { PATH_COMMAND_H_LINE = 0, PATH_COMMAND_V_LINE = 1, PATH_COMMAND_LINE = 2, PATH_COMMAND_CURVE = 3, }; enum { SHAPE_FLAG_TRANSFORM = 1 << 1, SHAPE_FLAG_HINTING = 1 << 2, SHAPE_FLAG_LOD_SCALE = 1 << 3, SHAPE_FLAG_HAS_TRANSFORMERS = 1 << 4, SHAPE_FLAG_TRANSLATION = 1 << 5, }; #define ERROR_RESULT(E) \ (hvif_result) { .status = E } #define SUCCESS_RESULT(I) \ (hvif_result) { .status = SUCCESS, .image = I } /* An affine transformation. Ugly until I understand better how they are used */ typedef struct hvif_matrix hvif_matrix; struct hvif_matrix { double v1; double v2; double v3; double v4; double v5; double v6; }; /* TODO: This is most likely wrong */ #define MATRIX_ID \ (hvif_matrix) { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0 } typedef struct hvif_point hvif_point; struct hvif_point { float x; float y; }; #define POINT_ORIGIN \ (hvif_point) { 0.0, 0.0 } #define hvif_decode(V, BUFFER) \ V = _Generic((V), uint32_t \ : hvif_decode_uint32, float \ : hvif_decode_float, hvif_matrix \ : hvif_decode_hvif_matrix)(BUFFER) static inline uint32_t hvif_decode_uint32(char buffer[static 1]) { return (buffer[0] << 0) | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); } static inline float hvif_decode_float(char buffer[static 1]) { int shortValue = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; int sign = (shortValue & 0x800000) >> 23; int exponent = ((shortValue & 0x7e0000) >> 17) - 32; int mantissa = (shortValue & 0x01ffff) << 6; if (shortValue == 0) return 0.0; else { uint32_t value = (sign << 31) | ((exponent + 127) << 23) | mantissa; return (float)value; } } static inline hvif_matrix hvif_decode_hvif_matrix(char buffer[static 1]) { return (hvif_matrix){ hvif_decode_float(buffer), hvif_decode_float(&buffer[3]), hvif_decode_float(&buffer[6]), hvif_decode_float(&buffer[9]), hvif_decode_float(&buffer[12]), hvif_decode_float(&buffer[15]) }; } typedef uint32_t hvif_color; /* 8bit each: alpha, blue, green, red */ #define COLOR_GET_RED(C) (C & 0xff) #define COLOR_GET_GREEN(C) ((C >> 8) & 0xff) #define COLOR_GET_BLUE(C) ((C >> 16) & 0xff) #define COLOR_GET_ALPHA(C) ((C >> 24) & 0xff) #define _BM8(N) (0xff << N) #define COLOR_SET_RED(C, V) (C = ((C & (~_BM8(0))) | (V & 0xff))) #define COLOR_SET_GREEN(C, V) (C = ((C & (~_BM8(8))) | ((V & 0xff) << 8))) #define COLOR_SET_BLUE(C, V) (C = ((C & (~_BM8(16))) | ((V & 0xff) << 16))) #define COLOR_SET_ALPHA(C, V) (C = ((C & (~_BM8(24))) | ((V & 0xff) << 24))) #define COLOR_SET_RGB(C, R, G, B) \ (C = (C & (~0xffffff)) | (R & 0xff) | ((G & 0xff) << 8) | ((B & 0xff) << 16)) typedef struct hvif_style hvif_style; struct hvif_style { signed gradient_type; uint8_t num_stops; hvif_color* colors; float* offsets; hvif_matrix transformation; }; typedef struct hvif_path hvif_path; struct hvif_path { uint8_t num_points; bool closed; uint8_t* commands; hvif_point* points; }; struct hvif_image { uint8_t num_styles; hvif_style* styles; uint8_t num_paths; hvif_path* paths; }; uint8_t path_command_at(hvif_path* path, uint8_t index) { uint8_t byte = path->commands[index / 4]; uint8_t byte_idx = index % 4; return (byte >> (2 * byte_idx)) & 0x03; } hvif_color decode_color(char* buffer, bool gray, bool alpha) { /* On disk a color is: red, green, blue, alpha Hence reading this as a (little-endian) uint32 result in the desired format. If `alpha` is `false` then only three bytes are used and alpha is `0xff`. If gray is `true` only one one byte is used for the three colors. */ hvif_color c = 0; if (alpha) { if (gray) { COLOR_SET_RGB(c, buffer[0], buffer[0], buffer[0]); COLOR_SET_ALPHA(c, buffer[1]); } else { hvif_decode(c, buffer); } } else { COLOR_SET_ALPHA(c, 0xff); if (gray) { COLOR_SET_RGB(c, buffer[0], buffer[0], buffer[0]); } else { COLOR_SET_RGB(c, buffer[0], buffer[1], buffer[2]); } } return c; } hvif_status read_color_style(hvif_style* style, char* buffer, bool gray, bool alpha) { hvif_color color = decode_color(buffer, gray, alpha); style->num_stops = 1; style->colors = calloc(1, sizeof(hvif_color)); style->offsets = calloc(1, sizeof(float)); if (!style->colors || !style->colors) return ERROR_NOMEM; style->colors[0] = color; style->offsets[0] = 1.0; /* TODO: Is this ok for solid colors */ return SUCCESS; } hvif_status read_gradient_style(hvif_style* style, FILE* file, char* style_buffer) { uint8_t gradient_type = style_buffer[0]; uint8_t gradient_flags = style_buffer[1]; uint8_t gradient_stops = style_buffer[2]; if (gradient_type >= HVIF_GRADIENT_TYPE_MAX) return ERROR_STYLE; style->gradient_type = gradient_type; bool gray = gradient_flags & GRADIENT_FLAG_GRAYS; bool alpha = !(gradient_flags & GRADIENT_FLAG_NO_ALPHA); /* structure: * if transformable [matrix] * then number of stops entries of the form: [stop][color] */ hvif_matrix transformation; if (gradient_flags & GRADIENT_FLAG_TRANSFORM) { /* The transformation matrix is 6 hvif-floats: 18 bytes */ char buffer[18]; if (fread(buffer, 1, 18, file) != 18) { return ERROR_EOF; } hvif_decode(transformation, buffer); } else { transformation = MATRIX_ID; } style->transformation = transformation; style->num_stops = gradient_stops; style->offsets = calloc(gradient_stops, sizeof(float)); style->colors = calloc(gradient_stops, sizeof(hvif_color)); if (!style->offsets || !style->colors) return ERROR_NOMEM; char buffer[4]; for (unsigned i = 0; i < gradient_stops; ++i) { if (fread(buffer, 1, 1, file) != 1) { return ERROR_EOF; } float offset = buffer[0] / 255.0; hvif_color c; if (alpha) { if (gray) { if (fread(buffer, 1, 2, file) != 2) { return ERROR_EOF; } c = decode_color(buffer, gray, alpha); } else { if (fread(buffer, 1, 4, file) != 4) { return ERROR_EOF; } c = decode_color(buffer, gray, alpha); } } else { if (gray) { if (fread(buffer, 1, 1, file) != 1) { return ERROR_EOF; } c = decode_color(buffer, gray, alpha); } else { if (fread(buffer, 1, 3, file) != 3) { return ERROR_EOF; } c = decode_color(buffer, gray, alpha); } } style->offsets[i] = offset; style->colors[i] = c; } return SUCCESS; } hvif_status read_style(FILE* file, hvif_style* style) { uint8_t type; if (fread(&type, 1, 1, file) != 1) { return ERROR_EOF; } char buffer[4]; switch (type) { case STYLE_TYPE_SOLID_COLOR: if (fread(buffer, 1, 4, file) != 4) { return ERROR_EOF; } read_color_style(style, buffer, false, true); break; case STYLE_TYPE_GRADIENT: if (fread(buffer, 1, 3, file) != 3) { return ERROR_EOF; } read_gradient_style(style, file, buffer); break; case STYLE_TYPE_SOLID_COLOR_NO_ALPHA: if (fread(buffer, 1, 3, file) != 3) { return ERROR_EOF; } read_color_style(style, buffer, false, false); break; case STYLE_TYPE_SOLID_GRAY: if (fread(buffer, 1, 2, file) != 2) { return ERROR_EOF; } read_color_style(style, buffer, true, true); break; case STYLE_TYPE_SOLID_GRAY_NO_ALPHA: if (fread(buffer, 1, 1, file) != 1) { return ERROR_EOF; } read_color_style(style, buffer, true, false); break; default: return ERROR_STYLE; } return SUCCESS; } hvif_status read_coordinate(FILE* file, float* coord) { uint8_t high_value; if (fread(&high_value, 1, 1, file) != 1) { return ERROR_EOF; } /* If the highest bit is set, the coordinate uses two bytes */ if (high_value & 128) { high_value &= 127; uint8_t low_value; if (fread(&low_value, 1, 1, file) != 1) { return ERROR_EOF; } uint16_t coord_value = (high_value << 8) | low_value; *coord = ((float)coord_value) / 102.0 - 128.0; } else { *coord = ((float)high_value) - 32.0; } return SUCCESS; } hvif_status read_path_points(FILE* file, hvif_path* path) { /* Every path point is three points: the proper point, in point, out point */ path->points = calloc(3 * path->num_points, sizeof(hvif_point)); if (!path->points) return ERROR_NOMEM; hvif_point last = POINT_ORIGIN; hvif_point point; hvif_point point_in; hvif_point point_out; hvif_status status; for (unsigned i = 0; i < path->num_points; i += 3) { switch (path_command_at(path, i)) { case PATH_COMMAND_H_LINE: point = last; status = read_coordinate(file, &point.x); if (status != SUCCESS) return status; point_in = point_out = point; break; case PATH_COMMAND_V_LINE: point = last; status = read_coordinate(file, &point.x); if (status != SUCCESS) return status; point_in = point_out = point; break; case PATH_COMMAND_LINE: status = read_coordinate(file, &point.x); if (status != SUCCESS) return status; status = read_coordinate(file, &point.y); if (status != SUCCESS) return status; point_in = point_out = point; break; case PATH_COMMAND_CURVE: status = read_coordinate(file, &point.x); if (status != SUCCESS) return status; status = read_coordinate(file, &point.y); if (status != SUCCESS) return status; point_in = point_out = point; status = read_coordinate(file, &point_in.x); if (status != SUCCESS) return status; status = read_coordinate(file, &point_in.y); if (status != SUCCESS) return status; point_in = point_out = point; status = read_coordinate(file, &point_out.x); if (status != SUCCESS) return status; status = read_coordinate(file, &point_out.y); if (status != SUCCESS) return status; point_in = point_out = point; break; } path->points[i] = point; path->points[i + 1] = point_in; path->points[i + 2] = point_out; last = point; } return SUCCESS; } static inline void create_static_commands( uint8_t num_command_bytes, uint8_t commands[static 1], const uint8_t cmd) { for (unsigned i = 0; i < num_command_bytes; ++i) commands[i] = cmd; } hvif_status read_path(FILE* file, hvif_path* path) { uint8_t flags; if (fread(&flags, 1, 1, file) != 1) { return ERROR_PATH; } if (fread(&path->num_points, 1, 1, file) != 1) { return ERROR_PATH; } path->closed = flags & PATH_FLAG_CLOSED; uint8_t num_command_bytes = (path->num_points + 3) / 4; path->commands = calloc(num_command_bytes, 1); if (!path->commands) return ERROR_NOMEM; if ((flags & PATH_FLAG_USES_COMMANDS) && (flags & PATH_FLAG_NO_CURVES)) { return ERROR_PATH; } if (flags & PATH_FLAG_USES_COMMANDS) { if (fread(&flags, 1, num_command_bytes, file) != num_command_bytes) { return ERROR_PATH; } } else if (flags & PATH_FLAG_NO_CURVES) { create_static_commands( num_command_bytes, path->commands, 0xFF); /* 0b11111111: all curves */ } else { /* All lines */ create_static_commands( num_command_bytes, path->commands, 0xAA); /* 0b10101010: all lines */ } return read_path_points(file, path); } hvif_result hvif_from_file(FILE* file) { uint32_t const magic = 0x6669636e; /* 'finc' (on disk little endian 'cnif') */ char magic_buffer[4]; if (fread(magic_buffer, 1, 4, file) != 4) { return ERROR_RESULT(ERROR_EOF); } uint32_t read_magic; hvif_decode(read_magic, magic_buffer); if (read_magic != magic) { return ERROR_RESULT(ERROR_MAGIC); } hvif_image* image = calloc(1, sizeof(hvif_image)); if (!image) return ERROR_RESULT(ERROR_NOMEM); if (fread(&image->num_styles, 1, 1, file) != 1) { hvif_free(image); return ERROR_RESULT(ERROR_EOF); } image->styles = calloc(image->num_styles, sizeof(hvif_style)); if (!image->styles) { hvif_free(image); return ERROR_RESULT(ERROR_NOMEM); } for (unsigned i = 0; i < image->num_styles; ++i) { hvif_status s = read_style(file, &image->styles[i]); if (s != SUCCESS) { hvif_free(image); return ERROR_RESULT(s); } } if (fread(&image->num_paths, 1, 1, file) != 1) { hvif_free(image); return ERROR_RESULT(ERROR_EOF); } image->paths = calloc(image->num_paths, sizeof(hvif_path)); if (!image->paths) { hvif_free(image); return ERROR_RESULT(ERROR_NOMEM); } for (unsigned i = 0; i < image->num_paths; ++i) { hvif_status s = read_path(file, &image->paths[i]); if (s != SUCCESS) { hvif_free(image); return ERROR_RESULT(s); } } return SUCCESS_RESULT(image); } void hvif_free(hvif_image* image) { for (unsigned i = 0; i < image->num_styles; ++i) { free(image->styles[i].colors); free(image->styles[i].offsets); } for (unsigned i = 0; i < image->num_paths; ++i) { free(image->paths[i].commands); free(image->paths[i].points); } free(image->styles); free(image); }