blob: 199eadd89965b30022e2204339b5278c9c283832 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "svg2spinel.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "spinel/ext/color/color.h"
#include "spinel/ext/geometry/ellipse.h"
#include "spinel/ext/geometry/svg_arc.h"
#include "spinel/spinel_assert.h"
#include "spinel/spinel_opcodes.h"
//
//
//
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
//
//
//
void
spn_svg_paths_release(const struct svg * const svg, spn_context_t context, spn_path_t * const paths)
{
uint32_t const n = svg_path_count(svg);
spn(path_release(context, paths, n));
free(paths);
}
void
spn_svg_rasters_release(const struct svg * const svg,
spn_context_t context,
spn_raster_t * const rasters)
{
uint32_t const n = svg_raster_count(svg);
spn(raster_release(context, rasters, n));
free(rasters);
}
//
//
//
static void
spn_svg_poly_read(struct svg_path_iterator * iter, spn_path_builder_t pb, bool const close)
{
union svg_path_cmd const * cmd;
float x0, y0;
bool lineto = false;
while (svg_path_iterator_next(iter, &cmd))
{
if (cmd->type != SVG_PATH_CMD_POLY_POINT)
break;
if (lineto)
{
spn(path_builder_line_to(pb, cmd->poly_point.x, cmd->poly_point.y));
}
else
{
spn(path_builder_move_to(pb, x0 = cmd->poly_point.x, y0 = cmd->poly_point.y));
lineto = true;
}
}
if (close && lineto)
{
spn(path_builder_line_to(pb, x0, y0));
}
}
//
//
//
static void
spn_svg_implicit_close_filled_path(spn_path_builder_t pb, float x0, float y0, float x, float y)
{
if ((x != x0) || (y != y0))
spn(path_builder_line_to(pb, x0, y0));
}
//
//
//
spn_path_t *
spn_svg_paths_decode(struct svg const * const svg, spn_path_builder_t pb)
{
spn_path_t * const paths = malloc(sizeof(*paths) * svg_path_count(svg));
union svg_path_cmd const * cmd;
float x0, y0, x, y;
struct svg_path_iterator * iter = svg_path_iterator_create(svg, UINT32_MAX);
while (svg_path_iterator_next(iter, &cmd))
{
switch (cmd->type)
{
case SVG_PATH_CMD_BEGIN:
spn(path_builder_begin(pb));
break;
case SVG_PATH_CMD_END:
spn(path_builder_end(pb, paths + cmd->end.path_index));
break;
case SVG_PATH_CMD_CIRCLE:
spn(path_builder_ellipse(pb,
cmd->circle.cx,
cmd->circle.cy,
cmd->circle.r,
cmd->circle.r));
break;
case SVG_PATH_CMD_ELLIPSE:
spn(path_builder_ellipse(pb,
cmd->ellipse.cx,
cmd->ellipse.cy,
cmd->ellipse.rx,
cmd->ellipse.ry));
break;
case SVG_PATH_CMD_LINE:
spn(path_builder_move_to(pb, cmd->line.x1, cmd->line.y1));
spn(path_builder_line_to(pb, cmd->line.x2, cmd->line.y2));
break;
case SVG_PATH_CMD_POLYGON:
spn_svg_poly_read(iter, pb, true);
break;
case SVG_PATH_CMD_POLYLINE:
spn_svg_poly_read(iter, pb, false);
break;
case SVG_PATH_CMD_RECT:
spn(path_builder_move_to(pb, cmd->rect.x, cmd->rect.y));
spn(path_builder_line_to(pb, cmd->rect.x + cmd->rect.width, cmd->rect.y));
spn(path_builder_line_to(pb,
cmd->rect.x + cmd->rect.width,
cmd->rect.y + cmd->rect.height));
spn(path_builder_line_to(pb, cmd->rect.x, cmd->rect.y + cmd->rect.height));
spn(path_builder_line_to(pb, cmd->rect.x, cmd->rect.y));
break;
case SVG_PATH_CMD_PATH_BEGIN:
x = x0 = 0.0f;
y = y0 = 0.0f;
break;
case SVG_PATH_CMD_PATH_END:
spn_svg_implicit_close_filled_path(pb, x0, y0, x, y);
break;
case SVG_PATH_CMD_MOVE_TO:
spn_svg_implicit_close_filled_path(pb, x0, y0, x, y);
spn_path_builder_move_to(pb, x0 = x = cmd->move_to.x, y0 = y = cmd->move_to.y);
break;
case SVG_PATH_CMD_MOVE_TO_REL:
spn_svg_implicit_close_filled_path(pb, x0, y0, x, y);
spn(path_builder_move_to(pb, x0 = (x += cmd->move_to.x), y0 = (y += cmd->move_to.y)));
break;
case SVG_PATH_CMD_CLOSE_UPPER:
case SVG_PATH_CMD_CLOSE:
spn_svg_implicit_close_filled_path(pb, x0, y0, x, y);
spn(path_builder_move_to(pb, x = x0, y = y0)); // reset to initial point
break;
case SVG_PATH_CMD_LINE_TO:
spn(path_builder_line_to(pb, x = cmd->line_to.x, y = cmd->line_to.y));
break;
case SVG_PATH_CMD_LINE_TO_REL:
spn(path_builder_line_to(pb, x += cmd->line_to.x, y += cmd->line_to.y));
break;
case SVG_PATH_CMD_HLINE_TO:
spn(path_builder_line_to(pb, x = cmd->hline_to.c, y));
break;
case SVG_PATH_CMD_HLINE_TO_REL:
spn(path_builder_line_to(pb, x += cmd->hline_to.c, y));
break;
case SVG_PATH_CMD_VLINE_TO:
spn(path_builder_line_to(pb, x, y = cmd->vline_to.c));
break;
case SVG_PATH_CMD_VLINE_TO_REL:
spn(path_builder_line_to(pb, x, y += cmd->vline_to.c));
break;
case SVG_PATH_CMD_CUBIC_TO:
spn(path_builder_cubic_to(pb,
cmd->cubic_to.x1,
cmd->cubic_to.y1,
cmd->cubic_to.x2,
cmd->cubic_to.y2,
x = cmd->cubic_to.x,
y = cmd->cubic_to.y));
break;
case SVG_PATH_CMD_CUBIC_TO_REL:
spn(path_builder_cubic_to(pb,
x + cmd->cubic_to.x1,
y + cmd->cubic_to.y1,
x + cmd->cubic_to.x2,
y + cmd->cubic_to.y2,
x + cmd->cubic_to.x,
y + cmd->cubic_to.y));
x += cmd->cubic_to.x;
y += cmd->cubic_to.y;
break;
case SVG_PATH_CMD_CUBIC_SMOOTH_TO:
spn(path_builder_cubic_smooth_to(pb,
cmd->cubic_smooth_to.x2,
cmd->cubic_smooth_to.y2,
x = cmd->cubic_smooth_to.x,
y = cmd->cubic_smooth_to.y));
break;
case SVG_PATH_CMD_CUBIC_SMOOTH_TO_REL:
spn(path_builder_cubic_smooth_to(pb,
x + cmd->cubic_smooth_to.x2,
y + cmd->cubic_smooth_to.y2,
x + cmd->cubic_smooth_to.x,
y + cmd->cubic_smooth_to.y));
x += cmd->cubic_smooth_to.x;
y += cmd->cubic_smooth_to.y;
break;
case SVG_PATH_CMD_QUAD_TO:
spn(path_builder_quad_to(pb,
cmd->quad_to.x1,
cmd->quad_to.y1,
x = cmd->quad_to.x,
y = cmd->quad_to.y));
break;
case SVG_PATH_CMD_QUAD_TO_REL:
spn(path_builder_quad_to(pb,
x + cmd->quad_to.x1,
y + cmd->quad_to.y1,
x + cmd->quad_to.x,
y + cmd->quad_to.y));
x += cmd->quad_to.x;
y += cmd->quad_to.y;
break;
case SVG_PATH_CMD_QUAD_SMOOTH_TO:
spn(path_builder_quad_smooth_to(pb,
x = cmd->quad_smooth_to.x,
y = cmd->quad_smooth_to.y));
break;
case SVG_PATH_CMD_QUAD_SMOOTH_TO_REL:
spn(path_builder_quad_smooth_to(pb,
x + cmd->quad_smooth_to.x,
y + cmd->quad_smooth_to.y));
x += cmd->quad_smooth_to.x;
y += cmd->quad_smooth_to.y;
break;
case SVG_PATH_CMD_RAT_CUBIC_TO:
spn(path_builder_rat_cubic_to(pb,
cmd->rat_cubic_to.x1,
cmd->rat_cubic_to.y1,
cmd->rat_cubic_to.x2,
cmd->rat_cubic_to.y2,
x = cmd->rat_cubic_to.x,
y = cmd->rat_cubic_to.y,
cmd->rat_cubic_to.w1,
cmd->rat_cubic_to.w2));
break;
case SVG_PATH_CMD_RAT_CUBIC_TO_REL:
spn(path_builder_rat_cubic_to(pb,
x + cmd->rat_cubic_to.x1,
y + cmd->rat_cubic_to.y1,
x + cmd->rat_cubic_to.x2,
y + cmd->rat_cubic_to.y2,
x + cmd->rat_cubic_to.x,
y + cmd->rat_cubic_to.y,
cmd->rat_cubic_to.w1,
cmd->rat_cubic_to.w2));
x += cmd->rat_cubic_to.x;
y += cmd->rat_cubic_to.y;
break;
case SVG_PATH_CMD_RAT_QUAD_TO:
spn(path_builder_rat_quad_to(pb,
cmd->rat_quad_to.x1,
cmd->rat_quad_to.y1,
x = cmd->rat_quad_to.x,
y = cmd->rat_quad_to.y,
cmd->rat_quad_to.w1));
break;
case SVG_PATH_CMD_RAT_QUAD_TO_REL:
spn(path_builder_rat_quad_to(pb,
x + cmd->rat_quad_to.x1,
y + cmd->rat_quad_to.y1,
x + cmd->rat_quad_to.x,
y + cmd->rat_quad_to.y,
cmd->rat_quad_to.w1));
x += cmd->rat_quad_to.x;
y += cmd->rat_quad_to.y;
break;
case SVG_PATH_CMD_ARC_TO:
// clang-format off
{
struct spn_arc_params arc_params;
spn_svg_arc(x,
y,
cmd->arc_to.x,
cmd->arc_to.y,
cmd->arc_to.rx,
cmd->arc_to.ry,
cmd->arc_to.x_axis_rotation * (float)(M_PI / 180.0),
cmd->arc_to.large_arc_flag != 0.0f,
cmd->arc_to.sweep_flag != 0.0f,
&arc_params);
spn_path_builder_arc(pb, &arc_params);
x = cmd->arc_to.x;
y = cmd->arc_to.y;
}
// clang-format on
break;
case SVG_PATH_CMD_ARC_TO_REL:
// clang-format off
{
struct spn_arc_params arc_params;
float x1 = x + cmd->arc_to.x;
float y1 = y + cmd->arc_to.y;
spn_svg_arc(x,
y,
x1,
y1,
cmd->arc_to.rx,
cmd->arc_to.ry,
cmd->arc_to.x_axis_rotation * (float)(M_PI / 180.0),
cmd->arc_to.large_arc_flag != 0.0f,
cmd->arc_to.sweep_flag != 0.0f,
&arc_params);
x = x1;
y = y1;
spn_path_builder_arc(pb, &arc_params);
}
// clang-format on
break;
default:
fprintf(stderr, "Error: unhandled path type - %u\n", cmd->type);
exit(-1);
}
}
svg_path_iterator_dispose(iter);
return paths;
}
//
//
//
spn_raster_t *
spn_svg_rasters_decode(struct svg const * const svg,
spn_raster_builder_t rb,
spn_path_t const * const paths,
struct transform_stack * const ts)
{
static struct spn_clip const raster_clips[] = { { 0.0f, 0.0f, FLT_MAX, FLT_MAX } };
spn_raster_t * const rasters = malloc(sizeof(*rasters) * svg_raster_count(svg));
uint32_t const ts_restore = transform_stack_save(ts);
union svg_raster_cmd const * cmd;
struct svg_raster_iterator * iter = svg_raster_iterator_create(svg, UINT32_MAX);
while (svg_raster_iterator_next(iter, &cmd))
{
switch (cmd->type)
{
case SVG_RASTER_CMD_BEGIN:
spn(raster_builder_begin(rb));
break;
case SVG_RASTER_CMD_END:
spn(raster_builder_end(rb, rasters + cmd->end.raster_index));
break;
case SVG_RASTER_CMD_FILL:
spn(raster_builder_add(rb,
paths + cmd->fill.path_index,
NULL, // transform_stack_top_weakref(ts),
(spn_transform_t *)transform_stack_top_transform(ts),
NULL,
raster_clips,
1));
break;
case SVG_RASTER_CMD_STROKE: // FIXME(allanmac): IGNORED
break;
case SVG_RASTER_CMD_MARKER: // FIXME(allanmac): IGNORED
break;
case SVG_RASTER_CMD_STROKE_WIDTH: // FIXME(allanmac): IGNORED
break;
case SVG_RASTER_CMD_TRANSFORM_PROJECT:
transform_stack_push_matrix(ts,
cmd->project.sx,
cmd->project.shx,
cmd->project.tx,
cmd->project.shy,
cmd->project.sy,
cmd->project.ty,
cmd->project.w0,
cmd->project.w1,
1);
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_MATRIX:
transform_stack_push_affine(ts,
cmd->matrix.sx,
cmd->matrix.shx,
cmd->matrix.tx,
cmd->matrix.shy,
cmd->matrix.sy,
cmd->matrix.ty);
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_TRANSLATE:
transform_stack_push_translate(ts, cmd->translate.tx, cmd->translate.ty);
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_SCALE:
transform_stack_push_scale(ts, cmd->scale.sx, cmd->scale.sy);
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_ROTATE:
transform_stack_push_rotate_xy(ts,
cmd->rotate.d * (float)(M_PI / 180.0),
cmd->rotate.cx,
cmd->rotate.cy);
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_SKEW_X:
transform_stack_push_skew_x(ts, cmd->skew_x.d * (float)(M_PI / 180.0));
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_SKEW_Y:
transform_stack_push_skew_y(ts, cmd->skew_y.d * (float)(M_PI / 180.0));
transform_stack_concat(ts);
break;
case SVG_RASTER_CMD_TRANSFORM_DROP:
transform_stack_drop(ts);
break;
}
}
// restore stack depth
transform_stack_restore(ts, ts_restore);
svg_raster_iterator_dispose(iter);
return rasters;
}
//
//
//
void
spn_styling_background_over_encoder(spn_styling_cmd_t * const cmds, float const colors[]);
//
//
//
void
spn_svg_layers_decode(struct svg const * const svg,
spn_raster_t const * const rasters,
spn_composition_t composition,
spn_styling_t styling,
bool const is_srgb)
{
//
// create the top level styling group
//
spn_group_id group_id;
spn(styling_group_alloc(styling, &group_id));
//
// enter
//
{
spn_styling_cmd_t * cmds_enter;
spn(styling_group_enter(styling, group_id, 1, &cmds_enter));
cmds_enter[0] = SPN_STYLING_OPCODE_COLOR_ACC_ZERO;
}
//
// leave
//
{
spn_styling_cmd_t * cmds_leave;
spn(styling_group_leave(styling, group_id, 4, &cmds_leave));
// white for now
float const background[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
// cmds[0-2]
spn_styling_background_over_encoder(cmds_leave, background);
cmds_leave[3] = SPN_STYLING_OPCODE_COLOR_ACC_STORE_TO_SURFACE;
}
// this is the root group
spn(styling_group_parents(styling, group_id, 0, NULL));
// how many layers in the svg doc?
uint32_t const layer_count = svg_layer_count(svg);
// the range of the root group is maximal [0,layer_count)
spn(styling_group_range_lo(styling, group_id, 0));
spn(styling_group_range_hi(styling, group_id, layer_count - 1));
//
//
//
union svg_layer_cmd const * cmd;
spn_layer_id layer_id;
spn_styling_cmd_t fill_rule = SPN_STYLING_OPCODE_COVER_NONZERO;
spn_styling_cmd_t blend_mode = SPN_STYLING_OPCODE_BLEND_OVER;
svg_color_t rgb = 0;
float opacity = 1.0f;
float fill_opacity = 1.0f;
struct svg_layer_iterator * iter = svg_layer_iterator_create(svg, UINT32_MAX);
while (svg_layer_iterator_next(iter, &cmd))
{
switch (cmd->type)
{
case SVG_LAYER_CMD_BEGIN: {
// this demo renders front to back
layer_id = layer_count - 1 - cmd->begin.layer_index;
}
break;
case SVG_LAYER_CMD_END: {
float rgba[4];
color_rgb32_to_rgba_f32(rgba, rgb, fill_opacity * opacity);
if (is_srgb)
{
color_srgb_to_linear_rgb_f32(rgba);
}
color_premultiply_rgba_f32(rgba);
spn_styling_cmd_t * cmds;
#define SPN_SVG2SPINEL_DISABLE_OPACITY
#ifndef SPN_SVG2SPINEL_DISABLE_OPACITY
spn(styling_group_layer(styling, group_id, layer_id, 6, &cmds));
#else
spn(styling_group_layer(styling, group_id, layer_id, 5, &cmds));
#endif
cmds[0] = fill_rule;
// encode solid fill and fp16v4 at cmds[1-3]
spn_styling_layer_fill_rgba_encoder(cmds + 1, rgba);
cmds[4] = blend_mode;
#ifndef SPN_SVG2SPINEL_DISABLE_OPACITY
cmds[5] = SPN_STYLING_OPCODE_COLOR_ACC_TEST_OPACITY;
#endif
}
break;
case SVG_LAYER_CMD_PLACE: {
spn(composition_place(composition,
rasters + cmd->place.raster_index,
&layer_id,
NULL, // &cmd->place.tx,
1));
}
break;
case SVG_LAYER_CMD_OPACITY: {
opacity = cmd->opacity.opacity;
}
break;
case SVG_LAYER_CMD_FILL_RULE: {
fill_rule = (cmd->fill_rule.fill_rule == SVG_FILL_RULE_OP_NONZERO)
? SPN_STYLING_OPCODE_COVER_NONZERO
: SPN_STYLING_OPCODE_COVER_EVENODD;
}
break;
case SVG_LAYER_CMD_FILL_COLOR: {
rgb = cmd->fill_color.fill_color;
}
break;
case SVG_LAYER_CMD_FILL_OPACITY: {
fill_opacity = cmd->fill_opacity.fill_opacity;
}
break;
case SVG_LAYER_CMD_STROKE_COLOR: { // FIXME(allanmac): IGNORED
}
break;
case SVG_LAYER_CMD_STROKE_OPACITY: { // FIXME(allanmac): IGNORED
break;
}
}
}
svg_layer_iterator_dispose(iter);
}
//
//
//