1162 lines
32 KiB
C++
1162 lines
32 KiB
C++
/*
|
|
* See Copyright Notice in atomorph.h
|
|
*/
|
|
|
|
#include "atomorph.h"
|
|
|
|
#ifdef ATOMORPH_DEPRECATED
|
|
const char * am_get_version() {
|
|
return "0.51";
|
|
}
|
|
|
|
size_t am_get_warning() {
|
|
size_t flags = 0;
|
|
if (sizeof(void *) < 8) flags = flags|AM_WARN_POINTER_SIZE;
|
|
if (sizeof(AM_ATOM)> 8) flags = flags|AM_WARN_ATOM_SIZE;
|
|
return flags;
|
|
}
|
|
|
|
AM_SCENE::AM_SCENE() {
|
|
atoms = 0;
|
|
frames = 0;
|
|
map = nullptr;
|
|
candidates = nullptr;
|
|
splines = nullptr;
|
|
}
|
|
|
|
AM_SCENE::~AM_SCENE() {
|
|
clear();
|
|
}
|
|
|
|
void AM_SCENE::clear(void) {
|
|
if (candidates) {
|
|
delete[] candidates;
|
|
candidates = nullptr;
|
|
}
|
|
if (splines) {
|
|
delete[] splines;
|
|
splines = nullptr;
|
|
}
|
|
if (map) {
|
|
for ( size_t i = 0; i < atoms; ++i) {
|
|
free (map[i]);
|
|
}
|
|
free(map);
|
|
map = NULL;
|
|
atoms = 0;
|
|
frames = 0;
|
|
}
|
|
}
|
|
|
|
bool AM_SCENE::init(size_t atoms, size_t frames) {
|
|
if (map != nullptr) return false;
|
|
|
|
this->atoms = atoms;
|
|
this->frames = frames;
|
|
|
|
size_t i;
|
|
map = (AM_ATOM **) malloc( atoms * sizeof(AM_ATOM *) );
|
|
if (!map) return false;
|
|
|
|
for (i = 0 ; i < atoms ; ++i ) {
|
|
map[i] = (AM_ATOM *) malloc( frames * sizeof(AM_ATOM) );
|
|
if (map[i] == nullptr) {
|
|
for(size_t j = 0; j<=i; ++j) {
|
|
free(map[j]);
|
|
}
|
|
free(map);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
candidates = new (std::nothrow) std::vector<AM_ATOM>[frames];
|
|
if (!candidates) {
|
|
clear();
|
|
return false;
|
|
}
|
|
for (i = 0; i < frames; ++i) candidates[i].reserve(atoms);
|
|
|
|
splines = new (std::nothrow) glnemo::CRSpline[atoms];
|
|
if (!splines) {
|
|
clear();
|
|
return false;
|
|
}
|
|
for (i = 0; i < atoms; ++i) splines[i].clearCPoints();
|
|
|
|
return true;
|
|
}
|
|
|
|
void AM_SCENE::renew_splines() {
|
|
for (size_t i = 0; i < atoms; ++i) {
|
|
splines[i].clearCPoints();
|
|
for (size_t j = 0; j < frames; ++j) {
|
|
double x = map[i][j].x / 65536.0;
|
|
double y = map[i][j].y / 65536.0;
|
|
splines[i].AddSplinePoint(glnemo::Vec3D(x, y, 0.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void AM_SCENE::get_xy(size_t atom, double t, double *x, double *y, unsigned interpolation) const {
|
|
if (interpolation == AM_SPLINE) {
|
|
glnemo::Vec3D v = splines[atom].GetInterpolatedSplinePoint(t);
|
|
*x = v.x;
|
|
*y = v.y;
|
|
return;
|
|
}
|
|
|
|
size_t cur_frame = get_current_frame(t);
|
|
AM_ATOM a1 = map[atom][cur_frame];
|
|
if (interpolation == AM_LINEAR) {
|
|
size_t next_frame = (cur_frame+1) % frames;
|
|
AM_ATOM a2 = map[atom][next_frame];
|
|
|
|
double p = 1.0/frames;
|
|
double str = 1.0 - (t - p*cur_frame)/p;
|
|
if (str > 1.0) str = 1.0;
|
|
if (str < 0.0) str = 0.0;
|
|
|
|
*x = (str*a1.x + (1.0-str)*a2.x)/65535.0;
|
|
*y = (str*a1.y + (1.0-str)*a2.y)/65535.0;
|
|
return;
|
|
}
|
|
|
|
*x = a1.x / 65535.0;
|
|
*y = a1.y / 65535.0;
|
|
}
|
|
|
|
// Returns 1.0 when interpolation at t is close to key frames.
|
|
// Returns 0.0 when interpolation at t is the farthest from key frames.
|
|
double AM_SCENE::get_certainty(double t) const {
|
|
const double pi = 3.14159265358;
|
|
size_t cur_frame = get_current_frame(t);
|
|
|
|
double p = 1.0/frames;
|
|
double str = 1.0 - (t - p*cur_frame)/p;
|
|
if (str > 1.0) str = 1.0;
|
|
if (str < 0.0) str = 0.0;
|
|
|
|
return ((cos(str*(pi/0.5))+1.0)/2.0);
|
|
}
|
|
|
|
size_t AM_SCENE::get_current_frame(double t) const {
|
|
size_t cur_frame = t*frames;
|
|
cur_frame %= frames;
|
|
return cur_frame;
|
|
}
|
|
|
|
AM_COLOR AM_SCENE::get_rgba(size_t atom, double t, double lag, double slope, unsigned interpolation) const {
|
|
size_t cur_frame = get_current_frame(t);
|
|
AM_COLOR color;
|
|
|
|
if (interpolation == AM_NONE) {
|
|
color.r = map[atom][cur_frame].r;
|
|
color.g = map[atom][cur_frame].g;
|
|
color.b = map[atom][cur_frame].b;
|
|
color.a = map[atom][cur_frame].a;
|
|
return color;
|
|
}
|
|
|
|
const double pi = 3.14159265358;
|
|
size_t next_frame = (cur_frame+1) % frames;
|
|
|
|
// Linear interpolation:
|
|
double p = 1.0/frames;
|
|
double str = 1.0 - (t - p*cur_frame)/p;
|
|
if (str > 1.0) str = 1.0;
|
|
if (str < 0.0) str = 0.0;
|
|
|
|
if (interpolation == AM_COSINE || interpolation == AM_PERLIN) {
|
|
double s = (slope+0.1)/1.1;
|
|
double l = (1.0 - s) * lag;
|
|
|
|
if (str <= l ) str = 0.0;
|
|
else if (str >= (l+s)) str = 1.0;
|
|
else str = ((-cos((str-l)*(pi/s))+1.0)/2.0);
|
|
}
|
|
|
|
color.r = (str)*map[atom][cur_frame].r + (1.0-str)*map[atom][next_frame].r;
|
|
color.g = (str)*map[atom][cur_frame].g + (1.0-str)*map[atom][next_frame].g;
|
|
color.b = (str)*map[atom][cur_frame].b + (1.0-str)*map[atom][next_frame].b;
|
|
color.a = (str)*map[atom][cur_frame].a + (1.0-str)*map[atom][next_frame].a;
|
|
return color;
|
|
}
|
|
|
|
double AM_SCENE::get_current_path_length(size_t atom, double t) const {
|
|
size_t cur_frame = get_current_frame(t);
|
|
size_t next_frame = (cur_frame+1) % frames;
|
|
|
|
return am_atom_distance(map[atom][cur_frame], map[atom][next_frame]);
|
|
}
|
|
|
|
bool AM_SCENE::push_atom(size_t frame, AM_ATOM atom) {
|
|
if (frame >= frames) return false;
|
|
size_t sz_before = candidates[frame].size();
|
|
candidates[frame].push_back(atom);
|
|
if (candidates[frame].size() == sz_before) return false;
|
|
return true;
|
|
}
|
|
|
|
bool AM_SCENE::elect_atoms() {
|
|
for (size_t i=0; i<frames; ++i) {
|
|
if (candidates[i].size() == 0) {
|
|
// If some frame has no candidates at all, artificially add
|
|
// candidates to the center of the frame to avoid crashing.
|
|
candidates[i].push_back(am_create_atom(0.5,0.5,0,0,0,0));
|
|
}
|
|
std::random_shuffle( candidates[i].begin(), candidates[i].end() );
|
|
|
|
size_t clone=0; // If not enough candidates, start cloning them.
|
|
for (size_t j=candidates[i].size(); (j != 0 && j < atoms); ++j) {
|
|
candidates[i].push_back( candidates[i].at(clone++) );
|
|
}
|
|
if (candidates[i].size() < atoms) return false;
|
|
|
|
for (size_t a=0; a<atoms; ++a) {
|
|
map[a][i].x = candidates[i].at(a).x;
|
|
map[a][i].y = candidates[i].at(a).y;
|
|
map[a][i].r = candidates[i].at(a).r;
|
|
map[a][i].g = candidates[i].at(a).g;
|
|
map[a][i].b = candidates[i].at(a).b;
|
|
map[a][i].a = candidates[i].at(a).a;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
double AM_SCENE::get_cost() const {
|
|
double cost = 0.0;
|
|
|
|
for (size_t a=0; a<atoms; ++a) {
|
|
for (size_t f=0; f<frames; ++f) {
|
|
size_t nf = (f+1) % frames;
|
|
cost += am_atom_distance(map[a][f], map[a][nf]);
|
|
}
|
|
}
|
|
return cost;
|
|
}
|
|
|
|
double AM_SCENE::get_path_length(size_t atom) const {
|
|
if (atom >= atoms) return -1.0;
|
|
|
|
double distance = 0.0;
|
|
for (size_t f=0; f<frames; ++f) {
|
|
size_t nf = (f+1) % frames;
|
|
distance += am_atom_distance(map[atom][f], map[atom][nf]);
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
double AM_SCENE::get_path_color(size_t atom) const {
|
|
if (atom >= atoms || frames==0) return -1.0;
|
|
|
|
double r = 0.0;
|
|
double g = 0.0;
|
|
double b = 0.0;
|
|
double a = 0.0;
|
|
|
|
for (size_t f=0; f<frames; ++f) {
|
|
r+=map[atom][f].r/255.0;
|
|
g+=map[atom][f].g/255.0;
|
|
b+=map[atom][f].b/255.0;
|
|
a+=map[atom][f].a/255.0;
|
|
}
|
|
r/=double(frames);
|
|
g/=double(frames);
|
|
b/=double(frames);
|
|
a/=double(frames);
|
|
|
|
return r*g*b*a;
|
|
}
|
|
|
|
void AM_SCENE::shuffle() {
|
|
size_t f,a;
|
|
std::vector<AM_ATOM> row;
|
|
row.reserve(atoms);
|
|
for (f=0; f<frames; ++f) {
|
|
row.clear();
|
|
for (a=0; a<atoms; ++a) {
|
|
row.push_back(map[a][f]);
|
|
}
|
|
std::random_shuffle( row.begin(), row.end() );
|
|
for (a=0; a<atoms; ++a) {
|
|
map[a][f] = row[a];
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t AM_SCENE::get_sorted_atom_at(size_t position) {
|
|
if (position >= sorted_atoms.size()) return 0;
|
|
return sorted_atoms[position];
|
|
}
|
|
|
|
bool am_compare_pairs( const std::pair<size_t, double>& i, const std::pair<size_t, double>& j ) {
|
|
return j.second < i.second;
|
|
}
|
|
|
|
void AM_SCENE::sort_atoms() {
|
|
std::vector< std::pair<size_t, double> > paths;
|
|
paths.reserve(atoms);
|
|
|
|
for (size_t a=0; a<atoms; ++a) {
|
|
paths.push_back(std::make_pair(a, get_path_color(a)));
|
|
}
|
|
|
|
std::sort(paths.begin(), paths.end(), am_compare_pairs);
|
|
|
|
sorted_atoms.clear();
|
|
size_t sz = paths.size();
|
|
for (size_t a=0; a<sz; ++a) {
|
|
sorted_atoms.push_back(paths[a].first);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
AM_ATOM AM_SCENE::get_atom(size_t atom, size_t frame) const {
|
|
return map[atom][frame];
|
|
}
|
|
|
|
const std::vector<AM_ATOM> *AM_SCENE::get_candidates(size_t frame) const {
|
|
return (const std::vector<AM_ATOM> *) &(candidates[frame]);
|
|
}
|
|
|
|
bool AM_SCENE::copy_map_from(const AM_SCENE *scene) {
|
|
size_t atoms = scene->atom_count();
|
|
size_t frames = scene->frame_count();
|
|
if (atoms == 0 || frames == 0) return false;
|
|
|
|
if (atom_count() != atoms || frame_count() != frames) {
|
|
clear();
|
|
if (!init(atoms, frames)) return false;
|
|
}
|
|
|
|
for (size_t j=0; j<frames; ++j) {
|
|
for (size_t i=0; i<atoms; ++i) {
|
|
map[i][j] = scene->get_atom(i,j);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AM_SCENE::copy_candidates_from(const AM_SCENE *scene) {
|
|
if (frame_count() != scene->frame_count()
|
|
|| atom_count() != scene->atom_count()) return false;
|
|
|
|
for (size_t i=0; i<frames; ++i) {
|
|
candidates[i] = *(scene->get_candidates(i));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AM_SCENE::swap_atoms(size_t frame, size_t atom1, size_t atom2) {
|
|
if (frame >= frames || atom1 >= atoms || atom2 >= atoms) return false;
|
|
if (atom1 == atom2) return true;
|
|
|
|
AM_ATOM abuf = map[atom1][frame];
|
|
map[atom1][frame] = map[atom2][frame];
|
|
map[atom2][frame] = abuf;
|
|
|
|
return true;
|
|
}
|
|
|
|
AM_ATOM am_create_atom(double x, double y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
uint16_t real_x, real_y;
|
|
if (x >= 1.0) real_x = 65535;
|
|
else if (x <= 0.0) real_x = 0;
|
|
else real_x = round(x*65536.0);
|
|
|
|
if (y >= 1.0) real_y = 65535;
|
|
else if (y <= 0.0) real_y = 0;
|
|
else real_y = round(y*65536.0);
|
|
|
|
AM_ATOM atom;
|
|
atom.x = real_x;
|
|
atom.y = real_y;
|
|
atom.r = r;
|
|
atom.g = g;
|
|
atom.b = b;
|
|
atom.a = a;
|
|
return atom;
|
|
}
|
|
|
|
double am_atom_gradient(AM_ATOM a1, AM_ATOM a2) {
|
|
double gradient = sqrt(pow(abs(a1.r-a2.r)/8.0, 2.0) + pow(abs(a1.g-a2.g)/8.0, 2.0) + pow(abs(a1.b-a2.b)/8.0, 2.0)) / 55.209119491;
|
|
return (gradient < 0.0 ? 0.0 : (gradient > 1.0 ? 1.0 : gradient));
|
|
}
|
|
|
|
double am_atom_distance(AM_ATOM a1, AM_ATOM a2) {
|
|
double cost = 0.0;
|
|
cost = sqrt(pow(fabs((a1.x - a2.x)/256.0),2.0) + pow(fabs((a1.y - a2.y)/256.0),2.0)) / 362.033147696;
|
|
return (cost < 0.0 ? 0.0 : (cost > 1.0 ? 1.0 : cost));
|
|
}
|
|
|
|
AM_THREAD::AM_THREAD() {
|
|
running = false;
|
|
signal_stop = false;
|
|
signal_pause = false;
|
|
paused = true;
|
|
subcost = nullptr;
|
|
cost = 0.0;
|
|
}
|
|
|
|
AM_THREAD::~AM_THREAD() {
|
|
if (step_thread.joinable()) {
|
|
pause();
|
|
stop();
|
|
}
|
|
clear();
|
|
}
|
|
|
|
bool AM_THREAD::init(const AM_SCENE *scene) {
|
|
if (running && !paused) return false;
|
|
if (!this->scene.copy_map_from(scene)) return false;
|
|
|
|
// Original candidates are copied from the input scene.
|
|
// Then individual atoms for this thread get elected so
|
|
// that the map of this thread would be different than
|
|
// of the input scene's.
|
|
if (!this->scene.copy_candidates_from(scene)) return false;
|
|
if (!this->scene.elect_atoms()) return false;
|
|
|
|
size_t atoms = scene->atom_count();
|
|
subcost = new (std::nothrow) double[atoms];
|
|
if (!subcost) return false;
|
|
|
|
cost = this->scene.get_cost();
|
|
for (size_t i = 0; i < atoms; ++i) subcost[i]=0.0;
|
|
|
|
std::default_random_engine e(0);
|
|
e1 = e;
|
|
|
|
this->step_size = 1000;
|
|
magic_exponent = 3.0;
|
|
|
|
gradient_importance = 0.0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void AM_THREAD::set_seed(unsigned seed) {
|
|
if (running && !paused) return;
|
|
std::default_random_engine e(seed);
|
|
e1 = e;
|
|
}
|
|
|
|
// Sane range: [100, 1000]
|
|
// Very high number will make the threads to respond to signals very slowly.
|
|
void AM_THREAD::set_step_size(int step_size) {
|
|
if (running && !paused) return;
|
|
this->step_size = step_size;
|
|
}
|
|
|
|
// Sane range: [1.0, 3.0]
|
|
void AM_THREAD::set_magic_exponent(double exponent) {
|
|
if (running && !paused) return;
|
|
magic_exponent = exponent;
|
|
}
|
|
|
|
// Sane range: [0.0, 1.0]
|
|
void AM_THREAD::set_gradient_importance(double importance) {
|
|
if (running && !paused) return;
|
|
gradient_importance = importance;
|
|
}
|
|
|
|
bool AM_THREAD::clear() {
|
|
if (running && !paused) return false;
|
|
scene.clear();
|
|
|
|
if (subcost) {
|
|
delete[] subcost;
|
|
subcost = nullptr;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
double AM_THREAD::chain_length(AM_ATOM a1, AM_ATOM a2, AM_ATOM a3) {
|
|
return am_atom_distance(a1, a2) + am_atom_distance(a2, a3);
|
|
}
|
|
|
|
double AM_THREAD::chain_gradient(AM_ATOM a1, AM_ATOM a2, AM_ATOM a3) {
|
|
return am_atom_gradient(a1, a2) + am_atom_gradient(a2, a3);
|
|
}
|
|
|
|
void AM_THREAD::step() {
|
|
size_t frames = scene.frame_count();
|
|
size_t atoms = scene.atom_count();
|
|
|
|
int max_tries=step_size;
|
|
|
|
if (frames == 0 || atoms <= 1) return;
|
|
|
|
std::uniform_int_distribution<size_t> uniform_dist_atoms (0, atoms - 1);
|
|
std::uniform_int_distribution<size_t> uniform_dist_frames(0, frames - 1);
|
|
|
|
double cost_per_atom = cost / atoms;
|
|
double cost_per_frame = cost_per_atom / frames;
|
|
|
|
size_t frame = uniform_dist_frames(e1);
|
|
size_t frame_before = (frame == 0 ? frames-1 : frame-1);
|
|
size_t frame_after = (frame + 1) % frames;
|
|
size_t atom1 = 0, atom2;
|
|
AM_ATOM a1b,a1c,a1a;
|
|
double chain1b;
|
|
double gradient1b=0.0;
|
|
|
|
EvalAtom1:
|
|
if (max_tries-- <= 0) return;
|
|
atom1 = uniform_dist_atoms(e1);
|
|
a1b = scene.get_atom(atom1, frame_before);
|
|
a1c = scene.get_atom(atom1, frame);
|
|
a1a = scene.get_atom(atom1, frame_after);
|
|
chain1b = chain_length (a1b,a1c,a1a);
|
|
|
|
if (gradient_importance > 0.0) {
|
|
gradient1b = chain_gradient(a1b,a1c,a1a);
|
|
}
|
|
|
|
if (chain1b/2.0 < cost_per_frame) goto EvalAtom1;
|
|
max_tries++;
|
|
Again:
|
|
if (max_tries-- <= 0) return;
|
|
atom2 = uniform_dist_atoms(e1);
|
|
if (atom1 == atom2) goto Again;
|
|
|
|
{
|
|
AM_ATOM a2b = scene.get_atom(atom2, frame_before);
|
|
AM_ATOM a2c = scene.get_atom(atom2, frame);
|
|
AM_ATOM a2a = scene.get_atom(atom2, frame_after);
|
|
|
|
double chain2b = chain_length(a2b,a2c,a2a);
|
|
double chain1a = chain_length(a1b,a2c,a1a);
|
|
double chain2a = chain_length(a2b,a1c,a2a);
|
|
|
|
double gain = (chain1b - chain1a) + (chain2b - chain2a);
|
|
|
|
double c1a3 = pow(chain1a, magic_exponent);
|
|
double c2a3 = pow(chain2a, magic_exponent);
|
|
double c1b3 = pow(chain1b, magic_exponent);
|
|
double c2b3 = pow(chain2b, magic_exponent);
|
|
|
|
if (gradient_importance > 0.0) {
|
|
double gradient2b = chain_gradient(a2b,a2c,a2a);
|
|
double gradient1a = chain_gradient(a1b,a2c,a1a);
|
|
double gradient2a = chain_gradient(a2b,a1c,a2a);
|
|
|
|
double inv = 1.0 - gradient_importance;
|
|
c1a3 = c1a3*inv + gradient_importance*gradient1a;
|
|
c2a3 = c2a3*inv + gradient_importance*gradient2a;
|
|
c1b3 = c1b3*inv + gradient_importance*gradient1b;
|
|
c2b3 = c2b3*inv + gradient_importance*gradient2b;
|
|
}
|
|
|
|
if ((c1a3 + c2a3) < (c1b3 + c2b3)) {
|
|
cost -= gain;
|
|
scene.swap_atoms(frame, atom1, atom2);
|
|
}
|
|
else goto Again;
|
|
}
|
|
}
|
|
|
|
void AM_THREAD::run() {
|
|
while (!signal_stop) {
|
|
if (!signal_pause && !paused) {
|
|
step();
|
|
}
|
|
else {
|
|
paused = true;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(0));
|
|
}
|
|
}
|
|
running = false;
|
|
signal_stop = false;
|
|
signal_pause = false;
|
|
paused = true;
|
|
}
|
|
|
|
bool AM_THREAD::fetch_scene(AM_SCENE *target) const {
|
|
if (running && !paused) return false;
|
|
return target->copy_map_from(&scene);
|
|
}
|
|
|
|
AM_IMAGE::AM_IMAGE() {
|
|
w = 0;
|
|
h = 0;
|
|
done = true;
|
|
|
|
running = false;
|
|
signal_stop = false;
|
|
signal_pause = false;
|
|
paused = true;
|
|
}
|
|
|
|
AM_IMAGE::~AM_IMAGE() {
|
|
if (step_thread.joinable()) {
|
|
pause();
|
|
stop();
|
|
}
|
|
}
|
|
|
|
void AM_IMAGE::run() {
|
|
while (!signal_stop) {
|
|
if (!signal_pause && !paused) {
|
|
render();
|
|
if (done) paused = true;
|
|
}
|
|
else {
|
|
paused = true;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(0));
|
|
}
|
|
}
|
|
running = false;
|
|
signal_stop = false;
|
|
signal_pause = false;
|
|
paused = true;
|
|
color_interpolation = AM_NONE;
|
|
path_interpolation = AM_NONE;
|
|
}
|
|
|
|
void AM_IMAGE::render() {
|
|
if (done) return;
|
|
|
|
if (w == 0 || h == 0) {
|
|
atoms.clear();
|
|
done = true;
|
|
return;
|
|
}
|
|
size_t atom_count = scene.atom_count();
|
|
|
|
scene.renew_splines();
|
|
atoms.clear();
|
|
atoms.reserve(atom_count);
|
|
|
|
std::map<size_t, double> rs; // red components
|
|
std::map<size_t, double> gs; // green components
|
|
std::map<size_t, double> bs; // blue components
|
|
std::map<size_t, double> as; // alpha components
|
|
std::map<size_t, double> weight; // sum of weights
|
|
|
|
for (size_t i=0; i<atom_count; ++i) {
|
|
int x,y;
|
|
double px,py;
|
|
|
|
scene.get_xy(i, t, &px, &py, path_interpolation);
|
|
if (px > 1.0) px = 1.0;
|
|
else if (px < 0.0) px = 0.0;
|
|
if (py > 1.0) py = 1.0;
|
|
else if (py < 0.0) py = 0.0;
|
|
x = std::min(size_t(round(px*w)),size_t(w-1));
|
|
y = std::min(size_t(round(py*h)),size_t(h-1));
|
|
|
|
AM_COLOR color;
|
|
AM_ATOM atom = scene.get_atom(i, scene.get_current_frame(t));
|
|
unsigned char r,g,b,a;
|
|
|
|
if (color_interpolation == AM_PERLIN) {
|
|
double f = 8.0; // Frequency
|
|
int octaves = 8; // Octaves
|
|
double perlin_x = (atom.x / 65535.0)*f;
|
|
double perlin_y = (atom.y / 65535.0)*f;
|
|
double lag = lag_map. octaveNoise(perlin_x, perlin_y, octaves)*0.5 + 0.5;
|
|
double slope = slope_map.octaveNoise(perlin_x,perlin_y,8)*0.5 + 0.5;
|
|
color = scene.get_rgba(i, t, lag, slope, AM_PERLIN);
|
|
}
|
|
else color = scene.get_rgba(i, t, 0.5, 0.5, color_interpolation);
|
|
|
|
r = color.r;
|
|
g = color.g;
|
|
b = color.b;
|
|
a = color.a;
|
|
|
|
size_t index = y*w + x;
|
|
|
|
double d = 1.0 - scene.get_current_path_length(i, t);
|
|
|
|
if (weight.find(index) == weight.end()) weight[index] = d; else weight[index]+= d;
|
|
if (rs.find(index) == rs.end()) rs [index] = d*(r/255.0); else rs [index]+= d*(r/255.0);
|
|
if (gs.find(index) == gs.end()) gs [index] = d*(g/255.0); else gs [index]+= d*(g/255.0);
|
|
if (bs.find(index) == bs.end()) bs [index] = d*(b/255.0); else bs [index]+= d*(b/255.0);
|
|
if (as.find(index) == as.end()) as [index] = d*(a/255.0); else as [index]+= d*(a/255.0);
|
|
}
|
|
|
|
AM_ATOM atom;
|
|
for (auto& kv : weight) {
|
|
double p = kv.second;
|
|
atom.r = 255*(rs[kv.first] / p);
|
|
atom.g = 255*(gs[kv.first] / p);
|
|
atom.b = 255*(bs[kv.first] / p);
|
|
atom.a = 255*(as[kv.first] / p);
|
|
atom.x = kv.first % w;
|
|
atom.y = kv.first / w;
|
|
atoms.push_back(atom);
|
|
}
|
|
|
|
done = true;
|
|
}
|
|
|
|
bool AM_IMAGE::set_scene(const AM_SCENE *scene) {
|
|
if (running && !paused) return false;
|
|
if (!this->scene.copy_map_from(scene)) return false;
|
|
|
|
done = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::set_resolution(size_t width, size_t height) {
|
|
if (running && !paused) return false;
|
|
w = width;
|
|
h = height;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::set_time(double time) {
|
|
if (running && !paused) return false;
|
|
t = time;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::set_seed(unsigned seed) {
|
|
if (running && !paused) return false;
|
|
this->seed = seed;
|
|
|
|
PerlinNoise l_map(seed); lag_map = l_map;
|
|
PerlinNoise s_map(seed+1); slope_map = s_map;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::set_color_interpolation(unsigned method) {
|
|
if (running && !paused) return false;
|
|
color_interpolation = method;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::set_path_interpolation(unsigned method) {
|
|
if (running && !paused) return false;
|
|
path_interpolation = method;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::get_xy(size_t pixel, int *x, int *y) const {
|
|
if (running && !paused) return false;
|
|
if (pixel >= atoms.size()) return false;
|
|
AM_ATOM a = atoms[pixel];
|
|
|
|
*x = a.x;
|
|
*y = a.y;
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::get_rgba(size_t pixel, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) const {
|
|
if (running && !paused) return false;
|
|
if (pixel >= atoms.size()) return false;
|
|
AM_ATOM atom = atoms[pixel];
|
|
|
|
*r = atom.r;
|
|
*g = atom.g;
|
|
*b = atom.b;
|
|
*a = atom.a;
|
|
return true;
|
|
}
|
|
|
|
bool AM_IMAGE::fetch_pixels(std::vector<AM_ATOM> *to) const {
|
|
if (running && !paused) return false;
|
|
if (to == nullptr) return false;
|
|
|
|
size_t sz = atoms.size();
|
|
to->reserve(sz);
|
|
|
|
for (size_t i=0; i<sz; ++i) {
|
|
to->push_back(atoms[i]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// AM_BLENDER:
|
|
|
|
AM_BLENDER::AM_BLENDER() {
|
|
w = 0;
|
|
h = 0;
|
|
done = true;
|
|
|
|
running = false;
|
|
signal_stop = false;
|
|
signal_pause = false;
|
|
paused = true;
|
|
median_combining = false;
|
|
}
|
|
|
|
AM_BLENDER::~AM_BLENDER() {
|
|
if (step_thread.joinable()) {
|
|
pause();
|
|
stop();
|
|
}
|
|
clear();
|
|
}
|
|
|
|
bool AM_BLENDER::clear() {
|
|
if (running && !paused) return false;
|
|
atoms.clear();
|
|
done = true;
|
|
layers.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
void AM_BLENDER::run() {
|
|
while (!signal_stop) {
|
|
if (!signal_pause && !paused) {
|
|
render();
|
|
if (done) paused = true;
|
|
}
|
|
else {
|
|
paused = true;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(0));
|
|
}
|
|
}
|
|
running = false;
|
|
signal_stop = false;
|
|
signal_pause = false;
|
|
paused = true;
|
|
}
|
|
|
|
void AM_BLENDER::render() {
|
|
if (done) return;
|
|
size_t layer_count = layers.size();
|
|
|
|
if (w == 0 || h == 0 || layer_count == 0) {
|
|
atoms.clear();
|
|
done = true;
|
|
return;
|
|
}
|
|
|
|
size_t atom_count = atoms.size();
|
|
|
|
std::map<size_t, size_t> counts; // number of atoms
|
|
std::map<size_t, std::vector<unsigned char> > rs; // red components for median combining
|
|
std::map<size_t, std::vector<unsigned char> > gs; // green components for median combining
|
|
std::map<size_t, std::vector<unsigned char> > bs; // blue components for median combining
|
|
std::map<size_t, std::vector<unsigned char> > as; // alpha components for median combining
|
|
std::map<size_t, size_t > rsa; // red components for averaging
|
|
std::map<size_t, size_t > gsa; // green components for averaging
|
|
std::map<size_t, size_t > bsa; // blue components for averaging
|
|
std::map<size_t, size_t > asa; // alpha components for averaging
|
|
std::vector<size_t> indices;
|
|
|
|
indices.reserve(atom_count);
|
|
|
|
double weight = 1.0 / layer_count;
|
|
|
|
for (size_t i=0; i<atom_count; ++i) {
|
|
AM_ATOM atom = atoms[i];
|
|
size_t index = atom.y*w + atom.x;
|
|
|
|
indices.push_back(index);
|
|
|
|
if (median_combining) {
|
|
rs[index].push_back(atom.r);
|
|
gs[index].push_back(atom.g);
|
|
bs[index].push_back(atom.b);
|
|
as[index].push_back(atom.a);
|
|
}
|
|
else {
|
|
if (rsa.find(index) == rsa.end()) rsa[index]=(atom.r)*weight; else rsa[index] += atom.r*weight;
|
|
if (gsa.find(index) == gsa.end()) gsa[index]=(atom.g)*weight; else gsa[index] += atom.g*weight;
|
|
if (bsa.find(index) == bsa.end()) bsa[index]=(atom.b)*weight; else bsa[index] += atom.b*weight;
|
|
if (asa.find(index) == asa.end()) asa[index]=(atom.a)*weight; else asa[index] += atom.a*weight;
|
|
}
|
|
|
|
if (counts.find(index) == counts.end()) counts[index] = 1; else counts[index]++;
|
|
}
|
|
|
|
atoms.clear();
|
|
|
|
AM_ATOM atom;
|
|
size_t sz = indices.size();
|
|
for (size_t i=0; i<sz; ++i) {
|
|
size_t index = indices[i];
|
|
size_t x = index % w;
|
|
size_t y = index / w;
|
|
|
|
if (counts[index] < layer_count) {
|
|
// If less than 50% of layers have a pixel on this position,
|
|
// calculate the number of neighbors and if not enough found
|
|
// skip this pixel.
|
|
size_t yw = y*w;
|
|
size_t n = 0;
|
|
if ( counts.find(yw+x+1) != counts.end()) n++;
|
|
if (x != 0 && counts.find(yw+x-1) != counts.end()) n++;
|
|
if ( counts.find(yw+w+x) != counts.end()) n++;
|
|
if (y != 0 && counts.find(yw-w+x) != counts.end()) n++;
|
|
|
|
if (n < 3) continue;
|
|
}
|
|
atom.x = x;
|
|
atom.y = y;
|
|
|
|
size_t sz = counts[index];
|
|
unsigned char r,g,b,a;
|
|
|
|
if (median_combining) {
|
|
std::sort(rs[index].begin(), rs[index].end());
|
|
std::sort(gs[index].begin(), gs[index].end());
|
|
std::sort(bs[index].begin(), bs[index].end());
|
|
std::sort(as[index].begin(), as[index].end());
|
|
|
|
if (sz % 2 == 0) {
|
|
r = (rs[index][sz/2 - 1] + rs[index][sz/2])/2;
|
|
g = (gs[index][sz/2 - 1] + gs[index][sz/2])/2;
|
|
b = (bs[index][sz/2 - 1] + bs[index][sz/2])/2;
|
|
a = (as[index][sz/2 - 1] + as[index][sz/2])/2;
|
|
}
|
|
else {
|
|
r = rs[index][sz/2];
|
|
g = gs[index][sz/2];
|
|
b = bs[index][sz/2];
|
|
a = as[index][sz/2];
|
|
}
|
|
}
|
|
else {
|
|
double p = double(counts[index])/layer_count;
|
|
|
|
r = rsa[index]/p; if (r > 255) r = 255;
|
|
g = gsa[index]/p; if (g > 255) g = 255;
|
|
b = bsa[index]/p; if (b > 255) b = 255;
|
|
a = asa[index]/p; if (a > 255) a = 255;
|
|
}
|
|
|
|
atom.r = r;
|
|
atom.g = g;
|
|
atom.b = b;
|
|
atom.a = a;
|
|
atoms.push_back(atom);
|
|
}
|
|
|
|
done = true;
|
|
}
|
|
|
|
bool AM_BLENDER::add_image(const AM_IMAGE *img) {
|
|
if (running && !paused) return false;
|
|
|
|
size_t pixels_before = atoms.size();
|
|
img->fetch_pixels(&atoms);
|
|
|
|
done = false;
|
|
layers.push_back(pixels_before);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_BLENDER::set_resolution(size_t width, size_t height) {
|
|
if (running && !paused) return false;
|
|
w = width;
|
|
h = height;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_BLENDER::set_median_combining(bool value) {
|
|
if (running && !paused) return false;
|
|
median_combining = value;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AM_BLENDER::get_xy(size_t pixel, int *x, int *y) const {
|
|
if (running && !paused) return false;
|
|
if (pixel >= atoms.size()) return false;
|
|
AM_ATOM a = atoms[pixel];
|
|
|
|
*x = a.x;
|
|
*y = a.y;
|
|
return true;
|
|
}
|
|
|
|
bool AM_BLENDER::get_rgba(size_t pixel, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) const {
|
|
if (running && !paused) return false;
|
|
if (pixel >= atoms.size()) return false;
|
|
AM_ATOM atom = atoms[pixel];
|
|
|
|
*r = atom.r;
|
|
*g = atom.g;
|
|
*b = atom.b;
|
|
*a = atom.a;
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace am {
|
|
|
|
pixel create_pixel(uint16_t x, uint16_t y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
pixel px;
|
|
px.x = x;
|
|
px.y = y;
|
|
px.c.r = r;
|
|
px.c.g = g;
|
|
px.c.b = b;
|
|
px.c.a = a;
|
|
return px;
|
|
}
|
|
|
|
pixel create_pixel(uint16_t x, uint16_t y, color c) {
|
|
return create_pixel(x, y, c.r, c.g, c.b, c.a);
|
|
}
|
|
|
|
const char * get_version() {
|
|
return "1.0";
|
|
}
|
|
|
|
size_t get_warning() {
|
|
size_t flags = 0;
|
|
if (sizeof(void *) < 8) flags = flags|WARN_POINTER_SIZE;
|
|
if (sizeof(pixel) > 8) flags = flags|WARN_PIXEL_SIZE;
|
|
if (sizeof(point) > 8) flags = flags|WARN_POINT_SIZE;
|
|
return flags;
|
|
}
|
|
|
|
bool uses_opencv() {
|
|
#ifdef ATOMORPH_OPENCV
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// Clears the chain, freeing dynamically allocated memory.
|
|
// After calling this, chain can be safely deleted.
|
|
void clear_chain(chain *c) {
|
|
if (c->points) {
|
|
for (size_t i=0; i<c->width; ++i) {
|
|
delete [] c->points[i];
|
|
}
|
|
delete [] c->points;
|
|
c->points = nullptr;
|
|
}
|
|
|
|
if (c->places) {
|
|
for (size_t i=0; i<c->width; ++i) {
|
|
delete [] c->places[i];
|
|
}
|
|
delete [] c->places;
|
|
c->places = nullptr;
|
|
}
|
|
|
|
if (c->splines) {
|
|
delete [] c->splines;
|
|
c->splines = nullptr;
|
|
}
|
|
|
|
#ifdef ATOMORPH_OPENCV
|
|
if (c->kdtrees) {
|
|
for (size_t i=0; i<c->height; ++i) {
|
|
if (c->kdtrees[i] == nullptr) continue;
|
|
cv::flann::Index *kdtree = (cv::flann::Index *) c->kdtrees[i];
|
|
delete kdtree;
|
|
}
|
|
delete [] c->kdtrees;
|
|
c->kdtrees = nullptr;
|
|
}
|
|
|
|
if (c->feature) {
|
|
for (size_t i=0; i<c->height; ++i) {
|
|
if (c->feature[i] == nullptr) continue;
|
|
cv::Mat *m = (cv::Mat *) c->feature[i];
|
|
delete m;
|
|
}
|
|
delete [] c->feature;
|
|
c->feature = nullptr;
|
|
}
|
|
#endif
|
|
|
|
c->width =0;
|
|
c->height=0;
|
|
c->energy=0.0;
|
|
c->max_surface=0;
|
|
}
|
|
|
|
// Returns false if memory allocation has failed, chain will be left in a cleared state.
|
|
bool renew_chain(chain *c, size_t width, size_t height) {
|
|
clear_chain(c);
|
|
|
|
c->points = new (std::nothrow) point* [width];
|
|
if (c->points == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t k = 0; k < width; ++k ) {
|
|
c->points[k] = new (std::nothrow) point [height];
|
|
if (c->points[k] == nullptr) {
|
|
for (size_t t = 0; t<=k; ++t) {
|
|
delete [] c->points[t];
|
|
}
|
|
delete [] c->points;
|
|
// Unable to allocate enough memory.
|
|
c->points = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
c->places = new (std::nothrow) size_t* [width];
|
|
if (c->places == nullptr) {
|
|
clear_chain(c);
|
|
return false;
|
|
}
|
|
|
|
for (size_t k = 0; k < width; ++k ) {
|
|
c->places[k] = new (std::nothrow) size_t [height];
|
|
if (c->places[k] == nullptr) {
|
|
for (size_t t = 0; t<=k; ++t) {
|
|
delete [] c->places[t];
|
|
}
|
|
delete [] c->places;
|
|
// Unable to allocate enough memory.
|
|
c->places = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
c->splines = new (std::nothrow) glnemo::CRSpline[width];
|
|
if (c->splines == nullptr) {
|
|
clear_chain(c);
|
|
return false;
|
|
}
|
|
|
|
#ifdef ATOMORPH_OPENCV
|
|
c->kdtrees = new (std::nothrow) void* [height];
|
|
if (c->kdtrees == nullptr) {
|
|
clear_chain(c);
|
|
return false;
|
|
}
|
|
|
|
for (size_t i=0; i<height; ++i) {
|
|
c->kdtrees[i] = nullptr;
|
|
}
|
|
|
|
c->feature = new (std::nothrow) void* [height];
|
|
if (c->feature == nullptr) {
|
|
clear_chain(c);
|
|
return false;
|
|
}
|
|
|
|
for (size_t i=0; i<height; ++i) {
|
|
c->feature[i] = nullptr;
|
|
}
|
|
#endif
|
|
|
|
c->width = width;
|
|
c->height= height;
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
|