1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
| /**
* @author Xavier Philippeau
*
* TextureSynthesis (Mono scale)
*
*/
public class TextureSynthesis {
// class NeighborhoodCache: precomputed "Neighborhood" value (rgb) around a pixel
private class NeighborhoodCache extends ArrayList<int[]> {};
// size of the half-window used as neighborhood
private int neighborhoodHalfSize = 0;
// precomputed "Neighborhood" for all pixels in the sample
private NeighborhoodCache neighboroodCache = null;;
/**
* Constructor
*
* @param neighborhoodHalfSize size of the half-window used as neighborhood
*/
public TextureSynthesis(int neighborhoodHalfSize) {
this.neighborhoodHalfSize = neighborhoodHalfSize;
}
/**
* Create, then initialize the output image with pseudo-random values
*
* @param input the input sample
* @param width the width of the output image to create
* @param height the height of the output image to create
* @return the output image
*/
private BufferedImage initialize(BufferedImage input, int width, int height) {
BufferedImage output = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Random rand = new Random(width+height);
for(int y=0;y<height;y++) {
for(int x=0;x<width;x++) {
// only the right & bottow bands are needed due to
// the toric boundary in neighborhood computation
if (x<width-neighborhoodHalfSize && y<height-neighborhoodHalfSize) {
continue;
}
// "scanline distance" in output
int d = x+y*width;
// corresponding "scanline distance" in input
int ds = d % (input.getWidth()*input.getHeight());
int ys = ds/input.getWidth();
int xs = ds-ys*input.getWidth();
// copy input to output
output.setRGB(x, y, input.getRGB(xs,ys));
}
}
return output;
}
/**
* Return the neighborhood list of pixels
*
* @param img the image to explore
* @param x coordinate x of the pixel
* @param y coordinate y of the pixel
* @return neighborhood of pixel(x,y)
*/
private int[] neighborhood(BufferedImage img, int x, int y) {
int width = img.getWidth();
int height = img.getHeight();
// neighborhood = previous pixel in scanline order
//
// X X X X X
// X X X X X
// X X O . .
// . . . . .
// . . . . .
int halfsize = this.neighborhoodHalfSize;
int neighborhood_size = (2*halfsize+1)*(halfsize) + halfsize;
int[] neighborhood = new int[neighborhood_size];
int index=0;
//ArrayList<Integer> neighborhood = new ArrayList<Integer>();
for(int dy=-halfsize; dy<=0; dy++) {
for(int dx=-halfsize; dx<=halfsize; dx++) {
int xk = x + dx;
int yk = y + dy;
// Shape constraint
if (dy==0 && dx==0) break;
// toric boundary
if (xk<0) xk=width+xk;
if (yk<0) yk=height+yk;
if (xk>=width) xk=xk-width;
if (yk>=height) yk=yk-height;
//neighborhood.add( img.getRGB(xk,yk) );
neighborhood[index++]=img.getRGB(xk,yk);
}
}
return neighborhood;
}
/**
* Square Difference of two Neighborhoods
*
* @param n1 Neighborhood 1
* @param n2 Neighborhood 2
* @param bestscore fast exit condition: exit if difference exceed this threshold
* @return the Square Difference
*/
private double squareDiff(int[] n1, int[] n2, double bestscore) {
double squareDiff = 0;
for(int i=0;i<n1.length;i++) {
int rgb1 = n1[i], rgb2=n2[i];
double dr = ((rgb1>>16)&0xFF)-((rgb2>>16)&0xFF);
double dg = ((rgb1>>8)&0xFF)-((rgb2>>8)&0xFF);
double db = ((rgb1>>0)&0xFF)-((rgb2>>0)&0xFF);
squareDiff += dr*dr + dg*dg + db*db;
// fast exit condition
if (squareDiff>=bestscore) break;
}
return squareDiff;
}
/**
*
* Precompute Neighborhood for all pixels in the image
*
* @param img the image to explore
* @return the list of Neighborhood
*/
private NeighborhoodCache precomputeNeighborhood(BufferedImage img) {
NeighborhoodCache cache = new NeighborhoodCache();
for(int y=0;y<img.getHeight();y++) {
for(int x=0;x<img.getWidth();x++) {
int[] nb = neighborhood(img,x,y);
cache.add(y*img.getWidth()+x,nb);
}
}
return cache;
}
/**
* compute the value of one pixel
*
* @param sample Image reference
* @param output Image computed
* @param x coordinate x of the pixel
* @param y coordinate y of the pixel
* @return (r,g,b) value of the computed pixel
*/
private int computePixel(BufferedImage sample, BufferedImage output, int x, int y) {
double bestscore = Double.MAX_VALUE;
int bestValue = 0;
// Neighborhood in the Ouput image
int[] outputNeighborhood = neighborhood(output,x,y);
// Search best matching Neighborhood in the Sample image
// (exhaustive search !)
int width = sample.getWidth();
int height = sample.getHeight();
for(int ys=0; ys<height; ys++) {
for(int xs=0; xs<width; xs++) {
// get Sample Neighborhood from the precomputed cache
int position = ys*width+xs;
int[] sampleNeighborhood = this.neighboroodCache.get(position);
// compare using square difference
double score = squareDiff(sampleNeighborhood,outputNeighborhood,bestscore);
//double score = cosDiff(sampleNeighborhood,outputNeighborhood,bestscore);
// keep the best score
if (score<bestscore) {
bestscore = score;
bestValue = sample.getRGB(xs,ys);
}
}
}
return bestValue;
}
/**
* Generate a new image of size width x height,
* using the sample image as a texture seed.
*
* @param sample The sample image
* @param outputwidth Generated image width
* @param outputheight Generated image height
* @return the generated image
*/
public BufferedImage synthesis(BufferedImage sample, int outputwidth, int outputheight) {
// initialize output with pseudo-random values
BufferedImage output = initialize(sample,outputwidth,outputheight);
// precompute the neighborhood of all pixels in the sample
this.neighboroodCache = precomputeNeighborhood(sample);
// compute each pixel of the output
for(int y=0; y<outputheight; y++) {
System.out.println(y+"/"+outputheight);
for(int x=0; x<outputwidth; x++) {
int rgb = computePixel(sample,output,x,y);
output.setRGB(x,y,rgb);
}
}
return output;
}
} |
Partager