thorvg/src/loaders/svg_loader/tvgSvgSceneBuilder.cpp
JunsuChoi 62aa24d9ad SvgLoader: Fix opacity propagation
Remove duplicate opacity calculation of the scene.

Change-Id: I1253ea290e78fdbf946a492ebfcaaa082fbfc121
2020-07-23 01:40:36 +00:00

375 lines
14 KiB
C++

/*
* Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef _TVG_SVG_SCENE_BUILDER_CPP_
#define _TVG_SVG_SCENE_BUILDER_CPP_
#include "tvgSvgSceneBuilder.h"
unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, Shape* vg, float rx, float ry, float rw, float rh)
{
Fill::ColorStop* stops;
int stopCount = 0;
float fillOpacity = 255.0f;
float gx, gy, gw, gh;
auto fillGrad = LinearGradient::gen();
if (g->usePercentage) {
g->linear->x1 = g->linear->x1 * rw + rx;
g->linear->y1 = g->linear->y1 * rh + ry;
g->linear->x2 = g->linear->x2 * rw + rx;
g->linear->y2 = g->linear->y2 * rh + ry;
}
//In case of objectBoundingBox it need proper scaling
if (!g->userSpace) {
float scaleX = 1.0, scaleReversedX = 1.0;
float scaleY = 1.0, scaleReversedY = 1.0;
//Check the smallest size, find the scale value
if (rh > rw) {
scaleY = ((float)rw) / rh;
scaleReversedY = ((float)rh) / rw;
} else {
scaleX = ((float)rh) / rw;
scaleReversedX = ((float)rw) / rh;
}
vg->bounds(&gx, &gy, &gw, &gh);
float cy = ((float)gh) * 0.5 + gy;
float cy_scaled = (((float)gh) * 0.5) * scaleReversedY;
float cx = ((float)gw) * 0.5 + gx;
float cx_scaled = (((float)gw) * 0.5) * scaleReversedX;
//= T(gx, gy) x S(scaleX, scaleY) x T(cx_scaled - cx, cy_scaled - cy) x (radial->x, radial->y)
g->linear->x1 = g->linear->x1 * scaleX + scaleX * (cx_scaled - cx) + gx;
g->linear->y1 = g->linear->y1 * scaleY + scaleY * (cy_scaled - cy) + gy;
g->linear->x2 = g->linear->x2 * scaleX + scaleX * (cx_scaled - cx) + gx;
g->linear->y2 = g->linear->y2 * scaleY + scaleY * (cy_scaled - cy) + gy;
}
if (g->transform) {
float cy = ((float) rh) * 0.5 + ry;
float cx = ((float) rw) * 0.5 + rx;
//Calc start point
//= T(x - cx, y - cy) x g->transform x T(cx, cy)
g->linear->x1 = cx * (g->transform->e11 + g->transform->e31 * (g->linear->x1 - cx)) +
cx * (g->transform->e12 + g->transform->e32 * (g->linear->x1 - cx)) +
cx * (g->transform->e13 + g->transform->e33 * (g->linear->x1 - cx));
g->linear->y1 = cy * (g->transform->e21 + g->transform->e31 * (g->linear->y1 - cy)) +
cy * (g->transform->e22 + g->transform->e32 * (g->linear->y1 - cy)) +
cy * (g->transform->e23 + g->transform->e33 * (g->linear->y1 - cy));
//Calc end point
g->linear->x2 = cx * (g->transform->e11 + g->transform->e31 * (g->linear->x2 - cx)) +
cx * (g->transform->e12 + g->transform->e32 * (g->linear->x2 - cx)) +
cx * (g->transform->e13 + g->transform->e33 * (g->linear->x2 - cx));
g->linear->y2 = cy * (g->transform->e21 + g->transform->e31 * (g->linear->y2 - cy)) +
cy * (g->transform->e22 + g->transform->e32 * (g->linear->y2 - cy)) +
cy * (g->transform->e23 + g->transform->e33 * (g->linear->y2 - cy));
}
fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
fillGrad->spread(g->spread);
//Update the stops
stopCount = g->stops.size();
if (stopCount > 0) {
float opacity;
float fopacity = fillOpacity / 255.0f; //fill opacity if any exists.
int i = 0;
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
for (auto colorStop : g->stops) {
//Use premultiplied color
opacity = ((float)colorStop->a / 255.0f) * fopacity;
stops[i].r = colorStop->r * opacity;
stops[i].g = colorStop->g * opacity;
stops[i].b = colorStop->b * opacity;
stops[i].a = colorStop->a * fopacity;
stops[i].offset = colorStop->offset;
i++;
}
fillGrad->colorStops(stops, stopCount);
free(stops);
}
return move(fillGrad);
}
unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, Shape* vg, float rx, float ry, float rw, float rh)
{
Fill::ColorStop *stops;
int stopCount = 0;
float gx, gy, gw, gh;
int radius;
float fillOpacity = 255.0f;
auto fillGrad = RadialGradient::gen();
radius = sqrt(pow(rw, 2) + pow(rh, 2)) / sqrt(2.0);
if (!g->userSpace) {
//That is according to Units in here
//https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
int min = (rh > rw) ? rw : rh;
radius = sqrt(pow(min, 2) + pow(min, 2)) / sqrt(2.0);
}
if (g->usePercentage) {
g->radial->cx = g->radial->cx * rw + rx;
g->radial->cy = g->radial->cy * rh + ry;
g->radial->r = g->radial->r * radius;
g->radial->fx = g->radial->fx * rw + rx;
g->radial->fy = g->radial->fy * rh + ry;
}
//In case of objectBoundingBox it need proper scaling
if (!g->userSpace) {
float scaleX = 1.0, scaleReversedX = 1.0;
float scaleY = 1.0, scaleReversedY = 1.0;
//Check the smallest size, find the scale value
if (rh > rw) {
scaleY = ((float)rw) / rh;
scaleReversedY = ((float)rh) / rw;
} else {
scaleX = ((float)rh) / rw;
scaleReversedX = ((float)rw) / rh;
}
vg->bounds(&gx, &gy, &gw, &gh);
float cy = ((float)gh) * 0.5 + gy;
float cy_scaled = (((float)gh) * 0.5) * scaleReversedY;
float cx = ((float)gw) * 0.5 + gx;
float cx_scaled = (((float)gw) * 0.5) * scaleReversedX;
//= T(gx, gy) x S(scaleX, scaleY) x T(cx_scaled - cx, cy_scaled - cy) x (radial->x, radial->y)
g->radial->cx = g->radial->cx * scaleX + scaleX * (cx_scaled - cx) + gx;
g->radial->cy = g->radial->cy * scaleY + scaleY * (cy_scaled - cy) + gy;
}
//TODO: Radial gradient transformation is not yet supported.
//if (g->transform) {}
//TODO: Tvg is not support to focal
//if (g->radial->fx != 0 && g->radial->fy != 0) {
// fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
//}
fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
fillGrad->spread(g->spread);
//Update the stops
stopCount = g->stops.size();
if (stopCount > 0) {
float opacity;
float fopacity = fillOpacity / 255.0f; //fill opacity if any exists.
int i = 0;
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
for (auto colorStop : g->stops) {
//Use premultiplied color
opacity = ((float)colorStop->a / 255.0f) * fopacity;
stops[i].r = colorStop->r * opacity;
stops[i].g = colorStop->g * opacity;
stops[i].b = colorStop->b * opacity;
stops[i].a = colorStop->a * fopacity;
stops[i].offset = colorStop->offset;
i++;
}
fillGrad->colorStops(stops, stopCount);
free(stops);
}
return move(fillGrad);
}
void _applyProperty(SvgNode* node, Shape* vg, float vx, float vy, float vw, float vh)
{
SvgStyleProperty* style = node->style;
if (node->transform) vg->transform(*node->transform);
if (node->type == SvgNodeType::Doc || !node->display) return;
//If fill property is nullptr then do nothing
if (style->fill.paint.none) {
//Do nothing
} else if (style->fill.paint.gradient) {
if (!style->fill.paint.gradient->userSpace) vg->bounds(&vx, &vy, &vw, &vh);
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh);
vg->fill(move(linear));
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh);
vg->fill(move(radial));
}
} else if (style->fill.paint.curColor) {
//Apply the current style color
float fa = ((float)style->fill.opacity / 255.0);
vg->fill(((float)style->r) * fa, ((float)style->g) * fa, ((float)style->b) * fa, style->fill.opacity);
} else {
//Apply the fill color
float fa = ((float)style->fill.opacity / 255.0);
vg->fill(((float)style->fill.paint.r) * fa, ((float)style->fill.paint.g) * fa, ((float)style->fill.paint.b) * fa, style->fill.opacity);
}
//Apply node opacity
if (style->opacity < 255) {
uint8_t r, g, b, a;
vg->fill(&r, &g, &b, &a);
float fa = ((float)style->opacity / 255.0);
vg->fill(((float)r) * fa, ((float)g) * fa, ((float)b) * fa, ((float)a) * fa);
}
if (node->type == SvgNodeType::G) return;
//Apply the stroke style property
vg->stroke(style->stroke.width);
vg->stroke(style->stroke.cap);
vg->stroke(style->stroke.join);
//If stroke property is nullptr then do nothing
if (style->stroke.paint.none) {
//Do nothing
} else if (style->stroke.paint.gradient) {
//TODO: Support gradient style
} else if (style->stroke.paint.url) {
//TODO: Apply the color pointed by url
} else if (style->stroke.paint.curColor) {
//Apply the current style color
vg->stroke(style->r, style->g, style->b, style->stroke.opacity);
} else {
//Apply the stroke color
vg->stroke(style->stroke.paint.r, style->stroke.paint.g, style->stroke.paint.b, style->stroke.opacity);
}
//Apply node opacity to stroke color
if (style->opacity < 255) {
uint8_t r, g, b, a;
vg->strokeColor(&r, &g, &b, &a);
float fa = ((float)style->opacity / 255.0);
vg->stroke(((float)r) * fa, ((float)g) * fa, ((float)b) * fa, ((float)a) * fa);
}
}
unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh)
{
auto shape = Shape::gen();
switch (node->type) {
case SvgNodeType::Path: {
if (node->node.path.path) {
auto pathResult = svgPathToTvgPath(node->node.path.path->c_str());
shape->appendPath(get<0>(pathResult).data(), get<0>(pathResult).size(), get<1>(pathResult).data(), get<1>(pathResult).size());
}
break;
}
case SvgNodeType::Ellipse: {
shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
break;
}
case SvgNodeType::Polygon: {
if (node->node.polygon.pointsCount < 2) break;
shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
for (int i = 2; i < node->node.polygon.pointsCount; i += 2) {
shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
}
shape->close();
break;
}
case SvgNodeType::Polyline: {
if (node->node.polygon.pointsCount < 2) break;
shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
for (int i = 2; i < node->node.polygon.pointsCount; i += 2) {
shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
}
break;
}
case SvgNodeType::Circle: {
shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
break;
}
case SvgNodeType::Rect: {
shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
break;
}
case SvgNodeType::Line: {
shape->moveTo(node->node.line.x1, node->node.line.y1);
shape->lineTo(node->node.line.x2, node->node.line.y2);
break;
}
default: {
break;
}
}
_applyProperty(node, shape.get(), vx, vy, vw, vh);
return shape;
}
unique_ptr<Scene> _sceneBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, int parentOpacity)
{
if (node->type == SvgNodeType::Doc || node->type == SvgNodeType::G) {
auto scene = Scene::gen();
if (node->transform) scene->transform(*node->transform);
node->style->opacity = (node->style->opacity * parentOpacity) / 255.0f;
if (node->display) {
for (auto child : node->child) {
if (child->type == SvgNodeType::Doc || child->type == SvgNodeType::G) scene->push(_sceneBuildHelper(child, vx, vy, vw, vh, node->style->opacity));
else {
child->style->opacity = (child->style->opacity * node->style->opacity) / 255.0f;
scene->push(_shapeBuildHelper(child, vx, vy, vw, vh));
}
}
}
return move(scene);
}
return nullptr;
}
SvgSceneBuilder::SvgSceneBuilder()
{
}
SvgSceneBuilder::~SvgSceneBuilder()
{
}
unique_ptr<Scene> SvgSceneBuilder::build(SvgNode* node)
{
if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
viewBox.x = node->node.doc.vx;
viewBox.y = node->node.doc.vy;
viewBox.w = node->node.doc.vw;
viewBox.h = node->node.doc.vh;
preserveAspect = node->node.doc.preserveAspect;
staticViewBox = true;
return _sceneBuildHelper(node, viewBox.x, viewBox.y, viewBox.w, viewBox.h, 255);
}
#endif //_TVG_SVG_SCENE_BUILDER_CPP_