-
Notifications
You must be signed in to change notification settings - Fork 18
/
index.js
133 lines (117 loc) · 4.25 KB
/
index.js
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
const getPixels = require("get-pixels");
const gifEncoder = require("gif-encoder");
const toGreyscale = require("./lib/grayscale");
// The party palette. Party on, Sirocco!
const colours = [
[255, 141, 139],
[254, 214, 137],
[136, 255, 137],
[135, 255, 255],
[139, 181, 254],
[215, 140, 255],
[255, 140, 255],
[255, 104, 247],
[254, 108, 183],
[255, 105, 104]
];
/**
* Rotates the point [x, y] frac of a rotation around the center of the image.
*
* @param {number} x The starting x coordinate.
* @param {number} y The starting y coordinate.
* @param {number} frac The fraction of a rotation by which to rotate (that is,
* we rotate by frac * 2pi radians).
* @param {Array} shape An array of length 2 containing the dimensions of the image.
*/
function rotate(x, y, frac, shape) {
const centerX = shape[0] / 2;
const centerY = shape[1] / 2;
const xRelCenter = x - centerX
const yRelCenter = y - centerY
const cos = Math.cos(2 * Math.PI * frac);
const sin = Math.sin(2 * Math.PI * frac);
return [
Math.round(centerX + xRelCenter * cos - yRelCenter * sin),
Math.round(centerY + yRelCenter * cos + xRelCenter * sin),
];
}
/**
* Writes a party version of the given input image to the specified output stream.
* @param {string} inputFilename A GIF image file to be partified
* @param {stream.Writable} outputStream The stream where the partified image is to be written
* @param {number} partyRadius The radius used to animate movement in the output image
* @param {number} rotationSpeed The speed of rotation in the output image (if desired)
*/
function createPartyImage(inputFilename, outputStream, partyRadius, rotationSpeed) {
//TODO(somewhatabstract): Add other variations to radius, like tilt (for bobbling side to side)
const partyOffset = [];
colours.forEach((c, colourIndex) => {
const x =
partyRadius * Math.sin(2 * Math.PI * (-colourIndex / colours.length));
const y =
partyRadius * Math.cos(2 * Math.PI * (-colourIndex / colours.length));
partyOffset.push([Math.round(x), Math.round(y)]);
});
function processImage(err, pixels) {
if (err) {
console.log("Invalid image path..");
console.log(err);
return;
}
const { shape } = pixels;
const greyscale = toGreyscale(pixels);
const gif = new gifEncoder(shape[0], shape[1]);
gif.pipe(outputStream);
gif.setDelay(50);
gif.setRepeat(0);
gif.setTransparent("0x00FF00");
gif.writeHeader();
gif.on("readable", function() {
gif.read();
});
function getPixelValue(arr, shape, x, y) {
if (x < 0 || x >= shape[0] || y < 0 || y >= shape[1]) {
return -1;
}
return arr[x + y * shape[0]];
}
colours.forEach(function(c, colourIndex) {
const offset = partyOffset[colourIndex];
const p = [];
let rotX, rotY;
for (let y = 0; y < shape[1]; y += 1) {
for (let x = 0; x < shape[0]; x += 1) {
if (rotationSpeed) {
[rotX, rotY] = rotate(
x, y, rotationSpeed * colourIndex / colours.length, shape);
} else {
[rotX, rotY] = [x, y];
}
let g = getPixelValue(
greyscale,
shape,
rotX + offset[0],
rotY + offset[1]
);
if (g === -1) {
p.push(0);
p.push(255);
p.push(0);
p.push(0);
} else {
g = g < 32 ? 32 : g;
p.push(g * c[0] / 255);
p.push(g * c[1] / 255);
p.push(g * c[2] / 255);
p.push(255);
}
}
}
gif.addFrame(p);
gif.flushData();
});
gif.finish();
}
getPixels(inputFilename, processImage);
}
module.exports = createPartyImage;