diff options
author | Alex Zolotarev <deathbaba@gmail.com> | 2010-12-05 19:24:16 +0300 |
---|---|---|
committer | Alex Zolotarev <alex@maps.me> | 2015-09-22 22:33:57 +0300 |
commit | d6e12b7ce4bcbf0ccd1c07eb25de143422913c34 (patch) | |
tree | a7e910c330ce4da9b4f2d8be76067adece2561c4 /skin_generator |
One Month In Minsk. Made in Belarus.
Diffstat (limited to 'skin_generator')
-rw-r--r-- | skin_generator/main.cpp | 54 | ||||
-rw-r--r-- | skin_generator/skin_generator.cpp | 398 | ||||
-rw-r--r-- | skin_generator/skin_generator.hpp | 99 | ||||
-rw-r--r-- | skin_generator/skin_generator.pro | 29 |
4 files changed, 580 insertions, 0 deletions
diff --git a/skin_generator/main.cpp b/skin_generator/main.cpp new file mode 100644 index 0000000000..825dc4f1e1 --- /dev/null +++ b/skin_generator/main.cpp @@ -0,0 +1,54 @@ +#include "skin_generator.hpp" + +#include <QtCore/QFile> +#include <QtGui/QApplication> + +#include <QtXml/QXmlSimpleReader> +#include <QtXml/QXmlInputSource> + +#include "../3party/gflags/src/gflags/gflags.h" + +DEFINE_string(fontFileName, "../../data/fonts/DejaVu/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_double(symbolScale, 1, "scale factor of the symbol"); +DEFINE_int32(smallGlyphSize, 12, "height of the small glyph"); +DEFINE_int32(bigGlyphSize, 16, "height of the big glyph"); +DEFINE_string(skinName, "../../data/basic", "prefix for the skin and skinImage file name"); + +int main(int argc, char *argv[]) +{ + google::ParseCommandLineFlags(&argc, &argv, true); + QApplication app(argc, argv); + + tools::SkinGenerator gen; + + std::vector<int8_t> glyphSizes; + +// glyphSizes.push_back(6); + glyphSizes.push_back(8); + glyphSizes.push_back(10); + glyphSizes.push_back(12); + glyphSizes.push_back(14); + glyphSizes.push_back(16); + glyphSizes.push_back(20); + glyphSizes.push_back(24); + +// glyphSizes.push_back(FLAGS_smallGlyphSize); +// glyphSizes.push_back(FLAGS_bigGlyphSize); + + std::vector<QSize> symbolSizes; + symbolSizes.push_back(QSize(FLAGS_symbolWidth, FLAGS_symbolHeight)); + + std::vector<double> symbolScales; + symbolScales.push_back(FLAGS_symbolScale); + + gen.processSymbols(FLAGS_symbolsDir, FLAGS_skinName, symbolSizes, symbolScales); + gen.processFont(FLAGS_fontFileName, FLAGS_skinName, FLAGS_symbolsFile, glyphSizes); + + gen.writeToFile(FLAGS_skinName); + + return 0; +} diff --git a/skin_generator/skin_generator.cpp b/skin_generator/skin_generator.cpp new file mode 100644 index 0000000000..013441824b --- /dev/null +++ b/skin_generator/skin_generator.cpp @@ -0,0 +1,398 @@ +#include "skin_generator.hpp" +#include <QtXml/QDomElement> +#include <QtXml/QDomDocument> +#include <QtCore/QDir> +#include "../std/bind.hpp" +#include "../coding/lodepng_io.hpp" +#include <boost/gil/gil_all.hpp> +#include "../std/algorithm.hpp" +#include "../std/iterator.hpp" +#include "../std/fstream.hpp" +#include "../std/iostream.hpp" + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H + + +namespace gil = boost::gil; + +namespace tools +{ + SkinGenerator::SkinGenerator() + : m_baseLineOffset(0) + {} + + void SkinGenerator::processFont(string const & fileName, string const & skinName, string const & symFreqFile, vector<int8_t> const & fontSizes) + { + FILE * file = fopen(symFreqFile.c_str(), "rb"); + if (!file) + throw std::exception(); + std::vector<unsigned short> ucs2Symbols; + + while (true) + { + unsigned short id; + int readBytes = fread(&id, 1, sizeof(unsigned short), file); + if (readBytes < 2) + break; + ucs2Symbols.push_back(id); + } + + fclose(file); + + FT_Library lib; + FT_Init_FreeType(&lib); + + FT_Face face; + FT_New_Face(lib, fileName.c_str(), 0, &face); + + FT_Glyph_Metrics glyphMetrics; + + for (size_t i = 0; i < fontSizes.size(); ++i) + { + m_pages.push_back(SkinPageInfo()); + SkinPageInfo & page = m_pages.back(); + + page.m_fonts.push_back(FontInfo()); + FontInfo & fontInfo = page.m_fonts.back(); + + fontInfo.m_size = fontSizes[i]; + + FT_Set_Pixel_Sizes(face, 0, fontSizes[i]); + for (size_t j = 0; j < ucs2Symbols.size(); ++j) + { + unsigned short symbol = ucs2Symbols[j]; + + int symbolIdx = FT_Get_Char_Index(face, symbol); + + if (symbolIdx == 0) + continue; + + FT_Load_Glyph(face, symbolIdx, FT_LOAD_DEFAULT); + glyphMetrics = face->glyph->metrics; + + CharInfo charInfo; + charInfo.m_width = int(glyphMetrics.width >> 6); + charInfo.m_height = int(glyphMetrics.height >> 6); + charInfo.m_xOffset = int(glyphMetrics.horiBearingX >> 6); + charInfo.m_yOffset = int(glyphMetrics.horiBearingY >> 6) - charInfo.m_height; + charInfo.m_xAdvance = int(glyphMetrics.horiAdvance >> 6); + + FT_GlyphSlot glyphSlot = face->glyph; + if ((charInfo.m_width != 0) && (charInfo.m_height != 0)) + { + FT_Render_Glyph(glyphSlot, FT_RENDER_MODE_NORMAL); + + typedef gil::gray8_pixel_t pixel_t; + + gil::gray8c_view_t grayview = gil::interleaved_view( + charInfo.m_width, + charInfo.m_height, + (pixel_t*)glyphSlot->bitmap.buffer, + sizeof(unsigned char) * glyphSlot->bitmap.width); + + charInfo.m_image.recreate(charInfo.m_width, charInfo.m_height); + gil::copy_pixels(grayview, gil::view(charInfo.m_image)); + } + + fontInfo.m_chars[symbol] = charInfo; + } + + std::stringstream out; + out << getBaseFileName(fileName) + "_" << (int)fontSizes[i]; + + page.m_fileName = out.str().c_str(); + + /// repacking symbols as tight as possible + page.m_width = 32; + page.m_height = 32; + + while (true) + { + m_overflowDetected = false; + + page.m_packer = m2::Packer(page.m_width, page.m_height); + page.m_packer.addOverflowFn(bind(&SkinGenerator::markOverflow, this), 10); + + for (TChars::iterator it = fontInfo.m_chars.begin(); it != fontInfo.m_chars.end(); ++it) + { + it->second.m_handle = page.m_packer.pack(it->second.m_width + 4, it->second.m_height + 4); + if (m_overflowDetected) + break; + } + + if (m_overflowDetected) + { + if (page.m_width == page.m_height) + page.m_width *= 2; + else + page.m_height *= 2; + continue; + } + else + break; + } + + gil::bgra8_image_t skinImage(page.m_width, page.m_height); + gil::fill_pixels(gil::view(skinImage), gil::rgba8_pixel_t(0, 0, 0, 0)); + + for (TChars::const_iterator it = fontInfo.m_chars.begin(); it != fontInfo.m_chars.end(); ++it) + { + m2::RectU dstRect(page.m_packer.find(it->second.m_handle).second); + + gil::rgba8_pixel_t color(0, 0, 0, 0); + + gil::bgra8_view_t dstView = gil::subimage_view(gil::view(skinImage), dstRect.minX(), dstRect.minY(), dstRect.SizeX(), dstRect.SizeY()); + gil::fill_pixels(dstView, color); + + dstView = gil::subimage_view(gil::view(skinImage), + dstRect.minX() + 2, + dstRect.minY() + 2, + dstRect.SizeX() - 4, + dstRect.SizeY() - 4); + + gil::gray8c_view_t srcView = gil::const_view(it->second.m_image); + + for (size_t x = 0; x < dstRect.SizeX() - 4; ++x) + for (size_t y = 0; y < dstRect.SizeY() - 4; ++y) + { + color[3] = srcView(x, y); + dstView(x, y) = color; + } + } + + gil::lodepng_write_view( + skinName.substr(0, skinName.find_last_of("/") + 1) + page.m_fileName + ".png", + gil::const_view(skinImage)); + } + + FT_Done_Face(face); + FT_Done_FreeType(lib); + } + + string const SkinGenerator::getBaseFileName(string const & fileName) + { + int startPos = fileName.find_last_of("/"); + int endPos = fileName.find_last_of("."); + if (endPos != string::npos) + endPos = endPos - startPos - 1; + + string s = fileName.substr(fileName.find_last_of("/") + 1, endPos); + for (size_t i = 0; i < s.size(); ++i) + s[i] = tolower(s[i]); + return s; + } + + struct LessHeight + { + bool operator()(SkinGenerator::SymbolInfo const & left, SkinGenerator::SymbolInfo const & right) + { + return left.m_size.height() < right.m_size.height(); + } + }; + + void SkinGenerator::processSymbols(string const & svgDataDir, string const & skinName, std::vector<QSize> const & symbolSizes, std::vector<double> const & symbolScales) + { + for (int i = 0; i < symbolSizes.size(); ++i) + { + QDir dir(QString(svgDataDir.c_str())); + QStringList fileNames = dir.entryList(QDir::Files); + + /// separate page for symbols + m_pages.push_back(SkinPageInfo()); + SkinPageInfo & page = m_pages.back(); + + double symbolScale = symbolScales[i]; + QSize symbolSize = symbolSizes[i]; + + for (int i = 0; i < fileNames.size(); ++i) + { + if (fileNames.at(i).endsWith(".svg")) + { + QString fullFileName = QString(svgDataDir.c_str()) + "/" + fileNames.at(i); + QString symbolID = fileNames.at(i).left(fileNames.at(i).lastIndexOf(".")); + if (m_svgRenderer.load(fullFileName)) + { + QRect viewBox = m_svgRenderer.viewBox(); + QSize defaultSize = m_svgRenderer.defaultSize(); + + QSize size = defaultSize * symbolScale; + + if (size.width() > symbolSize.width()) + { + size.setHeight((float)size.height() * symbolSize.width() / (float)size.width()); + size.setWidth(symbolSize.width()); + } + + if (size.height() > symbolSize.height()) + { + size.setWidth((float)size.width() * symbolSize.height() / (float)size.height()); + size.setHeight(symbolSize.height()); + } + + page.m_symbols.push_back(SymbolInfo(size + QSize(4, 4), fullFileName, symbolID)); + } + } + } + + /// Trying to repack symbols as tight as possible + page.m_width = 64; + page.m_height = 64; + + /// packing until we find a suitable rect + while (true) + { + page.m_packer = m2::Packer(page.m_width, page.m_height); + page.m_packer.addOverflowFn(bind(&SkinGenerator::markOverflow, this), 10); + + m_overflowDetected = false; + + for (TSymbols::iterator it = page.m_symbols.begin(); it != page.m_symbols.end(); ++it) + { + it->m_handle = page.m_packer.pack(it->m_size.width(), it->m_size.height()); + 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; + break; + } + } + + if (m_overflowDetected) + continue; + + break; + } + + /// rendering packed symbols + + gil::bgra8_image_t gilImage(page.m_width, page.m_height); + gil::fill_pixels(gil::view(gilImage), gil::rgba8_pixel_t(0, 0, 0, 0)); + QImage img((uchar*)&gil::view(gilImage)(0, 0), page.m_width, page.m_height, QImage::Format_ARGB32); + QPainter painter(&img); + painter.setClipping(true); + + for (TSymbols::const_iterator it = page.m_symbols.begin(); it != page.m_symbols.end(); ++it) + { + m2::RectU dstRect = page.m_packer.find(it->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); + + m_svgRenderer.load(it->m_fullFileName); + m_svgRenderer.render(&painter, QRect(dstRect.minX() + 2, dstRect.minY() + 2, dstRect.SizeX() - 4, dstRect.SizeY() - 4)); + } + + page.m_fileName = skinName.substr(0, skinName.find_last_of("/") + 1) + "symbols_" + QString("%1").arg(symbolSize.width()).toLocal8Bit().constData(); + + img.save((page.m_fileName + ".png").c_str()); + } + } + + void SkinGenerator::markOverflow() + { + m_overflowDetected = true; + } + + bool SkinGenerator::writeToFile(std::string const & skinName) + { + /// Creating Data file + QDomDocument doc = QDomDocument("skin"); + QDomElement skinElem = doc.createElement("skin"); + doc.appendChild(skinElem); + + for (vector<SkinPageInfo>::const_iterator it = m_pages.begin(); it != m_pages.end(); ++it) + { + SkinPageInfo const & page = *it; + + QDomElement pageElem = doc.createElement("page"); + skinElem.appendChild(pageElem); + pageElem.setAttribute("width", page.m_width); + pageElem.setAttribute("height", page.m_height); + pageElem.setAttribute("file", (page.m_fileName.substr(page.m_fileName.find_last_of("/") + 1) + ".png").c_str()); + + int minDynamicID = 0; + int maxFontResourceID = 0; + + for (TFonts::const_iterator fontIt = page.m_fonts.begin(); fontIt != page.m_fonts.end(); ++fontIt) + { + QDomElement fontInfo = doc.createElement("fontInfo"); + fontInfo.setAttribute("size", fontIt->m_size); + + for (TChars::const_iterator it = fontIt->m_chars.begin(); it != fontIt->m_chars.end(); ++it) + { + QDomElement charStyle = doc.createElement("charStyle"); + QDomElement resourceStyle = doc.createElement("resourceStyle"); + + m2::RectU const & texRect = page.m_packer.find(it->second.m_handle).second; + + resourceStyle.setAttribute("x", texRect.minX()); + resourceStyle.setAttribute("y", texRect.minY()); + resourceStyle.setAttribute("width", texRect.SizeX()); + resourceStyle.setAttribute("height", texRect.SizeY()); + + charStyle.appendChild(resourceStyle); + + charStyle.setAttribute("xAdvance", it->second.m_xAdvance); + charStyle.setAttribute("xOffset", it->second.m_xOffset); + charStyle.setAttribute("yOffset", it->second.m_yOffset); + charStyle.setAttribute("id", it->first); + + fontInfo.appendChild(charStyle); + + maxFontResourceID = max(it->first, maxFontResourceID); + } + + pageElem.appendChild(fontInfo); + } + + minDynamicID += maxFontResourceID + 1; + int maxImageResourceID = 0; + + for (vector<SymbolInfo>::const_iterator it = page.m_symbols.begin(); it != page.m_symbols.end(); ++it) + { + QDomElement symbolStyle = doc.createElement("symbolStyle"); + + QDomElement resourceStyle = doc.createElement("resourceStyle"); + + m2::RectU r = page.m_packer.find(it->m_handle).second; + + resourceStyle.setAttribute("x", r.minX()); + resourceStyle.setAttribute("y", r.minY()); + resourceStyle.setAttribute("width", r.SizeX()); + resourceStyle.setAttribute("height", r.SizeY()); + + symbolStyle.appendChild(resourceStyle); + symbolStyle.setAttribute("id", minDynamicID + it->m_handle); + symbolStyle.setAttribute("name", it->m_symbolID.toLower()); + + maxImageResourceID = max(maxImageResourceID, (int)it->m_handle); + + pageElem.appendChild(symbolStyle); + } + + minDynamicID += maxImageResourceID + 1; + } + + QFile::remove(QString((skinName + ".skn").c_str())); + + if (QFile::exists((skinName + ".skn").c_str())) + throw std::exception(); + + QFile file(QString((skinName + ".skn").c_str())); + + if (!file.open(QIODevice::ReadWrite)) + throw std::exception(); + QTextStream ts(&file); + ts.setCodec("UTF-8"); + ts << doc.toString(); + + return true; + } +} diff --git a/skin_generator/skin_generator.hpp b/skin_generator/skin_generator.hpp new file mode 100644 index 0000000000..3d6a80f585 --- /dev/null +++ b/skin_generator/skin_generator.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include "../../base/base.hpp" +#include "../../std/vector.hpp" +#include "../../std/list.hpp" +#include "../../std/string.hpp" +#include "../../std/map.hpp" +#include "../../geometry/rect2d.hpp" +#include "../../coding/writer.hpp" +#include "../../geometry/packer.hpp" +#include <boost/gil/gil_all.hpp> + +#include <QtGui/QPainter> +#include <QtGui/QImage> +#include <QtCore/QFileInfo> +#include <QtCore/QSize> +#include <QtSvg/QSvgRenderer> +#include <QtXml/QXmlContentHandler> +#include <QtXml/QXmlDefaultHandler> + +class QImage; + +namespace gil = boost::gil; + +namespace tools +{ + class SkinGenerator + { + public: + + struct CharInfo + { + int m_width; + int m_height; + int m_xOffset; + int m_yOffset; + int m_xAdvance; + gil::gray8_image_t m_image; + + m2::Packer::handle_t m_handle; + }; + + typedef map<int32_t, CharInfo> TChars; + + struct FontInfo + { + int8_t m_size; + TChars m_chars; + }; + + typedef vector<FontInfo> TFonts; + + 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) {} + }; + + typedef vector<SymbolInfo> TSymbols; + + struct SkinPageInfo + { + TFonts m_fonts; + TSymbols m_symbols; + int m_width; + int m_height; + string m_fileName; + m2::Packer m_packer; + }; + + string const getBaseFileName(string const & fileName); + + private: + + QSvgRenderer m_svgRenderer; + + int m_baseLineOffset; + QString m_fontFileName; + + vector<SkinPageInfo> m_pages; + + bool m_overflowDetected; + void markOverflow(); + + public: + + SkinGenerator(); + void processFont(string const & fileName, string const & skinName, string const & symFreqFile, vector<int8_t> const & fontSizes); + void processSymbols(string const & symbolsDir, string const & skinName, std::vector<QSize> const & symbolSizes, std::vector<double> const & symbolScales); + bool writeToFile(string const & skinName); + }; +} // namespace tools diff --git a/skin_generator/skin_generator.pro b/skin_generator/skin_generator.pro new file mode 100644 index 0000000000..5f0cd6cd72 --- /dev/null +++ b/skin_generator/skin_generator.pro @@ -0,0 +1,29 @@ +# ----------------------------------------------------- +# Project created by Alex Zolotarev 2010-01-22T14:39:29 +# ----------------------------------------------------- + +TARGET = skin_generator +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle + +ROOT_DIR = .. +include($$ROOT_DIR/common.pri) + +QT *= core gui svg xml + +PRE_TARGETDEPS += $$BINARIES_PATH/$${LIB_PREFIX}coding$$LIB_EXT +PRE_TARGETDEPS += $$BINARIES_PATH/$${LIB_PREFIX}geometry$$LIB_EXT +PRE_TARGETDEPS += $$BINARIES_PATH/$${LIB_PREFIX}freetype$$LIB_EXT +PRE_TARGETDEPS += $$BINARIES_PATH/$${LIB_PREFIX}gflags$$LIB_EXT +PRE_TARGETDEPS += $$BINARIES_PATH/$${LIB_PREFIX}base$$LIB_EXT + +LIBS += -lcoding -lgeometry -lfreetype -lgflags -lbase + +INCLUDEPATH += $$ROOT_DIR/3party/boost \ + $$ROOT_DIR/3party/freetype/include + +HEADERS += skin_generator.hpp + +SOURCES += main.cpp \ + skin_generator.cpp |