Replaced the NanoSVG library with LunaSVG.

This commit is contained in:
Leon Styhre 2022-10-03 18:43:30 +02:00
parent c35df18ad8
commit bf5cce31c6
8 changed files with 54 additions and 76 deletions

View file

@ -380,6 +380,7 @@ endif()
set(COMMON_INCLUDE_DIRS ${CURL_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/external/CImg
${CMAKE_CURRENT_SOURCE_DIR}/external/glm
${CMAKE_CURRENT_SOURCE_DIR}/external/lunasvg/include
${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src
${CMAKE_CURRENT_SOURCE_DIR}/external/rapidjson/include
${CMAKE_CURRENT_SOURCE_DIR}/external/rlottie/inc
@ -462,6 +463,7 @@ elseif(WIN32)
${PROJECT_SOURCE_DIR}/glew32.lib
${PROJECT_SOURCE_DIR}/libcurl-x64.lib
${PROJECT_SOURCE_DIR}/freetype.lib
${PROJECT_SOURCE_DIR}/lunasvg.lib
${PROJECT_SOURCE_DIR}/pugixml.lib
${PROJECT_SOURCE_DIR}/rlottie.lib
${PROJECT_SOURCE_DIR}/SDL2main.lib
@ -478,6 +480,7 @@ elseif(WIN32)
${PROJECT_SOURCE_DIR}/glew32.dll
${PROJECT_SOURCE_DIR}/libcurl-x64.dll
${PROJECT_SOURCE_DIR}/libfreetype.dll
${PROJECT_SOURCE_DIR}/liblunasvg.a
${PROJECT_SOURCE_DIR}/libpugixml.dll
${PROJECT_SOURCE_DIR}/libSDL2main.a
${PROJECT_SOURCE_DIR}/librlottie.dll
@ -527,8 +530,10 @@ else()
${SDL2_LIBRARY})
endif()
# Lottie animation library rlottie.
if(NOT WIN32)
# SVG rendering library LunaSVG.
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${PROJECT_SOURCE_DIR}/liblunasvg.a)
# Lottie animation library rlottie.
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${PROJECT_SOURCE_DIR}/librlottie.a)
endif()

View file

