├── .gitignore ├── Makefile ├── gen.cpp ├── img.cpp ├── img.h ├── kcoherence.h ├── misc └── htmlgen.pl └── tiles ├── crater.png ├── get_tile.pl ├── grass.png ├── grass2.png ├── ice.png ├── lava.png ├── mount.png ├── sand.png ├── snow.png ├── snow2.png ├── stone.png ├── swamp.png ├── tech.png ├── tilesheet.png └── water.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | out* 3 | gen 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -I/opt/local/include -Wall -O3 -g 2 | LDFLAGS = -L/opt/local/lib -lgd 3 | 4 | SRCS = $(wildcard *.cpp) 5 | 6 | OBJS = $(SRCS:.cpp=.o) 7 | 8 | gen: $(OBJS) 9 | g++ -o gen $(OBJS) $(LDFLAGS) 10 | 11 | .PHONY: clean 12 | 13 | clean:: 14 | rm -f gen *.o out.png 15 | 16 | gen.o: kcoherence.h 17 | 18 | -------------------------------------------------------------------------------- /gen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "img.h" 5 | #include "kcoherence.h" 6 | 7 | // maximum generator iterations 8 | #define ITERATIONS 16 9 | // k-coherence search size 10 | #define _K_ 6 11 | // number of similar versions to generate 12 | #define VERSIONS 8 13 | 14 | // neighborhood difference 15 | // Nsize is the radius of pixels surrounding the source pixel. 16 | // e.g. Nsize=2: 17 | // N N N N N 18 | // N N N N N 19 | // N N p N N 20 | // N N N N N 21 | // N N N N N 22 | unsigned neighbor_diff(Img *im1, Img *im2, int x1, int y1, int x2, int y2, int Nsize) 23 | { 24 | unsigned diff=0; 25 | int j1 = im1->wraph(y1-Nsize); 26 | int j2 = im2->wraph(y2-Nsize); 27 | int i1 = im1->wrapw(x1-Nsize); 28 | int i2 = im2->wrapw(x2-Nsize); 29 | int ywrap1 = im1->h - j1; 30 | int ywrap2 = im2->h - j2; 31 | for(int j=Nsize*2+1;j--;) { 32 | int idx1 = i1 + j1*im1->w; 33 | int idx2 = i2 + j2*im2->w; 34 | int xwrap1 = im1->w - i1; 35 | int xwrap2 = im2->w - i2; 36 | //printf("xy1=%d,%d xy2=%d,%d ij1=%d,%d ij2=%d,%d\n", x1,y1, x2,y2, i1,j2, i2,j2); 37 | for(int i=Nsize*2+1;i--;) { 38 | //printf("idx1=%d idx2=%d\n", idx1, idx2); 39 | diff += Img::normsqr(im1->p(idx1++), im2->p(idx2++)); 40 | xwrap1--; if(!xwrap1) { idx1 -= im1->w; } 41 | xwrap2--; if(!xwrap2) { idx2 -= im2->w; } 42 | } 43 | j1++; j2++; 44 | ywrap1--; if(!ywrap1) { j1 -= im1->h; } 45 | ywrap2--; if(!ywrap2) { j2 -= im2->h; } 46 | } 47 | return diff; 48 | } 49 | 50 | #if 0 51 | // find nearest neighborhood in srcim corresponding to a given block in dstim 52 | // returns an index 53 | unsigned nn_search(Img *srcim, Img *dstim, int di, int dj, int Nsize, bool srcwrap, unsigned &e) 54 | { 55 | unsigned bestdiff = ~0; 56 | unsigned bestidx = 0; 57 | int inset = srcwrap ? 0 : Nsize; 58 | for(int y=inset; y < srcim->h - inset; y++) { 59 | for(int x=inset; x < srcim->w - inset; x++) { 60 | unsigned diff = neighbor_diff(srcim, dstim, x,y, di,dj, Nsize); 61 | if(diff < bestdiff) { 62 | bestdiff = diff; 63 | bestidx = srcim->ij_to_idx(x,y); 64 | } 65 | } 66 | } 67 | e = bestdiff; 68 | return bestidx; 69 | } 70 | #endif 71 | 72 | 73 | // find nearest 74 | Kcoherence<_K_> nn_search(Img *srcim, int si, int sj, int Nsize, bool srcwrap) 75 | { 76 | int inset = srcwrap ? 0 : Nsize; 77 | Kcoherence<_K_> best; 78 | for(int y=inset; y < srcim->h - inset; y++) { 79 | for(int x=inset; x < srcim->w - inset; x++) { 80 | //if(x == si && y == sj) 81 | // continue; 82 | unsigned diff = neighbor_diff(srcim, srcim, x,y, si,sj, Nsize); 83 | // printf("sidx %d didx %d diff = %u\n", srcim->ij_to_idx(si,sj), srcim->ij_to_idx(x,y), diff); 84 | best.insert(srcim->ij_to_idx(x,y), diff); 85 | } 86 | } 87 | return best; 88 | } 89 | 90 | template 91 | class TextureSynth 92 | { 93 | public: 94 | Img *srcim, *dstim; 95 | Kcoherence *srck; 96 | Img *dstz, *dsto; // z array in M step, and origin of current pixel in E step 97 | bool srcwrap; 98 | int Nsize; 99 | 100 | TextureSynth(Img *_src, int dstx, int dsty, int _Nsize, bool _srcwrap) { 101 | srcim = _src; 102 | dstim = new Img(dstx, dsty); 103 | dstz = new Img(dstx, dsty); 104 | dsto = new Img(dstx, dsty); 105 | srcwrap = _srcwrap; 106 | Nsize = _Nsize; 107 | analyze_srcimg(); 108 | } 109 | 110 | void analyze_srcimg() 111 | { 112 | printf("analyzing source img..."); fflush(stdout); 113 | srck = new Kcoherence[srcim->w*srcim->h]; 114 | int inset = srcwrap ? 0 : Nsize; 115 | for(int j=inset;jh-inset;j++) 116 | for(int i=inset;iw-inset;i++) { 117 | int idx = srcim->ij_to_idx(i,j); 118 | srck[idx] = ::nn_search(srcim, i,j, Nsize, srcwrap); 119 | #if 0 120 | printf("%d: ", idx); 121 | for(int k=0;kh;j++) 132 | for(int i=offset;iw;i++) { 133 | int x = rand()%srcim->w; 134 | int y = rand()%srcim->h; 135 | dstz->p(i,j) = srcim->ij_to_idx(x,y); 136 | x = rand()%srcim->w; 137 | y = rand()%srcim->h; 138 | //dsto->p(i,j) = srcim->ij_to_idx(x,y); 139 | dstim->p(i,j) = srcim->p(x,y); 140 | } 141 | } 142 | 143 | // nearest neighbor search, using a given coherence set 144 | void nn_search(Img *destim, int di, int dj, const Kcoherence &kset, 145 | int offx, int offy, 146 | unsigned &bestdiff,unsigned &bestidx) 147 | { 148 | for(int k=0;kidx_to_ij(kset[k],i,j); 151 | i = srcim->wrapw(i+offx); 152 | j = srcim->wraph(j+offy); 153 | if(!srcwrap && (i=srcim->w-Nsize)) 154 | continue; 155 | if(!srcwrap && (j=srcim->h-Nsize)) 156 | continue; 157 | unsigned diff = neighbor_diff(destim, srcim, di,dj, i,j, Nsize); 158 | if(diff < bestdiff) { 159 | bestdiff = diff; 160 | bestidx = srcim->ij_to_idx(i,j); 161 | } 162 | } 163 | } 164 | 165 | unsigned estep(int offset) { 166 | unsigned E=0; 167 | for(int j=offset;jh;j++) 168 | for(int i=offset;iw;i++) { 169 | unsigned bestdiff = ~0, bestidx = 0; 170 | 171 | // x,y are the target patch (in the z array) 172 | //int x,y; srcim->idx_to_ij(dstz->p(i,j),x,y); 173 | 174 | // we search our candidate set k from the pixel's current source 175 | nn_search(dstim, i,j, srck[dstz->p(i,j)], 0,0, bestdiff, bestidx); 176 | E += bestdiff; 177 | dstim->p(i,j) = srcim->p(bestidx); 178 | dstz->p(i,j) = bestidx; 179 | } 180 | return E; 181 | } 182 | 183 | unsigned mstep(int offset) { 184 | unsigned E=0; 185 | bool changed = false; 186 | for(int j=offset;jh;j++) 187 | for(int i=offset;iw;i++) { 188 | // search k set of all neighborhood pixels to find best matching 189 | // neighborhood between src and dest(i,j) 190 | unsigned bestdiff = ~0, bestidx = 0; 191 | for(int nj=-Nsize;nj<=Nsize;nj++) 192 | for(int ni=-Nsize;ni<=Nsize;ni++) { 193 | int x=dstim->wrapw(i+ni), y=dstim->wraph(j+nj); 194 | nn_search(dstim, i,j, srck[dstz->p(x,y)], -ni,-nj, bestdiff, bestidx); 195 | } 196 | 197 | E += bestdiff; 198 | if(dstz->p(i,j) != bestidx) { 199 | changed = true; 200 | dstz->p(i,j) = bestidx; 201 | } 202 | dstim->p(i,j) = srcim->p(bestidx); 203 | } 204 | if(!changed) 205 | return 0; 206 | return E; 207 | } 208 | 209 | ~TextureSynth() { delete srcim; delete dstim; } 210 | }; 211 | 212 | int main(int argc, char **argv) 213 | { 214 | if(argc < 6) { 215 | printf("usage: %s \n", argv[0]); 216 | return -1; 217 | } 218 | srand(time(NULL)); 219 | 220 | Img *srcim = Img::load_png(argv[1]); 221 | printf("loaded %dx%d source\n", srcim->w, srcim->h); 222 | 223 | int w = atoi(argv[4]); 224 | int h = atoi(argv[5]); 225 | TextureSynth<_K_> synth(srcim, w, h, atoi(argv[3]), atoi(argv[2])?true:false); 226 | printf("synthesizing %dx%d with neighborhood=%d (%d^2), srcwrap=%s\n", w,h, 227 | synth.Nsize, synth.Nsize*2+1, 228 | synth.srcwrap ? "on" : "off"); 229 | int keeprows = (synth.Nsize+3)/2; 230 | 231 | for(int version=0;versionsave_png(buf); 245 | printf(" done\n"); 246 | 247 | } 248 | return 0; 249 | } 250 | 251 | -------------------------------------------------------------------------------- /img.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "img.h" 5 | 6 | Img* Img::load_png(const char *fname) 7 | { 8 | FILE *fp = fopen(fname, "rb"); 9 | if(!fp) { 10 | printf("can't open %s\n", fname); 11 | exit(-1); 12 | } 13 | gdImagePtr im = gdImageCreateFromPng(fp); 14 | fclose(fp); 15 | 16 | // load 17 | Img *img = new Img(gdImageSX(im), gdImageSY(im)); 18 | for(int j=0;jh;j++) { 19 | for(int i=0;iw;i++) { 20 | int c = gdImageGetPixel(im, i, j); 21 | img->p(i,j) = ((gdImageRed(im, c)<<16) + 22 | (gdImageGreen(im, c)<<8) + 23 | gdImageBlue(im, c)); 24 | } 25 | } 26 | gdImageDestroy(im); 27 | return img; 28 | } 29 | 30 | bool Img::save_png(const char *fname) 31 | { 32 | FILE *fp = fopen(fname, "wb"); 33 | if(!fp) { 34 | printf("cannot open %s\n", fname); 35 | return false; 36 | } 37 | 38 | gdImagePtr im = gdImageCreateTrueColor(w,h); 39 | 40 | for(int j=0;j>16, (c>>8)&255, c&255)); 44 | } 45 | } 46 | 47 | gdImagePng(im, fp); 48 | fclose(fp); 49 | gdImageDestroy(im); 50 | 51 | return true; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /img.h: -------------------------------------------------------------------------------- 1 | class Img 2 | { 3 | public: 4 | unsigned *_buf; 5 | int w, h; 6 | 7 | Img(int w, int h) { 8 | _buf = new unsigned[w*h]; 9 | this->w = w; this->h = h; 10 | } 11 | 12 | ~Img() { delete[] _buf; } 13 | // reference pixel at location 14 | unsigned& p(int i, int j) { return _buf[i+j*w]; } 15 | unsigned& p(int idx) { return _buf[idx]; } 16 | 17 | unsigned ij_to_idx(int i, int j) { return i+j*w; } 18 | void idx_to_ij(int idx, int &i, int &j) { i = idx%w; j = idx/w; } 19 | 20 | int wrapw(int i) { return (i+w)%w; } 21 | int wraph(int j) { return (j+h)%h; } 22 | 23 | static Img* load_png(const char *fname); 24 | bool save_png(const char *fname); 25 | 26 | // |c1 - c2|^2 in RGB distance 27 | static unsigned normsqr(unsigned c1, unsigned c2) { 28 | int dr = (c1>>16) - (c2>>16); 29 | int dg = ((c1>>8)&255) - ((c2>>8)&255); 30 | int db = (c1&255) - (c2&255); 31 | return dr*dr + dg*dg + db*db; 32 | } 33 | }; 34 | 35 | 36 | -------------------------------------------------------------------------------- /kcoherence.h: -------------------------------------------------------------------------------- 1 | template 2 | class Kcoherence 3 | { 4 | public: 5 | unsigned _idx[N]; 6 | unsigned _err[N]; 7 | int n; 8 | 9 | Kcoherence() { n=0; } 10 | void insert(unsigned idx, unsigned err) { 11 | // error is already greater than any member 12 | if(n==N && err > _err[n-1]) 13 | return; 14 | if(n=0;i--) { 20 | if(_err[i] > _err[i+1]) { 21 | unsigned t = _err[i]; _err[i] = _err[i+1]; _err[i+1] = t; 22 | t = _idx[i]; _idx[i] = _idx[i+1]; _idx[i+1] = t; 23 | } 24 | } 25 | #if 0 26 | printf("insert: "); 27 | for(int i=0;i', int(rand(8))); 4 | } 5 | printf("
\n"); 6 | } 7 | 8 | print("
\n"); 9 | print(' -> '); 10 | for($i=0;$i<8;$i++) { 11 | printf(' ', $i); 12 | } 13 | printf("
\n"); 14 | -------------------------------------------------------------------------------- /tiles/crater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/crater.png -------------------------------------------------------------------------------- /tiles/get_tile.pl: -------------------------------------------------------------------------------- 1 | #!/opt/local/bin/perl -w 2 | 3 | use strict; 4 | use GD; 5 | 6 | my $ts = new GD::Image("tilesheet.png") or die "can't open tilesheet"; 7 | 8 | my $im = new GD::Image(20,20); 9 | 10 | sub import_tile 11 | { 12 | my ($dst,$src,$tileidx) = @_; 13 | my $srcx = ($tileidx&31)*20; 14 | my $srcy = ($tileidx>>5)*20; 15 | $dst->copy($src, 0, 0, $srcx, $srcy, 20, 20); 16 | } 17 | 18 | import_tile($im, $ts, $ARGV[0]); 19 | 20 | open my $png, '>', 'in.png'; 21 | print $png $im->png; 22 | close $png; 23 | 24 | -------------------------------------------------------------------------------- /tiles/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/grass.png -------------------------------------------------------------------------------- /tiles/grass2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/grass2.png -------------------------------------------------------------------------------- /tiles/ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/ice.png -------------------------------------------------------------------------------- /tiles/lava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/lava.png -------------------------------------------------------------------------------- /tiles/mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/mount.png -------------------------------------------------------------------------------- /tiles/sand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/sand.png -------------------------------------------------------------------------------- /tiles/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/snow.png -------------------------------------------------------------------------------- /tiles/snow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/snow2.png -------------------------------------------------------------------------------- /tiles/stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/stone.png -------------------------------------------------------------------------------- /tiles/swamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/swamp.png -------------------------------------------------------------------------------- /tiles/tech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/tech.png -------------------------------------------------------------------------------- /tiles/tilesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/tilesheet.png -------------------------------------------------------------------------------- /tiles/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a1k0n/texturesynth/f20020596db799ce7c55d28ba9ada244603b993e/tiles/water.png --------------------------------------------------------------------------------