[drape] remove offline glyph generator. Glyphs will be generate online on device

This commit is contained in:
ExMix 2014-12-09 14:21:17 +03:00 committed by Alex Zolotarev
parent dd6f0d8d68
commit 9986dba239
18 changed files with 0 additions and 34271 deletions

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

View file

@ -1,198 +0,0 @@
#include "BinPacker.hpp"
#include <cassert>
#include <algorithm>
// ---------------------------------------------------------------------------
void BinPacker::Pack(
const std::vector<int>& rects,
std::vector< std::vector<int> >& packs,
int packSize,
bool allowRotation)
{
assert(!(rects.size() % 2));
Clear();
m_packSize = packSize;
// Add rects to member array, and check to make sure none is too big
for (size_t i = 0; i < rects.size(); i += 2) {
if (rects[i] > m_packSize || rects[i + 1] > m_packSize) {
assert(!"All rect dimensions must be <= the pack size");
}
m_rects.push_back(Rect(0, 0, rects[i], rects[i + 1], i >> 1));
}
// Sort from greatest to least area
std::sort(m_rects.rbegin(), m_rects.rend());
// Pack
while (m_numPacked < (int)m_rects.size()) {
int i = m_packs.size();
m_packs.push_back(Rect(m_packSize));
m_roots.push_back(i);
Fill(i, allowRotation);
}
// Write out
packs.resize(m_roots.size());
for (size_t i = 0; i < m_roots.size(); ++i) {
packs[i].clear();
AddPackToArray(m_roots[i], packs[i]);
}
// Check and make sure all rects were packed
for (size_t i = 0; i < m_rects.size(); ++i) {
if (!m_rects[i].packed) {
assert(!"Not all rects were packed");
}
}
}
// ---------------------------------------------------------------------------
void BinPacker::Clear()
{
m_packSize = 0;
m_numPacked = 0;
m_rects.clear();
m_packs.clear();
m_roots.clear();
}
// ---------------------------------------------------------------------------
void BinPacker::Fill(int pack, bool allowRotation)
{
assert(PackIsValid(pack));
int i = pack;
// For each rect
for (size_t j = 0; j < m_rects.size(); ++j) {
// If it's not already packed
if (!m_rects[j].packed) {
// If it fits in the current working area
if (Fits(m_rects[j], m_packs[i], allowRotation)) {
// Store in lower-left of working area, split, and recurse
++m_numPacked;
Split(i, j);
Fill(m_packs[i].children[0], allowRotation);
Fill(m_packs[i].children[1], allowRotation);
return;
}
}
}
}
// ---------------------------------------------------------------------------
void BinPacker::Split(int pack, int rect)
{
assert(PackIsValid(pack));
assert(RectIsValid(rect));
int i = pack;
int j = rect;
// Split the working area either horizontally or vertically with respect
// to the rect we're storing, such that we get the largest possible child
// area.
Rect left = m_packs[i];
Rect right = m_packs[i];
Rect bottom = m_packs[i];
Rect top = m_packs[i];
left.y += m_rects[j].h;
left.w = m_rects[j].w;
left.h -= m_rects[j].h;
right.x += m_rects[j].w;
right.w -= m_rects[j].w;
bottom.x += m_rects[j].w;
bottom.h = m_rects[j].h;
bottom.w -= m_rects[j].w;
top.y += m_rects[j].h;
top.h -= m_rects[j].h;
int maxLeftRightArea = left.GetArea();
if (right.GetArea() > maxLeftRightArea) {
maxLeftRightArea = right.GetArea();
}
int maxBottomTopArea = bottom.GetArea();
if (top.GetArea() > maxBottomTopArea) {
maxBottomTopArea = top.GetArea();
}
if (maxLeftRightArea > maxBottomTopArea) {
if (left.GetArea() > right.GetArea()) {
m_packs.push_back(left);
m_packs.push_back(right);
} else {
m_packs.push_back(right);
m_packs.push_back(left);
}
} else {
if (bottom.GetArea() > top.GetArea()) {
m_packs.push_back(bottom);
m_packs.push_back(top);
} else {
m_packs.push_back(top);
m_packs.push_back(bottom);
}
}
// This pack area now represents the rect we've just stored, so save the
// relevant info to it, and assign children.
m_packs[i].w = m_rects[j].w;
m_packs[i].h = m_rects[j].h;
m_packs[i].ID = m_rects[j].ID;
m_packs[i].rotated = m_rects[j].rotated;
m_packs[i].children[0] = m_packs.size() - 2;
m_packs[i].children[1] = m_packs.size() - 1;
// Done with the rect
m_rects[j].packed = true;
}
// ---------------------------------------------------------------------------
bool BinPacker::Fits(Rect& rect1, const Rect& rect2, bool allowRotation)
{
// Check to see if rect1 fits in rect2, and rotate rect1 if that will
// enable it to fit.
if (rect1.w <= rect2.w && rect1.h <= rect2.h) {
return true;
} else if (allowRotation && rect1.h <= rect2.w && rect1.w <= rect2.h) {
rect1.Rotate();
return true;
} else {
return false;
}
}
// ---------------------------------------------------------------------------
void BinPacker::AddPackToArray(int pack, std::vector<int>& array) const
{
assert(PackIsValid(pack));
int i = pack;
if (m_packs[i].ID != -1) {
array.push_back(m_packs[i].ID);
array.push_back(m_packs[i].x);
array.push_back(m_packs[i].y);
array.push_back(m_packs[i].rotated);
if (m_packs[i].children[0] != -1) {
AddPackToArray(m_packs[i].children[0], array);
}
if (m_packs[i].children[1] != -1) {
AddPackToArray(m_packs[i].children[1], array);
}
}
}
// ---------------------------------------------------------------------------
bool BinPacker::RectIsValid(int i) const
{
return i >= 0 && i < (int)m_rects.size();
}
// ---------------------------------------------------------------------------
bool BinPacker::PackIsValid(int i) const
{
return i >= 0 && i < (int)m_packs.size();
}
// ---------------------------------------------------------------------------

View file

