10 | * var options = { 11 | * colored : true, // boolean 12 | * edgeBrightness : 0.3, // value from 0 to 1 13 | * colorVariations : 0.2, // value from 0 to 1 14 | * brightnessNoise : 0.3, // value from 0 to 1 15 | * saturation : 0.5 // value from 0 to 1 16 | * } 17 | * 18 | * @param {mask} 19 | * @param {options} 20 | * @class Sprite 21 | * @constructor 22 | */ 23 | class Sprite 24 | { 25 | public int width; 26 | public int height; 27 | public Mask mask; 28 | public int[] data; 29 | public bool colored; 30 | double edgeBrightness; 31 | double colorVariations; 32 | double brightnessNoise; 33 | double saturation; 34 | Random random; 35 | int SEED; 36 | 37 | public Sprite(int width, int height, Mask mask, 38 | bool colored = true, 39 | double edgeBrightness = 0.3, 40 | double colorVariations = 0.2, 41 | double brightnessNoise = 0.3, 42 | double saturation = 0.5, 43 | int SEED = 0) 44 | { 45 | this.width = mask.width * (mask.mirrorX ? 2 : 1); 46 | this.height = mask.height * (mask.mirrorY ? 2 : 1); 47 | this.mask = mask; 48 | this.data = new int[this.width * this.height]; 49 | this.colored = colored; 50 | this.edgeBrightness = edgeBrightness; 51 | this.colorVariations = colorVariations; 52 | this.brightnessNoise = brightnessNoise; 53 | this.saturation = saturation; 54 | if (SEED == 0) 55 | this.SEED = Environment.TickCount; 56 | else 57 | this.SEED = SEED; 58 | this.init(); 59 | } 60 | 61 | /** 62 | * The init method calls all functions required to generate the sprite. 63 | * 64 | * @method init 65 | * @returns {undefined} 66 | */ 67 | private void init() 68 | { 69 | this.initData(); 70 | this.applyMask(); 71 | this.generateRandomSample(); 72 | 73 | if (this.mask.mirrorX) 74 | { 75 | this.mirrorX(); 76 | } 77 | 78 | if (this.mask.mirrorY) 79 | { 80 | this.mirrorY(); 81 | } 82 | 83 | this.generateEdges(); 84 | } 85 | 86 | public int getWidth() 87 | { 88 | return width; 89 | } 90 | public int getHeight() 91 | { 92 | return height; 93 | } 94 | 95 | /** 96 | * The getData method returns the sprite template data at location (x, y) 97 | *
98 | * -1 = Always border (black) 99 | * 0 = Empty 100 | * 1 = Randomly chosen Empty/Body 101 | * 2 = Randomly chosen Border/Body 102 | * 103 | * @param {x} 104 | * @param {y} 105 | * @method getData 106 | * @returns {undefined} 107 | */ 108 | public int getData(int x, int y) 109 | { 110 | return this.data[y * this.width + x]; 111 | } 112 | 113 | 114 | /** 115 | * The setData method sets the sprite template data at location (x, y) 116 | *
117 | * -1 = Always border (black) 118 | * 0 = Empty 119 | * 1 = Randomly chosen Empty/Body 120 | * 2 = Randomly chosen Border/Body 121 | * 122 | * @param {x} 123 | * @param {y} 124 | * @param {value} 125 | * @method setData 126 | * @returns {undefined} 127 | */ 128 | public void setData(int x, int y, int value) 129 | { 130 | this.data[y * this.width + x] = value; 131 | } 132 | 133 | /** 134 | * The initData method initializes the sprite data to completely solid. 135 | * 136 | * @method initData 137 | * @returns {undefined} 138 | */ 139 | public void initData() 140 | { 141 | int h = this.height; 142 | int w = this.width; 143 | for (int y = 0; y < h; y++) 144 | { 145 | for (int x = 0; x < w; x++) 146 | { 147 | this.setData(x, y, -1); 148 | } 149 | } 150 | } 151 | 152 | /** 153 | * The mirrorX method mirrors the template data horizontally. 154 | * 155 | * @method mirrorX 156 | * @returns {undefined} 157 | */ 158 | public void mirrorX() 159 | { 160 | int h = this.height; 161 | int w = (int)Math.Floor(this.width / (double)2); 162 | for (int y = 0; y < h; y++) 163 | { 164 | for (int x = 0; x < w; x++) 165 | { 166 | this.setData(this.width - x - 1, y, this.getData(x, y)); 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * The mirrorY method mirrors the template data vertically. 173 | * 174 | * @method 175 | * @returns {undefined} 176 | */ 177 | public void mirrorY() 178 | { 179 | int h = (int)Math.Floor(this.height / (double)2); 180 | int w = this.width; 181 | for (int y = 0; y < h; y++) 182 | { 183 | for (int x = 0; x < w; x++) 184 | { 185 | this.setData(x, this.height - y - 1, this.getData(x, y)); 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * The applyMask method copies the mask data into the template data array at 192 | * location (0, 0). 193 | *
194 | * (note: the mask may be smaller than the template data array) 195 | * 196 | * @method applyMask 197 | * @returns {undefined} 198 | */ 199 | public void applyMask() 200 | { 201 | int h = this.mask.height; 202 | int w = this.mask.width; 203 | 204 | for (int y = 0; y < h; y++) 205 | { 206 | for (int x = 0; x < w; x++) 207 | { 208 | this.setData(x, y, this.mask.data[y * w + x]); 209 | } 210 | } 211 | } 212 | 213 | /** 214 | * Apply a random sample to the sprite template. 215 | *
216 | * If the template contains a 1 (internal body part) at location (x, y), then 217 | * there is a 50% chance it will be turned empty. If there is a 2, then there 218 | * is a 50% chance it will be turned into a body or border. 219 | *
220 | * (feel free to play with this logic for interesting results) 221 | * 222 | * @method generateRandomSample 223 | * @returns {undefined} 224 | */ 225 | public void generateRandomSample() 226 | { 227 | random = new Random(SEED); 228 | int h = this.height; 229 | int w = this.width; 230 | 231 | for (int y = 0; y < h; y++) 232 | { 233 | for (int x = 0; x < w; x++) 234 | { 235 | int val = this.getData(x, y); 236 | if (val == 1) 237 | { 238 | val = random.Next(0, 2); 239 | } 240 | else if (val == 2) 241 | { 242 | if (random.NextDouble() > 0.5) 243 | { 244 | val = 1; 245 | } 246 | else { 247 | val = -1; 248 | } 249 | } 250 | this.setData(x, y, val); 251 | } 252 | } 253 | } 254 | 255 | /** 256 | * This method applies edges to any template location that is positive in 257 | * value and is surrounded by empty (0) pixels. 258 | * 259 | * @method generateEdges 260 | * @returns {undefined} 261 | */ 262 | public void generateEdges() 263 | { 264 | int h = this.height; 265 | int w = this.width; 266 | for (int y = 0; y < h; y++) 267 | { 268 | for (int x = 0; x < w; x++) 269 | { 270 | if (this.getData(x, y) > 0) 271 | { 272 | if (y - 1 >= 0 && this.getData(x, y - 1) == 0) 273 | { 274 | this.setData(x, y - 1, -1); 275 | } 276 | if (y + 1 < this.height && this.getData(x, y + 1) == 0) 277 | { 278 | this.setData(x, y + 1, -1); 279 | } 280 | if (x - 1 >= 0 && this.getData(x - 1, y) == 0) 281 | { 282 | this.setData(x - 1, y, -1); 283 | } 284 | if (x + 1 < this.width && this.getData(x + 1, y) == 0) 285 | { 286 | this.setData(x + 1, y, -1); 287 | } 288 | } 289 | } 290 | } 291 | } 292 | 293 | /** 294 | * This method converts HSL color values to RGB color values. 295 | * 296 | * @param {h} 297 | * @param {s} 298 | * @param {l} 299 | * @param {result} 300 | * @method hslToRgb 301 | * @returns {result} 302 | */ 303 | public double[] hslToRgb(double h, double s, double l) 304 | { 305 | double f, p, q, t; 306 | int i = (int)Math.Floor(h * (double)6); 307 | f = h * 6 - i; 308 | p = l * (1 - s); 309 | q = l * (1 - f * s); 310 | t = l * (1 - (1 - f) * s); 311 | switch (i % 6) 312 | { 313 | case 0: return new double[] { l, t, p }; 314 | case 1: return new double[] { q, l, p }; 315 | case 2: return new double[] { p, l, t }; 316 | case 3: return new double[] { p, q, l }; 317 | case 4: return new double[] { t, p, l }; 318 | case 5: return new double[] { l, p, q }; 319 | default: return null; 320 | } 321 | } 322 | 323 | /** 324 | * This method renders out the template data to a HTML canvas to finally 325 | * create the sprite. 326 | *
327 | * (note: only template locations with the values of -1 (border) are rendered)
328 | *
329 | * @method renderPixelData
330 | * @returns {undefined}
331 | */
332 | public int[] renderPixelData()
333 | {
334 | random = new Random(SEED);
335 | bool isVerticalGradient = random.NextDouble() > 0.5;
336 | double saturation = Math.Max(Math.Min(random.NextDouble() * this.saturation, 1), 0);
337 | double hue = random.NextDouble();
338 | int[] pixels = new int[height * width * 4];
339 |
340 | int ulen, vlen;
341 | if (isVerticalGradient)
342 | {
343 | ulen = this.height;
344 | vlen = this.width;
345 | }
346 | else {
347 | ulen = this.width;
348 | vlen = this.height;
349 | }
350 |
351 | for (int u = 0; u < ulen; u++)
352 | {
353 | // Create a non-uniform random number between 0 and 1 (lower numbers more likely)
354 | double isNewColor = Math.Abs(((random.NextDouble() * 2 - 1)
355 | + (random.NextDouble() * 2 - 1)
356 | + (random.NextDouble() * 2 - 1)) / 3);
357 | // Only change the color sometimes (values above 0.8 are less likely than others)
358 | if (isNewColor > (1 - this.colorVariations))
359 | {
360 | hue = random.NextDouble();
361 | }
362 |
363 | //MessageBox.Show(this.toString());
364 |
365 | for (int v = 0; v < vlen; v++)
366 | {
367 | int val, index;
368 | if (isVerticalGradient)
369 | {
370 | val = this.getData(v, u);
371 | index = (u * vlen + v) * 4;
372 | }
373 | else {
374 | val = this.getData(u, v);
375 | index = (v * ulen + u) * 4;
376 | }
377 |
378 | double[] rgb = new double[] { 1, 1, 1 };
379 |
380 | if (val != 0)
381 | {
382 | if (this.colored)
383 | {
384 | // Fade brightness away towards the edges
385 | var brightness = Math.Sin(((double)u / (double)ulen) * Math.PI) * (1 - this.brightnessNoise)
386 | + random.NextDouble() * this.brightnessNoise;
387 |
388 | // Get the RGB color value
389 | rgb = this.hslToRgb(hue, saturation, brightness);
390 |
391 | // If this is an edge, then darken the pixel
392 | if (val == -1)
393 | {
394 | rgb[0] *= this.edgeBrightness;
395 | rgb[1] *= this.edgeBrightness;
396 | rgb[2] *= this.edgeBrightness;
397 | }
398 |
399 | }
400 | else {
401 | // Not colored, simply output black
402 | if (val == -1)
403 | {
404 | rgb = new double[] { 0, 0, 0 };
405 | }
406 | }
407 | }
408 |
409 | pixels[index + 0] = (int)(rgb[0] * 255);
410 | pixels[index + 1] = (int)(rgb[1] * 255);
411 | pixels[index + 2] = (int)(rgb[2] * 255);
412 | if (val != 0)
413 | {
414 | pixels[index + 3] = 255;
415 | }
416 | else {
417 | pixels[index + 3] = 0;
418 | }
419 | }
420 | }
421 |
422 | return pixels;
423 | }
424 |
425 | public String toString()
426 | {
427 | int h = this.height;
428 | int w = this.width;
429 | String output = "";
430 | for (int y = 0; y < h; y++)
431 | {
432 | for (int x = 0; x < w; x++)
433 | {
434 | var val = this.getData(x, y);
435 | output += val >= 0 ? " " + val : "" + val;
436 | }
437 | output += '\n';
438 | }
439 | return output;
440 | }
441 | }
442 | }
443 |
--------------------------------------------------------------------------------
/Pixel-Sprite-Generator-CSharp/Window1.xaml:
--------------------------------------------------------------------------------
1 |