Initial commit.
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Hyena
|
||||
Copyright (c) 2016 Erich Erstu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
NAME = atomorph
|
||||
CC = gcc
|
||||
PROF = -O2
|
||||
C_FLAGS = -std=c++11 -Wall -pedantic $(PROF)
|
||||
OBJ_DIR = obj
|
||||
|
||||
SRC_FILES := $(wildcard *.cpp)
|
||||
O_FILES := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(SRC_FILES))
|
||||
|
||||
OUT = ./lib$(NAME).a
|
||||
|
||||
all:
|
||||
@printf "\033[0mHINT: On errors, try \033[1;33m-std=gnu++11 -stdlib=libc++\033[0m compiler flags.\033[0m\n"
|
||||
@printf "\033[0mHINT: Use \033[1;33mmake opencv\033[0m for OpenCV optimizations (experimental).\033[0m\n"
|
||||
@printf "\033[0mHINT: Use \033[1;33mmake deprecated\033[0m to compile the old version.\033[0m\n"
|
||||
@$(MAKE) $(OUT) -s
|
||||
|
||||
opencv: DEFINES = -D ATOMORPH_OPENCV
|
||||
opencv: $(O_FILES)
|
||||
@ar rcs $(OUT) $(O_FILES)
|
||||
@printf "\033[1;32mOpenCV dependent lib$(NAME).a DONE!\033[0m\n"
|
||||
|
||||
deprecated: DEFINES = -D ATOMORPH_DEPRECATED
|
||||
deprecated: $(O_FILES)
|
||||
@ar rcs $(OUT) $(O_FILES)
|
||||
@printf "\033[1;32mDeprecated lib$(NAME).a DONE!\033[0m\n"
|
||||
|
||||
$(OUT): $(O_FILES)
|
||||
@ar rcs $(OUT) $(O_FILES)
|
||||
@printf "\033[1;32mlib$(NAME).a DONE!\033[0m\n"
|
||||
|
||||
$(OBJ_DIR)/%.o: %.cpp
|
||||
@printf "\033[1m\033[31mCompiling \033[37m....\033[34m %-20s\t\033[33m%6s\033[31m lines\033[0m \n" $*.cpp "`wc -l $*.cpp | cut -f1 -d' '`"
|
||||
@$(CC) $(INCLUDE) $< $(DEFINES) $(C_FLAGS) -c -o $@
|
||||
|
||||
clean:
|
||||
@printf "\033[1;36mCleaning \033[37m ...."
|
||||
@rm -f $(O_FILES) $(OUT) *~ *.bak *.orig *.rej
|
||||
@printf "\033[1;37m lib$(NAME).a cleaned!\033[0m\n"
|
17
README.md
|
@ -1,2 +1,19 @@
|
|||
# atomorph
|
||||
Image morphing library that uses fluid dynamics and optimal transport to produce intuitive morphs.
|
||||
|
||||
http://atomorph.org
|
||||
|
||||
AtoMorph Library uses the C++11 standard. The included Makefiles
|
||||
have been tested on Linux operating systems. iOS users may encounter
|
||||
some problems due to poor support of the C++11 standard on their
|
||||
system. One should try modifying the Makefile by adding -std=gnu++11
|
||||
and -stdlib=libc++ compiler flags.
|
||||
|
||||
Build instructions:
|
||||
Run make in this folder.
|
||||
|
||||
Example usage:
|
||||
See README in demo folder.
|
||||
|
||||
If you like this software, please consider making a donation
|
||||
by sending Bitcoins to 1Erich1YUdkUAp9ynf4Rfw2ug8nBtuUmMu.
|
||||
|
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* AtoMorph - Simple Library for Morphing 2D Particle Clouds
|
||||
* See Copyright Notice at the end of this file.
|
||||
*/
|
||||
|
||||
#ifndef _ATOMORPH_H_
|
||||
#define _ATOMORPH_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <future>
|
||||
#include <random>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <limits>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef ATOMORPH_OPENCV
|
||||
#include <opencv/cv.h>
|
||||
#endif
|
||||
|
||||
#include "spline.h"
|
||||
#include "perlin.h"
|
||||
#include "fluidmodel.h"
|
||||
#include "color.h"
|
||||
|
||||
#ifdef ATOMORPH_DEPRECATED
|
||||
#define AM_NONE 0
|
||||
#define AM_LINEAR 1
|
||||
#define AM_COSINE 2
|
||||
#define AM_PERLIN 3
|
||||
#define AM_SPLINE 4
|
||||
|
||||
#define AM_WARN_POINTER_SIZE 1 // Set when sizeof(void *) is less than 64 bits.
|
||||
#define AM_WARN_ATOM_SIZE 2 // Set when sizeof(AM_ATOM) is more than 64 bits.
|
||||
|
||||
typedef struct AM_COLOR {
|
||||
unsigned char r;
|
||||
unsigned char g;
|
||||
unsigned char b;
|
||||
unsigned char a;
|
||||
} AM_COLOR;
|
||||
|
||||
typedef struct AM_ATOM {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
unsigned char r;
|
||||
unsigned char g;
|
||||
unsigned char b;
|
||||
unsigned char a;
|
||||
} AM_ATOM;
|
||||
|
||||
class AM_SCENE {
|
||||
public:
|
||||
AM_SCENE();
|
||||
~AM_SCENE();
|
||||
|
||||
void clear();
|
||||
bool init(size_t atoms, size_t frames);
|
||||
bool push_atom(size_t frame, AM_ATOM atom);
|
||||
void renew_splines();
|
||||
double get_certainty(double t) const;
|
||||
void get_xy (size_t atom, double t, double *x, double *y, unsigned method) const;
|
||||
AM_COLOR get_rgba(size_t atom, double t, double lag, double slope, unsigned interpolation) const;
|
||||
double get_current_path_length(size_t atom, double t) const;
|
||||
AM_ATOM get_atom(size_t atom, size_t frame) const;
|
||||
size_t atom_count() const {return atoms;}
|
||||
size_t frame_count() const {return frames;}
|
||||
size_t get_sorted_atom_at(size_t position);
|
||||
size_t get_current_frame(double t) const;
|
||||
bool copy_map_from(const AM_SCENE *scene);
|
||||
bool copy_candidates_from(const AM_SCENE *scene);
|
||||
void shuffle();
|
||||
void sort_atoms();
|
||||
double get_cost() const;
|
||||
double get_path_length(size_t atom) const;
|
||||
double get_path_color(size_t atom) const;
|
||||
bool elect_atoms();
|
||||
bool swap_atoms(size_t frame, size_t atom1, size_t atom2);
|
||||
const std::vector<AM_ATOM> *get_candidates(size_t frame) const;
|
||||
|
||||
private:
|
||||
size_t atoms;
|
||||
size_t frames;
|
||||
|
||||
std::vector<size_t> sorted_atoms;
|
||||
std::vector<AM_ATOM> *candidates;
|
||||
glnemo::CRSpline *splines;
|
||||
|
||||
AM_ATOM **map;
|
||||
};
|
||||
|
||||
class AM_THREAD {
|
||||
public:
|
||||
AM_THREAD();
|
||||
~AM_THREAD();
|
||||
|
||||
bool clear();
|
||||
bool init(const AM_SCENE *scene);
|
||||
|
||||
void set_seed(unsigned seed);
|
||||
void set_step_size(int step_size);
|
||||
void set_gradient_importance(double weight);
|
||||
void set_magic_exponent(double exponent);
|
||||
|
||||
bool is_running() const {return running;}
|
||||
bool is_paused() const {return paused;}
|
||||
|
||||
void stop() { signal_stop = true; while(running) std::this_thread::sleep_for(std::chrono::milliseconds(0)); step_thread.join();}
|
||||
void start() { running = true; step_thread = std::thread(&AM_THREAD::run, this);}
|
||||
void pause() { signal_pause = true; while(!paused) std::this_thread::sleep_for(std::chrono::milliseconds(0)); signal_pause = false;}
|
||||
void resume() { paused = false;}
|
||||
|
||||
double get_cost() const {return cost;}
|
||||
bool fetch_scene(AM_SCENE *target) const;
|
||||
|
||||
private:
|
||||
void run();
|
||||
void step();
|
||||
double chain_length(AM_ATOM a1, AM_ATOM a2, AM_ATOM a3);
|
||||
double chain_gradient(AM_ATOM a1, AM_ATOM a2, AM_ATOM a3);
|
||||
AM_SCENE scene;
|
||||
double *subcost;
|
||||
double cost;
|
||||
std::default_random_engine e1;
|
||||
int step_size;
|
||||
double magic_exponent;
|
||||
double gradient_importance;
|
||||
|
||||
std::atomic<bool> signal_stop;
|
||||
std::atomic<bool> running;
|
||||
std::atomic<bool> signal_pause;
|
||||
std::atomic<bool> paused;
|
||||
std::thread step_thread;
|
||||
};
|
||||
|
||||
class AM_IMAGE {
|
||||
public:
|
||||
AM_IMAGE();
|
||||
~AM_IMAGE();
|
||||
|
||||
bool set_scene(const AM_SCENE *scene);
|
||||
bool set_resolution(size_t width, size_t height);
|
||||
bool set_time(double time);
|
||||
bool set_seed(unsigned seed);
|
||||
bool set_color_interpolation(unsigned method);
|
||||
bool set_path_interpolation(unsigned method);
|
||||
|
||||
bool is_running() const {return running;}
|
||||
bool is_paused() const {return paused;}
|
||||
size_t pixel_count() const {return atoms.size();}
|
||||
|
||||
bool get_xy (size_t pixel, int *x, int *y) const;
|
||||
bool get_rgba(size_t pixel, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) const;
|
||||
size_t get_pixel_count() const {return atoms.size();}
|
||||
|
||||
bool fetch_pixels(std::vector<AM_ATOM> *to) const;
|
||||
|
||||
void stop() { signal_stop = true; while(running) std::this_thread::sleep_for(std::chrono::milliseconds(0)); step_thread.join();}
|
||||
void start() { running = true; step_thread = std::thread(&AM_IMAGE::run, this);}
|
||||
void pause() { signal_pause = true; while(!paused) std::this_thread::sleep_for(std::chrono::milliseconds(0)); signal_pause = false;}
|
||||
void resume() { paused = false;}
|
||||
|
||||
private:
|
||||
void run();
|
||||
void render();
|
||||
std::vector<AM_ATOM> atoms;
|
||||
size_t w;
|
||||
size_t h;
|
||||
double t;
|
||||
AM_SCENE scene;
|
||||
bool done;
|
||||
unsigned seed;
|
||||
unsigned color_interpolation;
|
||||
unsigned path_interpolation;
|
||||
PerlinNoise lag_map;
|
||||
PerlinNoise slope_map;
|
||||
|
||||
std::atomic<bool> signal_stop;
|
||||
std::atomic<bool> running;
|
||||
std::atomic<bool> signal_pause;
|
||||
std::atomic<bool> paused;
|
||||
std::thread step_thread;
|
||||
};
|
||||
|
||||
class AM_BLENDER {
|
||||
public:
|
||||
AM_BLENDER();
|
||||
~AM_BLENDER();
|
||||
|
||||
bool set_resolution(size_t width, size_t height);
|
||||
bool set_median_combining(bool value);
|
||||
bool clear();
|
||||
bool add_image(const AM_IMAGE *img);
|
||||
|
||||
bool get_xy (size_t pixel, int *x, int *y) const;
|
||||
bool get_rgba(size_t pixel, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) const;
|
||||
size_t pixel_count() const {return atoms.size();}
|
||||
|
||||
bool is_running() const {return running;}
|
||||
bool is_paused() const {return paused;}
|
||||
|
||||
void stop() { signal_stop = true; while(running) std::this_thread::sleep_for(std::chrono::milliseconds(0)); step_thread.join();}
|
||||
void start() { running = true; step_thread = std::thread(&AM_BLENDER::run, this);}
|
||||
void pause() { signal_pause = true; while(!paused) std::this_thread::sleep_for(std::chrono::milliseconds(0)); signal_pause = false;}
|
||||
void resume() { paused = false;}
|
||||
|
||||
private:
|
||||
void run();
|
||||
void render();
|
||||
std::vector<AM_ATOM> atoms;
|
||||
std::vector<size_t> layers;
|
||||
size_t w;
|
||||
size_t h;
|
||||
bool done;
|
||||
bool median_combining;
|
||||
|
||||
std::atomic<bool> signal_stop;
|
||||
std::atomic<bool> running;
|
||||
std::atomic<bool> signal_pause;
|
||||
std::atomic<bool> paused;
|
||||
std::thread step_thread;
|
||||
};
|
||||
|
||||
AM_ATOM am_create_atom(double x, double y, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
|
||||
double am_atom_distance(AM_ATOM a1, AM_ATOM a2);
|
||||
double am_atom_gradient(AM_ATOM a1, AM_ATOM a2);
|
||||
const char * am_get_version();
|
||||
size_t am_get_warning();
|
||||
#endif
|
||||
|
||||
namespace am {
|
||||
|
||||
const unsigned RGB = 0; // Don't change the color space.
|
||||
const unsigned HSP = 1; // Convert from RGB to HSP.
|
||||
const unsigned NONE = 2; // Color/motion interpolation is off.
|
||||
const unsigned LINEAR = 3; // Uses linear interpolation.
|
||||
const unsigned SPLINE = 4; // Spline interpolation (motion only).
|
||||
const unsigned COSINE = 5; // Cosine interpolation (colors only).
|
||||
const unsigned PERLIN = 6; // Perlin noise dependent interpolation (colors only).
|
||||
const unsigned STATE_BLOB_DETECTION = 0;
|
||||
const unsigned STATE_BLOB_UNIFICATION = 1;
|
||||
const unsigned STATE_BLOB_MATCHING = 2;
|
||||
const unsigned STATE_ATOM_MORPHING = 3;
|
||||
const unsigned STATE_DONE = 4;
|
||||
const unsigned char HAS_PIXEL = 1;
|
||||
const unsigned char HAS_FLUID = 2;
|
||||
typedef struct pixel {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
|
||||
color c;
|
||||
} pixel;
|
||||
|
||||
typedef struct blob {
|
||||
size_t index; // Position in the nest vector.
|
||||
std::set<size_t> surface; // Pixel positions for surface.
|
||||
std::set<size_t> border; // Pixel positions around surface.
|
||||
size_t group =0;
|
||||
bool unified=false;
|
||||
double x,y,r,g,b,a;
|
||||
} blob;
|
||||
|
||||
typedef struct key_frame {
|
||||
std::vector<blob*> blobs; // Vector of blobs.
|
||||
std::map<size_t, pixel> pixels; // Map of pixels by position.
|
||||
std::map<size_t, blob*> owners; // Map of blobs by position.
|
||||
double x,y,r,g,b,a;
|
||||
size_t index =0; // Position in the nest container.
|
||||
size_t first_expansion=0; // Blobs before this index cannot expand.
|
||||
size_t first_dust =0; // First under-sized blob to be unified.
|
||||
} frame;
|
||||
|
||||
typedef union key_point {
|
||||
struct {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint8_t x_fract;
|
||||
uint8_t y_fract;
|
||||
uint8_t flags;
|
||||
} s;
|
||||
uint64_t word;
|
||||
} point;
|
||||
|
||||
typedef struct blob_chain {
|
||||
size_t width =0;
|
||||
size_t height=0;
|
||||
point **points=nullptr;
|
||||
size_t **places=nullptr;
|
||||
double energy=0.0;
|
||||
size_t max_surface=0;
|
||||
glnemo::CRSpline *splines=nullptr;
|
||||
#ifdef ATOMORPH_OPENCV
|
||||
void **kdtrees=nullptr;
|
||||
void **feature=nullptr;
|
||||
#endif
|
||||
} chain;
|
||||
|
||||
const size_t WARN_POINTER_SIZE = 1; // Set when sizeof(void *) is less than 64 bits.
|
||||
const size_t WARN_PIXEL_SIZE = 2; // Set when sizeof(pixel) is more than 64 bits.
|
||||
const size_t WARN_POINT_SIZE = 3; // Set when sizeof(point) is more than 64 bits.
|
||||
|
||||
const unsigned TEXTURE = 0;
|
||||
const unsigned AVERAGE = 1;
|
||||
const unsigned DISTINCT = 2;
|
||||
|
||||
void clear_chain(chain *c);
|
||||
bool renew_chain(chain *c, size_t width, size_t height);
|
||||
|
||||
pixel create_pixel(uint16_t x, uint16_t y, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
|
||||
pixel create_pixel(uint16_t x, uint16_t y, color c);
|
||||
inline size_t xy2pos (uint16_t x, uint16_t y) {return (y*(UINT16_MAX+1)+x);}
|
||||
|
||||
inline void point2xy(point pt, float *x, float *y) {
|
||||
*x = pt.s.x + pt.s.x_fract/float(UINT8_MAX+1);
|
||||
*y = pt.s.y + pt.s.y_fract/float(UINT8_MAX+1);
|
||||
}
|
||||
|
||||
inline uint32_t pixel_distance (pixel p1, pixel p2 ) {
|
||||
int32_t xd = p1.x-p2.x;
|
||||
int32_t yd = p1.y-p2.y;
|
||||
|
||||
return xd*xd+yd*yd;
|
||||
}
|
||||
|
||||
inline uint32_t approx_point_distance (point p1, point p2) {
|
||||
int32_t xd = p1.s.x-p2.s.x;
|
||||
int32_t yd = p1.s.y-p2.s.y;
|
||||
|
||||
return xd*xd + yd*yd;
|
||||
}
|
||||
|
||||
inline uint64_t point_distance (point p1, point p2) {
|
||||
uint64_t xd = std::abs(((UINT8_MAX+1)*p1.s.x+p1.s.x_fract) - ((UINT8_MAX+1)*p2.s.x+p2.s.x_fract));
|
||||
uint64_t yd = std::abs(((UINT8_MAX+1)*p1.s.y+p1.s.y_fract) - ((UINT8_MAX+1)*p2.s.y+p2.s.y_fract));
|
||||
|
||||
return (xd*xd + yd*yd);
|
||||
}
|
||||
|
||||
inline double distance(double x1, double y1, double x2, double y2) {
|
||||
double xd = x1-x2;
|
||||
double yd = y1-y2;
|
||||
|
||||
return xd*xd+yd*yd;
|
||||
}
|
||||
|
||||
inline bool point_has_pixel(point p) {
|
||||
return (p.s.flags & HAS_PIXEL);
|
||||
}
|
||||
|
||||
inline bool point_has_fluid(point p) {
|
||||
return (p.s.flags & HAS_FLUID);
|
||||
}
|
||||
|
||||
const char * get_version();
|
||||
size_t get_warning();
|
||||
bool uses_opencv();
|
||||
|
||||
}
|
||||
|
||||
#include "thread.h"
|
||||
#include "morph.h"
|
||||
|
||||
#endif
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 Erich Erstu
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* See Copyright Notice in atomorph.h
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "color.h"
|
||||
|
||||
namespace am {
|
||||
|
||||
color create_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
||||
color c;
|
||||
c.r = r;
|
||||
c.g = g;
|
||||
c.b = b;
|
||||
c.a = a;
|
||||
return c;
|
||||
}
|
||||
|
||||
color create_color(double r, double g, double b, double a) {
|
||||
color c;
|
||||
c.r = std::round(r*255.0);
|
||||
c.g = std::round(g*255.0);
|
||||
c.b = std::round(b*255.0);
|
||||
c.a = std::round(a*255.0);
|
||||
return c;
|
||||
}
|
||||
|
||||
color rgb_to_hsp(color c) {
|
||||
color hsp = c;
|
||||
|
||||
double r,g,b,h,s,p;
|
||||
r = c.r/255.0;
|
||||
g = c.g/255.0;
|
||||
b = c.b/255.0;
|
||||
RGBtoHSP(r,g,b,&h,&s,&p);
|
||||
hsp.r = std::round(h*255.0);
|
||||
hsp.g = std::round(s*255.0);
|
||||
hsp.b = std::round(p*255.0);
|
||||
|
||||
return hsp;
|
||||
}
|
||||
|
||||
color hsp_to_rgb(color c) {
|
||||
color rgb = c;
|
||||
|
||||
double r,g,b,h,s,p;
|
||||
h = c.r/255.0;
|
||||
s = c.g/255.0;
|
||||
p = c.b/255.0;
|
||||
HSPtoRGB(h,s,p,&r,&g,&b);
|
||||
rgb.r = std::min(std::round(r*255.0), 255.0);
|
||||
rgb.g = std::min(std::round(g*255.0), 255.0);
|
||||
rgb.b = std::min(std::round(b*255.0), 255.0);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
const double Pr = 0.299;
|
||||
const double Pg = 0.587;
|
||||
const double Pb = 0.114;
|
||||
|
||||
// public domain function by Darel Rex Finley, 2006
|
||||
//
|
||||
// This function expects the passed-in values to be on a scale
|
||||
// of 0 to 1, and uses that same scale for the return values.
|
||||
//
|
||||
// See description/examples at alienryderflex.com/hsp.html
|
||||
void RGBtoHSP(double R, double G, double B, double *H, double *S, double *P) {
|
||||
// Calculate the Perceived brightness.
|
||||
*P=sqrt(R*R*Pr+G*G*Pg+B*B*Pb);
|
||||
|
||||
// Calculate the Hue and Saturation. (This part works
|
||||
// the same way as in the HSV/B and HSL systems???.)
|
||||
if (R==G && R==B) {
|
||||
*H=0.;
|
||||
*S=0.;
|
||||
return;
|
||||
}
|
||||
|
||||
if (R>=G && R>=B) {// R is largest
|
||||
if (B>=G) {
|
||||
*H=6./6.-1./6.*(B-G)/(R-G);
|
||||
*S=1.-G/R;
|
||||
}
|
||||
else {
|
||||
*H=0./6.+1./6.*(G-B)/(R-B);
|
||||
*S=1.-B/R;
|
||||
}
|
||||
}
|
||||
else if (G>=R && G>=B) {// G is largest
|
||||
if (R>=B) {
|
||||
*H=2./6.-1./6.*(R-B)/(G-B); *S=1.-B/G;
|
||||
}
|
||||
else {
|
||||
*H=2./6.+1./6.*(B-R)/(G-R); *S=1.-R/G;
|
||||
}
|
||||
}
|
||||
else {// B is largest
|
||||
if (G>=R) {
|
||||
*H=4./6.-1./6.*(G-R)/(B-R); *S=1.-R/B;
|
||||
}
|
||||
else {
|
||||
*H=4./6.+1./6.*(R-G)/(B-G); *S=1.-G/B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public domain function by Darel Rex Finley, 2006
|
||||
//
|
||||
// This function expects the passed-in values to be on a scale
|
||||
// of 0 to 1, and uses that same scale for the return values.
|
||||
//
|
||||
// Note that some combinations of HSP, even if in the scale
|
||||
// 0-1, may return RGB values that exceed a value of 1. For
|
||||
// example, if you pass in the HSP color 0,1,1, the result
|
||||
// will be the RGB color 2.037,0,0.
|
||||
//
|
||||
// See description/examples at alienryderflex.com/hsp.html
|
||||
void HSPtoRGB(double H, double S, double P, double *R, double *G, double *B) {
|
||||
double part, minOverMax=1.-S ;
|
||||
|
||||
if (minOverMax>0.) {
|
||||
if ( H<1./6.) { // R>G>B
|
||||
H= 6.*( H-0./6.); part=1.+H*(1./minOverMax-1.);
|
||||
*B=P/sqrt(Pr/minOverMax/minOverMax+Pg*part*part+Pb);
|
||||
*R=(*B)/minOverMax; *G=(*B)+H*((*R)-(*B));
|
||||
}
|
||||
else if ( H<2./6.) { // G>R>B
|
||||
H= 6.*(-H+2./6.); part=1.+H*(1./minOverMax-1.);
|
||||
*B=P/sqrt(Pg/minOverMax/minOverMax+Pr*part*part+Pb);
|
||||
*G=(*B)/minOverMax; *R=(*B)+H*((*G)-(*B));
|
||||
}
|
||||
else if ( H<3./6.) { // G>B>R
|
||||
H= 6.*( H-2./6.); part=1.+H*(1./minOverMax-1.);
|
||||
*R=P/sqrt(Pg/minOverMax/minOverMax+Pb*part*part+Pr);
|
||||
*G=(*R)/minOverMax; *B=(*R)+H*((*G)-(*R));
|
||||
}
|
||||
else if ( H<4./6.) { // B>G>R
|
||||
H= 6.*(-H+4./6.); part=1.+H*(1./minOverMax-1.);
|
||||
*R=P/sqrt(Pb/minOverMax/minOverMax+Pg*part*part+Pr);
|
||||
*B=(*R)/minOverMax; *G=(*R)+H*((*B)-(*R));
|
||||
}
|
||||
else if ( H<5./6.) { // B>R>G
|
||||
H= 6.*( H-4./6.); part=1.+H*(1./minOverMax-1.);
|
||||
*G=P/sqrt(Pb/minOverMax/minOverMax+Pr*part*part+Pg);
|
||||
*B=(*G)/minOverMax; *R=(*G)+H*((*B)-(*G)); }
|
||||
else { // R>B>G
|
||||
H= 6.*(-H+6./6.); part=1.+H*(1./minOverMax-1.);
|
||||
*G=P/sqrt(Pr/minOverMax/minOverMax+Pb*part*part+Pg);
|
||||
*R=(*G)/minOverMax; *B=(*G)+H*((*R)-(*G));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( H<1./6.) { // R>G>B
|
||||
H= 6.*( H-0./6.); *R=sqrt(P*P/(Pr+Pg*H*H)); *G=(*R)*H; *B=0.;
|
||||
}
|
||||
else if ( H<2./6.) { // G>R>B
|
||||
H= 6.*(-H+2./6.); *G=sqrt(P*P/(Pg+Pr*H*H)); *R=(*G)*H; *B=0.;
|
||||
}
|
||||
else if ( H<3./6.) { // G>B>R
|
||||
H= 6.*( H-2./6.); *G=sqrt(P*P/(Pg+Pb*H*H)); *B=(*G)*H; *R=0.;
|
||||
}
|
||||
else if ( H<4./6.) { // B>G>R
|
||||
H= 6.*(-H+4./6.); *B=sqrt(P*P/(Pb+Pg*H*H)); *G=(*B)*H; *R=0.;
|
||||
}
|
||||
else if ( H<5./6.) { // B>R>G
|
||||
H= 6.*( H-4./6.); *B=sqrt(P*P/(Pb+Pr*H*H)); *R=(*B)*H; *G=0.;
|
||||
}
|
||||
else { // R>B>G
|
||||
H= 6.*(-H+6./6.); *R=sqrt(P*P/(Pr+Pb*H*H)); *B=(*R)*H; *G=0.;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* See Copyright Notice in atomorph.h
|
||||
*/
|
||||
|
||||
namespace am {
|
||||
|
||||
typedef struct color {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
} color;
|
||||
|
||||
color create_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
|
||||
color create_color(double r, double g, double b, double a);
|
||||
|
||||
inline double color_distance(color c1, color c2) {
|
||||
int16_t rd = c1.r-c2.r;
|
||||
int16_t gd = c1.g-c2.g;
|
||||
int16_t bd = c1.b-c2.b;
|
||||
int16_t ad = c1.a-c2.a;
|
||||
|
||||
return sqrt(rd*rd+gd*gd+bd*bd+ad*ad)/510.0;
|
||||
}
|
||||
|
||||
color rgb_to_hsp(color c);
|
||||
color hsp_to_rgb(color c);
|
||||
|
||||
void RGBtoHSP(double R, double G, double B, double *H, double *S, double *P);
|
||||
void HSPtoRGB(double H, double S, double P, double *R, double *G, double *B);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
NAME = atomorph
|
||||
CC = gcc
|
||||
PROF = -O2
|
||||
C_FLAGS = -std=c++11 -Wall $(PROF)
|
||||
L_FLAGS = -lm -lstdc++ -pthread $(PROF)
|
||||
OBJ_DIR = obj
|
||||
OPENCV = 0
|
||||
|
||||
#Change the LIB_DIR to the path where the MODULES are located:
|
||||
LIB_DIR = ../
|
||||
MODULES = atomorph.a
|
||||
|
||||
SRC_FILES := $(wildcard *.cpp)
|
||||
LIB_FILES := $(patsubst %,$(LIB_DIR)/lib%,$(MODULES))
|
||||
O_FILES := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(SRC_FILES))
|
||||
|
||||
OUT = ./$(NAME)
|
||||
|
||||
all:
|
||||
@printf "\033[0mHINT: On errors, try \033[1;33m-std=gnu++11 -stdlib=libc++\033[0m compiler flags.\033[0m\n"
|
||||
@printf "\033[0mHINT: On linker errors, try \033[1;33mmake opencv\033[0m to link with OpenCV.\033[0m\n"
|
||||
@$(MAKE) independently_linked -s
|
||||
|
||||
opencv:
|
||||
@$(MAKE) opencv_linked -s
|
||||
|
||||
opencv_linked: L_FLAGS+= -lopencv_core -lopencv_flann
|
||||
opencv_linked: $(O_FILES)
|
||||
@printf "\033[1;33mMaking \033[37m ...."
|
||||
$(CC) -o ./$(NAME) $(O_FILES) $(LIB_FILES) $(L_FLAGS)
|
||||
@printf "\033[1;32m OpenCV dependent %s DONE!\033[0m\n" $(NAME)
|
||||
|
||||
independently_linked: $(O_FILES)
|
||||
@printf "\033[1;33mMaking \033[37m ...."
|
||||
$(CC) -o ./$(NAME) $(O_FILES) $(LIB_FILES) $(L_FLAGS)
|
||||
@printf "\033[1;32m %s DONE!\033[0m\n" $(NAME)
|
||||
|
||||
$(OBJ_DIR)/%.o: %.cpp
|
||||
@printf "\033[1m\033[31mCompiling \033[37m....\033[34m %-20s\t\033[33m%6s\033[31m lines\033[0m \n" $*.cpp "`wc -l $*.cpp | cut -f1 -d' '`"
|
||||
@$(CC) $< $(DEFINES) $(C_FLAGS) -c -o $@
|
||||
|
||||
clean:
|
||||
@printf "\033[1;36mCleaning \033[37m ...."
|
||||
@rm -f $(O_FILES) $(OUT) *~ *.bak *.orig *.rej
|
||||
@printf "\033[1;37m $(NAME) cleaned!\033[0m\n"
|
|
@ -0,0 +1,12 @@
|
|||
Build instructions:
|
||||
Run make in this folder.
|
||||
Edit the Makefile if needed.
|
||||
|
||||
Testing out AtoMorph:
|
||||
See README in tests folder.
|
||||
|
||||
For a graphical version of AtoMorph:
|
||||
See README in allegro5 folder.
|
||||
|
||||
If you like this software, please consider making a donation
|
||||
by sending Bitcoins to 1Erich1YUdkUAp9ynf4Rfw2ug8nBtuUmMu.
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<CodeBlocks_project_file>
|
||||
<FileVersion major="1" minor="6" />
|
||||
<Project>
|
||||
<Option title="AtoMorph GUI" />
|
||||
<Option pch_mode="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Build>
|
||||
<Target title="Debug">
|
||||
<Option output="atomorph-gui" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/" />
|
||||
<Option type="0" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-g" />
|
||||
</Compiler>
|
||||
</Target>
|
||||
<Target title="Release">
|
||||
<Option output="atomorph-gui" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/" />
|
||||
<Option type="0" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-O2" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-s" />
|
||||
</Linker>
|
||||
</Target>
|
||||
</Build>
|
||||
<Compiler>
|
||||
<Add option="-std=c++11" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add library="liballegro" />
|
||||
<Add library="liballegro_image" />
|
||||
<Add library="liballegro_font" />
|
||||
<Add library="liballegro_primitives" />
|
||||
<Add library="../../libatomorph.a" />
|
||||
</Linker>
|
||||
<Unit filename="main.cpp" />
|
||||
<Unit filename="main.h" />
|
||||
<Extensions>
|
||||
<code_completion />
|
||||
<debugger />
|
||||
</Extensions>
|
||||
</Project>
|
||||
</CodeBlocks_project_file>
|
|
@ -0,0 +1,44 @@
|
|||
NAME = atomorph-gui
|
||||
CC = gcc
|
||||
PROF = -O2
|
||||
C_FLAGS = -std=c++11 -Wall $(PROF)
|
||||
L_FLAGS = ../../libatomorph.a -lm -lstdc++ $(PROF)
|
||||
OBJ_DIR = obj
|
||||
DEFINES = -D ATOMORPH_DEPRECATED
|
||||
|
||||
#Change the LIB_DIR to the path where the MODULES are located:
|
||||
LIB_DIR = /usr/lib/
|
||||
MODULES = allegro.so allegro_image.so allegro_font.so allegro_primitives.so
|
||||
|
||||
SRC_FILES := $(wildcard *.cpp)
|
||||
LIB_FILES := $(patsubst %,$(LIB_DIR)/lib%,$(MODULES))
|
||||
O_FILES := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(SRC_FILES))
|
||||
|
||||
OUT = ./$(NAME)
|
||||
|
||||
all:
|
||||
@printf "\033[0mHINT: libatomorph.a has to be compiled using the \033[1;33mmake deprecated\033[0m command.\033[0m\n"
|
||||
@printf "\033[0mHINT: Use \033[1;33mmake static\033[0m to link statically.\033[0m\n"
|
||||
@$(MAKE) dynamically_linked -s
|
||||
|
||||
static:
|
||||
@$(MAKE) statically_linked -s
|
||||
|
||||
statically_linked: $(O_FILES)
|
||||
@printf "\033[1;33mMaking \033[37m ...."
|
||||
$(CC) -static -o ./$(NAME) $(O_FILES) $(LIB_FILES) $(L_FLAGS)
|
||||
@printf "\033[1;32m Statically linked %s DONE!\033[0m\n" $(NAME)
|
||||
|
||||
dynamically_linked: $(O_FILES)
|
||||
@printf "\033[1;33mMaking \033[37m ...."
|
||||
$(CC) -o ./$(NAME) $(O_FILES) $(LIB_FILES) $(L_FLAGS)
|
||||
@printf "\033[1;32m Dynamically linked %s DONE!\033[0m\n" $(NAME)
|
||||
|
||||
$(OBJ_DIR)/%.o: %.cpp
|
||||
@printf "\033[1m\033[31mCompiling \033[37m....\033[34m %-20s\t\033[33m%6s\033[31m lines\033[0m \n" $*.cpp "`wc -l $*.cpp | cut -f1 -d' '`"
|
||||
@$(CC) $< $(DEFINES) $(C_FLAGS) -c -o $@
|
||||
|
||||
clean:
|
||||
@printf "\033[1;36mCleaning \033[37m ...."
|
||||
@rm -f $(O_FILES) $(OUT) *~ *.bak *.orig *.rej
|
||||
@printf "\033[1;37m $(NAME) cleaned!\033[0m\n"
|
|
@ -0,0 +1,15 @@
|
|||
This AtoMorph-GUI implementation is deprecated because it uses
|
||||
an older version of the AtoMorph Library. The latter has to be
|
||||
compiled using the `deprecated` build target.
|
||||
|
||||
AtoMorph-GUI requires Allegro5.1 installed on your system.
|
||||
|
||||
Build instructions:
|
||||
Run make in this folder.
|
||||
Edit the Makefile if needed.
|
||||
|
||||
Alternative build instructions:
|
||||
Use CodeBlocks and open AtoMorph-GUI.cbp.
|
||||
|
||||
If you like this software, please consider making a donation
|
||||
by sending Bitcoins to 1Erich1YUdkUAp9ynf4Rfw2ug8nBtuUmMu.
|
|
@ -0,0 +1,561 @@
|
|||
/*
|
||||
* See Copyright Notice in main.h
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "main.h"
|
||||
|
||||
const float FPS = 60.0; // Maximum FPS.
|
||||
const int SCREEN_W = 800;
|
||||
const int SCREEN_H = 600;
|
||||
const int MORPH_W = 128; // Width of the morph. Should be at most the width of the input image.
|
||||
const int MORPH_H = 128; // Height of the morph. Should be at most the height of the input image.
|
||||
const int ATOMS = 10000; // Number of atoms used in one thread.
|
||||
const size_t THREAD_N = 5; // Number of threads to use to find a perfect morph.
|
||||
const size_t SLOWNESS = 50; // How many frames to render per animation cycle.
|
||||
|
||||
size_t morph_time = 0;
|
||||
int view_frame = 0;
|
||||
bool pressed_keys[ALLEGRO_KEY_MAX];
|
||||
|
||||
size_t active_thread = 0; // When render is ON, morph only one thread at a time.
|
||||
int color_fade = AM_NONE; // Color interpolation method.
|
||||
int trajectory = AM_NONE; // Atom trajectory interpolation method.
|
||||
bool median_combining = false; // Noise reduction method. FALSE for averaging.
|
||||
bool stop_morphing = false; // To halt the morph time temporarily.
|
||||
bool no_render = false; // When TRUE no blending is done, just atom morphing.
|
||||
|
||||
ALLEGRO_DISPLAY *display = NULL;
|
||||
ALLEGRO_EVENT_QUEUE *event_queue = NULL;
|
||||
ALLEGRO_TIMER *timer = NULL;
|
||||
ALLEGRO_FONT *font = NULL;
|
||||
ALLEGRO_BITMAP *morph_bmp = NULL; // Holds the final morph as a bitmap.
|
||||
ALLEGRO_BITMAP *thread_bmp[THREAD_N]; // Holds the results of the morphing threads.
|
||||
|
||||
// Helper function to initially populate the AM_SCENE object according to the provided
|
||||
// image file.
|
||||
bool fill_scene(AM_SCENE *scene, size_t frame, const char *png_file) {
|
||||
std::random_device rd;
|
||||
std::default_random_engine re(rd());
|
||||
std::uniform_real_distribution<double> uniform_dist(0.0, 1.0);
|
||||
|
||||
ALLEGRO_BITMAP * bmp = al_load_bitmap(png_file);
|
||||
int bmp_w = al_get_bitmap_width(bmp);
|
||||
int bmp_h = al_get_bitmap_height(bmp);
|
||||
|
||||
al_lock_bitmap(bmp, ALLEGRO_PIXEL_FORMAT_ANY, ALLEGRO_LOCK_READONLY);
|
||||
|
||||
for (int j=0; j<bmp_h; j++) {
|
||||
for (int i=0; i<bmp_w; i++) {
|
||||
ALLEGRO_COLOR c = al_get_pixel(bmp, i, j);
|
||||
|
||||
unsigned char r,g,b,a;
|
||||
al_unmap_rgba(c, &r, &g, &b, &a);
|
||||
|
||||
if (a == 0) continue;
|
||||
double px,py;
|
||||
px = double(i) / double(bmp_w);
|
||||
py = double(j) / double(bmp_h);
|
||||
scene->push_atom(frame, am_create_atom(px,py,r,g,b,a));
|
||||
}
|
||||
}
|
||||
|
||||
al_unlock_bitmap(bmp);
|
||||
al_destroy_bitmap(bmp);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (!init(argc, argv)) {
|
||||
fprintf(stderr, "Failed to initialize!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::default_random_engine seed_engine(rd());
|
||||
std::uniform_int_distribution<unsigned> uniform_dist(1, std::numeric_limits<unsigned>::max());
|
||||
|
||||
morph_bmp = al_create_bitmap(MORPH_W, MORPH_H);
|
||||
al_set_target_bitmap(morph_bmp);
|
||||
al_clear_to_color(al_map_rgba(0,0,0,0));
|
||||
|
||||
AM_BLENDER blender; // Used to combine the thread results into the final morph.
|
||||
blender.set_resolution(MORPH_W, MORPH_H);
|
||||
blender.set_median_combining(median_combining);
|
||||
blender.start();
|
||||
|
||||
AM_THREAD scene_thread[THREAD_N]; // Each of these will morph its own version of the animation.
|
||||
AM_SCENE scene_buf [THREAD_N]; // Temporarily holds the last results of the morphing threads.
|
||||
AM_IMAGE image_buf [THREAD_N]; // Used to render the final image of the provided scene.
|
||||
|
||||
{
|
||||
AM_SCENE scene; // Needed temporarily to store the raw input data.
|
||||
|
||||
scene.init(ATOMS, 6); // Reserve 6 frames for this scene.
|
||||
fill_scene(&scene, 0, "../tests/data/battlelord_1.png");
|
||||
fill_scene(&scene, 1, "../tests/data/battlelord_2.png");
|
||||
fill_scene(&scene, 2, "../tests/data/battlelord_3.png");
|
||||
fill_scene(&scene, 3, "../tests/data/battlelord_4.png");
|
||||
fill_scene(&scene, 4, "../tests/data/battlelord_5.png");
|
||||
fill_scene(&scene, 5, "../tests/data/battlelord_6.png");
|
||||
|
||||
for (size_t i=0; i<THREAD_N; ++i) {
|
||||
scene_buf [i].init(scene.atom_count(), scene.frame_count());
|
||||
scene_thread[i].init(&scene); // Give the initial work to the worker thread.
|
||||
scene_thread[i].set_seed(uniform_dist(seed_engine)); // Seed for its RNG.
|
||||
scene_thread[i].set_step_size(100); // Number of iterations to make per step.
|
||||
scene_thread[i].set_magic_exponent(3.0); // Affects the generated trajectories.
|
||||
scene_thread[i].set_gradient_importance(0.0); // Importance of the color values.
|
||||
scene_thread[i].start();
|
||||
|
||||
thread_bmp[i] = al_create_bitmap(MORPH_W, MORPH_H);
|
||||
al_set_target_bitmap(thread_bmp[i]);
|
||||
al_clear_to_color(al_map_rgba(0,0,0,0));
|
||||
|
||||
image_buf[i].set_scene(&scene); // Prepares the image rendering object.
|
||||
image_buf[i].set_resolution(MORPH_W, MORPH_H);
|
||||
image_buf[i].set_seed(i); // Seed for its RNG (used in Perlin noise).
|
||||
image_buf[i].start();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper variables:
|
||||
bool redraw = true;
|
||||
bool doexit = false;
|
||||
bool started = false;
|
||||
bool debug = false;
|
||||
int frame = 0;
|
||||
double scene_cost = 0.0; // Lower cost means better quality. Cost decreases over time.
|
||||
int old_morph_time = 0;
|
||||
bool refresh = false;
|
||||
|
||||
while(!doexit) {
|
||||
ALLEGRO_EVENT ev;
|
||||
al_wait_for_event(event_queue, &ev);
|
||||
|
||||
if(ev.type == ALLEGRO_EVENT_TIMER) {
|
||||
redraw = true;
|
||||
}
|
||||
else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
|
||||
break;
|
||||
}
|
||||
else if(ev.type == ALLEGRO_EVENT_KEY_DOWN) {
|
||||
pressed_keys[ev.keyboard.keycode] = true;
|
||||
switch(ev.keyboard.keycode) {
|
||||
case ALLEGRO_KEY_PAD_PLUS: view_frame++; break;
|
||||
case ALLEGRO_KEY_PAD_MINUS: view_frame--; break;
|
||||
case ALLEGRO_KEY_D: debug = !debug; break;
|
||||
case ALLEGRO_KEY_M: median_combining = !median_combining; break;
|
||||
case ALLEGRO_KEY_S: stop_morphing = !stop_morphing; break;
|
||||
case ALLEGRO_KEY_R: no_render = !no_render;
|
||||
if (no_render) old_morph_time = morph_time;
|
||||
else {
|
||||
morph_time = old_morph_time;
|
||||
refresh = true;
|
||||
}
|
||||
break;
|
||||
case ALLEGRO_KEY_C:
|
||||
if (color_fade == AM_NONE) color_fade = AM_LINEAR;
|
||||
else if (color_fade == AM_LINEAR) color_fade = AM_COSINE;
|
||||
else if (color_fade == AM_COSINE) color_fade = AM_PERLIN;
|
||||
else color_fade = AM_NONE;
|
||||
break;
|
||||
case ALLEGRO_KEY_T:
|
||||
if (trajectory == AM_NONE) trajectory = AM_LINEAR;
|
||||
else if (trajectory == AM_LINEAR) trajectory = AM_SPLINE;
|
||||
else trajectory = AM_NONE;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
else if(ev.type == ALLEGRO_EVENT_KEY_UP) {
|
||||
pressed_keys[ev.keyboard.keycode] = false;
|
||||
switch(ev.keyboard.keycode) {
|
||||
case ALLEGRO_KEY_ESCAPE:
|
||||
doexit = true;
|
||||
break;
|
||||
case ALLEGRO_KEY_SPACE:
|
||||
started = !started;
|
||||
|
||||
for (size_t i=0; i<THREAD_N; ++i) {
|
||||
if (!scene_thread[i].is_running()) continue;
|
||||
if (!started) scene_thread[i].pause();
|
||||
else scene_thread[i].resume();
|
||||
}
|
||||
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
else if(ev.type == ALLEGRO_EVENT_MOUSE_AXES ||
|
||||
ev.type == ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY);
|
||||
else if(ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) ;
|
||||
else if(ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) ;
|
||||
|
||||
if(redraw && al_is_event_queue_empty(event_queue)) {
|
||||
int fps;
|
||||
if ( (fps = calculate_fps()) == -1) continue;
|
||||
|
||||
frame++;
|
||||
|
||||
if (started) {
|
||||
size_t i;
|
||||
bool skip_render = false;
|
||||
|
||||
if (morph_time%SLOWNESS == 0 || refresh) {
|
||||
double cost =0.0;
|
||||
|
||||
active_thread = (active_thread + 1) % THREAD_N;
|
||||
|
||||
for (i=0; i<THREAD_N; ++i) {
|
||||
if (!scene_thread[i].is_paused()) {
|
||||
// Before thread results can be read it must be paused.
|
||||
scene_thread[i].pause();
|
||||
cost += scene_thread[i].get_cost();
|
||||
if (!no_render) {
|
||||
scene_thread[i].fetch_scene(&(scene_buf[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// When rendering is disabled all morphing threads will work,
|
||||
// othwerwise only the active morphing thread works.
|
||||
if (active_thread == i || no_render) scene_thread[i].resume();
|
||||
}
|
||||
scene_cost = cost / THREAD_N;
|
||||
skip_render = true;
|
||||
refresh = false;
|
||||
}
|
||||
else if (no_render) active_thread = (active_thread + 1) % THREAD_N;
|
||||
|
||||
if (no_render) skip_render = true;
|
||||
|
||||
bool slow_down = false; // Is set to TRUE when image rendering in not yet finished.
|
||||
// This is to slow down the animation rather than skip frames.
|
||||
|
||||
if (!skip_render) {
|
||||
bool all_images_done = true;
|
||||
for (i=0; i<THREAD_N; ++i) {
|
||||
if (!image_buf[i].is_paused()) {
|
||||
all_images_done = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_images_done && blender.is_paused()) {
|
||||
double t = (morph_time % SLOWNESS)/(double(SLOWNESS));
|
||||
|
||||
// Render blender image:
|
||||
blend_morphs(&blender, morph_bmp);
|
||||
blender.clear();
|
||||
blender.set_median_combining(median_combining);
|
||||
|
||||
for (i=0; i<THREAD_N; ++i) {
|
||||
// Render thread images:
|
||||
render_morph(&image_buf[i], thread_bmp[i]);
|
||||
|
||||
// Give a new job to image blender thread:
|
||||
blender.add_image(&image_buf[i]);
|
||||
|
||||
// Give new job to image thread:
|
||||
image_buf[i].set_scene(&scene_buf[i]);
|
||||
image_buf[i].set_time(t);
|
||||
image_buf[i].set_color_interpolation(color_fade);
|
||||
image_buf[i].set_path_interpolation(trajectory);
|
||||
image_buf[i].resume();
|
||||
}
|
||||
blender.resume();
|
||||
}
|
||||
else slow_down = true; // Image rendering is lagging behind!
|
||||
}
|
||||
|
||||
if (!stop_morphing) {
|
||||
if (!slow_down && ++morph_time == SLOWNESS) morph_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
redraw = false;
|
||||
|
||||
double k = double(MORPH_H) /double(MORPH_W); // Aspect ratio.
|
||||
int merged_h = SCREEN_H - SCREEN_H/8;
|
||||
int merged_w = merged_h * k;
|
||||
|
||||
al_set_target_bitmap(al_get_backbuffer(display));
|
||||
al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
|
||||
al_clear_to_color(al_map_rgb(128,128,128));
|
||||
al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
|
||||
int mh = SCREEN_H / 8;
|
||||
int mw = mh * k;
|
||||
double s = double(SCREEN_W)/double(THREAD_N);
|
||||
|
||||
// Draw the thread images to the upper side of the screen:
|
||||
for (size_t i=0; i<THREAD_N; ++i) {
|
||||
if (active_thread == i) {
|
||||
al_draw_rectangle(s*i+s/2.0 - mw/2.0, 0.0,
|
||||
s*i+s/2.0 + mw/2.0, mh,
|
||||
al_map_rgb(192,0,0), 4.0);
|
||||
}
|
||||
al_draw_scaled_bitmap(thread_bmp[i], 0.0, 0.0, MORPH_W, MORPH_H,
|
||||
s*i+s/2.0 - mw/2.0, 0.0, mw, mh, 0);
|
||||
}
|
||||
|
||||
// Draw the final morph to the center of the screen:
|
||||
al_draw_scaled_bitmap(morph_bmp, 0.0, 0.0, MORPH_W, MORPH_H,
|
||||
SCREEN_W/2 - merged_w/2, SCREEN_H - merged_h,
|
||||
merged_w, merged_h, 0);
|
||||
|
||||
// Draw textual information:
|
||||
if (font!=NULL) {
|
||||
al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
|
||||
|
||||
al_draw_filled_rectangle(0, 0, SCREEN_W-SCREEN_W/4, 14, al_map_rgba(0,0,0,128));
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), 0, 0, 0,
|
||||
"FPS: %3d; Atoms: %d; Cost: %2.3f; Thread: %lu/%lu; Morph time: %lu;",
|
||||
fps, ATOMS, scene_cost, active_thread+1, THREAD_N, morph_time
|
||||
);
|
||||
if (!started) {
|
||||
al_draw_filled_rectangle(0, 0, SCREEN_W, SCREEN_H/4, al_map_rgba(0,0,0,128));
|
||||
|
||||
al_draw_textf(font, al_map_rgb(0,255,0),
|
||||
SCREEN_W/2, SCREEN_H/8, ALLEGRO_ALIGN_CENTRE,
|
||||
"AtoMorph v%s Demo by Erich Erstu, 2013",
|
||||
am_get_version()
|
||||
);
|
||||
|
||||
al_draw_filled_rectangle(0, SCREEN_H - SCREEN_H/4,
|
||||
SCREEN_W, SCREEN_H, al_map_rgba(0,0,0,128)
|
||||
);
|
||||
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), SCREEN_W/2,
|
||||
SCREEN_H - SCREEN_H/8, ALLEGRO_ALIGN_CENTRE, "Press SPACE to start!"
|
||||
);
|
||||
}
|
||||
else {
|
||||
al_draw_filled_rectangle(0, 14, SCREEN_W/3, SCREEN_H/5, al_map_rgba(0,0,0,128));
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), 0, 12, 0,
|
||||
"[M]edian combining: %s", median_combining ? "ON" : "OFF"
|
||||
);
|
||||
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), 0, 24, 0,
|
||||
"[S]top morph time.%s", stop_morphing ? " (Stopped)" : ""
|
||||
);
|
||||
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), 0, 36, 0,
|
||||
"[C]olor interpolation: %s",
|
||||
color_fade == AM_NONE ? "NONE" :
|
||||
color_fade == AM_LINEAR ? "LINEAR" :
|
||||
color_fade == AM_COSINE ? "COSINE" :
|
||||
color_fade == AM_PERLIN ? "PERLIN" : ""
|
||||
);
|
||||
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), 0, 48, 0,
|
||||
"[T]rajectory interpolation: %s",
|
||||
trajectory == AM_NONE ? "NONE" :
|
||||
trajectory == AM_LINEAR ? "LINEAR" :
|
||||
trajectory == AM_SPLINE ? "SPLINE" : ""
|
||||
);
|
||||
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), 0, 60, 0,
|
||||
"[R]endering: %s", !no_render ? "ON" : "OFF");
|
||||
|
||||
al_draw_text (font, al_map_rgb(0,255,0), 0, 72, 0, "SPACE to pause.");
|
||||
al_draw_text (font, al_map_rgb(0,255,0), 0, 84, 0, "ESC to exit.");
|
||||
}
|
||||
}
|
||||
al_flip_display();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t t=0; t<THREAD_N; ++t) {
|
||||
scene_thread[t].stop();
|
||||
image_buf[t].stop();
|
||||
al_destroy_bitmap(thread_bmp[t]);
|
||||
}
|
||||
blender.stop();
|
||||
|
||||
al_destroy_bitmap(morph_bmp);
|
||||
al_destroy_font(font);
|
||||
al_destroy_timer(timer);
|
||||
al_destroy_display(display);
|
||||
al_destroy_event_queue(event_queue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void render_morph(AM_IMAGE *img, ALLEGRO_BITMAP *to) {
|
||||
// Clear old bitmap:
|
||||
al_set_target_bitmap(to);
|
||||
al_set_blender(ALLEGRO_DEST_MINUS_SRC, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);
|
||||
al_draw_filled_rectangle(0.0, 0.0, MORPH_W, MORPH_H, al_map_rgba(0,0,0,255));
|
||||
|
||||
// Prepare to render:
|
||||
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);
|
||||
ALLEGRO_LOCKED_REGION * lock = al_lock_bitmap(to,ALLEGRO_PIXEL_FORMAT_ANY,ALLEGRO_LOCK_READWRITE);
|
||||
if (lock == NULL) return;
|
||||
|
||||
// Put the pixels:
|
||||
size_t pixels = img->pixel_count();
|
||||
for (size_t i=0; i<pixels; ++i) {
|
||||
int x,y;
|
||||
unsigned char r,g,b,a;
|
||||
|
||||
img->get_xy(i, &x, &y);
|
||||
img->get_rgba(i, &r, &g, &b, &a);
|
||||
|
||||
al_put_pixel(x, y, al_map_rgba(r,g,b,a));
|
||||
}
|
||||
|
||||
// Finally unlock the bitmap:
|
||||
if (lock) al_unlock_bitmap(to);
|
||||
}
|
||||
|
||||
void blend_morphs(AM_BLENDER *blender, ALLEGRO_BITMAP *to) {
|
||||
// Clear old bitmap:
|
||||
al_set_target_bitmap(to);
|
||||
al_set_blender(ALLEGRO_DEST_MINUS_SRC, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);
|
||||
al_draw_filled_rectangle(0.0, 0.0, MORPH_W, MORPH_H, al_map_rgba(0,0,0,255));
|
||||
|
||||
// Prepare to render:
|
||||
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);
|
||||
ALLEGRO_LOCKED_REGION * lock = al_lock_bitmap(to,ALLEGRO_PIXEL_FORMAT_ANY,ALLEGRO_LOCK_READWRITE);
|
||||
if (lock == NULL) return;
|
||||
|
||||
// Put the pixels:
|
||||
size_t pixels = blender->pixel_count();
|
||||
for (size_t i=0; i<pixels; ++i) {
|
||||
int x,y;
|
||||
unsigned char r,g,b,a;
|
||||
|
||||
blender->get_xy(i, &x, &y);
|
||||
blender->get_rgba(i, &r, &g, &b, &a);
|
||||
|
||||
al_put_pixel(x, y, al_map_rgba(r,g,b,a));
|
||||
}
|
||||
|
||||
// Finally unlock the bitmap:
|
||||
if (lock) al_unlock_bitmap(to);
|
||||
}
|
||||
|
||||
bool init(int argc, char **argv) {
|
||||
if (true == (am_get_warning()&AM_WARN_POINTER_SIZE)) {
|
||||
fprintf(stderr, "Pointer size is insufficiently small.\n");
|
||||
}
|
||||
if (true == (am_get_warning()&AM_WARN_ATOM_SIZE)) {
|
||||
fprintf(stderr, "Atom size (%lu) is larger than optimal (%lu).\n",
|
||||
sizeof(AM_ATOM),
|
||||
sizeof(void *)
|
||||
);
|
||||
}
|
||||
|
||||
if(!al_init()) {
|
||||
fprintf(stderr, "failed to initialize allegro!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!al_install_keyboard()) {
|
||||
fprintf(stderr, "failed to initialize the keyboard!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
al_install_mouse();
|
||||
al_init_image_addon();
|
||||
al_init_font_addon();
|
||||
al_init_primitives_addon();
|
||||
|
||||
timer = al_create_timer(1.0 / FPS);
|
||||
if(!timer) {
|
||||
fprintf(stderr, "failed to create timer!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
display = al_create_display(SCREEN_W, SCREEN_H);
|
||||
if(!display) {
|
||||
fprintf(stderr, "failed to create display!\n");
|
||||
al_destroy_timer(timer);
|
||||
return false;
|
||||
}
|
||||
|
||||
al_set_new_bitmap_flags(ALLEGRO_MAG_LINEAR|ALLEGRO_MIN_LINEAR);
|
||||
|
||||
font = al_load_font("data/fixed_font.tga", 0, 0);
|
||||
if (font==NULL) {
|
||||
fprintf(stderr, "failed to load font!\n");
|
||||
al_destroy_display(display);
|
||||
al_destroy_timer(timer);
|
||||
return false;
|
||||
}
|
||||
|
||||
al_set_target_bitmap(al_get_backbuffer(display));
|
||||
|
||||
event_queue = al_create_event_queue();
|
||||
if(!event_queue) {
|
||||
fprintf(stderr, "failed to create event_queue!\n");
|
||||
al_destroy_display(display);
|
||||
al_destroy_timer(timer);
|
||||
al_destroy_font(font);
|
||||
return false;
|
||||
}
|
||||
|
||||
al_register_event_source(event_queue, al_get_display_event_source(display));
|
||||
al_register_event_source(event_queue, al_get_timer_event_source(timer));
|
||||
al_register_event_source(event_queue, al_get_keyboard_event_source());
|
||||
al_register_event_source(event_queue, al_get_mouse_event_source());
|
||||
|
||||
al_clear_to_color(al_map_rgb(0,0,0));
|
||||
al_draw_textf(font, al_map_rgb(0,255,0), SCREEN_W/2, SCREEN_H/2,
|
||||
ALLEGRO_ALIGN_CENTRE,
|
||||
"LOADING...");
|
||||
|
||||
al_flip_display();
|
||||
|
||||
al_start_timer(timer);
|
||||
calculate_fps();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int round_int( double r ) {
|
||||
return (r > 0.0) ? (r + 0.5) : (r - 0.5);
|
||||
}
|
||||
|
||||
int calculate_fps() {
|
||||
static int times = 0;
|
||||
static double old_time = 0.0;
|
||||
static double delta_sum = 0.0;
|
||||
static int old_fps = -1;
|
||||
|
||||
static bool first = true;
|
||||
if (first) {
|
||||
first = false;
|
||||
old_time = al_get_time();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rec_times = 0;
|
||||
int max_times = round_int(FPS);
|
||||
double new_time = al_get_time();
|
||||
double delta = new_time - old_time;
|
||||
delta_sum += delta;
|
||||
old_time = new_time;
|
||||
double p = delta_sum * max_times;
|
||||
rec_times = round_int(p);
|
||||
|
||||
if (times > rec_times) {
|
||||
return -1;
|
||||
}
|
||||
times++;
|
||||
|
||||
int fps = 0;
|
||||
if (delta_sum >= 1.0 || times>=max_times) {
|
||||
fps = times;
|
||||
old_fps = fps;
|
||||
times=0;
|
||||
delta_sum=0.0;
|
||||
}
|
||||
else {
|
||||
if (old_fps == -1) fps = times;
|
||||
else fps = old_fps;
|
||||
}
|
||||
|
||||
return fps;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* AtoMorph Demo - Simple Demo showing what AtoMorph is capable of doing.
|
||||
* See Copyright Notice at the end of this file.
|
||||
*/
|
||||
|
||||
#include "../../atomorph.h"
|
||||
|
||||
#include <allegro5/allegro.h>
|
||||
#include <allegro5/allegro_font.h>
|
||||
#include <allegro5/allegro_image.h>
|
||||
#include <allegro5/allegro_color.h>
|
||||
#include <allegro5/allegro_primitives.h>
|
||||
|
||||
extern const float FPS;
|
||||
extern const int SCREEN_W;
|
||||
extern const int SCREEN_H;
|
||||
|
||||
extern ALLEGRO_DISPLAY *display ;
|
||||
extern ALLEGRO_EVENT_QUEUE *event_queue ;
|
||||
extern ALLEGRO_TIMER *timer ;
|
||||
extern ALLEGRO_FONT *font ;
|
||||
|
||||
bool init(int argc, char **argv);
|
||||
int calculate_fps();
|
||||
void draw(AM_SCENE *scene, ALLEGRO_BITMAP *to, double t, double weight);
|
||||
void render_morph(AM_IMAGE *img, ALLEGRO_BITMAP *to);
|
||||
void blend_morphs(AM_BLENDER *b, ALLEGRO_BITMAP *to);
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 Erich Erstu
|
||||
|
||||
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.
|
||||
*/
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* See Copyright Notice in main.h
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "main.h"
|
||||
#include "lodepng.h"
|
||||
|
||||
MORPH_OPTIONS options;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (!init(argc, argv)) {
|
||||
fprintf(stderr, "Failed to initialize!\n");
|
||||
return -1;
|
||||
}
|
||||
if (options.exit_flag) return 0;
|
||||
|
||||
std::chrono::steady_clock::time_point program_start;
|
||||
std::chrono::steady_clock::time_point program_end;
|
||||
|
||||
program_start = std::chrono::steady_clock::now();
|
||||
{
|
||||
am::morph morph;
|
||||
|
||||
// Options should be set before any operations with
|
||||
// the morph instance. However, they can be changed
|
||||
// during the run time too.
|
||||
morph.set_blob_delimiter (options.differ_blobs);
|
||||
morph.set_blob_max_size (options.blob_max_size);
|
||||
morph.set_blob_min_size (options.blob_min_size);
|
||||
morph.set_blob_box_grip (options.blob_box_grip);
|
||||
morph.set_blob_box_samples(options.blob_box_samples);
|
||||
morph.set_blob_threshold (options.blob_threshold);
|
||||
morph.set_blob_number (options.blob_number);
|
||||
morph.set_seed (options.seed);
|
||||
morph.set_blob_rgba_weight(options.blob_rgba_weight);
|
||||
morph.set_blob_size_weight(options.blob_size_weight);
|
||||
morph.set_blob_xy_weight (options.blob_xy_weight);
|
||||
morph.set_degeneration (options.degenerate);
|
||||
morph.set_density (options.density); // Higher than 1 sets fluid to 0.
|
||||
morph.set_motion (options.motion);
|
||||
morph.set_fading (options.fading);
|
||||
morph.set_threads (options.threads);
|
||||
morph.set_cycle_length (options.cycle_length);
|
||||
morph.set_feather (options.feather);
|
||||
morph.set_keep_background (options.keep_background);
|
||||
morph.set_finite (options.finite);
|
||||
morph.set_show_blobs (options.show_blobs);
|
||||
morph.set_fluid (options.fluid); // Higher than 0 sets density to 1.
|
||||
|
||||
if (!load_files(&morph)) return -1;
|
||||
|
||||
main_loop (&morph);
|
||||
save_files(&morph);
|
||||
}
|
||||
program_end = std::chrono::steady_clock::now();
|
||||
|
||||
if (options.verbose) {
|
||||
size_t duration = std::chrono::duration_cast<std::chrono::microseconds>(program_end - program_start).count();
|
||||
if (duration < 1000) printf("Process took %lu microseconds to finish.\n", duration);
|
||||
else if (duration < 1000000) printf("Process took %lu milliseconds to finish.\n", duration/1000);
|
||||
else printf("Process took %lu seconds to finish.\n", duration/1000000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool init(int argc, char **argv) {
|
||||
if (true == (am::get_warning()&am::WARN_POINTER_SIZE)) {
|
||||
fprintf(stderr, "Pointer size is insufficiently small.\n");
|
||||
}
|
||||
if (true == (am::get_warning()&am::WARN_PIXEL_SIZE)) {
|
||||
fprintf(stderr, "Pixel size (%lu) is larger than optimal (%lu).\n",
|
||||
sizeof(am::pixel),
|
||||
sizeof(void *)
|
||||
);
|
||||
}
|
||||
if (true == (am::get_warning()&am::WARN_POINT_SIZE)) {
|
||||
fprintf(stderr, "Point size (%lu) is larger than optimal (%lu).\n",
|
||||
sizeof(am::point),
|
||||
sizeof(void *)
|
||||
);
|
||||
}
|
||||
if (am::uses_opencv()) {
|
||||
fprintf(stderr, "Experimental OpenCV optimizations are enabled.\n");
|
||||
}
|
||||
if (argc == 1) {
|
||||
fprintf(stderr, "No arguments specified, try \"\e[1;33m%s --help\e[0m\".\n", argv[0]);
|
||||
}
|
||||
return options.parse(argc, argv);
|
||||
}
|
||||
|
||||
bool fill_morph(am::morph *morph, size_t frame, std::vector<unsigned char> *image, unsigned width) {
|
||||
size_t sz = image->size();
|
||||
unsigned char r=0,g=0,b=0,a=0;
|
||||
size_t pixel = 0;
|
||||
bool empty = true;
|
||||
|
||||
if (width > UINT16_MAX) return false;
|
||||
|
||||
for (size_t j=0; j<sz; ++j) {
|
||||
switch (j%4) {
|
||||
case 0: r=image->at(j); break;
|
||||
case 1: g=image->at(j); break;
|
||||
case 2: b=image->at(j); break;
|
||||
default: {
|
||||
a=image->at(j);
|
||||
|
||||
if (a == 0) {
|
||||
pixel++;
|
||||
continue;
|
||||
}
|
||||
morph->add_pixel(frame, am::create_pixel(pixel%width, pixel/width, r, g, b, a));
|
||||
pixel++;
|
||||
empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty) morph->add_frame(frame);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_files(am::morph *morph) {
|
||||
for (unsigned f = 0; f<options.frames_out; ++f) {
|
||||
write_image(morph, f, morph->get_width(), morph->get_height());
|
||||
}
|
||||
}
|
||||
|
||||
bool load_files(am::morph *morph) {
|
||||
std::string buf;
|
||||
std::vector<unsigned char> image;
|
||||
unsigned width, height, error;
|
||||
unsigned max_width=0, max_height=0;
|
||||
image.reserve(262144);
|
||||
size_t i;
|
||||
|
||||
// Load input image files:
|
||||
for (i=0; i<options.files.size(); ++i) {
|
||||
buf=options.indir; buf.append("/"); buf.append(options.files[i]);
|
||||
|
||||
if (options.verbose) printf("Loading %-30s ... ", buf.c_str());
|
||||
error = lodepng::decode(image, width, height, buf.c_str());
|
||||
if (error) {
|
||||
std::cerr << lodepng_error_text(error) << "." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
max_width = std::max(max_width, width);
|
||||
max_height = std::max(max_height, height);
|
||||
|
||||
if (options.verbose) printf("%ux%u image decoded.\n", width, height);
|
||||
|
||||
if (!fill_morph(morph, i, &image, width) && options.verbose) {
|
||||
printf("Unable to fill %lu. frame.\n", i);
|
||||
}
|
||||
|
||||
image.clear();
|
||||
}
|
||||
|
||||
if (morph->get_frame_count()==0) {
|
||||
fprintf(stderr, "Error. Morph does not contain any key frames.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
width = max_width;
|
||||
height= max_height;
|
||||
morph->set_resolution(width, height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_image(am::morph *morph, size_t frame_out, unsigned width, unsigned height) {
|
||||
double t = morph->get_time(frame_out, options.frames_out);
|
||||
|
||||
char buf[1024];
|
||||
sprintf(buf, "%s/%s_%04lu.png", options.outdir.c_str(), options.file.c_str(), frame_out+1);
|
||||
if (options.verbose) printf("Rendering %4lu. frame ... ", frame_out+1);
|
||||
|
||||
std::vector<unsigned char> image;
|
||||
image.reserve(4*width*height);
|
||||
image.resize (4*width*height, 0);
|
||||
|
||||
std::vector<am::pixel> pixels;
|
||||
morph->get_pixels(t, &pixels);
|
||||
|
||||
while (!pixels.empty()) {
|
||||
am::pixel px = pixels.back();
|
||||
pixels.pop_back();
|
||||
|
||||
size_t pos = (px.y*width + px.x)*4;
|
||||
if (pos >= image.size()) continue;
|
||||
|
||||
image[pos + 0] = px.c.r;
|
||||
image[pos + 1] = px.c.g;
|
||||
image[pos + 2] = px.c.b;
|
||||
image[pos + 3] = px.c.a;
|
||||
}
|
||||
|
||||
if (options.verbose) printf("writing %s ... ", buf);
|
||||
|
||||
unsigned error = lodepng::encode(buf, image, width, height);
|
||||
if (error) {
|
||||
if (options.verbose) printf("[\e[1;31mFAIL\e[0m]\n");
|
||||
std::cerr << "encoder error " << error << ": "<< lodepng_error_text(error) << std::endl;
|
||||
}
|
||||
else if (options.verbose) printf("[\e[1;32mDONE\e[0m]\n");
|
||||
}
|
||||
|
||||
void main_loop(am::morph *morph) {
|
||||
if (options.verbose) {
|
||||
printf("Blobifying %ux%u morph.\n", morph->get_width(), morph->get_height());
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point start,end;
|
||||
start = std::chrono::steady_clock::now();
|
||||
|
||||
size_t blob_count = 0;
|
||||
size_t largest_frame = 0;
|
||||
double last_energy = 0.0;
|
||||
size_t match_blobs = 0;
|
||||
size_t morph_atoms = 0;
|
||||
size_t fs = options.files.size();
|
||||
|
||||
while (1) {
|
||||
morph->suspend();
|
||||
morph->synchronize();
|
||||
morph->compute();
|
||||
|
||||
blob_count = 0;
|
||||
for (size_t i=0; i<fs; ++i) {
|
||||
size_t bc = morph->get_blob_count(i);
|
||||
if (bc >= blob_count) {
|
||||
blob_count = bc;
|
||||
largest_frame = i;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned morph_state = morph->get_state();
|
||||
|
||||
end = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() > 1000) {
|
||||
if (morph_state == am::STATE_BLOB_DETECTION
|
||||
|| morph_state == am::STATE_BLOB_UNIFICATION) {
|
||||
if (options.verbose) {
|
||||
printf("%lu/%lu blob%s remaining on frame %lu.\n",
|
||||
blob_count, morph->get_blob_count(),
|
||||
blob_count == 1 ? "" : "s", largest_frame
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (morph_state == am::STATE_BLOB_MATCHING) {
|
||||
if (options.verbose) {
|
||||
double e = morph->get_energy();
|
||||
if (last_energy < e) printf("Matching blobs, absolute energy was %30.10f.\n", e);
|
||||
else printf("Matching blobs, energy decreased by %30.10f.\n", last_energy - e);
|
||||
last_energy = e;
|
||||
}
|
||||
if (++match_blobs >= options.match_time) {
|
||||
morph->next_state();
|
||||
last_energy = 0.0;
|
||||
}
|
||||
}
|
||||
else if (morph_state == am::STATE_ATOM_MORPHING) {
|
||||
if (options.verbose) {
|
||||
double e = morph->get_energy();
|
||||
if (last_energy < e) printf("Matching atoms, absolute energy was %30.2f.\n", e);
|
||||
else printf("Matching atoms, energy decreased by %30.2f.\n", last_energy - e);
|
||||
last_energy = e;
|
||||
}
|
||||
if (++morph_atoms >= options.morph_time) {
|
||||
morph->next_state();
|
||||
}
|
||||
}
|
||||
else if (morph_state == am::STATE_DONE) {
|
||||
if (options.verbose) printf("All done!\n");
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (options.verbose) printf("Unknown state!\n");
|
||||
break;
|
||||
}
|
||||
start = std::chrono::steady_clock::now();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
printf("Frame %lu had the most blobs (%lu).\n", largest_frame, blob_count);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* AtoMorph - Command line program for morphing images with AtoMorph Library.
|
||||
* See Copyright Notice at the end of this file.
|
||||
*/
|
||||
|
||||
#include "../atomorph.h"
|
||||
#include "options.h"
|
||||
|
||||
bool init (int argc, char **argv);
|
||||
bool fill_morph (am::morph *morph, size_t frame, std::vector<unsigned char> *image, unsigned width);
|
||||
void write_image (am::morph *morph, size_t frame, unsigned width, unsigned height);
|
||||
bool load_files (am::morph *morph);
|
||||
void save_files (am::morph *morph);
|
||||
void main_loop (am::morph *morph);
|
||||
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 Erich Erstu
|
||||
|
||||
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.
|
||||
*/
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* See Copyright Notice in main.h
|
||||
*/
|
||||
#include <getopt.h>
|
||||
|
||||
class MORPH_OPTIONS {
|
||||
public:
|
||||
MORPH_OPTIONS() {}
|
||||
~MORPH_OPTIONS() {}
|
||||
|
||||
const int AS_TEXTURE = am::TEXTURE;
|
||||
const int AS_AVERAGE = am::AVERAGE;
|
||||
const int AS_DISTINCT= am::DISTINCT;
|
||||
|
||||
const int BY_RGB = am::RGB;
|
||||
const int BY_HSP = am::HSP;
|
||||
|
||||
const int MOTION_NONE = am::NONE;
|
||||
const int MOTION_LINEAR = am::LINEAR;
|
||||
const int MOTION_SPLINE = am::SPLINE;
|
||||
|
||||
const int FADING_NONE = am::NONE;
|
||||
const int FADING_LINEAR = am::LINEAR;
|
||||
const int FADING_COSINE = am::COSINE;
|
||||
const int FADING_PERLIN = am::PERLIN;
|
||||
|
||||
int verbose = 0;
|
||||
int blend_blobs = 0;
|
||||
int finite = 0;
|
||||
int keep_background= 0;
|
||||
int show_blobs = AS_TEXTURE;
|
||||
int differ_blobs = BY_HSP;
|
||||
int motion = MOTION_SPLINE;
|
||||
int fading = FADING_PERLIN;
|
||||
int exit_flag = 0;
|
||||
std::string name = "";
|
||||
std::string indir = ".";
|
||||
std::string outdir = ".";
|
||||
std::string file = "untitled_morph";
|
||||
int height = 0;
|
||||
int width = 0;
|
||||
unsigned seed = 0;
|
||||
unsigned frames_out= 0;
|
||||
unsigned match_time= 1;
|
||||
unsigned morph_time= 3;
|
||||
unsigned feather = 0;
|
||||
unsigned fluid = 0;
|
||||
|
||||
unsigned cycle_length=100000;
|
||||
unsigned threads =8;
|
||||
|
||||
size_t blob_number = 1;
|
||||
size_t blob_max_size = SIZE_MAX;
|
||||
size_t blob_min_size = 1;
|
||||
uint16_t blob_box_grip = UINT16_MAX;
|
||||
size_t blob_box_samples = 100;
|
||||
double blob_threshold = 1.0;
|
||||
unsigned char blob_rgba_weight = 1;
|
||||
unsigned char blob_size_weight = 1;
|
||||
unsigned char blob_xy_weight = 1;
|
||||
size_t degenerate = 10000;
|
||||
uint16_t density = 2;
|
||||
|
||||
bool frames_out_defined = false;
|
||||
|
||||
std::vector<std::string> files;
|
||||
|
||||
inline void print_usage (FILE* stream) {
|
||||
fprintf (stream, "Usage: %s [options] <input files...>\n", name.c_str());
|
||||
fprintf (stream, "Example: %s data/otu_1.png data/otu_2.png data/otu_3.png --verbose\n", name.c_str());
|
||||
fprintf (stream, "Options:\n");
|
||||
fprintf (stream,
|
||||
" --blend-blobs Blob intersections are blended together.\n"
|
||||
" -E --blob-feather INT Number of gradually transparent outer layers.\n"
|
||||
" -c --blob-rgba-weight[0-255] Importance of a blob's color when matching.\n"
|
||||
" -z --blob-size-weight[0-255] Importance of a blob's size when matching.\n"
|
||||
" -p --blob-xy-weight [0-255] Importance of a blob's location when matching.\n"
|
||||
" -b --blobs INT Preferred number of blobs to keep.\n"
|
||||
" --blobs-as-texture A blob is textured by its pixels (default).\n"
|
||||
" --blobs-as-average A blob has the average color of its pixels.\n"
|
||||
" --blobs-as-distinct A blob has a random color.\n"
|
||||
" --blobs-by-rgb Differentiate blobs by RGB distance.\n"
|
||||
" --blobs-by-hsp Differentiate blobs by HSP distance (default).\n"
|
||||
" -B --blobs-max-size INT Maximum size of a single blob in pixels.\n"
|
||||
" -m --blobs-min-size INT Minimum size of a single blob in pixels.\n"
|
||||
" -g --blobs-box-grip INT Unifying grip bounds for undersized blobs.\n"
|
||||
" -S --blobs-box-samples INT Number of samples to take for each dust box.\n"
|
||||
" -t --blobs-threshold [0-255] Maximum color difference for merging blobs.\n"
|
||||
" --brief Print brief messages (default).\n"
|
||||
" -C --cycle-length INT Number of iterations per morph cycle.\n"
|
||||
" -d --degenerate INT Degeneration period for energy minimzation.\n"
|
||||
" -D --density INT Number of atoms per pixel at minimum.\n"
|
||||
" --fading-none Colours are not interpolated.\n"
|
||||
" --fading-linear Colours are interpolated linearly.\n"
|
||||
" --fading-cosine Colours are cosine interpolated.\n"
|
||||
" --fading-perlin Use Perlin noise colour transition (default).\n"
|
||||
" -f --file STR Output files with prefix.\n"
|
||||
" --finite Morph will not repeat itself seamlessly.\n"
|
||||
" -L --fluid INT Number of fluid simulation steps per frame.\n"
|
||||
" -F --frames INT Number of frames to generate.\n"
|
||||
" -h --help Display this usage information.\n"
|
||||
" -i --indir STR Read input from this directory.\n"
|
||||
" --keep-background Morph background is cross-dissolved.\n"
|
||||
" -M --match-time INT Time in seconds given for blob matching.\n"
|
||||
" -O --morph-time INT Time in seconds given for atom morphing.\n"
|
||||
" --motion-none Positions are not interpolated.\n"
|
||||
" --motion-linear Positions are linearly interpolated.\n"
|
||||
" --motion-spline Uses Catmull-Rom splines (default).\n"
|
||||
" -o --outdir STR Write output to this directory.\n"
|
||||
" -s --seed INT Seed for the random number generator.\n"
|
||||
" -T --threads INT Number of worker threads to spawn.\n"
|
||||
" --verbose Print verbose messages.\n"
|
||||
" -v --version Show version information.\n"
|
||||
);
|
||||
}
|
||||
|
||||
inline bool parse(int argc, char **argv) {
|
||||
int c;
|
||||
name = argv[0];
|
||||
while (1) {
|
||||
static struct option long_options[] = {
|
||||
// These options set a flag:
|
||||
{"verbose", no_argument, &verbose, 1 },
|
||||
{"blend-blobs", no_argument, &blend_blobs, 1 },
|
||||
{"finite", no_argument, &finite, 1 },
|
||||
{"blobs-as-texture", no_argument, &show_blobs, AS_TEXTURE },
|
||||
{"blobs-as-average", no_argument, &show_blobs, AS_AVERAGE },
|
||||
{"blobs-as-distinct", no_argument, &show_blobs, AS_DISTINCT },
|
||||
{"blobs-by-rgb", no_argument, &differ_blobs, BY_RGB },
|
||||
{"blobs-by-hsp", no_argument, &differ_blobs, BY_HSP },
|
||||
{"brief", no_argument, &verbose, 0 },
|
||||
{"keep-background", no_argument, &keep_background, 1 },
|
||||
// These options don't set a flag. We distinguish them by their indices:
|
||||
{"blob-feather", required_argument, 0, 'E'},
|
||||
{"blob-rgba-weight", required_argument, 0, 'c'},
|
||||
{"blob-size-weight", required_argument, 0, 'z'},
|
||||
{"blob-xy-weight", required_argument, 0, 'p'},
|
||||
{"blobs", required_argument, 0, 'b'},
|
||||
{"blobs-max-size", required_argument, 0, 'B'},
|
||||
{"blobs-min-size", required_argument, 0, 'm'},
|
||||
{"blobs-box-grip", required_argument, 0, 'g'},
|
||||
{"blobs-box-samples", required_argument, 0, 'S'},
|
||||
{"blobs-threshold", required_argument, 0, 't'},
|
||||
{"cycle-length", required_argument, 0, 'C'},
|
||||
{"degenerate", required_argument, 0, 'd'},
|
||||
{"density", required_argument, 0, 'D'},
|
||||
{"fading-none", no_argument, &fading, FADING_NONE },
|
||||
{"fading-linear", no_argument, &fading, FADING_LINEAR },
|
||||
{"fading-cosine", no_argument, &fading, FADING_COSINE },
|
||||
{"fading-perlin", no_argument, &fading, FADING_PERLIN },
|
||||
{"file", required_argument, 0, 'f'},
|
||||
{"fluid", required_argument, 0, 'L'},
|
||||
{"frames", required_argument, 0, 'F'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"indir", required_argument, 0, 'i'},
|
||||
{"match-time", required_argument, 0, 'M'},
|
||||
{"morph-time", required_argument, 0, 'O'},
|
||||
{"motion-none", no_argument, &motion, MOTION_NONE },
|
||||
{"motion-linear", no_argument, &motion, MOTION_LINEAR },
|
||||
{"motion-spline", no_argument, &motion, MOTION_SPLINE },
|
||||
{"outdir", required_argument, 0, 'o'},
|
||||
{"seed", required_argument, 0, 's'},
|
||||
{"threads", required_argument, 0, 'T'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
// getopt_long stores the option index here.
|
||||
int option_index = 0;
|
||||
|
||||
c = getopt_long(argc, argv, "b:B:c:C:d:D:E:m:M:O:g:p:S:t:T:f:L:F:hi:o:s:vz:", long_options, &option_index);
|
||||
|
||||
/* Detect the end of the options. */
|
||||
if (c == -1) break;
|
||||
|
||||
switch (c) {
|
||||
case 0: // If this option set a flag, do nothing else now.
|
||||
if (long_options[option_index].flag != 0) break;
|
||||
printf ("option %s", long_options[option_index].name);
|
||||
if (optarg) printf(" with arg %s", optarg); printf ("\n");
|
||||
break;
|
||||
case 'E': feather = atoi(optarg); break;
|
||||
case 'c': blob_rgba_weight= std::min(atoi(optarg),255); break;
|
||||
case 'z': blob_size_weight= std::min(atoi(optarg),255); break;
|
||||
case 'p': blob_xy_weight = std::min(atoi(optarg),255); break;
|
||||
case 'b': blob_number = (size_t) atoi(optarg); break;
|
||||
case 'B': blob_max_size = atoi(optarg); break;
|
||||
case 'C': cycle_length = atoi(optarg); break;
|
||||
case 'd': degenerate = (size_t) atoi(optarg); break;
|
||||
case 'D': density = (uint16_t) atoi(optarg); break;
|
||||
case 'm': blob_min_size = atoi(optarg); break;
|
||||
case 'M': match_time = atoi(optarg); break;
|
||||
case 'O': morph_time = atoi(optarg); break;
|
||||
case 'g': blob_box_grip = atoi(optarg); break;
|
||||
case 'S': blob_box_samples= atoi(optarg); break;
|
||||
case 't': blob_threshold = std::min(atoi(optarg),255)/255.0; break;
|
||||
case 's': seed = atoi(optarg); break;
|
||||
case 'T': threads = std::min(atoi(optarg),1024); break;
|
||||
case 'L': fluid = atoi(optarg); break;
|
||||
case 'F': frames_out = atoi(optarg); break;
|
||||
case 'i': indir = optarg; break;
|
||||
case 'o': outdir = optarg; break;
|
||||
case 'f': file = optarg; break;
|
||||
case 'h': print_usage(stdout); exit_flag = 1; break;
|
||||
case 'v':
|
||||
printf("AtoMorph %s Copyright (C) 2013-2014 Erich Erstu\n", am::get_version());
|
||||
exit_flag = 1;
|
||||
break;
|
||||
case '?':
|
||||
/* getopt_long already printed an error message. */
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (optind < argc) {
|
||||
files.push_back(argv[optind++]);
|
||||
}
|
||||
if (frames_out == 0) {
|
||||
if (files.size() > 1) frames_out = files.size()*2;
|
||||
else frames_out = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
ImageMagick is needed for these tests to run properly:
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install imagemagick --fix-missing
|
||||
|
||||
The completion of all of the tests requires approximately 10 minutes.
|
||||
However, some of the individual tests finish within seconds. Feel free
|
||||
to examine the test scripts and modify the parameters.
|
||||
|
After Width: | Height: | Size: 628 B |
After Width: | Height: | Size: 402 B |
After Width: | Height: | Size: 385 B |
After Width: | Height: | Size: 417 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 942 B |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 932 B |
After Width: | Height: | Size: 999 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 18 KiB |