#include "hvif-cairo.h" #include "hvif-light.h" #include #include #include #include #include #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 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) (c.red / 255.0) #define G(c) (c.green / 255.0) #define B(c) (c.blue / 255.0) #define A(c) (c.alpha / 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 HVIF_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 HVIF_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 HVIF_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 HVIF_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 HVIF_GRADIENT_XY: #ifdef DEBUG printf("HVIF_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 HVIF_GRADIENT_SQRT_XY: #ifdef DEBUG printf("HVIF_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; }