@ -22,7 +22,6 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic)
, mFlipX {false}
, mFlipY {false}
, mTargetIsMax {false}
, mScalableNonAspect {false}
, mTileWidth {0.0f}
, mTileHeight {0.0f}
, mColorShift {0xFFFFFFFF}
@ -88,7 +87,6 @@ void ImageComponent::setImage(const std::string& path, bool tile)
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation,
mMipmapping, static_cast<size_t>(mSize.x),
static_cast<size_t>(mSize.y), mTileWidth, mTileHeight);
mTexture->setScalableNonAspect(mScalableNonAspect);
mTexture->rasterizeAt(mSize.x, mSize.y);
onSizeChanged();
}
@ -404,8 +402,6 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
getParent()->getSize() :
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())};
bool noMax {false};
if (properties & ThemeFlags::SIZE) {
if (elem->has("size")) {
glm::vec2 imageSize {elem->get<glm::vec2>("size")};
@ -420,8 +416,6 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (imageSize.y > 0.0f)
imageSize.y = glm::clamp(imageSize.y, 0.001f, 3.0f);
setResize(imageSize * scale);
if (imageSize.x != 0.0f && imageSize.y != 0.0f)
noMax = true;
}
else if (elem->has("maxSize")) {
glm::vec2 imageMaxSize {elem->get<glm::vec2>("maxSize")};
@ -455,11 +449,6 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (properties & PATH && elem->has("path")) {
const std::string path {elem->get<std::string>("path")};
if (!tile && !theme->isLegacyTheme() && noMax && path.length() > 4 &&
Utils::String::toLower(path.substr(path.size() - 4, std::string::npos)) == ".svg") {
mScalableNonAspect = true;
}
if (tile && elem->has("tileSize")) {
glm::vec2 tileSize {elem->get<glm::vec2>("tileSize")};
if (tileSize.x == 0.0f && tileSize.y == 0.0f) {
@ -474,8 +463,6 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
tileSize.y = glm::clamp(tileSize.y, 0.0f, 1.0f);
mTileWidth = tileSize.x * scale.x;
mTileHeight = tileSize.y * scale.y;
if (mTileWidth != 0.0f && mTileHeight != 0.0f)
mScalableNonAspect = true;
}
}
@ -614,12 +601,8 @@ void ImageComponent::resize(bool rasterize)
glm::vec2 resizeScale {mTargetSize.x / mSize.x, mTargetSize.y / mSize.y};
if (resizeScale.x < resizeScale.y) {
// SVG rasterization is determined by height and rasterization is done in terms of
// pixels. If rounding is off enough in the rasterization step (for images with
// extreme aspect ratios), it can cause cutoff when the aspect ratio breaks.
// So we always make sure to round accordingly to avoid such issues.
mSize.x *= resizeScale.x;
mSize.y = floorf(std::min(mSize.y * resizeScale.x, mTargetSize.y));
mSize.y = std::min(mSize.y * resizeScale.x, mTargetSize.y);
}
else {
// This will be mTargetSize.y(). We can't exceed it.

View file

@ -119,7 +119,6 @@ private:
bool mFlipX;
bool mFlipY;
bool mTargetIsMax;
bool mScalableNonAspect;
float mTileWidth;
float mTileHeight;

View file

@ -6,9 +6,6 @@
// Low-level texture data functions.
//
#define NANOSVG_IMPLEMENTATION
#define NANOSVGRAST_IMPLEMENTATION
#include "resources/TextureData.h"
#include "ImageIO.h"
@ -16,13 +13,10 @@
#include "resources/ResourceManager.h"
#include "utils/StringUtil.h"
#include "nanosvg.h"
#include "nanosvgrast.h"
#include "lunasvg.h"
#include <string.h>
#define DPI 96
TextureData::TextureData(bool tile)
: mRenderer {Renderer::getInstance()}
, mTile {tile}
@ -34,10 +28,10 @@ TextureData::TextureData(bool tile)
, mSourceWidth {0.0f}
, mSourceHeight {0.0f}
, mScalable {false}
, mScalableNonAspect {false}
, mHasRGBAData {false}
, mPendingRasterization {false}
, mMipmapping {false}
, mInvalidSVGFile {false}
, mLinearMagnify {false}
{
}
@ -64,20 +58,23 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
if (!mDataRGBA.empty() && !mPendingRasterization)
return true;
NSVGimage* svgImage {nsvgParse(const_cast<char*>(fileData.c_str()), "px", DPI)};
auto svgImage = lunasvg::Document::loadFromData(fileData);
if (!svgImage || svgImage->width == 0 || svgImage->height == 0) {
LOG(LogError) << "Couldn't parse SVG image";
if (svgImage == nullptr) {
// LOG(LogError) << "Couldn't parse SVG image:" << mPath;
mInvalidSVGFile = true;
return false;
}
float svgWidth {static_cast<float>(svgImage->width())};
float svgHeight {static_cast<float>(svgImage->height())};
bool rasterize {true};
if (mTile) {
if (mTileWidth == 0.0f && mTileHeight == 0.0f) {
rasterize = false;
mSourceWidth = svgImage->width;
mSourceHeight = svgImage->height;
mSourceWidth = svgWidth;
mSourceHeight = svgHeight;
}
else {
mSourceWidth = static_cast<float>(mTileWidth);
@ -90,7 +87,7 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
rasterize = false;
// Set a small temporary size that maintains the image aspect ratio.
mSourceWidth = 64.0f;
mSourceHeight = 64.0f * (svgImage->height / svgImage->width);
mSourceHeight = 64.0f * (svgHeight / svgWidth);
}
mWidth = static_cast<int>(std::round(mSourceWidth));
@ -98,38 +95,25 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
if (mWidth == 0) {
// Auto scale width to keep aspect ratio.
mWidth = static_cast<size_t>(
std::round((static_cast<float>(mHeight) / svgImage->height) * svgImage->width));
mWidth =
static_cast<size_t>(std::round((static_cast<float>(mHeight) / svgHeight) * svgWidth));
}
else if (mHeight == 0) {
// Auto scale height to keep aspect ratio.
mHeight = static_cast<size_t>(
std::round((static_cast<float>(mWidth) / svgImage->width) * svgImage->height));
mHeight =
static_cast<size_t>(std::round((static_cast<float>(mWidth) / svgWidth) * svgHeight));
}
if (rasterize) {
const float aspectW {svgImage->width / static_cast<float>(mWidth)};
const float aspectH {svgImage->height / static_cast<float>(mHeight)};
// For very short and wide images, make a slight scale adjustment to prevent pixels
// to the right of the image from being cut off.
if (svgWidth / svgHeight > 4.0f)
svgImage->scale(0.998, 1.0);
// If the size property has been used to override the aspect ratio of the SVG image,
// then we need to rasterize at a lower resolution and let the GPU scale the texture.
// This is necessary as the rasterization always maintains the image aspect ratio.
if (mScalableNonAspect && aspectW != aspectH)
mWidth = static_cast<int>(std::round(svgImage->width / aspectH));
std::vector<unsigned char> tempVector;
tempVector.reserve(mWidth * mHeight * 4);
NSVGrasterizer* rast {nsvgCreateRasterizer()};
nsvgRasterize(rast, svgImage, 0, 0, static_cast<float>(mHeight) / svgImage->height,
tempVector.data(), mWidth, mHeight, mWidth * 4);
nsvgDeleteRasterizer(rast);
mDataRGBA.insert(mDataRGBA.begin(), std::make_move_iterator(tempVector.data()),
std::make_move_iterator(tempVector.data() + (mWidth * mHeight * 4)));
tempVector.erase(tempVector.begin(), tempVector.end());
auto bitmap = svgImage->renderToBitmap(mWidth, mHeight);
bitmap.convertToRGBA();
mDataRGBA.insert(mDataRGBA.begin(), std::move(bitmap.data()),
std::move(bitmap.data() + mWidth * mHeight * 4));
ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight);
mPendingRasterization = false;
@ -141,8 +125,6 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
mPendingRasterization = true;
}
nsvgDelete(svgImage);
return true;
}
@ -196,6 +178,9 @@ bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size
bool TextureData::load()
{
if (mInvalidSVGFile)
return false;
bool retval {false};
// Need to load. See if there is a file.

View file

@ -64,10 +64,6 @@ public:
}
glm::vec2 getSize() { return glm::vec2 {static_cast<int>(mWidth), static_cast<int>(mHeight)}; }
// Whether to stretch or squash SVG images if the size property has been used to override
// the aspect ratio (accomplished by rasterizing at a lower resolution and letting the GPU
// scale the texture).
void setScalableNonAspect(bool state) { mScalableNonAspect = state; }
// Whether to use linear filtering when magnifying the texture.
void setLinearMagnify(bool state) { mLinearMagnify = state; }
// Whether to use mipmapping and trilinear filtering.
@ -96,10 +92,10 @@ private:
std::atomic<float> mSourceWidth;
std::atomic<float> mSourceHeight;
std::atomic<bool> mScalable;
std::atomic<bool> mScalableNonAspect;
std::atomic<bool> mHasRGBAData;
std::atomic<bool> mPendingRasterization;
std::atomic<bool> mMipmapping;
std::atomic<bool> mInvalidSVGFile;
bool mLinearMagnify;
bool mReloadable;
};

View file

@ -24,7 +24,6 @@ TextureResource::TextureResource(const std::string& path,
bool scalable)
: mTextureData {nullptr}
, mForceLoad {false}
, mScalableNonAspect {false}
{
// Create a texture data object for this texture.
if (!path.empty()) {
@ -251,8 +250,6 @@ void TextureResource::rasterizeAt(float width, float height)
else
data = sTextureDataManager.get(this);
data->setScalableNonAspect(mScalableNonAspect);
if (mTextureData && mTextureData.get()->getScalable())
mSourceSize = glm::vec2 {static_cast<float>(width), static_cast<float>(height)};
data->setSourceSize(static_cast<float>(width), static_cast<float>(height));

View file

@ -55,7 +55,6 @@ public:
return (mTextureData != nullptr ? mTextureData->getScalable() : false);
}
void setScalableNonAspect(bool state) { mScalableNonAspect = state; }
void setLinearMagnify(bool state) { mTextureData->setLinearMagnify(state); }
std::string getTextureFilePath();
@ -103,7 +102,6 @@ private:
glm::ivec2 mSize;
glm::vec2 mSourceSize;
bool mForceLoad;
bool mScalableNonAspect;
// File path, tile, linear interpolation, scalable/SVG, width, height.
using TextureKeyType = std::tuple<std::string, bool, bool, bool, size_t, size_t>;

View file

@ -6,10 +6,8 @@
# CMake configuration for bundled dependencies built in-tree.
#
# On Windows, rlottie is built as a DLL file.
if(NOT WIN32)
set(BUILD_SHARED_LIBS OFF)
endif()
# Always build the external libraries with optimizations enabled and without debug info.
set (CMAKE_BUILD_TYPE "Release")
# Disabled threading support for rlottie as this functionality actually leads to far worse
# performance. As well there is a bug on Windows that makes rlottie hang forever on application
@ -18,12 +16,29 @@ option(LOTTIE_THREAD OFF)
option(LOTTIE_MODULE OFF)
# Only use the compiler and linker flags defined by rlottie.
# Only use the compiler and linker flags defined by LunaSVG and rlottie.
unset(CMAKE_CXX_FLAGS)
unset(CMAKE_EXE_LINKER_FLAGS)
add_subdirectory(lunasvg)
# On Windows, rlottie is built as a DLL file.
if(NOT WIN32)
set(BUILD_SHARED_LIBS OFF)
endif()
if(EMSCRIPTEN)
set(CMAKE_CXX_FLAGS -pthread)
endif()
# rlottie generates a lot of annoying compiler warnings that we don't need to show.
if(CMAKE_CXX_COMPILER_ID MATCHES MSVC)
set(CMAKE_CXX_FLAGS "/wd4244 /wd4251 /wd4267 /wd4530 /wd4996")
else()
set(CMAKE_CXX_FLAGS "-w")
endif()
add_subdirectory(rlottie EXCLUDE_FROM_ALL)
# Build LunaSVG before rlottie.
add_dependencies(rlottie lunasvg)