mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-12 05:36:59 -04:00
Added image to symbol conversion
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@203 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -99,6 +99,7 @@ magicseteditor_SOURCES += ./src/data/format/mws.cpp
|
|||||||
magicseteditor_SOURCES += ./src/data/format/mse1.cpp
|
magicseteditor_SOURCES += ./src/data/format/mse1.cpp
|
||||||
magicseteditor_SOURCES += ./src/data/format/mse2.cpp
|
magicseteditor_SOURCES += ./src/data/format/mse2.cpp
|
||||||
magicseteditor_SOURCES += ./src/data/format/clipboard.cpp
|
magicseteditor_SOURCES += ./src/data/format/clipboard.cpp
|
||||||
|
magicseteditor_SOURCES += ./src/data/format/image_to_symbol.cpp
|
||||||
magicseteditor_SOURCES += ./src/data/statistics.cpp
|
magicseteditor_SOURCES += ./src/data/statistics.cpp
|
||||||
magicseteditor_SOURCES += ./src/data/settings.cpp
|
magicseteditor_SOURCES += ./src/data/settings.cpp
|
||||||
magicseteditor_SOURCES += ./src/data/keyword.cpp
|
magicseteditor_SOURCES += ./src/data/keyword.cpp
|
||||||
|
|||||||
@@ -0,0 +1,450 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <data/format/image_to_symbol.hpp>
|
||||||
|
#include <gfx/bezier.hpp>
|
||||||
|
#include <util/error.hpp>
|
||||||
|
|
||||||
|
DECLARE_TYPEOF_COLLECTION(ControlPointP);
|
||||||
|
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Image preprocessing
|
||||||
|
|
||||||
|
enum ImageMarker
|
||||||
|
{ EMPTY = 0 // This cell is empty
|
||||||
|
, FULL = 1 // This cell is full
|
||||||
|
, MARKED = 2 // This cell is full, but it has been used as a starting point for finding symbols
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Convert an image to greyscale
|
||||||
|
/** The image becomes a single channel image, just an array of bytes.
|
||||||
|
* This means only the first 1/3 of the image data is used, and the image
|
||||||
|
* is no longer an actual image.
|
||||||
|
*/
|
||||||
|
void greyscale(Image& img) {
|
||||||
|
UInt size = img.GetWidth() * img.GetHeight();
|
||||||
|
Byte* data = img.GetData();
|
||||||
|
Byte* out = data;
|
||||||
|
for (UInt i = 0 ; i < size ; ++i) {
|
||||||
|
*out++ = (data[0] + data[1] + data[2]) / 3;
|
||||||
|
data += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thresholds an image, giving a black & white result
|
||||||
|
/** The threshold is determined automatically
|
||||||
|
* The output is stored in the data array, EMPTY for black, FULL for white
|
||||||
|
* If invert is used, use EMPTY for white and FULL for black
|
||||||
|
*/
|
||||||
|
void threshold(Byte* data, size_t size, bool invert = true) {
|
||||||
|
// make histogram of data
|
||||||
|
size_t hist[256];
|
||||||
|
fill_n(hist,256,0);
|
||||||
|
for (size_t i = 0 ; i < size ; ++i) {
|
||||||
|
hist[data[i]]++;
|
||||||
|
}
|
||||||
|
// find threshold
|
||||||
|
size_t threshold_pos = size / 2;
|
||||||
|
int threshold = 255;
|
||||||
|
size_t below = 0;
|
||||||
|
for (int i = 0 ; i < 255 ; ++i) {
|
||||||
|
if (below + hist[i]/2 > threshold_pos) {
|
||||||
|
threshold = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
below += hist[i];
|
||||||
|
if (below >= threshold_pos) {
|
||||||
|
threshold = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// threshold data
|
||||||
|
for (size_t i = 0 ; i < size ; ++i) {
|
||||||
|
data[i] = (data[i] >= threshold) != invert ? FULL : EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Image to symbol
|
||||||
|
|
||||||
|
bool is_mse1_symbol(const Image& img) {
|
||||||
|
// mse1 symbols are 60x80
|
||||||
|
if (img.GetWidth() != 60 || img.GetHeight() != 80) return false;
|
||||||
|
// the right side is black & white
|
||||||
|
int delta = 0;
|
||||||
|
for (int y = 0 ; y < 80 ; ++y) {
|
||||||
|
Byte* d = img.GetData() + 3 * (y * 60 + 20);
|
||||||
|
for (int x = 20 ; x < 60 ; ++x) {
|
||||||
|
int r = *d++;
|
||||||
|
int g = *d++;
|
||||||
|
int b = *d++;
|
||||||
|
delta += abs(r - b) + abs(r - g) + abs(b - g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (delta > 5000) return false; // not black & white enough
|
||||||
|
// TODO : more checks
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageData {
|
||||||
|
int width, height;
|
||||||
|
Byte* data;
|
||||||
|
mutable Byte dummy;
|
||||||
|
inline Byte& operator () (int x, int y) const {
|
||||||
|
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||||
|
return (dummy = EMPTY); // outside, return empty
|
||||||
|
} else {
|
||||||
|
return data[x + y*width];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool find_symbol_part_start(const ImageData& data, int& x_out, int& y_out) {
|
||||||
|
for (int x = 0 ; x < data.width ; ++x) {
|
||||||
|
for (int y = 0 ; y < data.height ; ++y) {
|
||||||
|
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
|
||||||
|
// the point above must be clear, we don't want to start in the 'ground'
|
||||||
|
// also, we don't want to find things we found before
|
||||||
|
x_out = x;
|
||||||
|
y_out = y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolPartP read_symbol_part(const ImageData& data) {
|
||||||
|
// find start point
|
||||||
|
int xs, ys;
|
||||||
|
if (!find_symbol_part_start(data, xs, ys)) return SymbolPartP();
|
||||||
|
data(xs, ys) |= MARKED;
|
||||||
|
|
||||||
|
SymbolPartP part(new SymbolPart);
|
||||||
|
|
||||||
|
// walk around, clockwise
|
||||||
|
xs += 1; // start right of the found point, otherwise last_move might think we came from above
|
||||||
|
int x = xs, y = ys;
|
||||||
|
int old_x = x, old_y = y;
|
||||||
|
int last_move = 1; // 1 = right or down, (as in x|y += 1)
|
||||||
|
do {
|
||||||
|
// the cursor (x,y) is between four pounts:
|
||||||
|
// a b
|
||||||
|
// .
|
||||||
|
// c d
|
||||||
|
bool a = data(x-1, y-1) & FULL;
|
||||||
|
bool b = data(x, y-1) & FULL;
|
||||||
|
bool c = data(x-1, y ) & FULL;
|
||||||
|
bool d = data(x, y ) & FULL;
|
||||||
|
UInt pack = (a << 12) + (b << 8) + (c << 4) + d; // 0xabcd
|
||||||
|
switch (pack) {
|
||||||
|
case 0x0001 : x += 1; break;
|
||||||
|
case 0x0010 : y += 1; break;
|
||||||
|
case 0x0011 : x += 1; break;
|
||||||
|
case 0x0100 : y -= 1; break;
|
||||||
|
case 0x0101 : y -= 1; break;
|
||||||
|
case 0x0110 : y -= last_move; break; // diagonal, we can come here from two sides, from left and right
|
||||||
|
case 0x0111 : y -= 1; break; // last_move indicates which of {b,c} we are 'attached' to
|
||||||
|
case 0x1000 : x -= 1; break;
|
||||||
|
case 0x1001 : x += last_move; break;
|
||||||
|
case 0x1010 : y += 1; break;
|
||||||
|
case 0x1011 : x += 1; break;
|
||||||
|
case 0x1100 : x -= 1; break;
|
||||||
|
case 0x1101 : x -= 1; break;
|
||||||
|
case 0x1110 : y += 1; break;
|
||||||
|
default:
|
||||||
|
throw InternalError(_("in the ground/air"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to part and place a mark
|
||||||
|
part->points.push_back(new_shared2<ControlPoint>(
|
||||||
|
double(x) / data.width,
|
||||||
|
double(y) / data.height
|
||||||
|
));
|
||||||
|
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the part
|
||||||
|
last_move = (x + y) - (old_x + old_y);
|
||||||
|
old_x = x;
|
||||||
|
old_y = y;
|
||||||
|
} while (x != xs || y != ys); // we will end up in the start point
|
||||||
|
|
||||||
|
// are we on the inside or the outside?
|
||||||
|
if (data(x-2,y-1) & FULL) {
|
||||||
|
part->combine = PART_SUBTRACT;
|
||||||
|
} else {
|
||||||
|
part->combine = PART_MERGE;
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SymbolP image_to_symbol(Image& img) {
|
||||||
|
int w = img.GetWidth(), h = img.GetHeight();
|
||||||
|
// 1. threshold the image
|
||||||
|
greyscale(img);
|
||||||
|
threshold(img.GetData(), w*h);
|
||||||
|
// 2. read as many symbol parts as we can
|
||||||
|
ImageData data = {w,h,img.GetData()};
|
||||||
|
SymbolP symbol(new Symbol);
|
||||||
|
while (true) {
|
||||||
|
SymbolPartP part = read_symbol_part(data);
|
||||||
|
if (!part) break;
|
||||||
|
symbol->parts.push_back(part);
|
||||||
|
}
|
||||||
|
reverse(symbol->parts.begin(), symbol->parts.end());
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolP import_symbol(Image& img) {
|
||||||
|
SymbolP symbol;
|
||||||
|
if (is_mse1_symbol(img)) {
|
||||||
|
Image img2 = img.GetSubImage(wxRect(20,0,40,40));
|
||||||
|
symbol = image_to_symbol(img2);
|
||||||
|
} else {
|
||||||
|
symbol = image_to_symbol(img);
|
||||||
|
}
|
||||||
|
simplify_symbol(*symbol);
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Simplify symbol
|
||||||
|
|
||||||
|
/// Finds corners, marks corners as LOCK_FREE, non-corners as LOCK_DIR
|
||||||
|
/** A corner is a point that has an angle between tangent greater then a treshold
|
||||||
|
*/
|
||||||
|
void mark_corners(SymbolPart& part) {
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
ControlPoint& current = *part.getPoint(i);
|
||||||
|
Vector2D before = .6 * part.getPoint(i-1)->pos + .2 * part.getPoint(i-2)->pos + .1 * part.getPoint(i-3)->pos + .1 * part.getPoint(i-4)->pos;
|
||||||
|
Vector2D after = .6 * part.getPoint(i+1)->pos + .2 * part.getPoint(i+2)->pos + .1 * part.getPoint(i+3)->pos + .1 * part.getPoint(i+4)->pos;
|
||||||
|
before = (before - current.pos).normalized();
|
||||||
|
after = (after - current.pos).normalized();
|
||||||
|
if (before.dot(after) >= -0.25f) {
|
||||||
|
// corner
|
||||||
|
current.lock = LOCK_FREE;
|
||||||
|
} else {
|
||||||
|
current.lock = LOCK_DIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge adjacent corners
|
||||||
|
/** Triangles will result in adjecent corners:
|
||||||
|
* XX
|
||||||
|
* XXXX _ corner 1;
|
||||||
|
* XXXXXX _ corner 2;
|
||||||
|
* XXXX
|
||||||
|
* XX
|
||||||
|
*
|
||||||
|
* Not all adjectent corners should be merged, for example
|
||||||
|
* X _ 1
|
||||||
|
* XXXXXXXXXXXXX _ 2
|
||||||
|
* should be kept
|
||||||
|
*
|
||||||
|
* The solution is to look at the tangent lines.
|
||||||
|
* Where these two lines (one for each corner) intersect,
|
||||||
|
* is the merged corner. If it is too far away, don't merge
|
||||||
|
*/
|
||||||
|
void merge_corners(SymbolPart& part) {
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
ControlPoint& cur = *part.getPoint(i);
|
||||||
|
ControlPoint& prev = *part.getPoint(i - 1);
|
||||||
|
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
|
||||||
|
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
|
||||||
|
// and take the one that has the largest angle with ab, i.e. the smallest dot,
|
||||||
|
// where ab is the line between the two corners
|
||||||
|
Vector2D ab = cur.pos - prev.pos;
|
||||||
|
double min_a_dot = 1e100, min_b_dot = 1e100;
|
||||||
|
Vector2D a, b;
|
||||||
|
for (int j = 0 ; j < 4 ; ++j) {
|
||||||
|
Vector2D a_ = (part.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||||
|
Vector2D b_ = (part.getPoint(i+j)->pos - cur.pos).normalized();
|
||||||
|
double a_dot = a_.dot(ab);
|
||||||
|
double b_dot = -b_.dot(ab);
|
||||||
|
if (a_dot < min_a_dot) {
|
||||||
|
min_a_dot = a_dot;
|
||||||
|
a = a_;
|
||||||
|
}
|
||||||
|
if (b_dot < min_b_dot) {
|
||||||
|
min_b_dot = b_dot;
|
||||||
|
b = b_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// step 2. find intersection point, to solve:
|
||||||
|
// t a + ab = u b, solve for t,u
|
||||||
|
// Gives us:
|
||||||
|
// t = ab cross b / b cross a
|
||||||
|
double tden = max(0.00000001, b.cross(a));
|
||||||
|
double t = ab.cross(b) / tden;
|
||||||
|
// do these tangent lines intersect, and not too far away?
|
||||||
|
// if so, then the intersection point is the merged point
|
||||||
|
if (t >= 0 && t < 20.0) {
|
||||||
|
prev.pos += a * -t;
|
||||||
|
part.points.erase(part.points.begin() + i);
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Avarage/'blur' a symbol part
|
||||||
|
void avarage(SymbolPart& part) {
|
||||||
|
// create a copy of the points
|
||||||
|
vector<Vector2D> old_points;
|
||||||
|
FOR_EACH(p, part.points) {
|
||||||
|
old_points.push_back(p->pos);
|
||||||
|
}
|
||||||
|
// avarage points
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
ControlPoint& p = *part.getPoint(i);
|
||||||
|
if (p.lock == LOCK_DIR) {
|
||||||
|
p.pos = .25 * old_points[mod(i-1, old_points.size())]
|
||||||
|
+ .50 * p.pos
|
||||||
|
+ .25 * old_points[mod(i+1, old_points.size())];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a symbol part to curves
|
||||||
|
void convert_to_curves(SymbolPart& part) {
|
||||||
|
// mark all segments as curves
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
ControlPoint& cur = *part.getPoint(i);
|
||||||
|
ControlPoint& next = *part.getPoint(i + 1);
|
||||||
|
cur.segment_after = SEGMENT_CURVE;
|
||||||
|
cur.segment_before = SEGMENT_CURVE;
|
||||||
|
cur.delta_after = (next.pos - cur.pos) / 3.0;
|
||||||
|
next.delta_before = (cur.pos - next.pos) / 3.0;
|
||||||
|
}
|
||||||
|
// make the curves smooth by enforcing direction constraints
|
||||||
|
FOR_EACH(p, part.points) {
|
||||||
|
p->onUpdateLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert almost straight curves in a symbol part to lines
|
||||||
|
void straighten(SymbolPart& part) {
|
||||||
|
const double treshold = 0.2;
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
ControlPoint& cur = *part.getPoint(i);
|
||||||
|
ControlPoint& next = *part.getPoint(i + 1);
|
||||||
|
Vector2D ab = (next.pos - cur.pos).normalized();
|
||||||
|
Vector2D aa = cur.delta_after.normalized();
|
||||||
|
Vector2D bb = next.delta_before.normalized();
|
||||||
|
// if the area beneath the polygon formed by the handles is small
|
||||||
|
// then it is a straight line
|
||||||
|
double cpDot = abs(aa.cross(ab)) + abs(bb.cross(ab));
|
||||||
|
if (cpDot < treshold) {
|
||||||
|
cur.segment_after = next.segment_before = SEGMENT_LINE;
|
||||||
|
cur.delta_after = next.delta_before = Vector2D();
|
||||||
|
cur.lock = next.lock = LOCK_FREE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove unneeded points between straight lines
|
||||||
|
void merge_lines(SymbolPart& part) {
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
Vector2D a = part.getPoint(i-1)->pos, b = part.getPoint(i)->pos, c = part.getPoint(i+1)->pos;
|
||||||
|
Vector2D ab = (a-b).normalized();
|
||||||
|
Vector2D bc = (b-c).normalized();
|
||||||
|
double angle_len = fabs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
|
||||||
|
bool keep = angle_len >= .0001;
|
||||||
|
if (!keep) {
|
||||||
|
part.points.erase(part.points.begin() + i);
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double cost_of_point_removal(SymbolPart& part, int i);
|
||||||
|
void remove_point(SymbolPart& part, int i);
|
||||||
|
|
||||||
|
/// Simplify a symbol part by removing points
|
||||||
|
/** Always remove the point with the lowest cost,
|
||||||
|
* stop when the cost becomes too high
|
||||||
|
*/
|
||||||
|
void remove_points(SymbolPart& part) {
|
||||||
|
const double treshold = 0.002; // maximum cost
|
||||||
|
while (true) {
|
||||||
|
// Find the point with the lowest cost of removal
|
||||||
|
int best = -1;
|
||||||
|
double best_cost = 1e100;
|
||||||
|
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||||
|
double cost = cost_of_point_removal(part, i);
|
||||||
|
if (cost < best_cost) {
|
||||||
|
best_cost = cost;
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_cost > treshold) break;
|
||||||
|
// ... and remove it
|
||||||
|
remove_point(part, best);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Cost of removing point i from a symbol part
|
||||||
|
double cost_of_point_removal(SymbolPart& part, int i) {
|
||||||
|
ControlPoint& cur = *part.getPoint(i);
|
||||||
|
ControlPoint& prev = *part.getPoint(i-1);
|
||||||
|
ControlPoint& next = *part.getPoint(i+1);
|
||||||
|
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
|
||||||
|
|
||||||
|
Vector2D before = cur.delta_before;
|
||||||
|
Vector2D after = cur.delta_after;
|
||||||
|
Vector2D ac = prev.pos - next.pos;
|
||||||
|
// Based on SinglePointRemoveAction
|
||||||
|
double bl = before.length() + 0.00001; // prevent division by 0
|
||||||
|
double al = after.length() + 0.00001;
|
||||||
|
double totl = bl + al;
|
||||||
|
// set new handle sizes
|
||||||
|
Vector2D after0 = prev.delta_after * totl / bl;
|
||||||
|
Vector2D before2 = next.delta_before * totl / al;
|
||||||
|
// determine closest point on the merged curve
|
||||||
|
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
|
||||||
|
double t = bl/totl;
|
||||||
|
Vector2D np = cur.pos - c.pointAt(t);
|
||||||
|
// cost is distance to new point * length of line ~= area added/removed from part
|
||||||
|
return np.length() * ac.length();
|
||||||
|
}
|
||||||
|
/// Remove a point from a bezier curve
|
||||||
|
/** See SinglePointRemoveAction for algorithm */
|
||||||
|
void remove_point(SymbolPart& part, int i) {
|
||||||
|
ControlPoint& cur = *part.getPoint(i);
|
||||||
|
ControlPoint& prev = *part.getPoint(i-1);
|
||||||
|
ControlPoint& next = *part.getPoint(i+1);
|
||||||
|
Vector2D before = cur.delta_before;
|
||||||
|
Vector2D after = cur.delta_after;
|
||||||
|
// Based on SinglePointRemoveAction
|
||||||
|
double bl = before.length() + 0.00001; // prevent division by 0
|
||||||
|
double al = after.length() + 0.00001;
|
||||||
|
double totl = bl + al;
|
||||||
|
// set new handle sizes
|
||||||
|
prev.delta_after *= totl / bl;
|
||||||
|
next.delta_before *= totl / al;
|
||||||
|
// remove
|
||||||
|
part.points.erase(part.points.begin() + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void simplify_symbol_part(SymbolPart& part) {
|
||||||
|
mark_corners(part);
|
||||||
|
merge_corners(part);
|
||||||
|
for (int i = 0 ; i < 3 ; ++i) {
|
||||||
|
avarage(part);
|
||||||
|
}
|
||||||
|
convert_to_curves(part);
|
||||||
|
remove_points(part);
|
||||||
|
straighten(part);
|
||||||
|
merge_lines(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
void simplify_symbol(Symbol& symbol) {
|
||||||
|
FOR_EACH(p, symbol.parts) {
|
||||||
|
simplify_symbol_part(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
#ifndef HEADER_DATA_FORMAT_IMAGE_TO_SYMBOL
|
||||||
|
#define HEADER_DATA_FORMAT_IMAGE_TO_SYMBOL
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <util/prec.hpp>
|
||||||
|
#include <data/symbol.hpp>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Image to symbol
|
||||||
|
|
||||||
|
/// Import an image as a symbol.
|
||||||
|
/** Handles MSE1 symbols by cutting out the symbol rectangle */
|
||||||
|
SymbolP import_symbol(Image& img);
|
||||||
|
|
||||||
|
/// Does the image represent a MSE1 symbol file?
|
||||||
|
/** Does some heuristic checks */
|
||||||
|
bool is_mse1_symbol(const Image& img);
|
||||||
|
|
||||||
|
/// Convert an image to a symbol, destroys the image in the process
|
||||||
|
SymbolP image_to_symbol(Image& img);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Simplify symbol
|
||||||
|
|
||||||
|
/// Simplify a symbol
|
||||||
|
void simplify_symbol(Symbol&);
|
||||||
|
|
||||||
|
/// Simplify a symbol parts, i.e. use bezier curves instead of lots of lines
|
||||||
|
void simplify_symbol_part(SymbolPart&);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
|
#endif
|
||||||
+7
-1
@@ -114,6 +114,12 @@ enum SymbolPartCombine
|
|||||||
, PART_BORDER
|
, PART_BORDER
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A sane mod function, always returns a result in the range [0..size)
|
||||||
|
inline size_t mod(int a, size_t size) {
|
||||||
|
int m = a % (int)size;
|
||||||
|
return m >= 0 ? m : m + size;
|
||||||
|
}
|
||||||
|
|
||||||
/// A single part (polygon/bezier-gon) in a Symbol
|
/// A single part (polygon/bezier-gon) in a Symbol
|
||||||
class SymbolPart {
|
class SymbolPart {
|
||||||
public:
|
public:
|
||||||
@@ -136,7 +142,7 @@ class SymbolPart {
|
|||||||
|
|
||||||
/// Get a control point, wraps around
|
/// Get a control point, wraps around
|
||||||
inline ControlPointP getPoint(int id) const {
|
inline ControlPointP getPoint(int id) const {
|
||||||
return points[id >= 0 ? id % points.size() : id + points.size()];
|
return points[mod(id, points.size())];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enforce lock constraints
|
/// Enforce lock constraints
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <gui/util.hpp>
|
#include <gui/util.hpp>
|
||||||
#include <data/set.hpp>
|
#include <data/set.hpp>
|
||||||
#include <data/field/symbol.hpp>
|
#include <data/field/symbol.hpp>
|
||||||
|
#include <data/format/image_to_symbol.hpp>
|
||||||
#include <data/action/value.hpp>
|
#include <data/action/value.hpp>
|
||||||
#include <util/window_id.hpp>
|
#include <util/window_id.hpp>
|
||||||
#include <util/io/reader.hpp>
|
#include <util/io/reader.hpp>
|
||||||
@@ -143,16 +144,18 @@ void SymbolWindow::onFileNew(wxCommandEvent& ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SymbolWindow::onFileOpen(wxCommandEvent& ev) {
|
void SymbolWindow::onFileOpen(wxCommandEvent& ev) {
|
||||||
String name = wxFileSelector(_("Open symbol"),_(""),_(""),_(""),_("Symbol files|*.mse-symbol;*.bmp|MSE2 symbol files (*.mse-symbol)|*.mse-symbol|MSE1 symbol files (*.bmp)|*.bmp"),wxOPEN|wxFILE_MUST_EXIST, this);
|
String name = wxFileSelector(_("Open symbol"),_(""),_(""),_(""),_("Symbol files|*.mse-symbol;*.bmp|MSE2 symbol files (*.mse-symbol)|*.mse-symbol|Images/MSE1 symbol files|*.bmp;*.png;*.jpg;*.gif"),wxOPEN|wxFILE_MUST_EXIST, this);
|
||||||
if (!name.empty()) {
|
if (!name.empty()) {
|
||||||
wxFileName n(name);
|
wxFileName n(name);
|
||||||
String ext = n.GetExt();
|
String ext = n.GetExt();
|
||||||
SymbolP symbol;
|
SymbolP symbol;
|
||||||
if (ext.Lower() == _("bmp")) {
|
if (ext.Lower() == _("mse-symbol")) {
|
||||||
//% symbol = importSymbol(wxImage(name));
|
|
||||||
} else {
|
|
||||||
Reader reader(new_shared1<wxFileInputStream>(name), name);
|
Reader reader(new_shared1<wxFileInputStream>(name), name);
|
||||||
reader.handle_greedy(symbol);
|
reader.handle_greedy(symbol);
|
||||||
|
} else {
|
||||||
|
Image image(name);
|
||||||
|
if (!image.Ok()) return;
|
||||||
|
symbol = import_symbol(image);
|
||||||
}
|
}
|
||||||
// show...
|
// show...
|
||||||
parts->setSymbol(symbol);
|
parts->setSymbol(symbol);
|
||||||
|
|||||||
@@ -1440,6 +1440,12 @@
|
|||||||
ObjectFile="$(IntDir)/$(InputName)4.obj"/>
|
ObjectFile="$(IntDir)/$(InputName)4.obj"/>
|
||||||
</FileConfiguration>
|
</FileConfiguration>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\data\format\image_to_symbol.cpp">
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\data\format\image_to_symbol.hpp">
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\data\format\mse1.cpp">
|
RelativePath=".\data\format\mse1.cpp">
|
||||||
</File>
|
</File>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class Vector2D {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Piecewise minimum
|
/// Piecewise minimum
|
||||||
inline Vector2D piecewise_min(Vector2D a, Vector2D b) {
|
inline Vector2D piecewise_min(const Vector2D& a, const Vector2D& b) {
|
||||||
return Vector2D(
|
return Vector2D(
|
||||||
a.x < b.x ? a.x : b.x,
|
a.x < b.x ? a.x : b.x,
|
||||||
a.y < b.y ? a.y : b.y
|
a.y < b.y ? a.y : b.y
|
||||||
@@ -131,13 +131,15 @@ inline Vector2D piecewise_min(Vector2D a, Vector2D b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Piecewise maximum
|
/// Piecewise maximum
|
||||||
inline Vector2D piecewise_max(Vector2D a, Vector2D b) {
|
inline Vector2D piecewise_max(const Vector2D& a, const Vector2D& b) {
|
||||||
return Vector2D(
|
return Vector2D(
|
||||||
a.x < b.x ? b.x : a.x,
|
a.x < b.x ? b.x : a.x,
|
||||||
a.y < b.y ? b.y : a.y
|
a.y < b.y ? b.y : a.y
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Vector2D operator * (double a, const Vector2D& b) { return b * a; }
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : EOF
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user