Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,17 @@
project(skin_generator_tool)
set(SRC
generator.cpp
generator.hpp
main.cpp
)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
geometry
gflags::gflags
Qt6::Xml
Qt6::Svg
Qt6::Widgets
)

View file

@ -0,0 +1,252 @@
#include "generator.hpp"
#include "base/logging.hpp"
#include "base/math.hpp"
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <QtCore/QDir>
#include <QtXml/QDomDocument>
#include <QtXml/QDomElement>
namespace tools
{
namespace
{
static constexpr double kLargeIconSize = 24.0; // Size of the -l SVG icons
static constexpr double kMediumIconSize = 18.0; // size of the -m SVG icons
struct GreaterHeight
{
bool operator()(SkinGenerator::SymbolInfo const & left, SkinGenerator::SymbolInfo const & right) const
{
QString symbolIDleft = left.m_fullFileName.left(left.m_fullFileName.lastIndexOf("."));
QString symbolIDright = right.m_fullFileName.left(right.m_fullFileName.lastIndexOf("."));
if (left.m_size.height() == right.m_size.height())
return symbolIDleft > symbolIDright;
return (left.m_size.height() > right.m_size.height());
}
};
struct MaxDimensions
{
uint32_t & m_width;
uint32_t & m_height;
MaxDimensions(uint32_t & width, uint32_t & height) : m_width(width), m_height(height)
{
m_width = 0;
m_height = 0;
}
void operator()(SkinGenerator::SymbolInfo const & info)
{
m_width = std::max(std::max(m_width, m_height), static_cast<uint32_t>(info.m_size.width()));
m_height = std::max(std::max(m_width, m_height), static_cast<uint32_t>(info.m_size.height()));
}
};
uint32_t NextPowerOf2(uint32_t n)
{
n = n - 1;
n |= (n >> 1);
n |= (n >> 2);
n |= (n >> 4);
n |= (n >> 8);
n |= (n >> 16);
return n + 1;
}
} // namespace
void SkinGenerator::ProcessSymbols(std::string const & svgDataDir, std::string const & skinName,
std::vector<QSize> const & symbolSizes, std::vector<std::string> const & suffixes)
{
for (size_t j = 0; j < symbolSizes.size(); ++j)
{
QDir dir(QString(svgDataDir.c_str()));
QStringList fileNames = dir.entryList(QDir::Files);
QDir pngDir(dir.absolutePath() + "/png");
fileNames += pngDir.entryList(QDir::Files);
// Separate page for symbols.
m_pages.emplace_back(SkinPageInfo());
SkinPageInfo & page = m_pages.back();
page.m_dir = skinName.substr(0, skinName.find_last_of("/") + 1);
page.m_suffix = suffixes[j];
page.m_fileName = page.m_dir + "symbols" + page.m_suffix;
for (int i = 0; i < fileNames.size(); ++i)
{
QString const & fileName = fileNames.at(i);
QString symbolID = fileName.left(fileName.lastIndexOf("."));
if (fileName.endsWith(".svg"))
{
QString fullFileName = QString(dir.absolutePath()) + "/" + fileName;
if (m_svgRenderer.load(fullFileName))
{
QSize svgSize = m_svgRenderer.defaultSize(); // Size of the SVG file
// Capping svg symbol to kLargeIconSize maximum, keeping aspect ratio
/*if (svgSize.width() > kLargeIconSize)
{
auto const h = static_cast<float>(svgSize.height()) * kLargeIconSize / svgSize.width();
svgSize.setHeight(static_cast<int>(h));
svgSize.setWidth(kLargeIconSize);
}
if (svgSize.height() > kLargeIconSize)
{
auto const w = static_cast<float>(svgSize.width()) * kLargeIconSize / svgSize.height();
svgSize.setWidth(static_cast<int>(w));
svgSize.setHeight(kLargeIconSize);
}*/
// Scale symbol to required size
QSize size = svgSize * (symbolSizes[j].width() / kMediumIconSize);
page.m_symbols.emplace_back(size + QSize(4, 4), fullFileName, symbolID);
}
}
else if (fileName.toLower().endsWith(".png"))
{
QString fullFileName = QString(pngDir.absolutePath()) + "/" + fileName;
QPixmap pix(fullFileName);
QSize s = pix.size();
page.m_symbols.emplace_back(s + QSize(4, 4), fullFileName, symbolID);
}
}
}
}
bool SkinGenerator::RenderPages(uint32_t maxSize)
{
for (auto & page : m_pages)
{
std::sort(page.m_symbols.begin(), page.m_symbols.end(), GreaterHeight());
MaxDimensions dim(page.m_width, page.m_height);
for_each(page.m_symbols.begin(), page.m_symbols.end(), dim);
page.m_width = NextPowerOf2(page.m_width);
page.m_height = NextPowerOf2(page.m_height);
// Packing until we find a suitable rect.
while (true)
{
page.m_packer = m2::Packer(page.m_width, page.m_height);
page.m_packer.addOverflowFn(std::bind(&SkinGenerator::MarkOverflow, this), 10);
m_overflowDetected = false;
for (auto & s : page.m_symbols)
{
s.m_handle =
page.m_packer.pack(static_cast<uint32_t>(s.m_size.width()), static_cast<uint32_t>(s.m_size.height()));
if (m_overflowDetected)
break;
}
if (m_overflowDetected)
{
// Enlarge packing area and try again.
if (page.m_width == page.m_height)
page.m_width *= 2;
else
page.m_height *= 2;
if (page.m_width > maxSize)
{
page.m_width = maxSize;
page.m_height *= 2;
if (page.m_height > maxSize)
return false;
}
continue;
}
break;
}
LOG(LINFO, ("Texture size =", page.m_width, "x", page.m_height));
std::vector<uchar> imgData(page.m_width * page.m_height * 4, 0);
QImage img(imgData.data(), page.m_width, page.m_height, QImage::Format_ARGB32);
QPainter painter(&img);
painter.setClipping(true);
for (auto const & s : page.m_symbols)
{
m2::RectU dstRect = page.m_packer.find(s.m_handle).second;
QRect dstRectQt(dstRect.minX(), dstRect.minY(), dstRect.SizeX(), dstRect.SizeY());
painter.fillRect(dstRectQt, QColor(0, 0, 0, 0));
painter.setClipRect(dstRect.minX() + 2, dstRect.minY() + 2, dstRect.SizeX() - 4, dstRect.SizeY() - 4);
QRect renderRect(dstRect.minX() + 2, dstRect.minY() + 2, dstRect.SizeX() - 4, dstRect.SizeY() - 4);
QString fullLowerCaseName = s.m_fullFileName.toLower();
if (fullLowerCaseName.endsWith(".svg"))
{
m_svgRenderer.load(s.m_fullFileName);
m_svgRenderer.render(&painter, renderRect);
}
else if (fullLowerCaseName.endsWith(".png"))
{
QPixmap pix(s.m_fullFileName);
painter.drawPixmap(renderRect, pix);
}
}
std::string s = page.m_fileName + ".png";
LOG(LINFO, ("saving skin image into: ", s));
img.save(s.c_str());
}
return true;
}
void SkinGenerator::MarkOverflow()
{
m_overflowDetected = true;
}
bool SkinGenerator::WriteToFileNewStyle(std::string const & skinName)
{
QDomDocument doc = QDomDocument("skin");
QDomElement rootElem = doc.createElement("root");
doc.appendChild(rootElem);
for (auto const & p : m_pages)
{
QDomElement fileNode = doc.createElement("file");
fileNode.setAttribute("width", p.m_width);
fileNode.setAttribute("height", p.m_height);
rootElem.appendChild(fileNode);
for (auto const & s : p.m_symbols)
{
m2::RectU r = p.m_packer.find(s.m_handle).second;
QDomElement symbol = doc.createElement("symbol");
symbol.setAttribute("minX", r.minX());
symbol.setAttribute("minY", r.minY());
symbol.setAttribute("maxX", r.maxX());
symbol.setAttribute("maxY", r.maxY());
symbol.setAttribute("name", s.m_symbolID.toLower());
fileNode.appendChild(symbol);
}
}
QFile file(QString(skinName.c_str()));
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate))
return false;
QTextStream ts(&file);
ts.setEncoding(QStringConverter::Utf8);
ts << doc.toString();
return true;
}
} // namespace tools

