466 lines
14 KiB
C
466 lines
14 KiB
C
#include "hvif-cairo.h"
|
|
|
|
#include "hvif-light.h"
|
|
|
|
#include <assert.h>
|
|
#include <cairo.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#define INTERNAL_DATASTRUCTURES
|
|
#include "hvif-light.c"
|
|
#undef INTERNAL_DATASTRUCTURES
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
/* TODO:
|
|
* - 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
|
|
cairo_matrix_init_from_matrix(
|
|
cairo_matrix_t* matrix, const hvif_matrix* hvif_matrix)
|
|
{
|
|
cairo_matrix_init(
|
|
matrix, hvif_matrix->xx, hvif_matrix->yx, hvif_matrix->xy, hvif_matrix->yy,
|
|
hvif_matrix->x0, hvif_matrix->y0);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
/**
|
|
* \brief Prints the transformation definet by a hvif matrix.
|
|
*/
|
|
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)
|
|
#define B(c) (COLOR_GET_BLUE(c) / 255.0)
|
|
#define A(c) (COLOR_GET_ALPHA(c) / 255.0)
|
|
|
|
/**
|
|
* \brief Create a patch for a diamond gradient.
|
|
*/
|
|
void
|
|
create_diamond_patch(
|
|
cairo_pattern_t* pat, double s1, double s2, hvif_color c1, hvif_color c2)
|
|
{
|
|
cairo_mesh_pattern_begin_patch(pat);
|
|
cairo_mesh_pattern_move_to(pat, s1, s1);
|
|
cairo_mesh_pattern_line_to(pat, s2, s2);
|
|
cairo_mesh_pattern_line_to(pat, s2, -s2);
|
|
cairo_mesh_pattern_line_to(pat, s1, -s1);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 0, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 1, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 2, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 3, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_end_patch(pat);
|
|
cairo_mesh_pattern_begin_patch(pat);
|
|
cairo_mesh_pattern_move_to(pat, s1, -s1);
|
|
cairo_mesh_pattern_line_to(pat, s2, -s2);
|
|
cairo_mesh_pattern_line_to(pat, -s2, -s2);
|
|
cairo_mesh_pattern_line_to(pat, -s1, -s1);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 0, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 1, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 2, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 3, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_end_patch(pat);
|
|
cairo_mesh_pattern_begin_patch(pat);
|
|
cairo_mesh_pattern_move_to(pat, -s1, -s1);
|
|
cairo_mesh_pattern_line_to(pat, -s2, -s2);
|
|
cairo_mesh_pattern_line_to(pat, -s2, s2);
|
|
cairo_mesh_pattern_line_to(pat, -s1, s1);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 0, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 1, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 2, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 3, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_end_patch(pat);
|
|
cairo_mesh_pattern_begin_patch(pat);
|
|
cairo_mesh_pattern_move_to(pat, -s1, s1);
|
|
cairo_mesh_pattern_line_to(pat, -s2, s2);
|
|
cairo_mesh_pattern_line_to(pat, s2, s2);
|
|
cairo_mesh_pattern_line_to(pat, s1, s1);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 0, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 1, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 2, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 3, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_end_patch(pat);
|
|
}
|
|
|
|
/**
|
|
* \brief Create a patch for a conic gradient.
|
|
*/
|
|
void
|
|
create_conic_patch(
|
|
cairo_pattern_t* pat, double r, double angle1, double angle2, hvif_color c1,
|
|
hvif_color c2)
|
|
{
|
|
double sin1 = r * sin(angle1);
|
|
double cos1 = r * cos(angle1);
|
|
double sin2 = r * sin(angle2);
|
|
double cos2 = r * cos(angle2);
|
|
double h = 4.0 / 3.0 * tan((angle2 - angle1) / 4.0);
|
|
cairo_mesh_pattern_begin_patch(pat);
|
|
cairo_mesh_pattern_move_to(pat, 0, 0);
|
|
cairo_mesh_pattern_line_to(pat, cos1, sin1);
|
|
cairo_mesh_pattern_curve_to(
|
|
pat, cos1 - h * sin1, sin1 + h * cos1, cos2 + h * sin2, sin2 - h * cos2,
|
|
cos2, sin2);
|
|
cairo_mesh_pattern_line_to(pat, 0, 0);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 0, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 1, R(c1), G(c1), B(c1), A(c1));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 2, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_set_corner_color_rgba(pat, 3, R(c2), G(c2), B(c2), A(c2));
|
|
cairo_mesh_pattern_end_patch(pat);
|
|
}
|
|
|
|
/**
|
|
* \brief Create a cairo pattern from a \a hvif style.
|
|
*
|
|
* XY and sqrt XY gradients are unsupported and replaced by radial gradients.
|
|
*/
|
|
hvif_cairo_status
|
|
create_pattern_style(cairo_pattern_t** pat, hvif_style* style)
|
|
{
|
|
assert(style->num_stops > 0);
|
|
|
|
double width = 64.0;
|
|
double height = 64.0;
|
|
cairo_matrix_t transformation;
|
|
cairo_matrix_init_from_matrix(&transformation, &style->transformation);
|
|
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);
|
|
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_CIRCULAR:
|
|
*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_DIAMOND:
|
|
*pat = cairo_pattern_create_mesh();
|
|
if (style->offsets[0] != 0.0) {
|
|
create_diamond_patch(
|
|
*pat, 0, 64.0 * style->offsets[0], style->colors[0],
|
|
style->colors[0]);
|
|
}
|
|
for (unsigned i = 0; i + 1 < style->num_stops; ++i) {
|
|
double s1 = style->offsets[i] * 64.0;
|
|
double s2 = style->offsets[i + 1] * 64.0;
|
|
hvif_color c1 = style->colors[i];
|
|
hvif_color c2 = style->colors[i + 1];
|
|
create_diamond_patch(*pat, s1, s2, c1, c2);
|
|
}
|
|
unsigned i = style->num_stops - 1;
|
|
create_diamond_patch(
|
|
*pat, 64.0 * style->offsets[i], extremum, style->colors[i],
|
|
style->colors[i]);
|
|
break;
|
|
case GRADIENT_CONIC:
|
|
*pat = cairo_pattern_create_mesh();
|
|
if (style->offsets[0] != 0.0) {
|
|
create_conic_patch(
|
|
*pat, extremum, 0, M_PI * style->offsets[0], style->colors[0],
|
|
style->colors[0]);
|
|
create_conic_patch(
|
|
*pat, extremum, 0, -M_PI * style->offsets[0], style->colors[0],
|
|
style->colors[0]);
|
|
}
|
|
for (unsigned i = 0; i + 1 < style->num_stops; ++i) {
|
|
create_conic_patch(
|
|
*pat, extremum, M_PI * style->offsets[i],
|
|
M_PI * style->offsets[i + 1], style->colors[i], style->colors[i + 1]);
|
|
create_conic_patch(
|
|
*pat, extremum, -M_PI * style->offsets[i],
|
|
-M_PI * style->offsets[i + 1], style->colors[i],
|
|
style->colors[i + 1]);
|
|
}
|
|
i = style->num_stops - 1;
|
|
if (style->offsets[i] != 1.0) {
|
|
create_conic_patch(
|
|
*pat, extremum, M_PI * style->offsets[i], 0, style->colors[i],
|
|
style->colors[i]);
|
|
create_conic_patch(
|
|
*pat, extremum, -M_PI * style->offsets[i], 0, style->colors[i],
|
|
style->colors[i]);
|
|
}
|
|
break;
|
|
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");
|
|
#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;
|
|
}
|
|
|
|
/**
|
|
* \brief Create a cairo path from a \a hvif path.
|
|
*/
|
|
void
|
|
create_path(cairo_t* cr, cairo_path_t** path, hvif_path* hvif_path)
|
|
{
|
|
assert(hvif_path->num_points > 0);
|
|
|
|
cairo_new_path(cr);
|
|
cairo_move_to(cr, hvif_path->points[0].x, hvif_path->points[0].y);
|
|
for (unsigned i = 1; i < hvif_path->num_points; ++i) {
|
|
hvif_point cp1 = hvif_path->points[3 * i - 1];
|
|
hvif_point cp2 = hvif_path->points[3 * i + 1];
|
|
hvif_point p = hvif_path->points[3 * i];
|
|
cairo_curve_to(cr, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y);
|
|
}
|
|
|
|
if (hvif_path->closed) {
|
|
unsigned max = hvif_path->num_points - 1;
|
|
hvif_point cp1 = hvif_path->points[3 * max + 2];
|
|
hvif_point cp2 = hvif_path->points[1];
|
|
hvif_point p = hvif_path->points[0];
|
|
cairo_curve_to(cr, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y);
|
|
cairo_close_path(cr);
|
|
}
|
|
*path = cairo_copy_path_flat(cr);
|
|
}
|
|
|
|
hvif_cairo_status
|
|
render_shape(
|
|
cairo_t* cr, hvif_shape* shape, cairo_pattern_t** pattern,
|
|
cairo_path_t** path)
|
|
{
|
|
cairo_matrix_t old_transform;
|
|
cairo_get_matrix(cr, &old_transform);
|
|
cairo_matrix_t shape_transform;
|
|
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) {
|
|
#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");
|
|
#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:
|
|
#ifdef DEBUG
|
|
printf("unsupported: transform\n");
|
|
#endif /* DEBUG */
|
|
unsupported = true;
|
|
}
|
|
}
|
|
|
|
cairo_new_path(cr);
|
|
for (unsigned i = 0; i < shape->num_paths; ++i) {
|
|
cairo_append_path(cr, path[shape->path_idxs[i]]);
|
|
cairo_new_sub_path(cr);
|
|
}
|
|
cairo_set_source(cr, pattern[shape->style_idx]);
|
|
|
|
if (stroke_idx >= 0) {
|
|
hvif_transformer* t = &shape->transformers[stroke_idx];
|
|
cairo_set_line_width(cr, t->transformer.stroke.width);
|
|
switch (t->transformer.stroke.line_join) {
|
|
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:
|
|
#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:
|
|
#ifdef DEBUG
|
|
printf("illegal line cap");
|
|
#endif /* DEBUG */
|
|
return HVIF_CAIRO_ERROR;
|
|
}
|
|
cairo_stroke(cr);
|
|
} else
|
|
cairo_fill(cr);
|
|
|
|
cairo_set_matrix(cr, &old_transform);
|
|
|
|
if (shape->hinting) {
|
|
#ifdef DEBUG
|
|
printf("unsupported: hinting\n");
|
|
#endif /* DEBUG */
|
|
unsupported = true;
|
|
}
|
|
return unsupported ? HVIF_CAIRO_UNSUPPORTED : HVIF_CAIRO_SUCCESS;
|
|
}
|
|
|
|
hvif_cairo_status
|
|
hvif_cairo_png_render(
|
|
const char* filename, hvif_image* image, unsigned pixel_size)
|
|
{
|
|
assert(image != NULL);
|
|
cairo_surface_t* surface =
|
|
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)
|
|
{
|
|
assert(surface != NULL);
|
|
assert(image != NULL);
|
|
cairo_t* cr = cairo_create(surface);
|
|
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)
|
|
{
|
|
assert(context != NULL);
|
|
assert(image != NULL);
|
|
cairo_save(context);
|
|
|
|
cairo_pattern_t** patterns =
|
|
malloc(sizeof(cairo_pattern_t*) * image->num_styles);
|
|
|
|
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(context, &paths[i], &image->paths[i]);
|
|
}
|
|
|
|
for (unsigned i = 0; i < image->num_shapes; ++i) {
|
|
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]);
|
|
}
|
|
free(paths);
|
|
free(patterns);
|
|
cairo_restore(context);
|
|
return result;
|
|
}
|
|
|