Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
17
tools/skin_generator/CMakeLists.txt
Normal file
17
tools/skin_generator/CMakeLists.txt
Normal 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
|
||||
)
|
||||
252
tools/skin_generator/generator.cpp
Normal file
252
tools/skin_generator/generator.cpp
Normal 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
|
||||
70
tools/skin_generator/generator.hpp
Normal file
70
tools/skin_generator/generator.hpp
Normal 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
|
||||
67
tools/skin_generator/main.cpp
Normal file
67
tools/skin_generator/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue