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

View file

@ -22,7 +22,6 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic)
, mFlipX {false} , mFlipX {false}
, mFlipY {false} , mFlipY {false}
, mTargetIsMax {false} , mTargetIsMax {false}
, mScalableNonAspect {false}
, mTileWidth {0.0f} , mTileWidth {0.0f}
, mTileHeight {0.0f} , mTileHeight {0.0f}
, mColorShift {0xFFFFFFFF} , mColorShift {0xFFFFFFFF}
@ -88,7 +87,6 @@ void ImageComponent::setImage(const std::string& path, bool tile)
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation, mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation,
mMipmapping, static_cast<size_t>(mSize.x), mMipmapping, static_cast<size_t>(mSize.x),
static_cast<size_t>(mSize.y), mTileWidth, mTileHeight); static_cast<size_t>(mSize.y), mTileWidth, mTileHeight);
mTexture->setScalableNonAspect(mScalableNonAspect);
mTexture->rasterizeAt(mSize.x, mSize.y); mTexture->rasterizeAt(mSize.x, mSize.y);
onSizeChanged(); onSizeChanged();
} }
@ -404,8 +402,6 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
getParent()->getSize() : getParent()->getSize() :
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())}; glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())};
bool noMax {false};
if (properties & ThemeFlags::SIZE) { if (properties & ThemeFlags::SIZE) {
if (elem->has("size")) { if (elem->has("size")) {
glm::vec2 imageSize {elem->get<glm::vec2>("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) if (imageSize.y > 0.0f)
imageSize.y = glm::clamp(imageSize.y, 0.001f, 3.0f); imageSize.y = glm::clamp(imageSize.y, 0.001f, 3.0f);
setResize(imageSize * scale); setResize(imageSize * scale);
if (imageSize.x != 0.0f && imageSize.y != 0.0f)
noMax = true;
} }
else if (elem->has("maxSize")) { else if (elem->has("maxSize")) {
glm::vec2 imageMaxSize {elem->get<glm::vec2>("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")) { if (properties & PATH && elem->has("path")) {
const std::string path {elem->get<std::string>("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")) { if (tile && elem->has("tileSize")) {
glm::vec2 tileSize {elem->get<glm::vec2>("tileSize")}; glm::vec2 tileSize {elem->get<glm::vec2>("tileSize")};
if (tileSize.x == 0.0f && tileSize.y == 0.0f) { 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); tileSize.y = glm::clamp(tileSize.y, 0.0f, 1.0f);
mTileWidth = tileSize.x * scale.x; mTileWidth = tileSize.x * scale.x;
mTileHeight = tileSize.y * scale.y; 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}; glm::vec2 resizeScale {mTargetSize.x / mSize.x, mTargetSize.y / mSize.y};
if (resizeScale.x < resizeScale.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.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 { else {
// This will be mTargetSize.y(). We can't exceed it. // This will be mTargetSize.y(). We can't exceed it.

View file

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

View file

@ -6,9 +6,6 @@
// Low-level texture data functions. // Low-level texture data functions.
// //
#define NANOSVG_IMPLEMENTATION
#define NANOSVGRAST_IMPLEMENTATION
#include "resources/TextureData.h" #include "resources/TextureData.h"
#include "ImageIO.h" #include "ImageIO.h"
@ -16,13 +13,10 @@
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "nanosvg.h" #include "lunasvg.h"
#include "nanosvgrast.h"
#include <string.h> #include <string.h>
#define DPI 96
TextureData::TextureData(bool tile) TextureData::TextureData(bool tile)
: mRenderer {Renderer::getInstance()} : mRenderer {Renderer::getInstance()}
, mTile {tile} , mTile {tile}
@ -34,10 +28,10 @@ TextureData::TextureData(bool tile)
, mSourceWidth {0.0f} , mSourceWidth {0.0f}
, mSourceHeight {0.0f} , mSourceHeight {0.0f}
, mScalable {false} , mScalable {false}
, mScalableNonAspect {false}
, mHasRGBAData {false} , mHasRGBAData {false}
, mPendingRasterization {false} , mPendingRasterization {false}
, mMipmapping {false} , mMipmapping {false}
, mInvalidSVGFile {false}
, mLinearMagnify {false} , mLinearMagnify {false}
{ {
} }
@ -64,20 +58,23 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
if (!mDataRGBA.empty() && !mPendingRasterization) if (!mDataRGBA.empty() && !mPendingRasterization)
return true; 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) { if (svgImage == nullptr) {
LOG(LogError) << "Couldn't parse SVG image"; // LOG(LogError) << "Couldn't parse SVG image:" << mPath;
mInvalidSVGFile = true;
return false; return false;
} }
float svgWidth {static_cast<float>(svgImage->width())};
float svgHeight {static_cast<float>(svgImage->height())};
bool rasterize {true}; bool rasterize {true};
if (mTile) { if (mTile) {
if (mTileWidth == 0.0f && mTileHeight == 0.0f) { if (mTileWidth == 0.0f && mTileHeight == 0.0f) {
rasterize = false; rasterize = false;
mSourceWidth = svgImage->width; mSourceWidth = svgWidth;
mSourceHeight = svgImage->height; mSourceHeight = svgHeight;
} }
else { else {
mSourceWidth = static_cast<float>(mTileWidth); mSourceWidth = static_cast<float>(mTileWidth);
@ -90,7 +87,7 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
rasterize = false; rasterize = false;
// Set a small temporary size that maintains the image aspect ratio. // Set a small temporary size that maintains the image aspect ratio.
mSourceWidth = 64.0f; mSourceWidth = 64.0f;
mSourceHeight = 64.0f * (svgImage->height / svgImage->width); mSourceHeight = 64.0f * (svgHeight / svgWidth);
} }
mWidth = static_cast<int>(std::round(mSourceWidth)); mWidth = static_cast<int>(std::round(mSourceWidth));
@ -98,38 +95,25 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
if (mWidth == 0) { if (mWidth == 0) {
// Auto scale width to keep aspect ratio. // Auto scale width to keep aspect ratio.
mWidth = static_cast<size_t>( mWidth =
std::round((static_cast<float>(mHeight) / svgImage->height) * svgImage->width)); static_cast<size_t>(std::round((static_cast<float>(mHeight) / svgHeight) * svgWidth));
} }
else if (mHeight == 0) { else if (mHeight == 0) {
// Auto scale height to keep aspect ratio. // Auto scale height to keep aspect ratio.
mHeight = static_cast<size_t>( mHeight =
std::round((static_cast<float>(mWidth) / svgImage->width) * svgImage->height)); static_cast<size_t>(std::round((static_cast<float>(mWidth) / svgWidth) * svgHeight));
} }
if (rasterize) { if (rasterize) {
const float aspectW {svgImage->width / static_cast<float>(mWidth)}; // For very short and wide images, make a slight scale adjustment to prevent pixels
const float aspectH {svgImage->height / static_cast<float>(mHeight)}; // 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, auto bitmap = svgImage->renderToBitmap(mWidth, mHeight);
// then we need to rasterize at a lower resolution and let the GPU scale the texture. bitmap.convertToRGBA();
// This is necessary as the rasterization always maintains the image aspect ratio. mDataRGBA.insert(mDataRGBA.begin(), std::move(bitmap.data()),
if (mScalableNonAspect && aspectW != aspectH) std::move(bitmap.data() + mWidth * mHeight * 4));
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());
ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight); ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight);
mPendingRasterization = false; mPendingRasterization = false;
@ -141,8 +125,6 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
mPendingRasterization = true; mPendingRasterization = true;
} }
nsvgDelete(svgImage);
return true; return true;
} }
@ -196,6 +178,9 @@ bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size
bool TextureData::load() bool TextureData::load()
{ {
if (mInvalidSVGFile)
return false;
bool retval {false}; bool retval {false};
// Need to load. See if there is a file. // 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)}; } 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. // Whether to use linear filtering when magnifying the texture.
void setLinearMagnify(bool state) { mLinearMagnify = state; } void setLinearMagnify(bool state) { mLinearMagnify = state; }
// Whether to use mipmapping and trilinear filtering. // Whether to use mipmapping and trilinear filtering.
@ -96,10 +92,10 @@ private:
std::atomic<float> mSourceWidth; std::atomic<float> mSourceWidth;
std::atomic<float> mSourceHeight; std::atomic<float> mSourceHeight;
std::atomic<bool> mScalable; std::atomic<bool> mScalable;
std::atomic<bool> mScalableNonAspect;
std::atomic<bool> mHasRGBAData; std::atomic<bool> mHasRGBAData;
std::atomic<bool> mPendingRasterization; std::atomic<bool> mPendingRasterization;
std::atomic<bool> mMipmapping; std::atomic<bool> mMipmapping;
std::atomic<bool> mInvalidSVGFile;
bool mLinearMagnify; bool mLinearMagnify;
bool mReloadable; bool mReloadable;
}; };

View file

@ -24,7 +24,6 @@ TextureResource::TextureResource(const std::string& path,
bool scalable) bool scalable)
: mTextureData {nullptr} : mTextureData {nullptr}
, mForceLoad {false} , mForceLoad {false}
, mScalableNonAspect {false}
{ {
// Create a texture data object for this texture. // Create a texture data object for this texture.
if (!path.empty()) { if (!path.empty()) {
@ -251,8 +250,6 @@ void TextureResource::rasterizeAt(float width, float height)
else else
data = sTextureDataManager.get(this); data = sTextureDataManager.get(this);
data->setScalableNonAspect(mScalableNonAspect);
if (mTextureData && mTextureData.get()->getScalable()) if (mTextureData && mTextureData.get()->getScalable())
mSourceSize = glm::vec2 {static_cast<float>(width), static_cast<float>(height)}; mSourceSize = glm::vec2 {static_cast<float>(width), static_cast<float>(height)};
data->setSourceSize(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); return (mTextureData != nullptr ? mTextureData->getScalable() : false);
} }
void setScalableNonAspect(bool state) { mScalableNonAspect = state; }
void setLinearMagnify(bool state) { mTextureData->setLinearMagnify(state); } void setLinearMagnify(bool state) { mTextureData->setLinearMagnify(state); }
std::string getTextureFilePath(); std::string getTextureFilePath();
@ -103,7 +102,6 @@ private:
glm::ivec2 mSize; glm::ivec2 mSize;
glm::vec2 mSourceSize; glm::vec2 mSourceSize;
bool mForceLoad; bool mForceLoad;
bool mScalableNonAspect;
// File path, tile, linear interpolation, scalable/SVG, width, height. // File path, tile, linear interpolation, scalable/SVG, width, height.
using TextureKeyType = std::tuple<std::string, bool, bool, bool, size_t, size_t>; 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. # CMake configuration for bundled dependencies built in-tree.
# #
# On Windows, rlottie is built as a DLL file. # Always build the external libraries with optimizations enabled and without debug info.
if(NOT WIN32) set (CMAKE_BUILD_TYPE "Release")
set(BUILD_SHARED_LIBS OFF)
endif()
# Disabled threading support for rlottie as this functionality actually leads to far worse # 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 # 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) 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_CXX_FLAGS)
unset(CMAKE_EXE_LINKER_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) if(EMSCRIPTEN)
set(CMAKE_CXX_FLAGS -pthread) set(CMAKE_CXX_FLAGS -pthread)
endif() 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) add_subdirectory(rlottie EXCLUDE_FROM_ALL)
# Build LunaSVG before rlottie.
add_dependencies(rlottie lunasvg)