264 lines
10 KiB
C++

/******************************************************************************
* Copyright (c) 2018(-2023) STMicroelectronics.
* All rights reserved.
*
* This file is part of the TouchGFX 4.21.2 distribution.
*
* This software is licensed under terms that can be found in the LICENSE file in
* the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
*******************************************************************************/
#include <math.h>
#include <touchgfx/hal/VectorRenderer.hpp>
#include <touchgfx/widgets/SVGImage.hpp>
// Declarations, defined in SVGDatabase.cpp
namespace SVGDatabase
{
const touchgfx::VGObject* getInstance();
uint16_t getInstanceSize();
} // namespace SVGDatabase
namespace touchgfx
{
void SVGImage::setSVG(uint16_t id)
{
if (id >= SVGDatabase::getInstanceSize())
{
svgId = SVG_INVALID;
return;
}
svgId = id;
reset();
// If the widget is empty, resize it to the size of the SVG
if (rect.isEmpty())
{
resizeToCurrentSVG();
}
}
void SVGImage::resizeToCurrentSVG()
{
if (svgId == SVG_INVALID)
{
return;
}
const VGObject* const table = SVGDatabase::getInstance();
const VGObject& image = table[svgId];
const Matrix3x3 matrix = getTransformationMatrix();
// Transform bounding box corners
const Matrix3x3::Point p1 = matrix.affineTransform(0.0f, 0.0f);
const Matrix3x3::Point p2 = matrix.affineTransform(0.0f, image.imageHeight);
const Matrix3x3::Point p3 = matrix.affineTransform(image.imageWidth, 0.0f);
const Matrix3x3::Point p4 = matrix.affineTransform(image.imageWidth, image.imageHeight);
// Find limits of transformed box
const float x_max = MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x));
const float y_max = MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y));
setWidthHeight(static_cast<int16_t>(ceilf(x_max)), static_cast<int16_t>(ceilf(y_max)));
}
void SVGImage::reset()
{
scaleX = 1.0f;
scaleY = 1.0f;
rotation = 0.0f;
rotationCenterX = 0.0f;
rotationCenterY = 0.0f;
imagePositionX = 0.0f;
imagePositionY = 0.0f;
}
float SVGImage::getImageWidth() const
{
if (svgId == SVG_INVALID)
{
return 0.0f;
}
const VGObject* const table = SVGDatabase::getInstance();
const VGObject& image = table[svgId];
return image.imageWidth;
}
float SVGImage::getImageHeight() const
{
if (svgId == SVG_INVALID)
{
return 0.0f;
}
const VGObject* const table = SVGDatabase::getInstance();
const VGObject& image = table[svgId];
return image.imageHeight;
}
void SVGImage::draw(const Rect& invalidatedArea) const
{
if (svgId == SVG_INVALID)
{
return;
}
const VGObject* const table = SVGDatabase::getInstance();
const VGObject& image = table[svgId];
VectorRenderer* renderer = VectorRenderer::getInstance();
assert(renderer && "No VectorRenderer instance found!");
renderer->setup(*this, invalidatedArea);
Matrix3x3 matrix = getTransformationMatrix();
for (const VGShape* shape = image.shape; shape != 0; shape = shape->next)
{
Matrix3x3 shapeMatrix;
shapeMatrix.setAffineTransformation(shape->transformation);
shapeMatrix = matrix.multiply(shapeMatrix);
// Skip entire shape if bounding box does not cover invalidatedArea
// Find bounding box including stroke width
const float strokeWidthHalf = shape->strokeWidth / 2.0f;
const float bb_xMin = shape->boundingbox[0] - strokeWidthHalf;
const float bb_yMin = shape->boundingbox[1] - strokeWidthHalf;
const float bb_xMax = shape->boundingbox[2] + strokeWidthHalf;
const float bb_yMax = shape->boundingbox[3] + strokeWidthHalf;
// Transform bounding box corners
const Matrix3x3::Point p1 = shapeMatrix.affineTransform(bb_xMin, bb_yMin);
const Matrix3x3::Point p2 = shapeMatrix.affineTransform(bb_xMin, bb_yMax);
const Matrix3x3::Point p3 = shapeMatrix.affineTransform(bb_xMax, bb_yMin);
const Matrix3x3::Point p4 = shapeMatrix.affineTransform(bb_xMax, bb_yMax);
// Find limits of transformed box
const int16_t x_min = static_cast<int16_t>(floorf(MIN(MIN(p1.x, p2.x), MIN(p3.x, p4.x))));
const int16_t x_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x))));
const int16_t y_min = static_cast<int16_t>(floorf(MIN(MIN(p1.y, p2.y), MIN(p3.y, p4.y))));
const int16_t y_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y))));
Rect shapeBox(x_min, y_min, (x_max - x_min) + 1, (y_max - y_min) + 1);
if (shapeBox.intersect(invalidatedArea))
{
renderer->setTransformationMatrix(shapeMatrix);
// First fill
if (shape->fillMode != VG_NONE)
{
// Set drawing mode and color for this shape
renderer->setMode((shape->drawingMode == VG_FILL_EVEN_ODD) ? VectorRenderer::FILL_EVEN_ODD : VectorRenderer::FILL_NON_ZERO);
renderer->setAlpha(shape->fillAlpha);
// Set the fill color
switch (shape->fillMode)
{
case VG_NONE:
break; // Guarded above
case VG_FIXED_COLOR:
{
const VGFixedColor* color = reinterpret_cast<const VGFixedColor*>(shape->fillPaint);
renderer->setColor(color->argb);
break;
}
case VG_LINEAR_GRADIENT:
{
//Set shape box size on linear (and radial) gradient
const float width = shape->boundingbox[2] - shape->boundingbox[0];
const float height = shape->boundingbox[3] - shape->boundingbox[1];
const VGLinearGradient* linear = reinterpret_cast<const VGLinearGradient*>(shape->fillPaint);
renderer->setLinearGradient(linear->x0, linear->y0, linear->x1, linear->y1,
linear->stops, linear->stopPositions, linear->colors,
width, height,
linear->isSolid, linear->palette);
break;
}
}
// Draw this Shape
renderer->drawPath(shape->commands, shape->numCommands, shape->points, shape->numPoints, shape->boundingbox);
}
// Then stroke
if (shape->strokeMode != VG_NONE)
{
// Stroking is nonzero
renderer->setMode(VectorRenderer::STROKE);
renderer->setStrokeWidth(shape->strokeWidth);
renderer->setStrokeLineCap(shape->strokeLineCap);
renderer->setStrokeLineJoin(shape->strokeLineJoin);
renderer->setStrokeMiterLimit(shape->strokeMiterLimit);
renderer->setAlpha(shape->strokeAlpha);
// Set the fill color
switch (shape->strokeMode)
{
case VG_NONE:
break; // Guarded above
case VG_FIXED_COLOR:
{
const VGFixedColor* color = reinterpret_cast<const VGFixedColor*>(shape->strokePaint);
renderer->setColor(color->argb);
break;
}
case VG_LINEAR_GRADIENT:
{
//Set shape box size on linear (and radial) gradient
const float width = shape->boundingbox[2] - shape->boundingbox[0] + shape->strokeWidth;
const float height = shape->boundingbox[3] - shape->boundingbox[1] + shape->strokeWidth;
const VGLinearGradient* linear = reinterpret_cast<const VGLinearGradient*>(shape->strokePaint);
renderer->setLinearGradient(linear->x0, linear->y0, linear->x1, linear->y1,
linear->stops, linear->stopPositions, linear->colors,
width, height,
linear->isSolid, linear->palette);
break;
}
}
// Draw this Shape
const float boundingbox[4] = { shape->boundingbox[0] - strokeWidthHalf, shape->boundingbox[1] - strokeWidthHalf,
shape->boundingbox[2] + strokeWidthHalf, shape->boundingbox[3] + strokeWidthHalf };
renderer->drawPath(shape->commands, shape->numCommands, shape->points, shape->numPoints, boundingbox);
}
}
}
// Finalize rendering
renderer->tearDown();
}
void SVGImage::invalidateContent() const
{
if (!visible || svgId == SVG_INVALID)
{
return;
}
const VGObject* const table = SVGDatabase::getInstance();
const VGObject& image = table[svgId];
const Matrix3x3 matrix = getTransformationMatrix();
// Transform bounding box corners
const Matrix3x3::Point p1 = matrix.affineTransform(image.x, image.y);
const Matrix3x3::Point p2 = matrix.affineTransform(image.x, image.y + image.height);
const Matrix3x3::Point p3 = matrix.affineTransform(image.x + image.width, image.y);
const Matrix3x3::Point p4 = matrix.affineTransform(image.x + image.width, image.y + image.height);
// Find limits of transformed box
const int16_t x_min = static_cast<int16_t>(floorf(MIN(MIN(p1.x, p2.x), MIN(p3.x, p4.x))));
const int16_t x_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x))));
const int16_t y_min = static_cast<int16_t>(floorf(MIN(MIN(p1.y, p2.y), MIN(p3.y, p4.y))));
const int16_t y_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y))));
Rect r(x_min, y_min, (x_max - x_min) + 1, (y_max - y_min) + 1);
invalidateRect(r);
}
touchgfx::Matrix3x3 SVGImage::getTransformationMatrix() const
{
Matrix3x3 matrix; // Implicit reset to identity matrix
if (svgId != SVG_INVALID)
{
matrix.scale(scaleX, scaleY);
matrix.translate(imagePositionX - rotationCenterX, imagePositionY - rotationCenterY);
matrix.rotate(rotation);
matrix.translate(rotationCenterX, rotationCenterY);
}
return matrix;
}
} // namespace touchgfx