@ -1,96 +0,0 @@
#ifndef BINPACKER_H
#define BINPACKER_H
#include <vector>
class BinPacker
{
public:
// The input and output are in terms of vectors of ints to avoid
// dependencies (although I suppose a public member struct could have been
// used). The parameters are:
// rects : An array containing the width and height of each input rect in
// sequence, i.e. [w0][h0][w1][h1][w2][h2]... The IDs for the rects are
// derived from the order in which they appear in the array.
// packs : After packing, the outer array contains the packs (therefore
// the number of packs is packs.size()). Each inner array contains a
// sequence of sets of 4 ints. Each set represents a rectangle in the
// pack. The elements in the set are 1) the rect ID, 2) the x position
// of the rect with respect to the pack, 3) the y position of the rect
// with respect to the pack, and 4) whether the rect was rotated (1) or
// not (0). The widths and heights of the rects are not included, as it's
// assumed they are stored on the caller's side (they were after all the
// input to the function).
// allowRotation : when true (the default value), the packer is allowed
// the option of rotating the rects in the process of trying to fit them
// into the current working area.
void Pack(
const std::vector<int>& rects,
std::vector< std::vector<int> >& packs,
int packSize,
bool allowRotation = true
);
private:
struct Rect
{
Rect(int size)
: x(0), y(0), w(size), h(size), ID(-1), rotated(false), packed(false)
{
children[0] = -1;
children[1] = -1;
}
Rect(int x, int y, int w, int h, int ID = 1)
: x(x), y(y), w(w), h(h), ID(ID), rotated(false), packed(false)
{
children[0] = -1;
children[1] = -1;
}
int GetArea() const {
return w * h;
}
void Rotate() {
std::swap(w, h);
rotated = !rotated;
}
bool operator<(const Rect& rect) const {
return GetArea() < rect.GetArea();
}
int x;
int y;
int w;
int h;
int ID;
int children[2];
bool rotated;
bool packed;
};
void Clear();
void Fill(int pack, bool allowRotation);
void Split(int pack, int rect);
bool Fits(Rect& rect1, const Rect& rect2, bool allowRotation);
void AddPackToArray(int pack, std::vector<int>& array) const;
bool RectIsValid(int i) const;
bool PackIsValid(int i) const;
int m_packSize;
int m_numPacked;
std::vector<Rect> m_rects;
std::vector<Rect> m_packs;
std::vector<int> m_roots;
};
#endif // #ifndef BINPACKER_H

View file

@ -1,265 +0,0 @@
#include "df_map.hpp"
#include "../base/math.hpp"
#include "../std/algorithm.hpp"
#include "../std/cmath.hpp"
#include <boost/gil/algorithm.hpp>
using boost::gil::gray8c_pixel_t;
using boost::gil::gray8_view_t;
using boost::gil::gray8_pixel_t;
using boost::gil::interleaved_view;
DFMap::DFMap(vector<uint8_t> const & data,
int32_t width, int32_t height,
uint8_t inValue, uint8_t outValue)
: m_width(width)
, m_height(height)
{
m_distances.resize(m_width * m_height);
for (int32_t i = 0; i < m_width * m_height; ++i)
m_distances[i] = 0.0f;
gray8c_view_t srcView = interleaved_view(width, height,
(gray8c_pixel_t *)&data[0],
width);
Do(srcView, inValue, outValue);
}
DFMap::~DFMap()
{
}
void DFMap::Minus(DFMap const & other)
{
for (int32_t j = 0; j < m_height; ++j)
for (int32_t i = 0; i < m_width; ++i)
{
float const srcD = m_distances[CalcIndex(i, j)];
float const dstD = other.m_distances[CalcIndex(i, j)];
SetDistance(srcD - dstD, i, j);
}
}
void DFMap::Normalize()
{
float minSignedDistance = 0;
for (int j = 0; j < m_height; ++j)
for (int i = 0; i < m_width; ++i)
minSignedDistance = min(minSignedDistance, GetDistance(i, j));
float maxValue = 0.0f;
for (int j = 0; j < m_height; ++j)
for (int i = 0; i < m_width; ++i)
{
float d = GetDistance(i, j) - minSignedDistance;
maxValue = max(d, maxValue);
SetDistance(d, i, j);
}
for (int j = 0; j < m_height; ++j)
for (int i = 0; i < m_width; ++i)
SetDistance(GetDistance(i, j) / maxValue, i, j);
}
void DFMap::GenerateImage(vector<uint8_t> & image, int32_t & w, int32_t & h)
{
w = m_width;
h = m_height;
image.resize(m_width * m_height);
gray8_view_t view = interleaved_view(m_width, m_height,
(gray8_pixel_t *)&image[0], m_width);
for (gray8_view_t::y_coord_t y = 0; y < view.height(); ++y)
{
for (gray8_view_t::x_coord_t x = 0; x < view.width(); ++x)
{
view(x, y) = my::clamp(192 * GetDistance(x, y), 0, 255);
}
}
}
void DFMap::Do(gray8c_view_t const & view, uint8_t inValue, uint8_t outValue)
{
for (gray8_view_t::y_coord_t y = 0; y < view.height(); ++y)
{
for (gray8_view_t::x_coord_t x = 0; x < view.width(); ++x)
{
if (view(x, y) == inValue)
SetDistance(sqrt(findRadialDistance(view, x, y, 256, outValue)), x, y);
}
}
}
namespace
{
class SquareGoRoundIterator
{
public:
SquareGoRoundIterator(gray8c_view_t const & view,
int32_t centerX, int32_t centerY, uint32_t r)
: m_view(view)
, m_state(ToRight)
{
m_startX = centerX - r;
m_startY = centerY - r;
m_rWidth = m_rHeight = (2 * r) + 1;
if (m_startX < 0)
{
m_rWidth += m_startX;
m_startX = 0;
}
else if (m_startX + m_rWidth >= m_view.width())
{
m_rWidth = (m_view.width() - 1) - m_startX;
}
if (m_startY < 0)
{
m_rHeight += m_startY;
m_startY = 0;
}
else if (m_startY + m_rHeight >= m_view.height())
{
m_rHeight = (m_view.height() - 1) - m_startY;
}
m_currentX = m_startX;
m_currentY = m_startY;
}
bool HasValue() const { return m_state != End; }
uint8_t GetValue() const { return m_view(m_currentX, m_currentY); }
int32_t GetXMetric() const { return m_currentX; }
int32_t GetYMetric() const { return m_currentY; }
void Next()
{
MoveForward();
if (m_currentX < 0 || m_currentX >= m_startX + m_rWidth ||
m_currentY < 0 || m_currentY >= m_startY + m_rHeight)
{
MoveBackward();
ChangeMoveType();
MoveForward();
}
}
private:
void MoveForward()
{
switch (m_state)
{
case ToRight:
m_currentX += 1;
break;
case ToLeft:
m_currentX -= 1;
break;
case ToBottom:
m_currentY += 1;
break;
case ToTop:
m_currentY -= 1;
break;
default:
break;
}
}
void MoveBackward()
{
switch (m_state)
{
case ToRight:
m_currentX -= 1;
break;
case ToLeft:
m_currentX += 1;
break;
case ToBottom:
m_currentY -= 1;
break;
case ToTop:
m_currentY += 1;
break;
default:
break;
}
}
void ChangeMoveType()
{
switch (m_state)
{
case ToRight:
m_state = ToBottom;
break;
case ToBottom:
m_state = ToLeft;
break;
case ToLeft:
m_state = ToTop;
break;
case ToTop:
m_state = End;
break;
default:
break;
}
}
private:
gray8c_view_t const & m_view;
enum
{
ToRight,
ToBottom,
ToLeft,
ToTop,
End
} m_state;
int32_t m_startX, m_startY;
int32_t m_currentX, m_currentY;
int32_t m_rWidth, m_rHeight;
};
}
float DFMap::findRadialDistance(gray8c_view_t const & view,
int32_t pointX, int32_t pointY,
int32_t maxRadius, uint8_t outValue) const
{
int32_t minDistance = maxRadius * maxRadius;
for (int32_t r = 1; (r < maxRadius) && (r * r) < minDistance; ++r)
{
SquareGoRoundIterator iter(view, pointX, pointY, r);
while (iter.HasValue())
{
uint8_t v = iter.GetValue();
if (v == outValue)
{
int32_t xDist = pointX - iter.GetXMetric();
int32_t yDist = pointY - iter.GetYMetric();
int32_t d = xDist * xDist + yDist * yDist;
minDistance = min(d, minDistance);
}
iter.Next();
}
}
return minDistance;
}
float DFMap::GetDistance(int32_t i, int32_t j) const
{
return get(m_distances, i, j);
}
void DFMap::SetDistance(float val, int32_t i, int32_t j)
{
put(m_distances, val, i, j);
}

