@meebs/meeb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/animals/alien.js +34 -0
- package/animals/axolotl.js +38 -0
- package/animals/bat.js +34 -0
- package/animals/bear.js +41 -0
- package/animals/bee.js +32 -0
- package/animals/bunny.js +45 -0
- package/animals/butterfly.js +36 -0
- package/animals/capybara.js +36 -0
- package/animals/cat.js +44 -0
- package/animals/chameleon.js +44 -0
- package/animals/chick.js +27 -0
- package/animals/cookie.js +30 -0
- package/animals/crab.js +32 -0
- package/animals/crocodile.js +32 -0
- package/animals/deer.js +35 -0
- package/animals/dino.js +37 -0
- package/animals/dog.js +44 -0
- package/animals/dragon.js +44 -0
- package/animals/duck.js +45 -0
- package/animals/eagle.js +37 -0
- package/animals/elephant.js +33 -0
- package/animals/firefly.js +37 -0
- package/animals/fish.js +32 -0
- package/animals/flamingo.js +36 -0
- package/animals/fox.js +43 -0
- package/animals/frog.js +46 -0
- package/animals/ghost.js +32 -0
- package/animals/giraffe.js +37 -0
- package/animals/gorilla.js +37 -0
- package/animals/hamster.js +35 -0
- package/animals/hedgehog.js +32 -0
- package/animals/hippo.js +34 -0
- package/animals/index.js +87 -0
- package/animals/jellyfish.js +34 -0
- package/animals/koala.js +34 -0
- package/animals/ladybug.js +34 -0
- package/animals/lion.js +36 -0
- package/animals/mantisshrimp.js +49 -0
- package/animals/monkey.js +35 -0
- package/animals/moth.js +41 -0
- package/animals/mouse.js +33 -0
- package/animals/narwhal.js +38 -0
- package/animals/octopus.js +31 -0
- package/animals/otter.js +43 -0
- package/animals/owl.js +38 -0
- package/animals/panda.js +42 -0
- package/animals/parrot.js +39 -0
- package/animals/peacock.js +42 -0
- package/animals/penguin.js +47 -0
- package/animals/penguin2.js +38 -0
- package/animals/pig.js +33 -0
- package/animals/raccoon.js +35 -0
- package/animals/redpanda.js +39 -0
- package/animals/robot.js +37 -0
- package/animals/scorpion.js +33 -0
- package/animals/seahorse.js +39 -0
- package/animals/shark.js +33 -0
- package/animals/shit.js +33 -0
- package/animals/sloth.js +34 -0
- package/animals/snail.js +34 -0
- package/animals/snake.js +36 -0
- package/animals/spider.js +33 -0
- package/animals/squid.js +33 -0
- package/animals/starfish.js +32 -0
- package/animals/stingray.js +41 -0
- package/animals/swan.js +34 -0
- package/animals/toucan.js +41 -0
- package/animals/turtle.js +35 -0
- package/animals/unicorn.js +40 -0
- package/animals/walrus.js +35 -0
- package/animals/whale.js +42 -0
- package/animals/wolf.js +36 -0
- package/animals/yak.js +36 -0
- package/animals/zebra.js +34 -0
- package/color.js +9 -0
- package/completions/_meeb +41 -0
- package/completions/meeb.bash +16 -0
- package/emoji.js +30 -0
- package/fortune.js +41 -0
- package/gif.js +435 -0
- package/index.js +915 -0
- package/info.js +80 -0
- package/package.json +39 -0
- package/png.js +88 -0
- package/svg.js +43 -0
- package/theme.js +80 -0
- package/tui.js +76 -0
- package/util.js +313 -0
package/gif.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const { stripAnsi } = require('./util')
|
|
3
|
+
|
|
4
|
+
// Parse ANSI art into a grid of {char, fg, bg} cells
|
|
5
|
+
function parseAnsi(art) {
|
|
6
|
+
const lines = art.split('\n')
|
|
7
|
+
const grid = []
|
|
8
|
+
for (const line of lines) {
|
|
9
|
+
const row = []
|
|
10
|
+
let fg = [200, 200, 200]
|
|
11
|
+
let bg = [0, 0, 0]
|
|
12
|
+
let i = 0
|
|
13
|
+
while (i < line.length) {
|
|
14
|
+
if (line[i] === '\x1b' && line[i + 1] === '[') {
|
|
15
|
+
// parse escape sequence
|
|
16
|
+
let j = i + 2
|
|
17
|
+
while (j < line.length && line[j] !== 'm') j++
|
|
18
|
+
const codes = line.slice(i + 2, j).split(';').map(Number)
|
|
19
|
+
let k = 0
|
|
20
|
+
while (k < codes.length) {
|
|
21
|
+
if (codes[k] === 0) {
|
|
22
|
+
fg = [200, 200, 200]
|
|
23
|
+
bg = [0, 0, 0]
|
|
24
|
+
} else if (codes[k] === 38 && codes[k + 1] === 2) {
|
|
25
|
+
fg = [codes[k + 2], codes[k + 3], codes[k + 4]]
|
|
26
|
+
k += 4
|
|
27
|
+
} else if (codes[k] === 48 && codes[k + 1] === 2) {
|
|
28
|
+
bg = [codes[k + 2], codes[k + 3], codes[k + 4]]
|
|
29
|
+
k += 4
|
|
30
|
+
}
|
|
31
|
+
k++
|
|
32
|
+
}
|
|
33
|
+
i = j + 1
|
|
34
|
+
} else {
|
|
35
|
+
row.push({ char: line[i], fg: [...fg], bg: [...bg] })
|
|
36
|
+
i++
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
grid.push(row)
|
|
40
|
+
}
|
|
41
|
+
return grid
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Block characters and how they fill a 2x2 sub-pixel cell (top-left, top-right, bottom-left, bottom-right)
|
|
45
|
+
// Each char maps to which sub-pixels are "foreground" vs "background"
|
|
46
|
+
// We render each character cell as cellW x cellH pixels
|
|
47
|
+
const CELL_W = 8
|
|
48
|
+
const CELL_H = 14
|
|
49
|
+
|
|
50
|
+
function renderCell(char, fg, bg) {
|
|
51
|
+
// Returns a cellW x cellH pixel grid
|
|
52
|
+
const pixels = []
|
|
53
|
+
for (let y = 0; y < CELL_H; y++) pixels.push(new Array(CELL_W))
|
|
54
|
+
|
|
55
|
+
const topHalf = y => y < CELL_H / 2
|
|
56
|
+
const botHalf = y => y >= CELL_H / 2
|
|
57
|
+
const leftHalf = x => x < CELL_W / 2
|
|
58
|
+
const rightHalf = x => x >= CELL_W / 2
|
|
59
|
+
|
|
60
|
+
for (let y = 0; y < CELL_H; y++) {
|
|
61
|
+
for (let x = 0; x < CELL_W; x++) {
|
|
62
|
+
let color = bg
|
|
63
|
+
switch (char) {
|
|
64
|
+
case '█': case '▓': color = fg; break
|
|
65
|
+
case '░': color = (x + y) % 3 === 0 ? fg : bg; break
|
|
66
|
+
case '▄': color = botHalf(y) ? fg : bg; break
|
|
67
|
+
case '▀': color = topHalf(y) ? fg : bg; break
|
|
68
|
+
case '▐': color = rightHalf(x) ? fg : bg; break
|
|
69
|
+
case '▌': color = leftHalf(x) ? fg : bg; break
|
|
70
|
+
case '▛': color = (topHalf(y) || leftHalf(x)) ? fg : bg; break
|
|
71
|
+
case '▜': color = (topHalf(y) || rightHalf(x)) ? fg : bg; break
|
|
72
|
+
case '▙': color = (botHalf(y) || leftHalf(x)) ? fg : bg; break
|
|
73
|
+
case '▟': color = (botHalf(y) || rightHalf(x)) ? fg : bg; break
|
|
74
|
+
case '▗': color = (botHalf(y) && rightHalf(x)) ? fg : bg; break
|
|
75
|
+
case '▖': color = (botHalf(y) && leftHalf(x)) ? fg : bg; break
|
|
76
|
+
case '▝': color = (topHalf(y) && rightHalf(x)) ? fg : bg; break
|
|
77
|
+
case '▘': color = (topHalf(y) && leftHalf(x)) ? fg : bg; break
|
|
78
|
+
case '●': case '◉': {
|
|
79
|
+
const cx = CELL_W / 2, cy = CELL_H / 2
|
|
80
|
+
const dx = x - cx + 0.5, dy = (y - cy + 0.5) * (CELL_W / CELL_H)
|
|
81
|
+
color = Math.sqrt(dx * dx + dy * dy) < CELL_W / 2.5 ? fg : bg
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
case '▼': case '▽': {
|
|
85
|
+
const progress = y / CELL_H
|
|
86
|
+
const halfW = (1 - progress) * CELL_W / 2
|
|
87
|
+
color = (x >= CELL_W / 2 - halfW && x < CELL_W / 2 + halfW) ? fg : bg
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
case '✦': case '·': case '★': {
|
|
91
|
+
const cx = CELL_W / 2, cy = CELL_H / 2
|
|
92
|
+
const dx = Math.abs(x - cx + 0.5), dy = Math.abs((y - cy + 0.5) * (CELL_W / CELL_H))
|
|
93
|
+
color = (dx + dy < CELL_W / 2.5) ? fg : bg
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
// most text chars: just fill with fg if not space
|
|
98
|
+
if (char !== ' ' && char !== '') color = fg
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
pixels[y][x] = color
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return pixels
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function artToPixels(art, bgColor = [25, 25, 30]) {
|
|
108
|
+
const grid = parseAnsi(art)
|
|
109
|
+
if (grid.length === 0) return { pixels: [], w: 0, h: 0 }
|
|
110
|
+
|
|
111
|
+
const maxCols = Math.max(...grid.map(r => r.length))
|
|
112
|
+
const padding = 2 // cells of padding
|
|
113
|
+
const totalCols = maxCols + padding * 2
|
|
114
|
+
const totalRows = grid.length + padding * 2
|
|
115
|
+
|
|
116
|
+
const w = totalCols * CELL_W
|
|
117
|
+
const h = totalRows * CELL_H
|
|
118
|
+
const pixels = []
|
|
119
|
+
for (let y = 0; y < h; y++) pixels.push(new Array(w))
|
|
120
|
+
|
|
121
|
+
// fill with background
|
|
122
|
+
for (let y = 0; y < h; y++)
|
|
123
|
+
for (let x = 0; x < w; x++)
|
|
124
|
+
pixels[y][x] = bgColor
|
|
125
|
+
|
|
126
|
+
for (let row = 0; row < grid.length; row++) {
|
|
127
|
+
for (let col = 0; col < grid[row].length; col++) {
|
|
128
|
+
const cell = grid[row][col]
|
|
129
|
+
const cellPixels = renderCell(cell.char, cell.fg, cell.bg)
|
|
130
|
+
const ox = (col + padding) * CELL_W
|
|
131
|
+
const oy = (row + padding) * CELL_H
|
|
132
|
+
for (let cy = 0; cy < CELL_H; cy++) {
|
|
133
|
+
for (let cx = 0; cx < CELL_W; cx++) {
|
|
134
|
+
pixels[oy + cy][ox + cx] = cellPixels[cy][cx]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { pixels, w, h }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- GIF89a encoder ---
|
|
143
|
+
|
|
144
|
+
// Build a color table from pixel data (max 256 colors via median cut)
|
|
145
|
+
function buildPalette(pixels, maxColors = 256) {
|
|
146
|
+
// Collect unique colors (quantized to reduce)
|
|
147
|
+
const colorCounts = new Map()
|
|
148
|
+
for (const row of pixels) {
|
|
149
|
+
for (const [r, g, b] of row) {
|
|
150
|
+
// quantize to 6-bit per channel for grouping
|
|
151
|
+
const qr = r >> 2, qg = g >> 2, qb = b >> 2
|
|
152
|
+
const key = (qr << 12) | (qg << 6) | qb
|
|
153
|
+
colorCounts.set(key, (colorCounts.get(key) || 0) + 1)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Get top N colors by frequency
|
|
158
|
+
const sorted = [...colorCounts.entries()].sort((a, b) => b[1] - a[1])
|
|
159
|
+
const palette = []
|
|
160
|
+
for (let i = 0; i < Math.min(maxColors, sorted.length); i++) {
|
|
161
|
+
const key = sorted[i][0]
|
|
162
|
+
const r = ((key >> 12) & 0x3f) << 2
|
|
163
|
+
const g = ((key >> 6) & 0x3f) << 2
|
|
164
|
+
const b = (key & 0x3f) << 2
|
|
165
|
+
palette.push([r, g, b])
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// pad to power of 2
|
|
169
|
+
while (palette.length < 2) palette.push([0, 0, 0])
|
|
170
|
+
let bits = 1
|
|
171
|
+
while ((1 << bits) < palette.length) bits++
|
|
172
|
+
while (palette.length < (1 << bits)) palette.push([0, 0, 0])
|
|
173
|
+
|
|
174
|
+
return { palette, bits }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function findClosest(palette, r, g, b) {
|
|
178
|
+
let best = 0, bestD = Infinity
|
|
179
|
+
const qr = r >> 2 << 2, qg = g >> 2 << 2, qb = b >> 2 << 2
|
|
180
|
+
for (let i = 0; i < palette.length; i++) {
|
|
181
|
+
const dr = palette[i][0] - qr
|
|
182
|
+
const dg = palette[i][1] - qg
|
|
183
|
+
const db = palette[i][2] - qb
|
|
184
|
+
const d = dr * dr + dg * dg + db * db
|
|
185
|
+
if (d === 0) return i
|
|
186
|
+
if (d < bestD) { bestD = d; best = i }
|
|
187
|
+
}
|
|
188
|
+
return best
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function indexPixels(pixels, palette) {
|
|
192
|
+
const h = pixels.length, w = pixels[0].length
|
|
193
|
+
const indices = new Uint8Array(w * h)
|
|
194
|
+
// build lookup cache
|
|
195
|
+
const cache = new Map()
|
|
196
|
+
for (let y = 0; y < h; y++) {
|
|
197
|
+
for (let x = 0; x < w; x++) {
|
|
198
|
+
const [r, g, b] = pixels[y][x]
|
|
199
|
+
const key = (r << 16) | (g << 8) | b
|
|
200
|
+
if (cache.has(key)) {
|
|
201
|
+
indices[y * w + x] = cache.get(key)
|
|
202
|
+
} else {
|
|
203
|
+
const idx = findClosest(palette, r, g, b)
|
|
204
|
+
cache.set(key, idx)
|
|
205
|
+
indices[y * w + x] = idx
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return indices
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// LZW compression for GIF
|
|
213
|
+
function lzwEncode(indices, minCodeSize) {
|
|
214
|
+
const clearCode = 1 << minCodeSize
|
|
215
|
+
const eoiCode = clearCode + 1
|
|
216
|
+
|
|
217
|
+
const output = []
|
|
218
|
+
let codeSize = minCodeSize + 1
|
|
219
|
+
let nextCode = eoiCode + 1
|
|
220
|
+
const maxTableSize = 4096
|
|
221
|
+
|
|
222
|
+
// init table
|
|
223
|
+
let table = new Map()
|
|
224
|
+
for (let i = 0; i < clearCode; i++) table.set(String(i), i)
|
|
225
|
+
|
|
226
|
+
let buffer = 0
|
|
227
|
+
let bitsInBuf = 0
|
|
228
|
+
|
|
229
|
+
function emit(code) {
|
|
230
|
+
buffer |= (code << bitsInBuf)
|
|
231
|
+
bitsInBuf += codeSize
|
|
232
|
+
while (bitsInBuf >= 8) {
|
|
233
|
+
output.push(buffer & 0xff)
|
|
234
|
+
buffer >>= 8
|
|
235
|
+
bitsInBuf -= 8
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
emit(clearCode)
|
|
240
|
+
|
|
241
|
+
let prev = String(indices[0])
|
|
242
|
+
for (let i = 1; i < indices.length; i++) {
|
|
243
|
+
const curr = String(indices[i])
|
|
244
|
+
const combined = prev + ',' + curr
|
|
245
|
+
if (table.has(combined)) {
|
|
246
|
+
prev = combined
|
|
247
|
+
} else {
|
|
248
|
+
emit(table.get(prev))
|
|
249
|
+
if (nextCode < maxTableSize) {
|
|
250
|
+
table.set(combined, nextCode++)
|
|
251
|
+
if (nextCode > (1 << codeSize) && codeSize < 12) codeSize++
|
|
252
|
+
} else {
|
|
253
|
+
// reset
|
|
254
|
+
emit(clearCode)
|
|
255
|
+
table = new Map()
|
|
256
|
+
for (let j = 0; j < clearCode; j++) table.set(String(j), j)
|
|
257
|
+
nextCode = eoiCode + 1
|
|
258
|
+
codeSize = minCodeSize + 1
|
|
259
|
+
}
|
|
260
|
+
prev = curr
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
emit(table.get(prev))
|
|
264
|
+
emit(eoiCode)
|
|
265
|
+
|
|
266
|
+
// flush remaining bits
|
|
267
|
+
if (bitsInBuf > 0) output.push(buffer & 0xff)
|
|
268
|
+
|
|
269
|
+
return Buffer.from(output)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function subBlock(data) {
|
|
273
|
+
// split into 255-byte sub-blocks
|
|
274
|
+
const chunks = []
|
|
275
|
+
let i = 0
|
|
276
|
+
while (i < data.length) {
|
|
277
|
+
const size = Math.min(255, data.length - i)
|
|
278
|
+
chunks.push(size)
|
|
279
|
+
for (let j = 0; j < size; j++) chunks.push(data[i + j])
|
|
280
|
+
i += size
|
|
281
|
+
}
|
|
282
|
+
chunks.push(0) // block terminator
|
|
283
|
+
return Buffer.from(chunks)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function encodeGif(frames, delays, w, h) {
|
|
287
|
+
const bufs = []
|
|
288
|
+
|
|
289
|
+
// use first frame for palette
|
|
290
|
+
const { palette, bits } = buildPalette(frames[0])
|
|
291
|
+
|
|
292
|
+
// Header
|
|
293
|
+
bufs.push(Buffer.from('GIF89a'))
|
|
294
|
+
|
|
295
|
+
// Logical Screen Descriptor
|
|
296
|
+
const lsd = Buffer.alloc(7)
|
|
297
|
+
lsd.writeUInt16LE(w, 0)
|
|
298
|
+
lsd.writeUInt16LE(h, 2)
|
|
299
|
+
lsd.writeUInt8(0x80 | ((bits - 1) << 4) | (bits - 1), 4) // GCT flag + color resolution + GCT size
|
|
300
|
+
lsd.writeUInt8(0, 5) // bg color index
|
|
301
|
+
lsd.writeUInt8(0, 6) // pixel aspect ratio
|
|
302
|
+
bufs.push(lsd)
|
|
303
|
+
|
|
304
|
+
// Global Color Table
|
|
305
|
+
const gct = Buffer.alloc(3 * (1 << bits))
|
|
306
|
+
for (let i = 0; i < palette.length; i++) {
|
|
307
|
+
gct[i * 3] = palette[i][0]
|
|
308
|
+
gct[i * 3 + 1] = palette[i][1]
|
|
309
|
+
gct[i * 3 + 2] = palette[i][2]
|
|
310
|
+
}
|
|
311
|
+
bufs.push(gct)
|
|
312
|
+
|
|
313
|
+
// Netscape loop extension
|
|
314
|
+
bufs.push(Buffer.from([
|
|
315
|
+
0x21, 0xff, 0x0b,
|
|
316
|
+
...Buffer.from('NETSCAPE2.0'),
|
|
317
|
+
0x03, 0x01, 0x00, 0x00, // loop forever
|
|
318
|
+
0x00
|
|
319
|
+
]))
|
|
320
|
+
|
|
321
|
+
for (let f = 0; f < frames.length; f++) {
|
|
322
|
+
// Graphic Control Extension
|
|
323
|
+
const gce = Buffer.alloc(8)
|
|
324
|
+
gce[0] = 0x21 // extension
|
|
325
|
+
gce[1] = 0xf9 // graphic control
|
|
326
|
+
gce[2] = 0x04 // block size
|
|
327
|
+
gce[3] = 0x00 // no transparency, no disposal
|
|
328
|
+
gce.writeUInt16LE(delays[f], 4) // delay in 1/100s
|
|
329
|
+
gce[6] = 0x00 // transparent color index
|
|
330
|
+
gce[7] = 0x00 // block terminator
|
|
331
|
+
bufs.push(gce)
|
|
332
|
+
|
|
333
|
+
// Image Descriptor
|
|
334
|
+
const imgDesc = Buffer.alloc(10)
|
|
335
|
+
imgDesc[0] = 0x2c // image separator
|
|
336
|
+
imgDesc.writeUInt16LE(0, 1) // left
|
|
337
|
+
imgDesc.writeUInt16LE(0, 3) // top
|
|
338
|
+
imgDesc.writeUInt16LE(w, 5) // width
|
|
339
|
+
imgDesc.writeUInt16LE(h, 7) // height
|
|
340
|
+
imgDesc[9] = 0x00 // no local color table
|
|
341
|
+
bufs.push(imgDesc)
|
|
342
|
+
|
|
343
|
+
// Image Data
|
|
344
|
+
const indices = indexPixels(frames[f], palette)
|
|
345
|
+
const minCodeSize = Math.max(2, bits)
|
|
346
|
+
bufs.push(Buffer.from([minCodeSize]))
|
|
347
|
+
const compressed = lzwEncode(indices, minCodeSize)
|
|
348
|
+
bufs.push(subBlock(compressed))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Trailer
|
|
352
|
+
bufs.push(Buffer.from([0x3b]))
|
|
353
|
+
|
|
354
|
+
return Buffer.concat(bufs)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function scalePixels(pixels, w, h, scaleX, scaleY) {
|
|
358
|
+
if (scaleY === undefined) scaleY = scaleX
|
|
359
|
+
const nw = Math.round(w * scaleX), nh = Math.round(h * scaleY)
|
|
360
|
+
const out = []
|
|
361
|
+
for (let y = 0; y < nh; y++) {
|
|
362
|
+
const row = new Array(nw)
|
|
363
|
+
const sy = Math.min(Math.floor(y / scaleY), h - 1)
|
|
364
|
+
for (let x = 0; x < nw; x++) {
|
|
365
|
+
row[x] = pixels[sy][Math.min(Math.floor(x / scaleX), w - 1)]
|
|
366
|
+
}
|
|
367
|
+
out.push(row)
|
|
368
|
+
}
|
|
369
|
+
return { pixels: out, w: nw, h: nh }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function applyWhiteBg(pixels, w, h, bgColor) {
|
|
373
|
+
if (bgColor[0] !== 255 || bgColor[1] !== 255 || bgColor[2] !== 255) return
|
|
374
|
+
for (let y = 0; y < h; y++)
|
|
375
|
+
for (let x = 0; x < w; x++) {
|
|
376
|
+
const [r, g, b] = pixels[y][x]
|
|
377
|
+
if (r === 0 && g === 0 && b === 0) pixels[y][x] = bgColor
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function correctAspect(pixels, w, h) {
|
|
382
|
+
return scalePixels(pixels, w, h, 1, CELL_W / CELL_H)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function generateGif(art, outPath, bgColor) {
|
|
386
|
+
const bg = bgColor || [25, 25, 30]
|
|
387
|
+
let { pixels, w, h } = artToPixels(art, bg)
|
|
388
|
+
applyWhiteBg(pixels, w, h, bg)
|
|
389
|
+
const corrected = correctAspect(pixels, w, h)
|
|
390
|
+
if (corrected.w === 0 || corrected.h === 0) {
|
|
391
|
+
console.error(' empty art, cannot generate gif')
|
|
392
|
+
process.exit(1)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const gif = encodeGif([corrected.pixels], [100], corrected.w, corrected.h)
|
|
396
|
+
fs.writeFileSync(outPath, gif)
|
|
397
|
+
return outPath
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function generateAnimatedGif(arts, outPath, delay = 80, bgColor) {
|
|
401
|
+
const bg = bgColor || [25, 25, 30]
|
|
402
|
+
const frames = []
|
|
403
|
+
let maxW = 0, maxH = 0
|
|
404
|
+
|
|
405
|
+
const parsed = arts.map(art => {
|
|
406
|
+
let { pixels, w, h } = artToPixels(art, bg)
|
|
407
|
+
applyWhiteBg(pixels, w, h, bg)
|
|
408
|
+
const corrected = correctAspect(pixels, w, h)
|
|
409
|
+
return corrected
|
|
410
|
+
})
|
|
411
|
+
for (const { w, h } of parsed) {
|
|
412
|
+
maxW = Math.max(maxW, w)
|
|
413
|
+
maxH = Math.max(maxH, h)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const { pixels, w, h } of parsed) {
|
|
417
|
+
const frame = []
|
|
418
|
+
for (let y = 0; y < maxH; y++) {
|
|
419
|
+
const row = []
|
|
420
|
+
for (let x = 0; x < maxW; x++) {
|
|
421
|
+
if (y < h && x < w) row.push(pixels[y][x])
|
|
422
|
+
else row.push(bg)
|
|
423
|
+
}
|
|
424
|
+
frame.push(row)
|
|
425
|
+
}
|
|
426
|
+
frames.push(frame)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const delays = frames.map(() => delay)
|
|
430
|
+
const gif = encodeGif(frames, delays, maxW, maxH)
|
|
431
|
+
fs.writeFileSync(outPath, gif)
|
|
432
|
+
return outPath
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
module.exports = { generateGif, generateAnimatedGif, artToPixels, parseAnsi, renderCell, scalePixels, applyWhiteBg, correctAspect, CELL_W, CELL_H }
|