BombBloke

BLittle (ComputerCraft)

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