View file

@ -1,60 +0,0 @@
#pragma once
#include "../std/vector.hpp"
#include "../std/stdint.hpp"
#include <boost/gil/typedefs.hpp>
using boost::gil::gray8c_view_t;
class DFMap
{
public:
DFMap(vector<uint8_t> const & data,
int32_t width, int32_t height,
uint8_t inValue,
uint8_t outValue);
~DFMap();
void Minus(DFMap const & other);
void Normalize();
void GenerateImage(vector<uint8_t> & image, int32_t & w, int32_t & h);
private:
void Do(gray8c_view_t const & view, uint8_t inValue, uint8_t outValue);
float findRadialDistance(gray8c_view_t const & view,
int32_t pointX, int32_t pointY,
int32_t radius, uint8_t outValue) const;
template<typename T>
T get(vector<T> const & data, int32_t i, int32_t j) const
{
int index = CalcIndex(i, j);
return data[index];
}
template <typename T>
void put(vector<T> & data, T val, int32_t i, int32_t j)
{
int index = CalcIndex(i, j);
data[index] = val;
}
template <typename T>
void put(T * data, T val, int32_t i, int32_t j)
{
int index = CalcIndex(i, j);
data[index] = val;
}
float GetDistance(int32_t i, int32_t j) const;
void SetDistance(float val, int32_t i, int32_t j);
int32_t CalcIndex(int32_t i, int32_t j) const { return j * m_width + i; }
private:
int32_t m_width;
int32_t m_height;
vector<float> m_distances;
};

View file

