More pretty images <3
[Dynartic.git] / DynImage.js
1 /*
2 * DynImage.js
3 * node.js module to perform the bulk of the calculations used in Dynartic
4 * main.js is a light frontend to this data structure
5 */
6
7 function DynImage(width, height, buffer) {
8 this.width = width;
9 this.height = height;
10
11 this.lesserDimension = this.width > this.height ?
12 this.height :
13 this.width;
14
15 this.buffer = buffer;
16
17 // prefill with black as opposed to nonsense from memory
18 this.fillColor(0, 0, 0, 0);
19 }
20
21 // gets or sets the color; RGBA<->XY
22
23 DynImage.prototype.coordIndex = function(x, y) {
24 return 4 * ((y * this.width) + x);
25 }
26
27 DynImage.prototype.getColor = function(x, y) {
28 x = Math.floor(x);
29 y = Math.floor(y);
30
31 if(
32 (x < 0) ||
33 (y < 0) ||
34 (x >= this.width) ||
35 (y >= this.height)
36 ) {
37 return [0,0,0,0]; // out of bounds
38 }
39
40 var index = this.coordIndex(x, y);
41
42 return [
43 this.buffer[index],
44 this.buffer[index+1],
45 this.buffer[index+2],
46 this.buffer[index+3]
47 ];
48 }
49
50 DynImage.prototype.setColor = function(x, y, r, g, b, a) {
51 if(typeof a === 'undefined') a = 0; // A is optional; opaque if unspecified
52
53 x = Math.floor(x);
54 y = Math.floor(y);
55
56 if(
57 (x < 0) ||
58 (y < 0) ||
59 (x >= this.width) ||
60 (y >= this.height)
61 ) {
62 return; // out of bounds
63 }
64
65 var index = this.coordIndex(x, y);
66
67
68 this.buffer[index+0] = Math.floor(r);
69 this.buffer[index+1] = Math.floor(g);
70 this.buffer[index+2] = Math.floor(b);
71 this.buffer[index+3] = Math.floor(a);
72 }
73
74 // blindly fills the entirety of the buffer with a given color
75 // used for initialization, etc.
76 DynImage.prototype.fillColor = function(r, g, b, a) {
77 /*for(var i = 0; i < (this.width * this.height * 4); i += 4) {
78 this.buffer[i + 0] = r;
79 this.buffer[i + 1] = g;
80 this.buffer[i + 2] = b;
81 this.buffer[i + 3] = a;
82 }*/
83 for(var x = 0; x < this.width; ++x) {
84 for(var y = 0; y < this.height; ++y) {
85 this.setColor(x, y, r, g, b, a);
86 }
87 }
88 }
89
90 // "pulls" the image up
91 // starting with a point, that point is raised a certain, specified amount (this is of course an analogy)
92 // this causes the alpha value at that point to be raised to this value,
93 // and ripples the effect the nearby pixels as well
94 DynImage.prototype.pointRing = function(sx, sy, height, layerIntensity, intensityMultiplier) {
95 var rippleAmount = (255 / this.lesserDimension) * height;
96
97 var usedPoints = {};
98
99 for(var r = 0; r < height; r += 0.1) {
100 c: for(var theta = 0; theta < (2 * Math.PI); theta += 0.01) {
101 // tracing points around many concentric circles
102
103 var x = (r * Math.cos(theta)) + sx,
104 y = (r * Math.sin(theta)) + sy;
105
106 if(usedPoints[x+";"+y]) continue c;
107
108 usedPoints[x+";"+y] = 1;
109
110 var distance = Math.sqrt( ( (sx - x) * (sx - x) ) + ( (sy - y) * (sy - y) ) );
111 var intensity = Math.log(height - distance) * intensityMultiplier; // linear function.
112 // this outputs a value 0-1, where 1 is the full effect of pulling and 0 is no effect
113 // in the future, we may want a smoother curve
114 // TODO: find alternative that works better
115
116 //var intensity = 0.5;
117
118 var currentColor = this.getColor(x, y);
119 this.setColor(x, y,
120 currentColor[0] - (intensity * layerIntensity),
121 currentColor[1] - (intensity * layerIntensity),
122 currentColor[2] - (intensity * layerIntensity)
123 );
124 }
125 }
126 }
127
128 DynImage.prototype.explosion = function(width, radius, thresh, dk, ck ) {
129 for(var i = (width / 2) - (radius); i < (width / 2) + radius; ++i) {
130 for(var j = (width / 2) - (radius); j < (width / 2) + radius; ++j) {
131 if(Math.sqrt( ( ((width / 2) - i) * ((width / 2) - i) ) + ((width / 2) - j) * ((width / 2) - j) ) > radius) break;
132
133 if(Math.random() < thresh) {
134 // explode pixel
135
136 var theta = Math.random() * Math.PI * 2;
137
138 var newX = i + ( (dk * (width / 2)) * Math.cos(theta));
139 var newY = j + ( (dk * (width / 2)) * Math.sin(theta));
140
141 var color = this.getColor(i, j);
142 this.setColor(newX, newY, color[0] + ( (Math.random() - 0.5) * ck), color[1] + ( (Math.random() - 0.5) * ck), color[2] + ( (Math.random() - 0.5) * ck));
143 this.setColor(i, j, color[0] + ( (Math.random() - 0.5) * ck), color[1] + ( (Math.random() - 0.5) * ck), color[2] + ( (Math.random() - 0.5) * ck));
144 } else {
145 var color = this.getColor(i, j);
146
147 this.setColor(i, j, (color[0] < 128) ? color[0] - 50 : color[0] + 50, (color[1] < 128) ? color[1] - 50 : color[1] + 50, (color[2] < 128) ? color[2] - 50 : color[2] + 50);
148 }
149 }
150 }
151 }
152
153 DynImage.prototype.prettyNoise = function(width, height, mul) {
154 for(var i = 0; i < width; ++i) {
155 for(var j = 0; j < height; ++j) {
156 var color = this.getColor(i, j);
157
158 this.setColor(i, j,
159 color[0] + ( (Math.random() - 0.5) * mul),
160 color[1] + ( (Math.random() - 0.5) * mul),
161 color[2] + ( (Math.random() - 0.5) * mul)
162 );
163 }
164 }
165 }
166
167 DynImage.prototype.character2 = function(width, height) {
168 for(var x = 0; x < width; ++x) {
169 for(var y = 0; y < height; ++y) {
170 var color = this.getColor(x, y);
171 var mod = 110 + Math.floor(Math.random() * 40);
172 var mult = Math.ceil(Math.random() * 120);
173
174 this.setColor(x, y,
175 Math.floor((color[0] * 0.5) + 128*Math.log(((x / width) * mod) + ((y / height) * mod))) & 255,
176 Math.floor((color[1] * 0.5) + 128*Math.log(((x / width) * mod) + ((y / height) * mod))) & 255,
177 Math.floor((color[2] * 0.5) + 128*Math.log(((x / width) * mod) + ((y / height) * mod))) & 255
178 );
179 }
180 }
181 }
182
183 DynImage.prototype.punchImage = function(sx, sy, sw, sh, force, spread) {
184 for(var i = -(sw / 2); i < (sw/2); ++i) {
185 for(var j = (-(sh/2)); j < (sh/2); ++j) {
186 var x = sx + i,
187 y = sy + j;
188
189 var intensity = Math.sqrt( (i*i) + (j*j) );
190 intensity = Math.pow(2, -intensity / spread);
191 if( Math.sqrt( ((sx - x) * (sx - x)) + ((sy - y) * (sy - y))) < (sw/2))
192 this.punchPoint(x, y, intensity * force);
193 }
194 }
195 }
196
197 DynImage.prototype.punchWave = function(sx, sy, sw, sh, force, spread, period) {
198 for(var i = -(sw / 2); i < (sw/2); ++i) {
199 for(var j = (-(sh/2)); j < (sh/2); ++j) {
200 var x = sx + i,
201 y = sy + j;
202
203 var intensity = Math.sqrt( (i*i) + (j*j) );
204 intensity = Math.pow(2, -intensity / spread);
205
206 this.punchPoint(x, y, intensity * force * Math.sin(intensity / period));
207 }
208 }
209 }
210
211 // opposite of point pulling; works on a single point only
212
213 DynImage.prototype.punchPoint = function(x, y, force) {
214 var color = this.getColor(x, y);
215 color[0] -= force;
216 color[1] -= force;
217 color[2] -= force;
218
219 color[0] = color[0] > 0 ? color[0] < 256 ? color[0] : 255 : 0;
220 color[1] = color[1] > 0 ? color[0] < 256 ? color[1] : 255 : 0;
221 color[2] = color[2] > 0 ? color[0] < 256 ? color[2] : 255 : 0;
222
223 this.setColor(x, y, color[0], color[1], color[2], color[3]);
224 }
225
226 // contrast changer
227 // any pixel below a threshold becomes darker,
228 // and any pixel above the threshold becomes lighter
229
230 DynImage.prototype.recontrast = function(threshold, amount) {
231 for(var i = 0; i < this.width; ++i) {
232 for(var j = 0; j < this.height; ++j) {
233 var pix = this.getColor(i, j);
234
235 // TODO: use HSL for proper value testing
236
237 var avg = (pix[0] + pix[1] + pix[2]) / 3;
238 if(avg > threshold) {
239 this.setColor(i, j, pix[0] + amount, pix[1] + amount, pix[2] + amount);
240 } else {
241 this.setColor(i, j, pix[0] - amount, pix[1] - amount, pix[2] - amount);
242 }
243 }
244 }
245 }
246
247 DynImage.prototype.borderPoint = function(x, y, bred, bgreen, bblue, percent) {
248 var color = this.getColor(x, y);
249
250 // weighted average
251
252 this.setColor( ((bred * percent) + (color[0] * (100 - percent))) / 200,
253 ((bgreen * percent) + (color[1] * (100 - percent))) / 200,
254 ((bblue * percent) + (color[2] * (100 - percent))) / 200);
255 }
256
257 DynImage.prototype.border = function(width, height, bred, bgreen, bblue, mul) {
258 var borderIterations = mul;
259
260 for(var i = 0; i < borderIterations; ++i) {
261 for(var x = i; x < (width - i); ++x) {
262 // top
263
264 var y = i;
265 this.borderPoint(x, y, bred, bgreen, bblue, 100 - (i * mul));
266
267 y = height - i - 1;
268 this.borderPoint(x, y, bred, bgreen, bblue, 100 - (i * mul));
269 }
270 }
271 }
272
273 DynImage.prototype.antialias = function(width, height) {
274 var buf = new Buffer(width * height * 4);
275
276 for(var i = 0; i < width; ++i) {
277 for(var j = 0; j < height; ++j) {
278 var successPixels = 0;
279 var rSum = 0, gSum = 0, bSum = 0, aSum = 0;
280 var that = this;
281
282 function run(x, y) {
283 var color = that.getColor(x, y);
284 rSum += color[0];
285 gSum += color[1];
286 bSum += color[2];
287 aSum += color[3];
288 successPixels++;
289 }
290
291 run(i, j);
292
293 if( (i - 1) >= 0) {
294 run(i - 1, j);
295
296 if( (j - 1) >= 0) {
297 run(i - 1, j -1);
298 }
299
300 if( (j + 1) < width) {
301 run(i -1, j + 1);
302 }
303 }
304
305 if( (i + 1) < width) {
306 run(i + 1, j);
307
308 if( (j - 1) >= 0) {
309 run(i + 1, j - 1);
310 }
311
312 if( (j + 1) < width) {
313 run(i + 1, j + 1);
314 }
315 }
316
317 if( (j - 1) >= 0) {
318 run(i, j - 1);
319 }
320
321 if( (j + 1) < width) {
322 run(i, j + 1);
323 }
324
325 this.setColor(i, j, rSum / successPixels, gSum / successPixels, bSum / successPixels, aSum / successPixels);
326 }
327 }
328 }
329
330
331 module.exports = DynImage;
This page took 0.148555 seconds and 5 git commands to generate.