1
0
Fork 0

Cleanup and extension of CAIRO renderer

- Add support for LODs
- Error handling
- Nicer API
- Extend command line tool
- Marking unsupported features
This commit is contained in:
Hans-Joerg Schurr 2020-04-28 21:30:58 +02:00
parent 76afd0b903
commit 504c5fe606
4 changed files with 203 additions and 46 deletions

View File

@ -1,3 +1,5 @@
#include "hvif-cairo.h"
#include "hvif-light.h"
#include <assert.h>
@ -15,10 +17,11 @@
#endif
/* TODO:
* - Color space
* - Contour
* - LOD
* - Check miter limit
* - Color space -> leave it a this for now since cairo doesn't support
* - Contour -> We don't support this for now
* - Multiple strokes -> We don't support this for now
* - Check miter limit -> why unit8_t for something that can well be a float
* in (0, 1)
*/
void
@ -30,12 +33,14 @@ cairo_matrix_init_from_matrix(
hvif_matrix->x0, hvif_matrix->y0);
}
#ifdef DEBUG
void
print_matrix(const hvif_matrix* matrix)
{
printf("x_new = %f * x + %f * y + %f\n", matrix->xx, matrix->xy, matrix->x0);
printf("y_new = %f * x + %f * y + %f\n", matrix->yx, matrix->yy, matrix->y0);
}
#endif /* DEBUG */
#define R(c) (COLOR_GET_RED(c) / 255.0)
#define G(c) (COLOR_GET_GREEN(c) / 255.0)
@ -112,7 +117,7 @@ create_conic_patch(
cairo_mesh_pattern_end_patch(pat);
}
void
hvif_cairo_status
create_pattern_style(cairo_pattern_t** pat, hvif_style* style)
{
assert(style->num_stops > 0);
@ -124,6 +129,7 @@ create_pattern_style(cairo_pattern_t** pat, hvif_style* style)
cairo_matrix_transform_point(&transformation, &width, &height);
double extremum = width > height ? width : height;
bool unsupported = false;
switch (style->gradient_type) {
case GRADIENT_LINEAR:
*pat = cairo_pattern_create_linear(-64, 32, 64, 32);
@ -189,16 +195,44 @@ create_pattern_style(cairo_pattern_t** pat, hvif_style* style)
style->colors[i]);
}
break;
case GRADIENT_XY: printf("GRADIENT_XY not supported\n"); exit(1);
case GRADIENT_SQRT_XY: printf("GRADIENT_SQRT_XY not supported\n"); exit(1);
default: printf("unsupported gradient type\n"); exit(1);
case GRADIENT_XY:
#ifdef DEBUG
printf("GRADIENT_XY not supported\n");
exit(1);
#endif /* DEBUG */
unsupported = true;
*pat = cairo_pattern_create_radial(0, 0, 0, 0, 0, 64);
for (unsigned i = 0; i < style->num_stops; ++i) {
hvif_color c = style->colors[i];
cairo_pattern_add_color_stop_rgba(
*pat, style->offsets[i], R(c), G(c), B(c), A(c));
}
break;
case GRADIENT_SQRT_XY:
#ifdef DEBUG
printf("GRADIENT_SQRT_XY not supported\n");
exit(1);
#endif /* DEBUG */
unsupported = true;
*pat = cairo_pattern_create_radial(0, 0, 0, 0, 0, 64);
for (unsigned i = 0; i < style->num_stops; ++i) {
hvif_color c = style->colors[i];
cairo_pattern_add_color_stop_rgba(
*pat, style->offsets[i], R(c), G(c), B(c), A(c));
}
break;
default: return HVIF_CAIRO_ERROR;
}
if (cairo_matrix_invert(&transformation) != CAIRO_STATUS_SUCCESS) {
#ifdef DEBUG
printf("could not invert style transformation\n");
exit(1);
#endif /* DEBUG */
cairo_pattern_destroy(*pat);
return HVIF_CAIRO_ERROR;
} else
cairo_pattern_set_matrix(*pat, &transformation);
return unsupported ? HVIF_CAIRO_UNSUPPORTED : HVIF_CAIRO_SUCCESS;
}
void
@ -226,7 +260,7 @@ create_path(cairo_t* cr, cairo_path_t** path, hvif_path* hvif_path)
*path = cairo_copy_path_flat(cr);
}
void
hvif_cairo_status
render_shape(
cairo_t* cr, hvif_shape* shape, cairo_pattern_t** pattern,
cairo_path_t** path)
@ -237,23 +271,39 @@ render_shape(
cairo_matrix_init_from_matrix(&shape_transform, &shape->transformation);
cairo_transform(cr, &shape_transform);
bool unsupported = false;
int stroke_idx = -1;
for (unsigned i = 0; i < shape->num_transformers; ++i) {
hvif_transformer* t = &shape->transformers[i];
switch (t->transformer_type) {
case TRANSFORMER_TYPE_STROKE:
if (stroke_idx != -1)
if (stroke_idx != -1) {
#ifdef DEBUG
printf("unsupported: multiple stroke transform\n");
#endif /* DEBUG */
unsupported = true;
}
stroke_idx = i;
break;
case TRANSFORMER_TYPE_AFFINE:
/* TODO: this is a guess what this does, but it's not exposed in
Icon-o-Matic. */
#ifdef DEBUG
printf("warning: affine transform\n");
cairo_matrix_init_from_matrix(
&shape_transform, &t->transformer.affine.matrix);
#endif /* DEBUG */
/* We only apply this if it occurs before the first stroke for now*/
if (stroke_idx == -1) {
cairo_matrix_init_from_matrix(
&shape_transform, &t->transformer.affine.matrix);
cairo_transform(cr, &shape_transform);
}
break;
default: printf("unsupported: transform\n");
default:
#ifdef DEBUG
printf("unsupported: transform\n");
#endif /* DEBUG */
unsupported = true;
}
}
@ -271,14 +321,22 @@ render_shape(
case (MITER_JOIN): cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER); break;
case (ROUND_CAP): cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); break;
case (BEVEL_JOIN): cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); break;
default: printf("unsupported: line join\n");
default:
#ifdef DEBUG
printf("unsupported: line join\n");
#endif /* DEBUG */
unsupported = true;
}
cairo_set_miter_limit(cr, t->transformer.stroke.miter_limit);
switch (t->transformer.stroke.line_cap) {
case (BUTT_CAP): cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT); break;
case (SQUARE_CAP): cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); break;
case (ROUND_CAP): cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); break;
default: printf("illegal line cap"); exit(1);
default:
#ifdef DEBUG
printf("illegal line cap");
#endif /* DEBUG */
return HVIF_CAIRO_ERROR;
}
cairo_stroke(cr);
} else
@ -286,49 +344,99 @@ render_shape(
cairo_set_matrix(cr, &old_transform);
if (shape->hinting) printf("unsupported: hinting\n");
/* TODO: implement LOD */
if (shape->hinting) {
#ifdef DEBUG
printf("unsupported: hinting\n");
#endif /* DEBUG */
unsupported = true;
}
return unsupported ? HVIF_CAIRO_UNSUPPORTED : HVIF_CAIRO_SUCCESS;
}
bool
hvif_render_image(const char* filename, hvif_image* image)
hvif_cairo_status
hvif_cairo_png_render(
const char* filename, hvif_image* image, unsigned pixel_size)
{
unsigned image_size = 512;
cairo_surface_t* surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, image_size, image_size);
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pixel_size, pixel_size);
double scale = pixel_size / 64.0;
hvif_cairo_status result = hvif_cairo_surface_render(surface, image, scale);
if (result > HVIF_CAIRO_UNSUPPORTED) {
cairo_surface_destroy(surface);
return result;
}
if (cairo_surface_write_to_png(surface, filename) != CAIRO_STATUS_SUCCESS)
result = HVIF_CAIRO_ERROR;
cairo_surface_destroy(surface);
return result;
}
hvif_cairo_status
hvif_cairo_surface_render(
cairo_surface_t* surface, hvif_image* image, double scale)
{
cairo_t* cr = cairo_create(surface);
cairo_scale(cr, image_size / 64.0, image_size / 64.0);
cairo_scale(cr, scale, scale);
hvif_cairo_status result = hvif_cairo_render(cr, image, scale);
cairo_destroy(cr);
return result;
}
hvif_cairo_status
hvif_cairo_render(cairo_t* context, hvif_image* image, double scale)
{
cairo_save(context);
cairo_pattern_t** patterns =
malloc(sizeof(cairo_pattern_t*) * image->num_styles);
if (!patterns) return false;
cairo_path_t** paths = malloc(sizeof(cairo_path_t*) * image->num_paths);
if (!paths) return false;
for (unsigned i = 0; i < image->num_styles; ++i) {
create_pattern_style(&patterns[i], &image->styles[i]);
/* TODO: error handling */
if (!patterns) return HVIF_CAIRO_ERROR_NOMEM;
cairo_path_t** paths = malloc(sizeof(cairo_path_t*) * image->num_paths);
if (!paths) {
free(patterns);
return HVIF_CAIRO_ERROR_NOMEM;
}
hvif_cairo_status result = HVIF_CAIRO_ERROR;
bool unsupported = false;
for (unsigned i = 0; i < image->num_styles; ++i) {
result = create_pattern_style(&patterns[i], &image->styles[i]);
if (result > HVIF_CAIRO_UNSUPPORTED) {
for (unsigned j = 0; j < i; ++j) cairo_pattern_destroy(patterns[j]);
free(paths);
free(patterns);
return result;
}
if (result == HVIF_CAIRO_UNSUPPORTED) unsupported = true;
}
for (unsigned i = 0; i < image->num_paths; ++i) {
create_path(cr, &paths[i], &image->paths[i]);
/* TODO: error handling */
create_path(context, &paths[i], &image->paths[i]);
}
for (unsigned i = 0; i < image->num_shapes; ++i) {
render_shape(cr, &image->shapes[i], patterns, paths);
const hvif_shape* shape = &image->shapes[i];
if (
scale < shape->min_visibility ||
(scale > shape->max_visibility && scale < 4.0))
continue;
result = render_shape(context, &image->shapes[i], patterns, paths);
if (result > HVIF_CAIRO_UNSUPPORTED) break;
if (result == HVIF_CAIRO_UNSUPPORTED) unsupported = true;
}
if (unsupported == true) result = HVIF_CAIRO_UNSUPPORTED;
for (unsigned i = 0; i < image->num_paths; ++i) {
cairo_path_destroy(paths[i]);
}
for (unsigned i = 0; i < image->num_styles; ++i) {
cairo_pattern_destroy(patterns[i]);
}
bool result = true;
if (cairo_surface_write_to_png(surface, filename) != CAIRO_STATUS_SUCCESS)
result = false;
cairo_surface_destroy(surface);
cairo_destroy(cr);
free(paths);
free(patterns);
cairo_restore(context);
return result;
}

View File

@ -3,6 +3,23 @@
#include "hvif-light.h"
bool hvif_render_image(const char* filename, hvif_image* image);
#include <cairo.h>
typedef enum hvif_cairo_status
{
HVIF_CAIRO_SUCCESS = 0,
HVIF_CAIRO_UNSUPPORTED,
HVIF_CAIRO_ERROR_NOMEM,
HVIF_CAIRO_ERROR
} hvif_cairo_status;
hvif_cairo_status hvif_cairo_png_render(
const char* filename, hvif_image* image, unsigned pixel_size);
hvif_cairo_status hvif_cairo_surface_render(
cairo_surface_t* surface, hvif_image* image, double scale);
hvif_cairo_status hvif_cairo_render(
cairo_t* context, hvif_image* image, double scale);
#endif /* HVIF_CAIRO_H */

View File

@ -209,7 +209,7 @@ struct hvif_image
hvif_shape* shapes;
};
inline uint8_t
static inline uint8_t
path_command_at(hvif_path* path, uint8_t index)
{
uint8_t byte = path->commands[index / 4];
@ -674,8 +674,8 @@ read_shape(FILE* file, hvif_image const* const image, hvif_shape* shape)
if (fread(&scale, 1, 1, file) != 1) { return ERROR_EOF; }
shape->max_visibility = scale / 63.75;
} else {
shape->min_visibility = -1.0;
shape->max_visibility = -1.0;
shape->min_visibility = 0.0;
shape->max_visibility = 4.0;
}
shape->num_transformers = 0;