@ -1,485 +0,0 @@
#include "engine.hpp"
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_tt.hpp"
#include "BinPacker.hpp"
#include "df_map.hpp"
#include <QFile>
#include <QTextStream>
#include "../base/macros.hpp"
#include "../base/logging.hpp"
#include "../std/cmath.hpp"
#include "../std/vector.hpp"
#include "../std/map.hpp"
#include "../std/function.hpp"
#include "../std/bind.hpp"
#include <boost/gil/typedefs.hpp>
#include <boost/gil/algorithm.hpp>
#include "image.h"
using boost::gil::gray8c_view_t;
using boost::gil::gray8_view_t;
using boost::gil::gray8c_pixel_t;
using boost::gil::gray8_pixel_t;
using boost::gil::interleaved_view;
using boost::gil::subimage_view;
using boost::gil::copy_pixels;
typedef function<void (void)> simple_fun_t;
typedef function<void (int)> int_fun_t;
namespace
{
uint32_t const border = 4;
float const secondScale = 2.0f;
static int EtalonTextureSize = 1024;
struct ZeroPoint
{
int32_t m_x, m_y;
};
class AtlasCompositor
{
public:
AtlasCompositor(int pageCount)
{
InitMetrics(pageCount);
}
int GetWidth() const
{
return m_width;
}
int GetHeight() const
{
return m_height;
}
ZeroPoint GetZeroPoint(int pageNumber) const
{
ZeroPoint zPoint;
zPoint.m_x = pageNumber % GetWidth();
zPoint.m_y = pageNumber / GetWidth();
return zPoint;
}
private:
struct MetricTemplate
{
MetricTemplate(int pageCount, int width, int height)
: m_pageCount(pageCount)
, m_width(width)
, m_height(height)
{
}
int32_t m_pageCount;
int32_t m_width;
int32_t m_height;
};
void InitMetrics(int32_t pageCount)
{
static MetricTemplate templates[] =
{
MetricTemplate(1, 1, 1),
MetricTemplate(2, 2, 1),
MetricTemplate(3, 2, 2),
MetricTemplate(4, 2, 2),
MetricTemplate(5, 3, 2),
MetricTemplate(6, 3, 2),
MetricTemplate(7, 3, 3),
MetricTemplate(8, 3, 3),
MetricTemplate(9, 3, 3),
MetricTemplate(10, 4, 3),
MetricTemplate(11, 4, 3),
MetricTemplate(12, 4, 3),
MetricTemplate(13, 4, 4),
MetricTemplate(14, 4, 4),
MetricTemplate(15, 4, 4),
MetricTemplate(16, 4, 4),
MetricTemplate(17, 5, 4),
MetricTemplate(18, 5, 4),
MetricTemplate(19, 5, 4),
MetricTemplate(20, 5, 4),
MetricTemplate(21, 5, 5),
MetricTemplate(22, 5, 5),
MetricTemplate(23, 5, 5),
MetricTemplate(24, 5, 5),
MetricTemplate(25, 5, 5),
MetricTemplate(26, 6, 5),
MetricTemplate(27, 6, 5),
MetricTemplate(28, 6, 5),
MetricTemplate(29, 6, 5),
MetricTemplate(30, 6, 5),
MetricTemplate(31, 6, 6),
MetricTemplate(32, 6, 6),
MetricTemplate(33, 6, 6),
MetricTemplate(34, 6, 6),
MetricTemplate(35, 6, 6),
MetricTemplate(36, 6, 6)
};
for (unsigned long i = 0; i < ARRAY_SIZE(templates); ++ i)
{
if (templates[i].m_pageCount == pageCount)
{
m_width = templates[i].m_width;
m_height = templates[i].m_height;
}
}
}
private:
int32_t m_width;
int32_t m_height;
};
class MyThread : public QThread
{
public:
struct GlyphInfo
{
int32_t m_unicodePoint;
int32_t m_glyphIndex;
int32_t m_x, m_y;
int32_t m_width, m_height;
float m_xoff;
float m_yoff;
float m_advance;
vector<uint8_t> m_img;
};
MyThread(QList<FontRange> const & fonts, int fontSize,
int_fun_t const & startFn,
int_fun_t const & updateFn,
simple_fun_t const & endFn)
: m_start(startFn)
, m_update(updateFn)
, m_end(endFn)
, m_fontSize(fontSize)
{
m_summaryGlyphCount = 0;
foreach(FontRange r, fonts)
{
if (r.m_validFont)
{
m_summaryGlyphCount += ((r.m_endRange - r.m_startRange) + 1);
m_fonts[r.m_fontPath].push_back(qMakePair(r.m_startRange, r.m_endRange));
}
}
}
void run()
{
m_start(m_summaryGlyphCount);
m_summaryGlyphCount = 0;
QList<GlyphInfo> infos;
QList<GlyphInfo> emptyInfos;
for (map_iter_t font = m_fonts.begin(); font != m_fonts.end(); ++font)
{
QFile f(font.key());
if (f.open(QIODevice::ReadOnly) == false)
throw 1;
stbtt_fontinfo fontInfo;
{
vector<uint8_t> fontBuffer(f.size(), 0);
f.read((char *)&fontBuffer[0], fontBuffer.size());
stbtt_InitFont(&fontInfo, &fontBuffer[0], 0);
}
float sc = 4.0f;
float scale = stbtt_ScaleForPixelHeight(&fontInfo, /*GlyphScaler * */m_fontSize)*sc;
for (range_iter_t range = font.value().begin(); range != font.value().end(); ++range)
{
for (int unicodeCode = range->first; unicodeCode <= range->second; ++unicodeCode)
{
m_update(m_summaryGlyphCount);
m_summaryGlyphCount++;
if (isInterruptionRequested())
return;
int width = 0, height = 0, xoff = 0, yoff = 0;
int glyphCode = stbtt_FindGlyphIndex(&fontInfo, unicodeCode);
if (glyphCode == 0)
continue;
unsigned char * image = NULL;
if (!stbtt_IsGlyphEmpty(&fontInfo, glyphCode))
image = stbtt_GetGlyphBitmap(&fontInfo, scale, scale, glyphCode, &width, &height, &xoff, &yoff);
int advance = 0, leftBear = 0;
stbtt_GetGlyphHMetrics(&fontInfo, glyphCode, &advance, &leftBear);
GlyphInfo info;
info.m_unicodePoint = unicodeCode;
info.m_glyphIndex = glyphCode;
info.m_xoff = xoff / sc * secondScale;
info.m_yoff = yoff / sc * secondScale - border;
info.m_advance = advance * scale / sc * secondScale;
if (info.m_width == 0 || info.m_height == 0)
{
emptyInfos.push_back(info);
}
else
{
processGlyph(image, width, height, info.m_img, info.m_width, info.m_height, sc);
infos.push_back(info);
stbtt_FreeBitmap(image, NULL);
}
}
}
}
vector<int> rects;
rects.reserve(2 * infos.size());
foreach(GlyphInfo info, infos)
{
if (!info.m_img.empty())
{
rects.push_back(info.m_width + 1);
rects.push_back(info.m_height + 1);
}
}
rects.push_back(4);
rects.push_back(4);
vector< vector<int> > outRects;
BinPacker bp;
bp.Pack(rects, outRects, EtalonTextureSize, false);
AtlasCompositor compositor(outRects.size());
m_w = EtalonTextureSize * compositor.GetWidth();
m_h = EtalonTextureSize * compositor.GetHeight();
m_image.resize(m_w * m_h);
memset(&m_image[0], 0, m_image.size() * sizeof(uint8_t));
gray8_view_t resultView = interleaved_view(m_w, m_h,
(gray8_pixel_t *)&m_image[0],
m_w);
bool firstEmpty = true;
for (size_t k = 0; k < outRects.size(); ++k)
{
vector<int> & outValues = outRects[k];
for (size_t i = 0; i < outValues.size(); i += 4)
{
int id = outValues[i];
if (id != infos.size())
{
GlyphInfo & info = infos[id];
ZeroPoint zPoint = compositor.GetZeroPoint(k);
info.m_x = EtalonTextureSize * zPoint.m_x + outValues[i + 1];
info.m_y = EtalonTextureSize * zPoint.m_y + outValues[i + 2];
int packX = info.m_x;
int packY = info.m_y;
gray8_view_t subResultView = subimage_view(resultView,
packX + 1, packY + 1,
info.m_width, info.m_height);
gray8c_view_t symbolView = interleaved_view(info.m_width, info.m_height,
(gray8c_pixel_t *)&info.m_img[0],
info.m_width);
copy_pixels(symbolView, subResultView);
}
else
{
Q_ASSERT(firstEmpty);
for (int j = 0; j < emptyInfos.size(); ++j)
{
emptyInfos[j].m_x = outValues[i + 1];
emptyInfos[j].m_y = outValues[i + 2];
}
firstEmpty = false;
}
}
}
m_infos.clear();
m_infos.append(infos);
m_infos.append(emptyInfos);
}
vector<uint8_t> const & GetImage(int & w, int & h) const
{
w = m_w;
h = m_h;
return m_image;
}
QList<GlyphInfo> const & GetInfos() const { return m_infos; }
private:
static void processGlyph(unsigned char * glyphImage, int32_t width, int32_t height,
vector<uint8_t> & im, int32_t & newW, int32_t & newH, float sc)
{
sc /= secondScale;
image img(height, width, glyphImage);
image imgWithBorder(img.add_border(border * sc));
int32_t const sWidth = imgWithBorder.width / sc;
int32_t const sHeight = imgWithBorder.height / sc;
im.resize(sWidth * sHeight);
memset(&im[0], 0, im.size() * sizeof(uint8_t));
newW = sWidth;
newH = sHeight;
image res(imgWithBorder.generate_SDF(1.0f/sc));
res.to_uint8_t_vec(im);
}
private:
int_fun_t m_start;
int_fun_t m_update;
simple_fun_t m_end;
private:
int m_summaryGlyphCount;
typedef QPair<int, int> range_t;
typedef QList<range_t> ranges_t;
typedef ranges_t::const_iterator range_iter_t;
typedef QMap<QString, ranges_t> map_t;
typedef map_t::const_iterator map_iter_t;
map_t m_fonts;
vector<uint8_t> m_image;
int m_w, m_h;
int m_fontSize;
QList<GlyphInfo> m_infos;
};
}
Engine::Engine()
: m_workThread(NULL)
{
}
void Engine::SetFonts(QList<FontRange> const & fonts, int fontSize)
{
if (m_workThread != NULL)
{
disconnect(m_workThread, SIGNAL(finished()), this, SLOT(WorkThreadFinished()));
connect(m_workThread, SIGNAL(finished()), m_workThread, SLOT(deleteLater()));
if (!m_workThread->isFinished())
m_workThread->requestInterruption();
else
delete m_workThread;
}
m_workThread = new MyThread(fonts, fontSize,
bind(&Engine::StartEngine, this, _1),
bind(&Engine::UpdateProgress, this, _1),
bind(&Engine::EndEngine, this));
connect(m_workThread, SIGNAL(finished()), this, SLOT(WorkThreadFinished()));
m_workThread->start();
}
void Engine::SetExportPath(const QString & dirName)
{
m_dirName = dirName;
}
bool Engine::IsReadyToExport() const
{
return !m_dirName.isEmpty() && m_workThread;
}
void Engine::RunExport()
{
Q_ASSERT(IsReadyToExport() == true);
if (!m_workThread->isFinished())
m_workThread->wait();
QString dirName(m_dirName.trimmed());
QString inPathName(dirName + "/font.png");
m_tempPngFile = inPathName;
QString outPathName(dirName + "/font.png");
GetImage().save(outPathName, "png");
MyThread * thread = static_cast<MyThread *>(m_workThread);
QList<MyThread::GlyphInfo> const & infos = thread->GetInfos();
QFile file(m_dirName.trimmed() + "/font.fdf");
if (!file.open(QIODevice::WriteOnly))
throw -1;
QTextStream stream(&file);
for (int i = 0; i < infos.size(); ++i)
{
MyThread::GlyphInfo const & info = infos[i];
stream << info.m_unicodePoint << "\t"
<< info.m_x << "\t"
<< info.m_y << "\t"
<< info.m_width << "\t"
<< info.m_height << "\t"
<< info.m_xoff << "\t"
<< info.m_yoff << "\t"
<< info.m_advance << "\n";
}
}
void Engine::WorkThreadFinished()
{
QThread * t = qobject_cast<QThread *>(sender());
if (t == NULL)
return;
emit UpdatePreview(GetImage(t));
}
QImage Engine::GetImage(QThread * sender) const
{
MyThread * thread = static_cast<MyThread *>(sender == NULL ? m_workThread : sender);
int w, h;
vector<uint8_t> const & imgData = thread->GetImage(w, h);
QImage image = QImage(&imgData[0], w, h, QImage::Format_Indexed8);
image.setColorCount(256);
for (int i = 0; i < 256; ++i)
image.setColor(i, qRgb(i, i, i));
return image;
}
void Engine::EmitStartEngine(int maxValue)
{
emit StartEngine(maxValue);
}
void Engine::EmitUpdateProgress(int currentValue)
{
emit UpdateProgress(currentValue);
}
void Engine::EmitEndEngine()
{
emit EndEngine();
}
void Engine::processChanged(QProcess::ProcessState state)
{
if(state == QProcess::Starting)
{
emit ConvertStarted();
}
else if(state == QProcess::NotRunning)
{
emit ConvertEnded();
QFile::remove(m_tempPngFile);
}
}