View file

@ -0,0 +1,70 @@
#pragma once
#include "geometry/packer.hpp"
#include "geometry/rect2d.hpp"
#include "coding/writer.hpp"
#include "base/base.hpp"
#include <QtCore/QFileInfo>
#include <QtCore/QSize>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <QtSvg/QSvgRenderer>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
class QImage;
namespace tools
{
class SkinGenerator
{
public:
struct SymbolInfo
{
QSize m_size;
QString m_fullFileName;
QString m_symbolID;
m2::Packer::handle_t m_handle = {};
SymbolInfo() {}
SymbolInfo(QSize size, QString const & fullFileName, QString const & symbolID)
: m_size(size)
, m_fullFileName(fullFileName)
, m_symbolID(symbolID)
{}
};
using TSymbols = std::vector<SymbolInfo>;
struct SkinPageInfo
{
TSymbols m_symbols;
uint32_t m_width = 0;
uint32_t m_height = 0;
std::string m_fileName;
std::string m_dir;
std::string m_suffix;
m2::Packer m_packer;
};
void ProcessSymbols(std::string const & symbolsDir, std::string const & skinName,
std::vector<QSize> const & symbolSizes, std::vector<std::string> const & suffix);
bool RenderPages(uint32_t maxSize);
bool WriteToFileNewStyle(std::string const & skinName);
private:
QSvgRenderer m_svgRenderer;
using TSkinPages = std::vector<SkinPageInfo>;
TSkinPages m_pages;
bool m_overflowDetected;
void MarkOverflow();
};
} // namespace tools

