· 4 years ago · Aug 28, 2021, 09:54 AM
1-- +--------------------------------------------------------+
2-- | |
3-- | JLittle |
4-- | |
5-- +--------------------------------------------------------+
6
7local jLittleAPI = {}
8local version = "Version 1.0"
9
10-- By Jeffrey Alexander, aka Bomb Bloke.
11-- Convenience functions to make use of ComputerCraft 1.76's new "drawing" characters.
12-- http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-jlittle-api/
13
14-------------------------------------------------------------
15
16if shell then
17 local arg = { ... }
18
19 if #arg == 0 then
20 print("Usage:")
21 print("jlittle <scriptName> [args]")
22 return
23 end
24
25 if not jlittle then
26 os.loadAPI(shell.getRunningProgram())
27 end
28 local oldTerm = term.redirect(jlittle.createWindow())
29 shell.run(unpack(arg))
30 term.redirect(oldTerm)
31
32 return
33end
34
35local relations = {
36 [0] = { 8, 4, 3, 6, 5 },
37 { 4, 14, 8, 7 },
38 { 6, 10, 8, 7 },
39 { 9, 11, 8, 0 },
40 { 1, 14, 8, 0 },
41 { 13, 12, 8, 0 },
42 { 2, 10, 8, 0 },
43 { 15, 8, 10, 11, 12, 14 },
44 { 0, 7, 1, 9, 2, 13 },
45 { 3, 11, 8, 7 },
46 { 2, 6, 7, 15 },
47 { 9, 3, 7, 15 },
48 { 13, 5, 7, 15 },
49 { 5, 12, 8, 7 },
50 { 1, 4, 7, 15 },
51 { 7, 10, 11, 12, 14 },
52}
53
54local colourNum, exponents, colourChar = {}, {}, {}
55for i = 0, 15 do
56 exponents[2 ^ i] = i
57end
58do
59 local hex = "0123456789abcdef"
60 for i = 1, 16 do
61 colourNum[hex:sub(i, i)] = i - 1
62 colourNum[i - 1] = hex:sub(i, i)
63 colourChar[hex:sub(i, i)] = 2 ^ (i - 1)
64 colourChar[2 ^ (i - 1)] = hex:sub(i, i)
65
66 local thisRel = relations[i - 1]
67 for i = 1, #thisRel do
68 thisRel[i] = 2 ^ thisRel[i]
69 end
70 end
71end
72
73local function getBestColourMatch(usage)
74 local lastCol = relations[exponents[usage[#usage][1]]]
75
76 for j = 1, #lastCol do
77 local thisRelation = lastCol[j]
78 for i = 1, #usage - 1 do
79 if usage[i][1] == thisRelation then
80 return i
81 end
82 end
83 end
84
85 return 1
86end
87
88local function colsToChar(pattern, totals)
89 if not totals then
90 local newPattern = {}
91 totals = {}
92 for i = 1, 6 do
93 local thisVal = pattern[i]
94 local thisTot = totals[thisVal]
95 totals[thisVal], newPattern[i] = thisTot and (thisTot + 1) or 1, thisVal
96 end
97 pattern = newPattern
98 end
99
100 local usage = {}
101 for key, value in pairs(totals) do
102 usage[#usage + 1] = { key, value }
103 end
104
105 if #usage > 1 then
106 -- Reduce the chunk to two colours:
107 while #usage > 2 do
108 table.sort(usage, function(a, b)
109 return a[2] > b[2]
110 end)
111 local matchToInd, usageLen = getBestColourMatch(usage), #usage
112 local matchFrom, matchTo = usage[usageLen][1], usage[matchToInd][1]
113 for i = 1, 6 do
114 if pattern[i] == matchFrom then
115 pattern[i] = matchTo
116 usage[matchToInd][2] = usage[matchToInd][2] + 1
117 end
118 end
119 usage[usageLen] = nil
120 end
121
122 -- Convert to character. Adapted from oli414's function:
123 -- http://www.computercraft.info/forums2/index.php?/topic/25340-cc-176-easy-drawing-characters/
124 local data = 128
125 for i = 1, #pattern - 1 do
126 if pattern[i] ~= pattern[6] then
127 data = data + 2 ^ (i - 1)
128 end
129 end
130 return string.char(data),
131 colourChar[usage[1][1] == pattern[6] and usage[2][1] or usage[1][1]],
132 colourChar[pattern[6]]
133 else
134 -- Solid colour character:
135 return "\128", colourChar[pattern[1]], colourChar[pattern[1]]
136 end
137end
138
139local function snooze()
140 local myEvent = tostring({})
141 os.queueEvent(myEvent)
142 os.pullEvent(myEvent)
143end
144
145function jLittleAPI.shrink(image, bgCol)
146 local results, width, height, bgCol = { {}, {}, {} }, 0, #image + #image % 3, bgCol or colours.black
147 for i = 1, #image do
148 if #image[i] > width then
149 width = #image[i]
150 end
151 end
152
153 for y = 0, height - 1, 3 do
154 local cRow, tRow, bRow, counter = {}, {}, {}, 1
155
156 for x = 0, width - 1, 2 do
157 -- Grab a 2x3 chunk:
158 local pattern, totals = {}, {}
159
160 for yy = 1, 3 do
161 for xx = 1, 2 do
162 pattern[#pattern + 1] = (image[y + yy] and image[y + yy][x + xx])
163 and (image[y + yy][x + xx] == 0 and bgCol or image[y + yy][x + xx])
164 or bgCol
165 totals[pattern[#pattern]] = totals[pattern[#pattern]] and (totals[pattern[#pattern]] + 1) or 1
166 end
167 end
168
169 cRow[counter], tRow[counter], bRow[counter] = colsToChar(pattern, totals)
170 counter = counter + 1
171 end
172
173 results[1][#results[1] + 1], results[2][#results[2] + 1], results[3][#results[3] + 1] =
174 table.concat(cRow), table.concat(tRow), table.concat(bRow)
175 end
176
177 results.width, results.height = #results[1][1], #results[1]
178
179 return results
180end
181
182function jLittleAPI.shrinkGIF(image, bgCol)
183 if not GIF and not os.loadAPI("GIF") then
184 error("jlittle.shrinkGIF: Load GIF API first.", 2)
185 end
186
187 image = GIF.flattenGIF(image)
188 snooze()
189
190 local prev = GIF.toPaintutils(image[1])
191 snooze()
192
193 prev = jlittle.shrink(prev, bgCol)
194 prev.delay = image[1].delay
195 image[1] = prev
196 snooze()
197
198 image.width, image.height = prev.width, prev.height
199
200 for i = 2, #image do
201 local temp = GIF.toPaintutils(image[i])
202 snooze()
203
204 temp = jlittle.shrink(temp, bgCol)
205 snooze()
206
207 local newImage = { {}, {}, {}, ["delay"] = image[i].delay, ["width"] = temp.width, ["height"] = 0 }
208
209 local a, b, c, pa, pb, pc = temp[1], temp[2], temp[3], prev[1], prev[2], prev[3]
210 for i = 1, temp.height do
211 local a1, b1, c1, pa1, pb1, pc1 = a[i], b[i], c[i], pa[i], pb[i], pc[i]
212
213 if a1 ~= pa1 or b1 ~= pb1 or c1 ~= pc1 then
214 local min, max = 1, #a1
215 local a2, b2, c2, pa2, pb2, pc2 =
216 { a1:byte(1, max) },
217 { b1:byte(1, max) },
218 { c1:byte(1, max) },
219 { pa1:byte(1, max) },
220 { pb1:byte(1, max) },
221 { pc1:byte(1, max) }
222
223 for j = 1, max do
224 if a2[j] ~= pa2[j] or b2[j] ~= pb2[j] or c2[j] ~= pc2[j] then
225 min = j
226 break
227 end
228 end
229
230 for j = max, min, -1 do
231 if a2[j] ~= pa2[j] or b2[j] ~= pb2[j] or c2[j] ~= pc2[j] then
232 max = j
233 break
234 end
235 end
236
237 newImage[1][i], newImage[2][i], newImage[3][i], newImage.height =
238 min > 1 and { min - 1, a1:sub(min, max) } or a1:sub(min, max), b1:sub(min, max), c1:sub(min, max), i
239 end
240
241 snooze()
242 end
243
244 image[i], prev = newImage, temp
245
246 for j = 1, i - 1 do
247 local oldImage = image[j]
248
249 if type(oldImage[1]) == "table" and oldImage.height == newImage.height then
250 local same = true
251
252 for k = 1, oldImage.height do
253 local comp1, comp2 = oldImage[1][k], newImage[1][k]
254
255 if
256 type(comp1) ~= type(comp2)
257 or (type(comp1) == "string" and comp1 ~= comp2)
258 or (type(comp1) == "table" and (comp1[1] ~= comp2[1] or comp1[2] ~= comp2[2]))
259 or oldImage[2][k] ~= newImage[2][k]
260 or oldImage[3][k] ~= newImage[3][k]
261 then
262 same = false
263 break
264 end
265 end
266
267 if same then
268 newImage[1], newImage[2], newImage[3] = j
269 break
270 end
271 end
272
273 snooze()
274 end
275 end
276
277 return image
278end
279
280local function newLine(width, bCol)
281 local line = {}
282 for i = 1, width do
283 line[i] = { bCol, bCol, bCol, bCol, bCol, bCol }
284 end
285 return line
286end
287
288function jLittleAPI.createWindow(parent, x, y, width, height, visible, imgW, imgH)
289 if parent == term or not parent then
290 parent = term.current()
291 elseif type(parent) ~= "table" or not parent.write then
292 error('jlittle.newWindow: "parent" does not appear to be a terminal object.', 2)
293 end
294 local workBuffer, backBuffer, frontBuffer, window, tCol, bCol, curX, curY, blink, cWidth, cHeight, pal =
295 {}, {}, {}, {}, colours.white, colours.black, 1, 1, false
296 if type(visible) ~= "boolean" then
297 visible = true
298 end
299 x, y = x and math.floor(x) or 1, y and math.floor(y) or 1
300
301 do
302 local xSize, ySize = parent.getSize()
303 cWidth, cHeight = (width or xSize), (height or ySize)
304 width, height = cWidth * (height / imgH) * imgW, imgH
305 end
306
307 if parent.setPaletteColour then
308 pal = {}
309
310 local counter = 1
311 for i = 1, 16 do
312 pal[counter] = { parent.getPaletteColour(counter) }
313 counter = counter * 2
314 end
315
316 window.getPaletteColour = function(colour)
317 return unpack(pal[colour])
318 end
319
320 window.setPaletteColour = function(colour, r, g, b)
321 pal[colour] = { r, g, b }
322 if visible then
323 return parent.setPaletteColour(colour, r, g, b)
324 end
325 end
326
327 window.getPaletteColor, window.setPaletteColor = window.getPaletteColour, window.setPaletteColour
328 end
329
330 window.blit = function(_, _, bC)
331 local bClen = #bC
332 if curX > width or curX + bClen < 2 or curY < 1 or curY > height then
333 curX = curX + bClen
334 return
335 end
336
337 if curX < 1 then
338 bC = bC:sub(2 - curX)
339 curX, bClen = 1, #bC
340 end
341
342 if curX + bClen - 1 > width then
343 bC, bClen = bC:sub(1, width - curX + 1), width - curX + 1
344 end
345
346 local colNum, rowNum, thisX, yBump =
347 math.floor((curX - 1) / 2) + 1, math.floor((curY - 1) / 3) + 1, (curX - 1) % 2, ((curY - 1) % 3) * 2
348 local firstColNum, lastColNum, thisRow = colNum, math.floor((curX + bClen) / 2), backBuffer[rowNum]
349 local thisChar = thisRow[colNum]
350
351 for i = 1, bClen do
352 thisChar[thisX + yBump + 1] = colourChar[bC:sub(i, i)]
353
354 if thisX == 1 then
355 thisX, colNum = 0, colNum + 1
356 thisChar = thisRow[colNum]
357 if not thisChar then
358 break
359 end
360 else
361 thisX = 1
362 end
363 end
364
365 if visible then
366 local chars1, chars2, chars3, count = {}, {}, {}, 1
367
368 for i = firstColNum, lastColNum do
369 chars1[count], chars2[count], chars3[count] = colsToChar(thisRow[i])
370 count = count + 1
371 end
372
373 chars1, chars2, chars3 = table.concat(chars1), table.concat(chars2), table.concat(chars3)
374 parent.setCursorPos(
375 x + math.floor((curX - 1) / (imgH / height)),
376 y + math.floor((curY - 1) / (imgW / width))
377 )
378 parent.blit(chars1, chars2, chars3)
379 local thisRow = frontBuffer[rowNum]
380 frontBuffer[rowNum] = {
381 thisRow[1]:sub(1, firstColNum - 1) .. chars1 .. thisRow[1]:sub(lastColNum + 1),
382 thisRow[2]:sub(1, firstColNum - 1) .. chars2 .. thisRow[2]:sub(lastColNum + 1),
383 thisRow[3]:sub(1, firstColNum - 1) .. chars3 .. thisRow[3]:sub(lastColNum + 1),
384 }
385 else
386 local thisRow = workBuffer[rowNum]
387
388 if not thisRow[firstColNum] or thisRow[firstColNum] < lastColNum then
389 local x, newLastColNum = 1, lastColNum
390
391 while x <= lastColNum + 1 do
392 local thisSpot = thisRow[x]
393
394 if thisSpot then
395 if thisSpot >= firstColNum - 1 then
396 if x < firstColNum then
397 firstColNum = x
398 else
399 thisRow[x] = nil
400 end
401 if thisSpot > newLastColNum then
402 newLastColNum = thisSpot
403 end
404 end
405 x = thisSpot + 1
406 else
407 x = x + 1
408 end
409 end
410
411 thisRow[firstColNum] = newLastColNum
412 if thisRow.max <= newLastColNum then
413 thisRow.max = firstColNum
414 end
415 end
416 end
417
418 curX = curX + bClen
419 end
420
421 window.write = function(text)
422 window.blit(nil, nil, string.rep(colourChar[bCol], #tostring(text)))
423 end
424
425 window.clearLine = function()
426 local oldX = curX
427 curX = 1
428 window.blit(nil, nil, string.rep(colourChar[bCol], width))
429 curX = oldX
430 end
431
432 window.clear = function()
433 local t, fC, bC =
434 string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
435 for y = 1, cHeight do
436 workBuffer[y], backBuffer[y], frontBuffer[y] = { ["max"] = 0 }, newLine(cWidth, bCol), { t, fC, bC }
437 end
438 window.redraw()
439 end
440
441 window.getCursorPos = function()
442 return curX, curY
443 end
444
445 window.setCursorPos = function(newX, newY)
446 curX, curY = math.floor(newX), math.floor(newY)
447 if visible and blink then
448 window.restoreCursor()
449 end
450 end
451
452 window.restoreCursor = function() end
453 window.setCursorBlink = window.restoreCursor
454
455 window.isColour = function()
456 return parent.isColour()
457 end
458 window.isColor = window.isColour
459
460 window.getSize = function()
461 return width, height
462 end
463
464 window.scroll = function(lines)
465 lines = math.floor(lines)
466
467 if lines ~= 0 then
468 if lines % 3 == 0 then
469 local newWB, newBB, newFB, line1, line2, line3 =
470 {},
471 {},
472 {},
473 string.rep("\128", cWidth),
474 string.rep(colourChar[tCol], cWidth),
475 string.rep(colourChar[bCol], cWidth)
476 for y = 1, cHeight do
477 newWB[y], newBB[y], newFB[y] =
478 workBuffer[y + lines] or { ["max"] = 0 },
479 backBuffer[y + lines] or newLine(cWidth, bCol),
480 frontBuffer[y + lines] or { line1, line2, line3 }
481 end
482 workBuffer, backBuffer, frontBuffer = newWB, newBB, newFB
483 else
484 local newBB, tRowNum, tBump, sRowNum, sBump = {}, 1, 0, math.floor(lines / (imgW / width)) + 1, (lines % (imgW / width)) * (imgH / height)
485 local sRow, tRow = backBuffer[sRowNum], {}
486 for x = 1, cWidth do
487 tRow[x] = {}
488 end
489
490 for y = 1, height do
491 if sRow then
492 for x = 1, cWidth do
493 local tChar, sChar = tRow[x], sRow[x]
494 tChar[tBump + 1], tChar[tBump + 2] = sChar[sBump + 1], sChar[sBump + 2]
495 end
496 else
497 for x = 1, cWidth do
498 local tChar = tRow[x]
499 tChar[tBump + 1], tChar[tBump + 2] = bCol, bCol
500 end
501 end
502
503 tBump, sBump = tBump + 2, sBump + 2
504
505 if tBump > 4 then
506 tBump, newBB[tRowNum] = 0, tRow
507 tRowNum, tRow = tRowNum + 1, {}
508 for x = 1, cWidth do
509 tRow[x] = {}
510 end
511 end
512
513 if sBump > 4 then
514 sRowNum, sBump = sRowNum + 1, 0
515 sRow = backBuffer[sRowNum]
516 end
517 end
518
519 for y = 1, cHeight do
520 workBuffer[y] = { ["max"] = 1, cWidth }
521 end
522
523 backBuffer = newBB
524 end
525
526 window.redraw()
527 end
528 end
529
530 window.setTextColour = function(newCol)
531 tCol = newCol
532 end
533 window.setTextColor = window.setTextColour
534
535 window.setBackgroundColour = function(newCol)
536 bCol = newCol
537 end
538 window.setBackgroundColor = window.setBackgroundColour
539
540 window.getTextColour = function()
541 return tCol
542 end
543 window.getTextColor = window.getTextColour
544
545 window.getBackgroundColour = function()
546 return bCol
547 end
548 window.getBackgroundColor = window.getBackgroundColour
549
550 window.redraw = function()
551 if visible then
552 for i = 1, cHeight do
553 local work, front = workBuffer[i], frontBuffer[i]
554 local front1, front2, front3 = front[1], front[2], front[3]
555
556 if work.max > 0 then
557 local line1, line2, line3, lineLen, skip, back, count = {}, {}, {}, 1, 0, backBuffer[i], 1
558
559 while count <= work.max do
560 if work[count] then
561 if skip > 0 then
562 line1[lineLen], line2[lineLen], line3[lineLen] =
563 front1:sub(count - skip, count - 1),
564 front2:sub(count - skip, count - 1),
565 front3:sub(count - skip, count - 1)
566 skip, lineLen = 0, lineLen + 1
567 end
568
569 for i = count, work[count] do
570 line1[lineLen], line2[lineLen], line3[lineLen] = colsToChar(back[i])
571 lineLen = lineLen + 1
572 end
573
574 count = work[count] + 1
575 else
576 skip, count = skip + 1, count + 1
577 end
578 end
579
580 if count < cWidth + 1 then
581 line1[lineLen], line2[lineLen], line3[lineLen] =
582 front1:sub(count), front2:sub(count), front3:sub(count)
583 end
584
585 front1, front2, front3 = table.concat(line1), table.concat(line2), table.concat(line3)
586 frontBuffer[i], workBuffer[i] = { front1, front2, front3 }, { ["max"] = 0 }
587 end
588
589 parent.setCursorPos(x, y + i - 1)
590 parent.blit(front1, front2, front3)
591 end
592
593 if pal then
594 local counter = 1
595 for i = 1, 16 do
596 parent.setPaletteColour(counter, unpack(pal[counter]))
597 counter = counter * 2
598 end
599 end
600 end
601 end
602
603 window.setVisible = function(newVis)
604 newVis = newVis and true or false
605
606 if newVis and not visible then
607 visible = true
608 window.redraw()
609 else
610 visible = newVis
611 end
612 end
613
614 window.getPosition = function()
615 return x, y
616 end
617
618 window.reposition = function(newX, newY, newWidth, newHeight)
619 x, y = type(newX) == "number" and math.floor(newX) or x, type(newY) == "number" and math.floor(newY) or y
620
621 if type(newWidth) == "number" then
622 newWidth = math.floor(newWidth)
623 if newWidth > cWidth then
624 local line1, line2, line3 =
625 string.rep("\128", newWidth - cWidth),
626 string.rep(colourChar[tCol], newWidth - cWidth),
627 string.rep(colourChar[bCol], newWidth - cWidth)
628 for y = 1, cHeight do
629 local bRow, fRow = backBuffer[y], frontBuffer[y]
630 for x = cWidth + 1, newWidth do
631 bRow[x] = { bCol, bCol, bCol, bCol, bCol, bCol }
632 end
633 frontBuffer[y] = { fRow[1] .. line3, fRow[2] .. line2, fRow[3] .. line3 }
634 end
635 elseif newWidth < cWidth then
636 for y = 1, cHeight do
637 local wRow, bRow, fRow = workBuffer[y], backBuffer[y], frontBuffer[y]
638 for x = newWidth + 1, cWidth do
639 bRow[x] = nil
640 end
641 frontBuffer[y] = { fRow[1]:sub(1, newWidth), fRow[2]:sub(1, newWidth), fRow[3]:sub(1, newWidth) }
642
643 while wRow[wRow.max] and wRow[wRow.max] > newWidth do
644 wRow[wRow.max] = nil
645 wRow.max = table.maxn(wRow)
646 end
647 end
648 end
649 width, cWidth = newWidth * 2, newWidth
650 end
651
652 if type(newHeight) == "number" then
653 newHeight = math.floor(newHeight)
654 if newHeight > cHeight then
655 local line1, line2, line3 =
656 string.rep("\128", cWidth),
657 string.rep(colourChar[tCol], cWidth),
658 string.rep(colourChar[bCol], cWidth)
659 for y = cHeight + 1, newHeight do
660 workBuffer[y], backBuffer[y], frontBuffer[y] =
661 { ["max"] = 0 }, newLine(cWidth, bCol), { line1, line2, line3 }
662 end
663 elseif newHeight < cHeight then
664 for y = newHeight + 1, cHeight do
665 workBuffer[y], backBuffer[y], frontBuffer[y] = nil, nil, nil
666 end
667 end
668 height, cHeight = newHeight * 3, newHeight
669 end
670
671 window.redraw()
672 end
673
674 window.clear()
675 return window
676end
677
678function jLittleAPI.draw(image, x, y, terminal)
679 local t, tC, bC = image[1], image[2], image[3]
680 x, y, terminal = x or 1, y or 1, terminal or term.current()
681
682 for i = 1, image.height do
683 local tI = t[i]
684 if type(tI) == "string" then
685 terminal.setCursorPos(x, y + i - 1)
686 terminal.blit(tI, tC[i], bC[i])
687 elseif type(tI) == "table" then
688 terminal.setCursorPos(x + tI[1], y + i - 1)
689 terminal.blit(tI[2], tC[i], bC[i])
690 end
691 end
692end
693
694function jLittleAPI.save(image, filename)
695 local output = fs.open(filename, "wb")
696 if not output then
697 error("Can't open " .. filename .. " for output.")
698 end
699
700 local writeByte = output.write
701
702 local function writeInt(num)
703 writeByte(bit.band(num, 255))
704 writeByte(bit.brshift(num, 8))
705 end
706
707 writeByte(66) -- B
708 writeByte(76) -- L
709 writeByte(84) -- T
710
711 local animated = image[1].delay ~= nil
712 writeByte(animated and 1 or 0)
713
714 if animated then
715 writeInt(#image)
716 else
717 local tempImage = { image[1], image[2], image[3] }
718 image[1], image[2], image[3] = tempImage, nil, nil
719 end
720
721 local width, height = image.width, image.height
722
723 writeInt(width)
724 writeInt(height)
725
726 for k = 1, #image do
727 local thisImage = image[k]
728
729 if type(thisImage[1]) == "number" then
730 writeByte(3)
731 writeInt(thisImage[1])
732 else
733 for i = 1, height do
734 if thisImage[1][i] then
735 local rowType, len, thisRow = type(thisImage[1][i])
736
737 if rowType == "string" then
738 writeByte(1)
739 len = #thisImage[1][i]
740 writeInt(len)
741 thisRow = { thisImage[1][i]:byte(1, len) }
742 elseif rowType == "table" then
743 writeByte(2)
744 len = #thisImage[1][i][2]
745 writeInt(len)
746 writeInt(thisImage[1][i][1])
747 thisRow = { thisImage[1][i][2]:byte(1, len) }
748 else
749 error(
750 "Malformed row record #"
751 .. i
752 .. " in frame #"
753 .. k
754 .. ' when attempting to save "'
755 .. filename
756 .. '", type is '
757 .. rowType
758 .. "."
759 )
760 end
761
762 for x = 1, len do
763 writeByte(thisRow[x])
764 end
765
766 local txt, bg = thisImage[2][i], thisImage[3][i]
767 for x = 1, len do
768 writeByte(colourNum[txt:sub(x, x)] + colourNum[bg:sub(x, x)] * 16)
769 end
770 else
771 writeByte(0)
772 end
773 end
774 end
775
776 if animated then
777 writeInt(thisImage.delay * 20)
778 end
779
780 snooze()
781 end
782
783 if image.pal then
784 writeByte(#image.pal)
785 for i = 0, #image.pal do
786 for j = 1, 3 do
787 writeByte(image.pal[i][j])
788 end
789 end
790 end
791
792 if not animated then
793 image[2], image[3] = image[1][2], image[1][3]
794 image[1] = image[1][1]
795 end
796
797 output.close()
798end
799
800function jLittleAPI.load(filename)
801 local input = fs.open(filename, "rb")
802 if not input then
803 error("Can't open " .. filename .. " for input.")
804 end
805
806 local read = input.read
807
808 local function readInt()
809 local result = read()
810 return result + bit.blshift(read(), 8)
811 end
812
813 if string.char(read(), read(), read()) ~= "BLT" then
814 -- Assume legacy format.
815 input.close()
816 input = fs.open(filename, "rb")
817
818 read = input.read
819
820 function jLittleAPI.readInt()
821 local result = input.read()
822 return result + bit.blshift(input.read(), 8)
823 end
824
825 local image = {}
826 image.width, image.height = readInt(), readInt()
827
828 for i = 1, 3 do
829 local thisSet = {}
830 for y = 1, image.height do
831 local thisRow = {}
832 for x = 1, image.width do
833 thisRow[x] = string.char(input.read())
834 end
835 thisSet[y] = table.concat(thisRow)
836 end
837 image[i] = thisSet
838 end
839
840 input.close()
841
842 return image
843 end
844
845 local image, animated, frames = {}, read() == 1
846 if animated then
847 frames = readInt()
848 else
849 frames = 1
850 end
851
852 local width, height = readInt(), readInt()
853 image.width, image.height = width, height
854
855 for k = 1, frames do
856 local thisImage = { ["width"] = width, ["height"] = 0 }
857 local chr, txt, bg = {}, {}, {}
858
859 for i = 1, height do
860 local lineType = read()
861
862 if lineType == 3 then
863 chr, txt, bg = readInt()
864 break
865 elseif lineType > 0 then
866 local l1, l2, len, bump = {}, {}, readInt()
867 if lineType == 2 then
868 bump = readInt()
869 end
870
871 for x = 1, len do
872 l1[x] = read()
873 end
874 chr[i] = string.char(unpack(l1))
875 if lineType == 2 then
876 chr[i] = { bump, chr[i] }
877 end
878
879 for x = 1, len do
880 local thisVal = read()
881 l1[x], l2[x] = colourNum[bit.band(thisVal, 15)], colourNum[bit.brshift(thisVal, 4)]
882 end
883
884 txt[i], bg[i], thisImage.height = table.concat(l1), table.concat(l2), i
885 end
886 end
887
888 if animated then
889 thisImage["delay"] = readInt() / 20
890 end
891 thisImage[1], thisImage[2], thisImage[3] = chr, txt, bg
892 image[k] = thisImage
893
894 snooze()
895 end
896
897 local palLength = read()
898 if palLength and palLength > 0 then
899 image.pal = {}
900 for i = 0, palLength do
901 image.pal[i] = { read(), read(), read() }
902 end
903 end
904
905 if not animated then
906 image[2], image[3] = image[1][2], image[1][3]
907 image[1] = image[1][1]
908 end
909
910 input.close()
911
912 return image
913end
914
915if term.setPaletteColour then
916 function jLittleAPI.applyPalette(image, terminal)
917 terminal = terminal or term
918
919 local col, pal = 1, image.pal
920
921 for i = 0, #pal do
922 local thisCol = pal[i]
923 terminal.setPaletteColour(col, thisCol[1] / 255, thisCol[2] / 255, thisCol[3] / 255)
924 col = col * 2
925 end
926 end
927end
928
929return jLittleAPI
930