View file

@ -1,57 +0,0 @@
#pragma once
#include <QString>
#include <QAtomicInt>
#include <QThread>
#include <QImage>
#include <QProcess>
struct FontRange
{
QString m_fontPath;
bool m_validFont;
int m_startRange;
int m_endRange;
};
class Engine : public QObject
{
Q_OBJECT
public:
Engine();
void SetFonts(QList<FontRange> const & fonts, int fontSize);
void SetExportPath(QString const & dirName);
bool IsReadyToExport() const;
Q_SIGNAL void UpdatePreview(QImage img);
Q_SIGNAL void StartEngine(int maxValue);
Q_SIGNAL void UpdateProgress(int currentValue);
Q_SIGNAL void EndEngine();
Q_SIGNAL void ConvertStarted();
Q_SIGNAL void ConvertEnded();
Q_SLOT void processChanged(QProcess::ProcessState state);
void RunExport();
private:
Q_SLOT void WorkThreadFinished();
private:
QImage GetImage(QThread * sender = NULL) const;
private:
void EmitStartEngine(int maxValue);
void EmitUpdateProgress(int currentValue);
void EmitEndEngine();
private:
QString m_dirName;
QAtomicInt m_dataGenerated;
QThread * m_workThread;
QString m_tempPngFile;
QProcess m_process;
};

View file

@ -1,33 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2014-05-29T10:57:50
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
ROOT_DIR = ..
include($$ROOT_DIR/common.pri)
INCLUDEPATH *= $$ROOT_DIR/3party/boost
TARGET = FontGenerator
TEMPLATE = app
SOURCES += main.cpp\
widget.cpp \
engine.cpp \
qlistmodel.cpp \
BinPacker.cpp \
df_map.cpp \
image.cpp
HEADERS += widget.hpp \
engine.hpp \
qlistmodel.hpp \
stb_tt.hpp \
BinPacker.hpp \
df_map.hpp \
image.h
FORMS += widget.ui

View file

