forked from organicmaps/organicmaps
[drape] remove offline glyph generator. Glyphs will be generate online on device
This commit is contained in:
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 |
|
@ -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();
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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
|
|
@ -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 ©)
|
||||
{
|
||||
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. */
|
||||
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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>
|
Loading…
Add table
Reference in a new issue