blob: 581ab6c86792c92cfd4710fc172b72a7b12789cc [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 "svg_spinel_image.h"
#include "spinel/ext/color/color.h"
#include "spinel/ext/transform_stack/transform_stack.h"
#include "spinel/spinel.h"
#include "spinel/spinel_assert.h"
#include "spinel/spinel_opcodes.h"
#include "tests/common/spinel/spinel_path_sink.h"
#include "tests/common/svg/svg_utils.h"
#include "tests/common/utils.h" // For ASSERT_MSG()
// Set to 1 to enable debug logs.
#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) ((void)0)
#endif
void
SvgSpinelImage::init(const svg * svg, spn_context_t context, const SpinelImage::Config & config)
{
svg_ = svg;
SpinelImage::init(context, config);
}
void
SvgSpinelImage::init(const svg * svg, spn_context_t context, uint32_t width, uint32_t height)
{
svg_ = svg;
SpinelImage::init(context, width, height);
}
void
SvgSpinelImage::init(const svg * svg, spn_context_t context)
{
svg_ = svg;
SpinelImage::init(context);
}
void
SvgSpinelImage::reset()
{
resetLayers();
resetRasters();
resetPaths();
SpinelImage::reset();
}
void
SvgSpinelImage::setupPaths()
{
ASSERT_MSG(!paths_, "Cannot call setupPaths() twice without resetPaths()");
SpinelPathSink spinel_paths(context);
uint32_t path_count = svg_path_count(svg_);
for (uint32_t nn = 0; nn < path_count; ++nn)
svg_decode_path(svg_, nn, nullptr, &spinel_paths);
std::vector<spn_path_t> paths = spinel_paths.release();
ASSERT(paths.size() == path_count);
// Copy from vector to heap-allocated array.
paths_ = new spn_path_t[path_count];
paths_count_ = path_count;
for (uint32_t nn = 0; nn < path_count; ++nn)
paths_[nn] = paths[nn];
paths.clear();
}
void
SvgSpinelImage::resetPaths()
{
if (paths_count_ > 0)
{
spn(path_release(context, paths_, paths_count_));
delete[] paths_;
paths_ = nullptr;
paths_count_ = 0;
}
}
void
SvgSpinelImage::setupRasters(const spn_transform_t * transform)
{
ASSERT_MSG(!rasters_, "Cannot call setupRasters() twice without resetRasters()");
// IMPORTANT: Some documents (e.g. insect.svg) have rasters that only
// contain PathStroke commands, which are currently ignored. These rasters
// will _not_ be enumerated by svg_decode_rasters, so initialize their path
// handles with SPN_HANDLE_INVALID to be safe.
uint32_t raster_count = svg_raster_count(svg_);
rasters_ = new spn_raster_t[raster_count];
rasters_count_ = raster_count;
for (uint32_t nn = 0; nn < raster_count; ++nn)
rasters_[nn] = SPN_RASTER_INVALID;
if (transform)
{
transform_stack_push_matrix(transform_stack,
transform->sx,
transform->shx,
transform->tx,
transform->shy,
transform->sy,
transform->ty,
transform->w0,
transform->w1,
1.0);
transform_stack_concat(transform_stack);
}
auto callback = [&](const SvgDecodedRaster & r) -> bool {
transform_stack_push_affine(transform_stack,
(float)r.transform.sx,
(float)r.transform.shx,
(float)r.transform.tx,
(float)r.transform.shy,
(float)r.transform.sy,
(float)r.transform.ty);
transform_stack_concat(transform_stack);
spn(raster_builder_begin(raster_builder));
static const struct spn_clip raster_clips[] = { { 0., 0., FLT_MAX, FLT_MAX } };
spn(raster_builder_add(raster_builder,
&paths_[r.path_id],
nullptr, // transform_weakrefs
(const spn_transform_t *)transform_stack_top_transform(transform_stack),
nullptr, // clip_weakrefs,
raster_clips,
1));
ASSERT_MSG(r.raster_id < raster_count,
"Invalid raster id=%u (should be < %u)\n",
r.raster_id,
raster_count);
spn(raster_builder_end(raster_builder, &rasters_[r.raster_id]));
LOG("raster_id:%u raster_handle:%u raster_count:%u\n",
r.raster_id,
rasters_[r.raster_id].handle,
raster_count);
transform_stack_drop(transform_stack);
return true;
};
svg_decode_rasters(svg_, nullptr, callback);
if (transform)
transform_stack_drop(transform_stack);
}
void
SvgSpinelImage::resetRasters()
{
if (rasters_count_ > 0)
{
// Remove SPN_RASTER_INVALID values from the |rasters_| array.
uint32_t read_count = 0;
uint32_t write_count = 0;
while (read_count < rasters_count_)
{
if (rasters_[read_count].handle != UINT32_MAX)
{
if (write_count < read_count)
rasters_[write_count] = rasters_[read_count];
write_count++;
}
read_count++;
}
spn(raster_release(context, rasters_, write_count));
delete[] rasters_;
rasters_ = nullptr;
rasters_count_ = 0;
}
}
void
SvgSpinelImage::setupLayers()
{
//
// Setup layers
//
bool is_srgb = false;
uint32_t layer_count = svg_layer_count(svg_);
// Create top-level styling group
spn_group_id group_id;
spn(styling_group_alloc(styling, &group_id));
// This is the root group.
spn(styling_group_parents(styling, group_id, 0, nullptr));
// 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));
{
spn_styling_cmd_t * cmds;
spn(styling_group_enter(styling, group_id, 1, &cmds));
cmds[0] = SPN_STYLING_OPCODE_COLOR_ACC_ZERO;
}
{
spn_styling_cmd_t * cmds;
spn(styling_group_leave(styling, group_id, 4, &cmds));
const float background[4] = { 1., 1., 1., 1. };
spn_styling_background_over_encoder(cmds, background);
cmds[3] = SPN_STYLING_OPCODE_COLOR_ACC_STORE_TO_SURFACE;
}
uint32_t count = 0;
auto callback = [&](const SvgDecodedLayer & l) -> bool {
// Spinel renders front to back.
spn_layer_id layer_id = layer_count - 1 - l.layer_id;
float rgba[4];
color_rgb32_to_rgba_f32(rgba, l.fill_color, l.fill_opacity);
if (is_srgb)
color_srgb_to_linear_rgb_f32(rgba);
color_premultiply_rgba_f32(rgba);
spn_styling_cmd_t * cmds;
spn(styling_group_layer(styling, group_id, layer_id, 5, &cmds));
cmds[0] = l.fill_even_odd ? SPN_STYLING_OPCODE_COVER_EVENODD : SPN_STYLING_OPCODE_COVER_NONZERO;
spn_styling_layer_fill_rgba_encoder(cmds + 1, rgba);
cmds[4] = SPN_STYLING_OPCODE_BLEND_OVER;
for (const auto & print : l.prints)
{
// Ignore raster ids without a valid raster handle. This happens
// when a layer references a raster with a PathStroke command.
spn_raster_t raster = rasters_[print.raster_id];
if (raster.handle == UINT32_MAX)
continue;
const spn_txty_t txty = {
.tx = print.tx,
.ty = print.ty,
};
LOG("layer_id:%u styling layer_id:%u raster_id:%u raster_handle:%u\n",
l.layer_id,
layer_id,
print.raster_id,
raster.handle);
spn(composition_place(composition, &raster, &layer_id, &txty, 1));
}
count++;
return true;
};
svg_decode_layers(svg_, callback);
if (DEBUG)
ASSERT_MSG(count == layer_count, "Invalid layer count %u (should be %u)\n", count, layer_count);
spn(styling_seal(styling));
spn(composition_seal(composition));
}
void
SvgSpinelImage::resetLayers()
{
if (styling)
{
spn(styling_unseal(styling));
spn(styling_reset(styling));
}
if (composition)
{
spn(composition_unseal(composition));
spn(composition_reset(composition));
}
}