@ -1,815 +0,0 @@
/*
Copyright (C) 2009 by Stefan Gustavson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "image.h"
#define DISTAA(c,xc,yc,xi,yi) (distaa3(gx, gy, w, c, xc, yc, xi, yi))
using namespace std;
void mexFunction(image & img, image & out)
{
image gx(img.height, img.width);
image gy(img.height, img.width);
short * xdist = (short*)malloc(img.width*img.height*sizeof(short));
short * ydist = (short*)malloc(img.width*img.height*sizeof(short));
img.computegradient(gx, gy);
img.edtaa3(gx, gy, img.width, img.height, xdist, ydist, out);
// Pixels with grayscale>0.5 will have a negative distance.
// This is correct, but we don't want values <0 returned here.
for (int i = 0; i < img.width * img.height; i++)
{
if (out.data[i] < 0)
out.data[i] = 0.0;
}
free(xdist);
free(ydist);
}
double edgedf(double gx, double gy, double a)
{
double df, glength, temp, a1;
if ((gx == 0) || (gy == 0))
{ // Either A) gu or gv are zero, or B) both
df = 0.5-a; // Linear approximation is A) correct or B) a fair guess
}
else
{
glength = sqrt(gx*gx + gy*gy);
if(glength>0)
{
gx = gx/glength;
gy = gy/glength;
}
/* Everything is symmetric wrt sign and transposition,
* so move to first octant (gx>=0, gy>=0, gx>=gy) to
* avoid handling all possible edge directions.
*/
gx = fabs(gx);
gy = fabs(gy);
if (gx<gy)
{
temp = gx;
gx = gy;
gy = temp;
}
a1 = 0.5*gy/gx;
if (a < a1)
{ // 0 <= a < a1
df = 0.5*(gx + gy) - sqrt(2.0*gx*gy*a);
}
else if (a < (1.0-a1))
{ // a1 <= a <= 1-a1
df = (0.5-a)*gx;
}
else
{ // 1-a1 < a <= 1
df = -0.5*(gx + gy) + sqrt(2.0*gx*gy*(1.0-a));
}
}
return df;
}
image::image(int H, int W)
{
height = H;
width = W;
data = new float[height * width];
for(int i = 0 ; i < height * width ; ++i)
{
data[i] = 0.0f;
}
}
image::image(int H, int W, float *arr)
{
height = H;
width = W;
data = new float[height * width];
for(int i = 0 ; i < height * width ; ++i)
{
data[i] = arr[i];
}
}
image::image(int H, int W, unsigned char *arr)
{
height = H;
width = W;
data = new float[height * width];
for(int i = 0 ; i < height * width ; ++i)
{
data[i] = (float)arr[i]/255.0f;
}
}
image::image(const image &copy)
{
height = copy.height;
width = copy.width;
data = new float[height * width];
for(int i = 0 ; i < height * width ; ++i)
{
data[i] = copy.data[i];
}
}
void image::to_uint8_t_vec(vector<uint8_t> &dst)
{
for(int i = 0 ; i < height * width ; ++i)
{
dst[i] = data[i]*255.0f;
}
}
image::~image()
{
if(data != NULL)
delete [] data;
}
image::image(string path)
{
}
void image::write_as_tga(string path)
{
FILE *sFile = 0;
unsigned char tgaHeader[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char header[6];
unsigned char bits = 0;
int colorMode = 0;
sFile = fopen(path.c_str(), "wb");
if(!sFile)
{
printf("Can't open ScreenShot File! Error\n");
return;
}
colorMode = 3;
bits = 24;
header[0] = width % 256;
header[1] = width / 256;
header[2] = height % 256;
header[3] = height / 256;
header[4] = bits;
header[5] = 0;
fwrite(tgaHeader, sizeof(tgaHeader), 1, sFile);
fwrite(header, sizeof(header), 1, sFile);
char *data2 = new char[width * height *3];
for(int i = 0; i < width * height; ++i)
{
data2[3*i] = data[i] * 255.0f;
data2[3*i+1] = data[i] * 255.0f;
data2[3*i+2] = data[i] * 255.0f;
}
fwrite(data2, width * height * colorMode, 1, sFile);
delete [] data2;
fclose(sFile);
}
void image::computegradient(image &grad_x, image &grad_y)
{
int i,j,k;
float glength;
const float SQRT2 = 1.4142136f;
for(i = 1; i < height-1; i++) // Avoid edges where the kernels would spill over
{
for(j = 1; j < width-1; j++)
{
k = i*width + j;
if((data[k]>0.0) && (data[k]<1.0)) // Compute gradient for edge pixels only
{
grad_x.data[k] = -(data[k-width-1] + SQRT2*data[k-1] + data[k+width-1]) +
(data[k-width+1] + SQRT2*data[k+1] + data[k+width+1]);
grad_y.data[k] = -(data[k-width-1] + SQRT2*data[k-width] + data[k+width-1]) +
(data[k-width+1] + SQRT2*data[k+width] + data[k+width+1]);
glength = grad_x.data[k]*grad_x.data[k] + grad_y.data[k]*grad_y.data[k];
if(glength > 0.0) // Avoid division by zero
{
glength = sqrt(glength);
grad_x.data[k] /= glength;
grad_y.data[k] /= glength;
}
}
}
}
}
void image::scale()
{
float maxi = -1000, mini = 1000;
for(int i = 0; i < width * height; ++i)
{
maxi = max(maxi, data[i]);
mini = min(mini, data[i]);
}
for(int i = 0; i < width * height; ++i)
{
data[i] -= mini;
}
maxi -= mini;
for(int i = 0; i < width * height; ++i)
{
data[i] /= maxi;
}
}
void image::invert()
{
for(int i = 0; i < width * height; ++i)
{
data[i] = 1.0f - data[i];
}
}
void image::minus(image &im)
{
for(int i = 0; i < width * height; ++i)
{
data[i] -= im.data[i];
}
}
void image::distquant()
{
for(int i = 0; i < width * height; ++i)
{
float k = 0.0325f;
data[i] = 0.5f + data[i]*k;
data[i] = max(0.0f, data[i]);
data[i] = min(1.0f, data[i]);
}
}
image image::add_border(int size)
{
image res(height + 2.0 * size, width + 2.0 * size);
for(int i = 0; i < size; ++i)
{
int index = i*res.width;
for(int j = 0; j < res.width ; ++j)
{
res.data[index + j] = 0;
}
}
for(int i = height + size; i < height + 2*size; ++i)
{
int index = i*res.width;
for(int j = 0; j < res.width ; ++j)
{
res.data[index + j] = 0;
}
}
for(int i = size; i < res.height - size; ++i)
{
int index = i*res.width;
int index2 = (i-size)*width;
for(int j = 0; j < size ; ++j)
{
res.data[index + j] = 0;
}
for(int j = size ; j < width+size ; ++j)
{
res.data[index + j] = data[index2 + j - size];
}
for(int j = width+size; j < width + 2*size ; ++j)
{
res.data[index + j] = 0;
}
}
return res;
}
image image::generate_SDF(float sc)
{
scale();
image inv(*this);
inv.invert();
image outside(height, width);
image inside(height, width);
mexFunction(*this, outside);
mexFunction(inv, inside);
outside.minus(inside);
outside.distquant();
outside.invert();
return outside.bilinear(sc);
}
image image::bilinear(float scale)
{
image result(height*scale, width*scale);
int x, y, index;
float A, B, C, D, gray;
int w2 = result.width;
int h2 = result.height;
float x_ratio = ((float)(width-1))/w2 ;
float y_ratio = ((float)(height-1))/h2 ;
float x_diff, y_diff;
int offset = 0 ;
for (int i=0;i<h2;i++)
{
for (int j=0;j<w2;j++)
{
x = (int)(x_ratio * j) ;
y = (int)(y_ratio * i) ;
x_diff = (x_ratio * j) - x ;
y_diff = (y_ratio * i) - y ;
index = y*width+x ;
// range is 0 to 255 thus bitwise AND with 0xff
A = data[index];
B = data[index+1];
C = data[index+width];
D = data[index+width+1];
// Y = A(1-w)(1-h) + B(w)(1-h) + C(h)(1-w) + Dwh
gray = A*(1.0f-x_diff)*(1.0f-y_diff) + B*(x_diff)*(1.0f-y_diff) +
C*(y_diff)*(1.0f-x_diff) + D*(x_diff*y_diff);
result.data[offset++] = gray;
}
}
return result ;
}
float image::distaa3(image &gximg, image &gyimg, int w, int c, int xc, int yc, int xi, int yi)
{
double di, df, dx, dy, gx, gy, a;
int closest;
closest = c-xc-yc*w; // Index to the edge pixel pointed to from c
a = data[closest]; // Grayscale value at the edge pixel
gx = gximg.data[closest]; // X gradient component at the edge pixel
gy = gyimg.data[closest]; // Y gradient component at the edge pixel
if(a > 1.0) a = 1.0;
if(a < 0.0) a = 0.0; // Clip grayscale values outside the range [0,1]
if(a == 0.0) return 1000000.0; // Not an object pixel, return "very far" ("don't know yet")
dx = (double)xi;
dy = (double)yi;
di = sqrt(dx*dx + dy*dy); // Length of integer vector, like a traditional EDT
if(di==0) { // Use local gradient only at edges
// Estimate based on local gradient only
df = edgedf(gx, gy, a);
} else {
// Estimate gradient based on direction to edge (accurate for large di)
df = edgedf(dx, dy, a);
}
return di + df; // Same metric as edtaa2, except at edges (where di=0)
}
void image::edtaa3(image &gx, image &gy, int w, int h, short *distx, short *disty, image &dist)
{
int x, y, i, c;
int offset_u, offset_ur, offset_r, offset_rd,
offset_d, offset_dl, offset_l, offset_lu;
double olddist, newdist;
int cdistx, cdisty, newdistx, newdisty;
int changed;
double epsilon = 1e-3;
/* Initialize index offsets for the current image width */
offset_u = -w;
offset_ur = -w+1;
offset_r = 1;
offset_rd = w+1;
offset_d = w;
offset_dl = w-1;
offset_l = -1;
offset_lu = -w-1;
/* Initialize the distance images */
for(i=0; i<w*h; i++) {
distx[i] = 0; // At first, all pixels point to
disty[i] = 0; // themselves as the closest known.
if(data[i] <= 0.0)
{
dist.data[i]= 1000000.0; // Big value, means "not set yet"
}
else if (data[i]<1.0) {
dist.data[i] = edgedf(gx.data[i], gy.data[i], data[i]); // Gradient-assisted estimate
}
else {
dist.data[i]= 0.0; // Inside the object
}
}
/* Perform the transformation */
do
{
changed = 0;
/* Scan rows, except first row */
for(y=1; y<h; y++)
{
/* move index to leftmost pixel of current row */
i = y*w;
/* scan right, propagate distances from above & left */
/* Leftmost pixel is special, has no left neighbors */
olddist = dist.data[i];
if(olddist > 0) // If non-zero distance or not set yet
{
c = i + offset_u; // Index of candidate for testing
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_ur;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
i++;
/* Middle pixels have all neighbors */
for(x=1; x<w-1; x++, i++)
{
olddist = dist.data[i];
if(olddist <= 0) continue; // No need to update further
c = i+offset_l;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_lu;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_u;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_ur;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
/* Rightmost pixel of row is special, has no right neighbors */
olddist = dist.data[i];
if(olddist > 0) // If not already zero distance
{
c = i+offset_l;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_lu;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_u;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx;
newdisty = cdisty+1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
/* Move index to second rightmost pixel of current row. */
/* Rightmost pixel is skipped, it has no right neighbor. */
i = y*w + w-2;
/* scan left, propagate distance from right */
for(x=w-2; x>=0; x--, i--)
{
olddist = dist.data[i];
if(olddist <= 0) continue; // Already zero distance
c = i+offset_r;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
}
/* Scan rows in reverse order, except last row */
for(y=h-2; y>=0; y--)
{
/* move index to rightmost pixel of current row */
i = y*w + w-1;
/* Scan left, propagate distances from below & right */
/* Rightmost pixel is special, has no right neighbors */
olddist = dist.data[i];
if(olddist > 0) // If not already zero distance
{
c = i+offset_d;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_dl;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
i--;
/* Middle pixels have all neighbors */
for(x=w-2; x>0; x--, i--)
{
olddist = dist.data[i];
if(olddist <= 0) continue; // Already zero distance
c = i+offset_r;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_rd;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_d;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_dl;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
/* Leftmost pixel is special, has no left neighbors */
olddist = dist.data[i];
if(olddist > 0) // If not already zero distance
{
c = i+offset_r;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_rd;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx-1;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
olddist=newdist;
changed = 1;
}
c = i+offset_d;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx;
newdisty = cdisty-1;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
/* Move index to second leftmost pixel of current row. */
/* Leftmost pixel is skipped, it has no left neighbor. */
i = y*w + 1;
for(x=1; x<w; x++, i++)
{
/* scan right, propagate distance from left */
olddist = dist.data[i];
if(olddist <= 0) continue; // Already zero distance
c = i+offset_l;
cdistx = distx[c];
cdisty = disty[c];
newdistx = cdistx+1;
newdisty = cdisty;
newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
if(newdist < olddist-epsilon)
{
distx[i]=newdistx;
disty[i]=newdisty;
dist.data[i]=newdist;
changed = 1;
}
}
}
}
while(changed); // Sweep until no more updates are made
/* The transformation is completed. */
}

View file

@ -1,84 +0,0 @@
#pragma once
// +----------------------------------------+
// | |
// | http://contourtextures.wikidot.com |
// | |
// +----------------------------------------+
/*
Copyright (C) 2009 by Stefan Gustavson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
namespace
{
struct TGAHeader
{
char Header[12];
};
struct TGA
{
char header[6];
unsigned int bytesPerPixel;
unsigned int imageSize;
unsigned int temp;
unsigned int type;
unsigned int Height;
unsigned int Width;
unsigned int Bpp;
};
}
class image
{
public:
int height;
int width;
float *data;
public:
image(int H, int W);
image(int H, int W, float *arr);
image(int H, int W, unsigned char *arr);
image(){}
image(image const & copy);
image(std::string path);
~image();
void write_as_tga(std::string path);
void computegradient(image &grad_x, image &grad_y);
void scale();
void invert();
float distaa3(image &gximg, image &gyimg, int w, int c, int xc, int yc, int xi, int yi);
void edtaa3(image &gx, image &gy, int w, int h, short *distx, short *disty, image &dist);
void minus(image &im);
void distquant();
void to_uint8_t_vec(std::vector<uint8_t> &dst);
image bilinear(float scale);
image generate_SDF(float sc);
image add_border(int size);
};

View file

@ -1,11 +0,0 @@
#include "widget.hpp"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

View file

@ -1,43 +0,0 @@
#include "qlistmodel.hpp"
#include <QBrush>
QListModel::QListModel(QObject * parent, const QList<FontRange> & ranges)
: base_t(parent)
, m_ranges(ranges)
{
}
int QListModel::rowCount(const QModelIndex & /*parent*/) const
{
return m_ranges.size();
}
int QListModel::columnCount(const QModelIndex & /*parent*/) const
{
return 1;
}
QVariant QListModel::data(const QModelIndex & index, int role) const
{
if (role == Qt::DisplayRole)
{
QString fontName = m_ranges[index.row()].m_fontPath;
int pos = fontName.lastIndexOf("/");
if (pos != -1)
fontName = fontName.right(fontName.size() - pos - 1);
return QString("%1 : 0x%2 - 0x%3").arg(fontName)
.arg(m_ranges[index.row()].m_startRange, 0, 16)
.arg(m_ranges[index.row()].m_endRange, 0, 16);
}
else if (role == Qt::BackgroundRole)
return m_ranges[index.row()].m_validFont ? QBrush(Qt::white) : QBrush(Qt::red);
return QVariant();
}
bool QListModel::setData(QModelIndex const & /*index*/, QVariant const & /*value*/, int /*role*/)
{
return false;
}

View file

@ -1,20 +0,0 @@
#pragma once
#include "engine.hpp"
#include <QAbstractListModel>
class QListModel : public QAbstractListModel
{
typedef QAbstractListModel base_t;
public:
QListModel(QObject * parent, QList<FontRange> const & ranges);
int rowCount(const QModelIndex & parent) const;
int columnCount(const QModelIndex & parent) const;
QVariant data(const QModelIndex & index, int role) const;
bool setData(const QModelIndex & index, const QVariant & value, int role);
private:
QList<FontRange> m_ranges;
};

File diff suppressed because it is too large Load diff

View file

@ -1,180 +0,0 @@
#include "widget.hpp"
#include "ui_widget.h"
#include "qlistmodel.hpp"
#include <QSettings>
#include <QTextStream>
#include <QFile>
#include <QList>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->selectunicodeblocks, SIGNAL(clicked()), this, SLOT(LoadUnicodeBlocks()));
connect(ui->selectExportPath, SIGNAL(clicked()), this, SLOT(ResolveExportPath()));
connect(ui->runExport, SIGNAL(clicked()), this, SLOT(Export()));
connect(ui->fontsize, SIGNAL(valueChanged(int)), this, SLOT(UpdateEngine(int)));
connect(&m_engine, SIGNAL(UpdatePreview(QImage)), this, SLOT(UpdatePreview(QImage)));
connect(&m_engine, SIGNAL(StartEngine(int)), this, SLOT(StartEngine(int)), Qt::QueuedConnection);
connect(&m_engine, SIGNAL(UpdateProgress(int)), this, SLOT(UpdateProgress(int)), Qt::QueuedConnection);
connect(&m_engine, SIGNAL(EndEngine()), this, SLOT(EndEngine()), Qt::QueuedConnection);
connect(&m_engine, SIGNAL(ConvertStarted()), this, SLOT(ConvertStart()));
connect(&m_engine, SIGNAL(ConvertEnded()), this, SLOT(ConvertEnd()));
ui->progressBar->reset();
QSettings s("settings.ini", QSettings::IniFormat, this);
QString v = s.value("unicode_file", "").toString();
if (!v.isEmpty())
if (LoadUnicodeBlocksImpl(v) == false)
s.setValue("unicode_file", "");
QString exportPath = s.value("export_path", "").toString();
if (!exportPath.isEmpty())
{
ResolveExportPath(exportPath);
}
QPixmap p(350, 350);
p.fill(Qt::black);
ui->preview->setPixmap(p);
UpdateButton();
}
Widget::~Widget()
{
QSettings s("settings.ini", QSettings::IniFormat, this);
if (!ui->unicodesblocks->text().isEmpty())
s.setValue("unicode_file", ui->unicodesblocks->text());
if (!ui->exportpath->text().isEmpty())
s.setValue("export_path", ui->exportpath->text());
delete ui;
}
void Widget::LoadUnicodeBlocks()
{
QString filePath = QFileDialog::getOpenFileName(this, "Open Unicode blocks discription", QString(), "Text (*.txt)");
LoadUnicodeBlocksImpl(filePath);
}
void Widget::ResolveExportPath()
{
QString dir = QFileDialog::getExistingDirectory(this, "ExportPath");
ResolveExportPath(dir);
}
void Widget::ResolveExportPath(const QString & filePath)
{
ui->exportpath->setText(filePath);
m_engine.SetExportPath(filePath);
UpdateButton();
}
void Widget::Export()
{
m_engine.RunExport();
}
void Widget::UpdatePreview(QImage img)
{
ui->preview->setPixmap(QPixmap::fromImage(img.scaled(ui->preview->size(), Qt::KeepAspectRatio)));
}
void Widget::UpdateEngine(int)
{
m_engine.SetFonts(m_ranges, ui->fontsize->value());
}
void Widget::StartEngine(int maxValue)
{
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(maxValue);
ui->progressBar->setValue(0);
}
void Widget::UpdateProgress(int value)
{
ui->progressBar->setValue(value);
}
void Widget::EndEngine()
{
ui->progressBar->reset();
}
void Widget::ConvertStart()
{
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(0);
ui->progressBar->setValue(0);
ui->runExport->setEnabled(false);
ui->selectExportPath->setEnabled(false);
ui->selectunicodeblocks->setEnabled(false);
}
void Widget::ConvertEnd()
{
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(100);
ui->progressBar->setValue(0);
ui->runExport->setEnabled(true);
ui->selectExportPath->setEnabled(true);
ui->selectunicodeblocks->setEnabled(true);
}
void Widget::UpdateButton()
{
ui->runExport->setEnabled(m_engine.IsReadyToExport());
}
bool Widget::LoadUnicodeBlocksImpl(const QString & filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, "Could't open file", "Unicode blocks description not valid");
return false;
}
ui->unicodesblocks->setText(filePath);
QList<FontRange> ranges;
QString dirPath = filePath.left(filePath.lastIndexOf("/") + 1);
QTextStream stream(&file);
stream.setIntegerBase(16);
while (!stream.atEnd())
{
FontRange range;
stream >> range.m_fontPath;
if (range.m_fontPath.isEmpty())
continue;
stream >> range.m_startRange >> range.m_endRange;
range.m_validFont = QFile::exists(range.m_fontPath);
if (range.m_validFont == false)
{
QString fontPath = dirPath + range.m_fontPath;
range.m_validFont = QFile::exists(fontPath);
range.m_fontPath = fontPath;
}
ranges.push_back(range);
}
m_ranges = ranges;
UpdateEngine(0);
QListModel * model = new QListModel(ui->fontsView, ranges);
ui->fontsView->setModel(model);
UpdateButton();
return true;
}

View file

@ -1,43 +0,0 @@
#pragma once
#include "engine.hpp"
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Q_SLOT void LoadUnicodeBlocks();
Q_SLOT void ResolveExportPath();
Q_SLOT void ResolveExportPath(const QString &filePath);
Q_SLOT void Export();
Q_SLOT void UpdatePreview(QImage);
Q_SLOT void UpdateEngine(int);
Q_SLOT void StartEngine(int maxValue);
Q_SLOT void UpdateProgress(int value);
Q_SLOT void EndEngine();
Q_SLOT void ConvertStart();
Q_SLOT void ConvertEnd();
private:
void UpdateButton();
bool LoadUnicodeBlocksImpl(QString const & filePath);
private:
Engine m_engine;
QList<FontRange> m_ranges;
private:
Ui::Widget * ui;
};

View file

@ -1,210 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>661</width>
<height>471</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Unicode blocks</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="unicodesblocks">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="selectunicodeblocks">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Fonts</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="fontsView"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Font Size </string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="fontsize">
<property name="maximum">
<number>30</number>
</property>
<property name="value">
<number>20</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="preview">
<property name="minimumSize">
<size>
<width>350</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>350</width>
<height>350</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Export path</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="exportpath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="selectExportPath">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="runExport">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>