View file

@ -0,0 +1,67 @@
#include "generator.hpp"
#include <iostream>
#include "base/logging.hpp"
#include <QApplication>
#include <QtCore/QFile>
#include <QtCore/QHash>
#include <QtCore/QString>
#include <gflags/gflags.h>
DEFINE_string(fontFileName, "../../data/01_dejavusans.ttf", "path to TrueType font file");
DEFINE_string(symbolsFile, "../../data/results.unicode",
"file with 2bytes symbols for which the skin should be generated");
DEFINE_string(symbolsDir, "../../data/styles/symbols", "directory with svg symbol files");
DEFINE_int32(symbolWidth, 24, "width of the rendered symbol");
DEFINE_int32(symbolHeight, 24, "height of the rendered symbol");
DEFINE_string(skinName, "../../data/basic", "prefix for the skin and skinImage file name");
DEFINE_string(skinSuffix, "mdpi", "suffix for skinName<suffix>.skn and symbols<suffix>.png");
DEFINE_int32(searchIconWidth, 24, "width of the search category icon");
DEFINE_int32(searchIconHeight, 24, "height of the search category icon");
DEFINE_int32(maxSize, 4096, "max width/height of output textures");
int main(int argc, char * argv[])
{
// Used to lock the hash seed, so the order of XML attributes is always the same.
QHashSeed::setDeterministicGlobalSeed();
try
{
gflags::ParseCommandLineFlags(&argc, &argv, true);
QApplication app(argc, argv);
tools::SkinGenerator gen;
std::vector<QSize> symbolSizes;
symbolSizes.emplace_back(QSize(FLAGS_symbolWidth, FLAGS_symbolHeight));
std::vector<std::string> suffixes;
suffixes.push_back(FLAGS_skinSuffix);
gen.ProcessSymbols(FLAGS_symbolsDir, FLAGS_skinName, symbolSizes, suffixes);
if (!gen.RenderPages(FLAGS_maxSize))
{
LOG(LINFO, ("Error: The texture is overflown."));
return 1;
}
QString newSkin(FLAGS_skinName.c_str());
newSkin.replace("basic", "symbols");
auto const filename = newSkin.toStdString() + FLAGS_skinSuffix + ".sdf";
if (!gen.WriteToFileNewStyle(filename))
{
std::cerr << "Could not write file" << filename << std::endl;
return -1;
}
std::cout << "Done" << std::endl;
return 0;
}
catch (std::exception const & e)
{
std::cerr << "Exception " << e.what() << std::endl;
return -1;
}
}