View File

@ -1,9 +1,10 @@
#include "config.h"
#include "hvif-light.h"
#include "hvif-cairo.h"
#include "hvif-light.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main(int argc, char* argv[argc + 1])
@ -11,7 +12,7 @@ main(int argc, char* argv[argc + 1])
puts(PACKAGE_STRING);
if (argc < 2) {
if (argc > 0) printf("Usage: %s FILE\n", argv[0]);
if (argc > 0) printf("Usage: %s FILE [SIZE]\n", argv[0]);
return EXIT_FAILURE;
}
@ -27,8 +28,9 @@ main(int argc, char* argv[argc + 1])
if (result.status != SUCCESS) {
fputs("Reading image failed.\n", stderr);
switch (result.status) {
case ERROR_NOMEM: fputs("Out of memory.\n", stderr); break;
case ERROR_EOF: fputs("File ended prematurely.\n", stderr); break;
case ERROR_NOMEM: fputs("Out of memory.\n", stderr); break;
case ERROR_MAGIC: fputs("Wrong magic number.\n", stderr); break;
case ERROR_STYLE: fputs("Incorrect style definition.\n", stderr); break;
case ERROR_PATH: fputs("Incorrect path definition.\n", stderr); break;
case ERROR_SHAPE: fputs("Incorrect shape definition.\n", stderr); break;
@ -38,9 +40,39 @@ main(int argc, char* argv[argc + 1])
}
puts("Reading image succeeded.");
hvif_render_image("test.png", result.image);
const char* pf = ".png";
size_t out_len = strlen(argv[1]) + strlen(pf) + 1;
char out_filename[out_len];
strcpy(out_filename, argv[1]);
strcat(out_filename, pf);
unsigned pixel_size = 64;
if (argc > 2) {
int n = atoi(argv[2]);
if (n <= 1)
fputs("Illegal image size. Using default of 64.\n", stderr);
else
pixel_size = n;
}
printf(
"Creating %s with a size of %dx%d\n", out_filename, pixel_size, pixel_size);
hvif_cairo_status out_result =
hvif_cairo_png_render(out_filename, result.image, pixel_size);
hvif_free(result.image);
if (out_result != HVIF_CAIRO_SUCCESS) {
switch (out_result) {
case HVIF_CAIRO_UNSUPPORTED:
puts("Image uses features no supported by cairo renderer.");
break;
case HVIF_CAIRO_ERROR_NOMEM: fputs("Out of memory.", stderr); break;
case HVIF_CAIRO_ERROR: fputs("Error rendering picture.", stderr); break;
default: break;
}
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}