SHARE
TWEET

GIF API (ComputerCraft)

BombBloke Apr 26th, 2015 (edited) 3,247 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- +---------------------+-----------+---------------------+
  2. -- |                     |           |                     |
  3. -- |                     |    GIF    |                     |
  4. -- |                     |           |                     |
  5. -- +---------------------+-----------+---------------------+
  6.  
  7. local version = "Version 1.3.0pr1"
  8.  
  9. -- Loads and renders GIF files in ComputerCraft.
  10. -- Capable of handling animations.
  11. -- http://www.computercraft.info/forums2/index.php?/topic/23056-gif-renderer
  12.  
  13. ---------------------------------------------
  14. ------------Variable Declarations------------
  15. ---------------------------------------------
  16.  
  17. if not bbpack then os.loadAPI("bbpack") end
  18.  
  19. local CCPal = io.lines and  -- CC 1.55 adds this function, and also changes the colours, so...
  20.         {[0] = {240,240,240},{242,178, 51},{229,127,216},{153,178,242},
  21.         {222,222,108},{127,204, 25},{242,178,204},{ 76, 76, 76},
  22.         {153,153,153},{ 76,153,178},{178,102,229},{ 51,102,204},
  23.         {127,102, 76},{ 87,166, 78},{204, 76, 76},{ 25, 25, 25}}
  24.     or
  25.         {[0] = {240,240,240},{235,136, 68},{195, 84,205},{102,137,211},
  26.         {222,222,108},{65,205, 52},{216,129,152},{ 67, 67, 67},
  27.         {153,153,153},{ 40,118,151},{123, 47,190},{ 37, 49,146},
  28.         { 81, 48, 26},{ 59, 81, 26},{179, 49, 44},{  0,  0,  0}}
  29.  
  30. local GIF87a, GIF89a, colourNum = {71,73,70,56,55,97}, {71,73,70,56,57,97}, {}
  31.  
  32. do
  33.     local hex = "0123456789abcdef"
  34.     for i = 1, 16 do colourNum[i - 1] = hex:sub(i, i) end
  35.     for i = 1, 16 do colourNum[hex:sub(i, i)] = i - 1 end
  36. end
  37.  
  38. ---------------------------------------------
  39. ------------Function Declarations------------
  40. ---------------------------------------------
  41.  
  42. local function snooze()
  43.     local myEvent = tostring({})
  44.     os.queueEvent(myEvent)
  45.     os.pullEvent(myEvent)
  46. end
  47.  
  48. local function simpleWindow(terminal, xPos, yPos, width, height)
  49.     local results, curX, curY = {}, 1, 1
  50.    
  51.     results.clear = function(colour)
  52.         local text = string.rep(colour, width)
  53.         for y = 1, height do results[y] = text end
  54.     end
  55.    
  56.     results.draw = function(x1, y1, x2, y2)
  57.         if x1 then
  58.             local text = string.rep(" ", x2 - x1 + 1)
  59.             for y = y1, y2 do
  60.                 local snip = results[y]:sub(x1, x2)
  61.                 terminal.setCursorPos(xPos + x1 - 1, yPos + y - 1)
  62.                 terminal.blit(text, snip, snip)
  63.             end
  64.         else
  65.             local text = string.rep(" ", width)
  66.             for y = 1, height do
  67.                 terminal.setCursorPos(xPos, yPos + y - 1)
  68.                 terminal.blit(text, results[y], results[y])
  69.             end
  70.         end
  71.     end
  72.    
  73.     results.setCursorPos = function(newX, newY)
  74.         curX, curY = newX, newY
  75.     end
  76.    
  77.     results.blit = function(_, _, colString)
  78.         if curY < 1 or curY > height or curX > width or curX + #colString < 2 then return end
  79.        
  80.         local ending = (curX + #colString - 1 > width) and (width - curX + 1) or #colString
  81.         if curX < 1 then
  82.             colString = colString:sub(2 - curX, ending)
  83.             curX = 1
  84.         elseif ending ~= #colString then colString = colString:sub(1, ending) end
  85.        
  86.         if curX > 1 then
  87.             if curX + #colString - 1 < width then
  88.                 results[curY] = results[curY]:sub(1, curX - 1) .. colString .. results[curY]:sub(curX + #colString)
  89.             else
  90.                 results[curY] = results[curY]:sub(1, curX - 1) .. colString
  91.             end
  92.         elseif #colString < width then
  93.             results[curY] = colString .. results[curY]:sub(curX + #colString)
  94.         else results[curY] = colString end
  95.     end
  96.    
  97.     return results
  98. end
  99.  
  100. -- ComputerCraft builds prior to 1.74 lack term.blit(). This function implements a substitute:
  101. local function checkTerm(terminal)
  102.     if not terminal.blit then terminal.blit = function(_, _, backCol)
  103.         local counter, lastChar = 1, backCol:sub(1, 1)
  104.  
  105.         for i = 2, #backCol do if backCol:sub(i, i) ~= lastChar then
  106.             terminal.setBackgroundColour(bit.blshift(1, colourNum[lastChar]))
  107.             terminal.write(string.rep(" ", counter))
  108.             counter, lastChar = 1, backCol:sub(i, i)
  109.         else counter = counter + 1 end end
  110.  
  111.         terminal.setBackgroundColour(bit.blshift(1, colourNum[lastChar]))
  112.         terminal.write(string.rep(" ", counter))
  113.        
  114.         snooze()
  115.     end end
  116. end
  117.  
  118. if term.setPaletteColour then
  119.     function applyPalette(image, terminal)
  120.         terminal = terminal or term
  121.  
  122.         local col, pal = 1, image.pal
  123.  
  124.         for i = 0, #pal do
  125.             local thisCol = pal[i]
  126.             terminal.setPaletteColour(col, thisCol[1] / 255, thisCol[2] / 255, thisCol[3] / 255)
  127.             col = col * 2
  128.         end
  129.     end
  130. end
  131.  
  132. function drawGIF(image, xPos, yPos, terminal)
  133.     xPos, yPos, terminal = xPos or 1, yPos or 1, terminal or term
  134.     if type(image[1][1]) == "table" then image = image[1] end
  135.     checkTerm(terminal)
  136.    
  137.     for y = 1, image.yend do
  138.         local x, ystart, imageY = image.xstart + 1, image.ystart, image[y]
  139.         for i = 1, #imageY do
  140.             local imageYI = imageY[i]
  141.             if type(imageYI) == "number" then
  142.                 x = x + imageYI
  143.             else
  144.                 terminal.setCursorPos(x + xPos - 1, y + ystart + yPos - 1)
  145.                 terminal.blit(string.rep(" ", #imageYI), imageYI, imageYI)
  146.                 x = x + #imageYI
  147.             end
  148.         end
  149.     end
  150. end
  151.  
  152. function animateGIF(image, xPos, yPos, terminal)
  153.     if terminal == term or not terminal then terminal = term.current() end
  154.     checkTerm(terminal)
  155.     local buffer = simpleWindow(terminal, xPos, yPos, image.width, image.height)
  156.        
  157.     while true do for i = 1, #image do
  158.         local imageI, imageIneg1 = image[i], image[i - 1]
  159.         if i == 1 then buffer.clear(image.backgroundCol2) end
  160.        
  161.         drawGIF(imageI, 1, 1, buffer)
  162.        
  163.         if i == 1 then
  164.             buffer.draw()
  165.         elseif imageIneg1.disposal == 2 then
  166.             buffer.draw(math.min(imageIneg1.xstart, imageI.xstart) + 1, math.min(imageIneg1.ystart, imageI.ystart) + 1, math.max(imageIneg1.xstart + imageIneg1.xend, imageI.xstart + imageI.xend), math.max(imageIneg1.ystart + imageIneg1.yend, imageI.ystart + imageI.yend))
  167.         else
  168.             buffer.draw(imageI.xstart + 1, imageI.ystart + 1, imageI.xstart + imageI.xend, imageI.ystart + imageI.yend)
  169.         end
  170.        
  171.         sleep(imageI.delay)
  172.        
  173.         if imageI.disposal == 2 then
  174.             local blit = string.rep(image.backgroundCol2, imageI.xend)
  175.             for y = 1, imageI.yend do
  176.                 buffer.setCursorPos(imageI.xstart + 1, y + imageI.ystart)
  177.                 buffer.blit(nil, nil, blit)
  178.             end
  179.         end
  180.     end end
  181. end
  182.  
  183. local function rasteriseGIF(image)
  184.     local results, buffer = {["width"] = image.width, ["height"] = image.height, ["backgroundCol"] = image.backgroundCol, ["backgroundCol2"] = image.backgroundCol2, ["pal"] = image.pal}, {}
  185.    
  186.     for i = 1, image.height do
  187.         local temp = {}
  188.         for j = 1, image.width do temp[j] = " " end
  189.         buffer[i] = temp
  190.     end
  191.    
  192.     for i = 1, #image do
  193.         local thisImg = image[i]
  194.         local xStart, yStart = thisImg.xstart + 1, thisImg.ystart
  195.        
  196.         for y = 1, thisImg.yend do
  197.             local x, thisImgY, buffY = xStart, thisImg[y], buffer[y + yStart]
  198.             for i = 1, #thisImgY do
  199.                 local thisImgYI = thisImgY[i]
  200.                 if type(thisImgYI) == "number" then
  201.                     x = x + thisImgYI
  202.                 elseif type(thisImgYI) == "string" then
  203.                     for j = 1, #thisImgYI do buffY[x + j - 1] = colourNum[thisImgYI:sub(j, j)] end
  204.                     x = x + #thisImgYI
  205.                 else
  206.                     for j = 1, #thisImgYI do buffY[x + j - 1] = thisImgYI[j] end
  207.                     x = x + #thisImgYI
  208.                 end
  209.             end
  210.         end
  211.  
  212.         local resultsI = {["delay"] = thisImg.delay or 0.1}
  213.         for j = 1, #buffer do
  214.             local newRow, buffRow = {}, buffer[j]
  215.             for k = 1, #buffRow do newRow[k] = buffRow[k] end
  216.             resultsI[j] = newRow
  217.         end
  218.         results[i] = resultsI
  219.  
  220.         if thisImg.disposal == 2 then
  221.             local xEnd = thisImg.xstart + thisImg.xend
  222.             for y = yStart + 1, yStart + thisImg.yend do
  223.                 local bufferY = buffer[y]
  224.                 for x = xStart, xEnd do bufferY[x] = " " end
  225.             end
  226.         end
  227.  
  228.         snooze()
  229.     end
  230.  
  231.     return results 
  232. end
  233.  
  234. function flattenGIF(image, optimise, prerastered)
  235.     if not prerastered then image = rasteriseGIF(image) end
  236.    
  237.     local width, bump, cacheImg = image.width, 0
  238.     for i = 1, #image do
  239.         local prevImg, thisImg, nextImg, same = image[i - 1 - bump], image[i - bump], image[i + 1 - bump], true
  240.         if not thisImg then break end
  241.         local txStart, txEnd, tyStart, tyEnd, cxStart, cxEnd, cyStart, cyEnd = image.width, 1, image.height, 1
  242.         if cacheImg then cxStart, cxEnd, cyStart, cyEnd = cacheImg.xstart, cacheImg.xend, cacheImg.ystart, cacheImg.yend end
  243.        
  244.         if optimise and i > 1 then
  245.             local prevDisp = prevImg.disposal
  246.             for y = 1, #thisImg do
  247.                 local thisImgY, cacheImgY = thisImg[y], cacheImg[y]
  248.                 for x = 1, width do if thisImgY[x] ~= ((prevDisp == 2 and not (x < cxStart + 1 or y < cyStart + 1 or x > cxStart + cxEnd or y > cyStart + cyEnd)) and " " or cacheImgY[x]) then
  249.                     same = false
  250.                     if y < tyStart + 1 then tyStart = y - 1 end
  251.                     if y > tyEnd then tyEnd = y end
  252.                     if x < txStart + 1 then txStart = x - 1 end
  253.                     if x > txEnd then txEnd = x end
  254.                     snooze()
  255.                 end end
  256.             end
  257.         elseif optimise == false then
  258.             same = false
  259.             for y = 1, #thisImg do
  260.                 local thisImgY = thisImg[y]
  261.                 for x = 1, width do if thisImgY[x] ~= " " then
  262.                     if y < tyStart + 1 then tyStart = y - 1 end
  263.                     if y > tyEnd then tyEnd = y end
  264.                     if x < txStart + 1 then txStart = x - 1 end
  265.                     if x > txEnd then txEnd = x end
  266.                 end end
  267.                 snooze()
  268.             end
  269.         else
  270.             same, txStart, txEnd, tyStart, tyEnd = false, 0, width, 0, #thisImg
  271.         end
  272.        
  273.         if same and i > 1 then
  274.             image[i - 1 - bump].delay = prevImg.delay + thisImg.delay
  275.             table.remove(image, i - bump)
  276.             bump = bump + 1
  277.             snooze()
  278.         else
  279.             if i - bump ~= #image and optimise ~= nil then
  280.                 local obscured = true
  281.                 for y = 1, #thisImg do
  282.                     local thisImgY, nextImgY = thisImg[y], nextImg[y]
  283.                     for x = 1, width do if thisImgY[x] ~= " " and nextImgY[x] == " " then
  284.                         obscured = false
  285.                         if y < tyStart + 1 then tyStart = y - 1 end
  286.                         if y > tyEnd then tyEnd = y end
  287.                         if x < txStart + 1 then txStart = x - 1 end
  288.                         if x > txEnd then txEnd = x end
  289.                     end end
  290.                     snooze()
  291.                 end
  292.                 thisImg.disposal = obscured and 1 or 2
  293.             else thisImg.disposal = 1 end
  294.  
  295.             if txStart > txEnd then txStart, txEnd, tyStart, tyEnd = 0, 1, 0, 1 end
  296.            
  297.             local nxStart, nxEnd, nyStart, nyEnd, cacheDisp = txStart, txEnd - txStart, tyStart, tyEnd - tyStart, cacheImg and cacheImg.disposal
  298.             newImg = {["delay"] = thisImg.delay, ["disposal"] = thisImg.disposal, ["xstart"] = nxStart, ["xend"] = nxEnd, ["ystart"] = nyStart, ["yend"] = nyEnd}
  299.            
  300.             for y = 1, nyEnd do
  301.                 local skip, chars, temp, thisImgY, cacheImgY = 0, {}, {}, thisImg[y + nyStart], cacheImg and cacheImg[y + nyStart]
  302.                 for x = nxStart + 1, nxStart + nxEnd do
  303.                     local thisVal = thisImgY[x]
  304.                     if optimise ~= nil and cacheImg and thisVal == cacheImgY[x] and (cacheDisp == 1 or (cacheDisp == 2 and (x < cxStart + 1 or y + nyStart < cyStart + 1 or x > cxStart + cxEnd or y + nyStart > cyStart + cyEnd))) then thisVal = " " end
  305.                    
  306.                     if thisVal == " " then
  307.                         skip = skip + 1
  308.                         if #chars > 0 then
  309.                             temp[#temp + 1] = (image.pal and image.pal[1][4]) and chars or table.concat(chars)
  310.                             chars = {}
  311.                         end
  312.                     else
  313.                         chars[#chars + 1] = (image.pal and image.pal[1][4]) and thisVal or colourNum[thisVal]
  314.                         if skip > 0 then
  315.                             temp[#temp + 1] = skip
  316.                             skip = 0
  317.                         end
  318.                     end
  319.                 end
  320.                 if skip == 0 then temp[#temp + 1] = (image.pal and image.pal[1][4]) and chars or table.concat(chars) end
  321.                 newImg[y] = temp
  322.                 snooze()
  323.             end
  324.             thisImg.xstart, thisImg.xend, thisImg.ystart, thisImg.yend = txStart, txEnd, tyStart, tyEnd
  325.             cacheImg = thisImg
  326.             image[i - bump] = newImg
  327.         end
  328.     end
  329.    
  330.     return image
  331. end
  332.  
  333. function resizeGIF(image, width, height)
  334.     if not width then width = math.floor(height / image.height * image.width) end
  335.     if not height then height = math.floor(width / image.width * image.height) end
  336.     local xInc, yInc, pal = image.width / width, image.height / height, image.pal or CCPal
  337.     image = rasteriseGIF(image)
  338.    
  339.     for i = 1, #image do
  340.         local oldFrame, yPos2 = image[i], 1
  341.         local newFrame = {["delay"] = oldFrame.delay or 0.1}
  342.         for y = 1, height do
  343.             local thisLine, xPos2 = {}, 1
  344.             for x = 1, width do
  345.                 local remY, R, G, B, totalWeight, yPos = yInc, 0, 0, 0, 0, yPos2
  346.                 while remY > 0 do
  347.                     local remX, maxInc, oldFrameY, xPos, yWeight = xInc, (math.floor(yPos) == yPos) and 1 or (math.ceil(yPos) - yPos), oldFrame[math.min(math.floor(yPos), image.height)], xPos2
  348.                     if remY < maxInc then yWeight = remY remY = 0 else yWeight = maxInc remY = remY - maxInc end
  349.                     while remX > 0 do
  350.                         local maxInc, thisChar, xWeight = (math.floor(xPos) == xPos) and 1 or (math.ceil(xPos) - xPos), oldFrameY[math.min(math.floor(xPos), image.width)]
  351.                         if remX < maxInc then xWeight = remX remX = 0 else xWeight = maxInc remX = remX - maxInc end
  352.  
  353.                         if thisChar ~= " " then
  354.                             local thisWeight = xWeight * yWeight
  355.                             totalWeight, thisChar = totalWeight + thisWeight, pal[thisChar]
  356.                             R, G, B = R + thisChar[1] * thisWeight, G + thisChar[2] * thisWeight, B + thisChar[3] * thisWeight
  357.                         end
  358.                        
  359.                         xPos = (maxInc < 1 and xWeight == maxInc) and math.ceil(xPos) or (xPos + xWeight)
  360.                     end
  361.                     yPos = (maxInc < 1 and yWeight == maxInc) and math.ceil(yPos) or (yPos + yWeight)
  362.                 end
  363.                
  364.                 if totalWeight > (xInc * yInc) / 2 then
  365.                     local closest, difference = 0, 10000
  366.                     R, G, B = R / totalWeight, G / totalWeight, B / totalWeight
  367.                     for j = 0, #pal do
  368.                         local thisDiff = math.abs(pal[j][1] - R) + math.abs(pal[j][2] - G) + math.abs(pal[j][3] - B)
  369.                         if thisDiff < difference then
  370.                             difference = thisDiff
  371.                             closest = j
  372.                             if difference == 0 then break end
  373.                         end
  374.                     end
  375.                     thisLine[#thisLine + 1] = closest
  376.                 else thisLine[#thisLine + 1] = " " end
  377.                 xPos2 = xPos2 + xInc
  378.             end
  379.             yPos2 = yPos2 + yInc
  380.             newFrame[y] = thisLine
  381.             snooze()
  382.         end
  383.         image[i] = newFrame
  384.     end
  385.    
  386.     image.width, image.height = width, height
  387.    
  388.     return flattenGIF(image, false, true)
  389. end
  390.  
  391. function loadGIF(targetfile, pal)
  392.     local results, fileread = {["backgroundCol"] = 0}, fs.open(targetfile, "rb")
  393.     if not fileread then error("GIF.loadGIF(): Unable to open " .. targetfile .. " for input.", 2) end
  394.    
  395.     if type(pal) == "string" then
  396.         local file = fs.open(pal, "r")
  397.         pal = textutils.unserialize(file.readAll())
  398.         file.close()
  399.         for i = 1, #pal do
  400.             local palI = pal[i]
  401.             if not palI[5] then palI[5] = 0 end
  402.         end
  403.         if not pal[0] then pal[0] = {1000, 1000, 1000, "minecraft:air", 0} end
  404.         results.pal = pal
  405.     elseif pal then
  406.         -- Construct a reduced palette from the GIF's own.
  407.         if not fileread.readAll then error("Installed version of CC does not support palette alterations.") end
  408.  
  409.         pal = {}
  410.        
  411.         fileread.read(10)
  412.         local temp, counter = fileread.read(), 0
  413.         fileread.read(2)
  414.  
  415.         -- Read in global palette:
  416.         if bit.band(temp, 128) == 128 then for i = 1, 2 ^ (bit.band(temp, 7) + 1) do
  417.             local thisCol, found = fileread.read(3), false
  418.  
  419.             for i = 1, counter do if pal[i] == thisCol then
  420.                 found = true
  421.                 break
  422.             end end
  423.  
  424.             if not found then
  425.                 counter = counter + 1
  426.                 pal[counter] = thisCol
  427.             end
  428.         end end
  429.        
  430.         -- Read in any additional palettes:
  431.         while true do
  432.             local record = fileread.read()
  433.            
  434.             if record == 33 then
  435.                 fileread.read()
  436.                
  437.                 while true do
  438.                     record = fileread.read()
  439.                     if record == 0 then break end
  440.                     fileread.read(record)
  441.                     snooze()
  442.                 end
  443.             elseif record == 44 then
  444.                 fileread.read(8)
  445.                
  446.                 record = fileread.read()
  447.  
  448.                 if bit.band(record, 128) == 128 then for i = 1, 2 ^ (bit.band(record, 7) + 1) do
  449.                     local thisCol, found = fileread.read(3), false
  450.  
  451.                     for i = 1, counter do if pal[i] == thisCol then
  452.                         found = true
  453.                         break
  454.                     end end
  455.  
  456.                     if not found then
  457.                         counter = counter + 1
  458.                         pal[counter] = thisCol
  459.                     end
  460.                 end end
  461.                
  462.                 fileread.read()
  463.                
  464.                 while true do
  465.                     record = fileread.read()
  466.                     if record == 0 then break end
  467.                     fileread.read(record)
  468.                     snooze()
  469.                 end
  470.             elseif record == 59 then
  471.                 fileread.close()
  472.                 break
  473.             else
  474.                 error("Malformed record")
  475.             end
  476.         end
  477.  
  478.         -- Condense palette:
  479.         for i = 1, counter do pal[i] = {pal[i]:byte(1, 3)} end
  480.        
  481.         while #pal > 16 do
  482.             local matches, tally, bestDiff, curLen = {}, tally, 1000, #pal
  483.            
  484.             for i = 1, curLen - 1 do
  485.                 local palI, found = pal[i], false
  486.                
  487.                 for j = i + 1, curLen do
  488.                     local palJ = pal[j]
  489.                     local thisDiff = math.abs(palJ[1] - palI[1]) + math.abs(palJ[2] - palI[2]) + math.abs(palJ[3] - palI[3])
  490.                    
  491.                     if thisDiff < bestDiff then
  492.                         matches, tally, bestDiff, found = {[j] = i}, 1, thisDiff, true
  493.                     elseif thisDiff == bestDiff and not matches[j] and not matches[i] and not found then
  494.                         matches[j], tally, found = i, tally + 1, true
  495.                     end
  496.                 end
  497.             end
  498.            
  499.             local counter, max = 1, (curLen - tally > 15) and tally or (curLen - 16)
  500.             for i, j in pairs(matches) do
  501.                 local palI, palJ = pal[i], pal[j]
  502.                 --print(#pal,i,j,type(palI),type(palJ))
  503.                 pal[i] = {math.floor((palI[1] + palJ[1]) / 2), math.floor((palI[2] + palJ[2]) / 2), math.floor((palI[3] + palJ[3]) / 2)}
  504.                 pal[j], counter = "remove", counter + 1
  505.                 if counter > max then break end
  506.             end
  507.            
  508.             for i = curLen, 1, -1 do if pal[i] == "remove" then table.remove(pal, i) end end
  509.         end
  510.        
  511.         pal[0] = pal[#pal]
  512.         pal[#pal] = nil
  513.         results.pal = pal
  514.        
  515.         fileread = fs.open(targetfile, "rb")
  516.     else pal = CCPal end
  517.    
  518.     local readByte = fileread.read
  519.    
  520.     local readBytes = fileread.readAll and
  521.         function(num)
  522.             local output = readByte(num)
  523.             return {output:byte(1, #output)}
  524.         end
  525.     or
  526.         function(num)
  527.             local output = {}
  528.             for i = 1, num do output[#output + 1] = readByte() end
  529.             return output
  530.         end
  531.    
  532.     local function readInt()
  533.         local results = readByte()
  534.         return results + readByte() * 256
  535.     end
  536.  
  537.     -- GIF signature:
  538.     local temp = readBytes(6)
  539.     for i = 1, 6 do if temp[i] ~= GIF87a[i] and temp[i] ~= GIF89a[i] then
  540.         fileread.close()
  541.         error("GIF.loadGIF(): " .. targetfile .. " does not appear to be a GIF.", 2)
  542.     end end
  543.  
  544.     -- Screen discriptor:
  545.     local width = readInt()
  546.     local height = readInt()
  547.     results.width, results.height = width, height
  548.    
  549.     local cacheFrame, cacheBackup = {}
  550.     for y = 1, height do
  551.         local cacheFrameY = {}
  552.         for x = 1, width do cacheFrameY[x] = " " end
  553.         cacheFrame[y] = cacheFrameY
  554.     end
  555.        
  556.     temp = readByte()
  557.     local globalColourCount = 2 ^ (bit.band(temp, 7) + 1)
  558.     readInt()  -- This is the background colour, which we'll ignore, and instead pick something that contrasts the image later.
  559.     snooze()
  560.  
  561.     -- Palette:
  562.     local globalPal = bit.band(temp, 128) == 128
  563.     if globalPal then
  564.         globalPal = {}
  565.         for i = 0, globalColourCount - 1 do globalPal[i] = readBytes(3) end
  566.     end
  567.    
  568.     -- Read image records:
  569.     while true do
  570.         local thisImg, interlace, thisPal, transparent = {}
  571.        
  572.         -- Read image headers:
  573.         while true do
  574.             snooze()
  575.             local record = readByte()
  576.  
  577.             if record == 33 then
  578.                 record = readByte()
  579.  
  580.                 -- Gif extensions:
  581.                 if record == 249 then
  582.                     -- Graphics control extension:
  583.                     readByte()
  584.                     local flags = readByte()
  585.                     local isTransparent = bit.band(flags, 1) == 1
  586.                     thisImg.inputExpected = bit.band(flags, 2) == 2
  587.                     thisImg.disposal = bit.brshift(bit.band(flags, 28), 2)
  588.                     thisImg.delay = readInt()
  589.                     thisImg.delay = (thisImg.delay > 0) and (thisImg.delay / 100) or nil
  590.                     transparent = readByte()
  591.                     if not isTransparent then transparent = nil end
  592.                     readByte()
  593.                 elseif record == 254 then
  594.                     -- File comment:
  595.                     local fullComment = {}
  596.                    
  597.                     repeat
  598.                         local length = readByte()
  599.                         if length > 0 then fullComment[#fullComment + 1] = string.char(unpack(readBytes(length))) end
  600.                     until length == 0
  601.                            
  602.                     results.comment = table.concat(fullComment)
  603.                 elseif record == 1 then
  604.                     -- Plain text extension:
  605.                     readBytes(readByte())
  606.                    
  607.                     local fullText = {}
  608.                    
  609.                     repeat
  610.                         local length = readByte()
  611.                         if length > 0 then fullText[#fullText + 1] = string.char(unpack(readBytes(length))) end
  612.                     until length == 0
  613.                            
  614.                     results.text = table.concat(fullText)
  615.                 else
  616.                     -- All other extensions we skip past:
  617.                     repeat
  618.                         local length = readByte()
  619.                         if length > 0 then readBytes(length) end
  620.                     until length == 0
  621.                 end
  622.             elseif record == 44 then
  623.                 -- Image descriptor:
  624.                 thisImg.xstart = readInt()
  625.                 thisImg.ystart = readInt()
  626.                 thisImg.xend = readInt()
  627.                 thisImg.yend = readInt()
  628.  
  629.                 record = readByte()
  630.                 interlace = bit.band(record, 64) == 64
  631.  
  632.                 if bit.band(record, 128) == 128 then
  633.                     -- Local colour map:
  634.                     thisPal = {[0] = readBytes(3)}
  635.                     for i = 2, 2 ^ (bit.band(record, 7) + 1) do thisPal[#thisPal + 1] = readBytes(3) end
  636.                 else
  637.                     -- Global colour map:
  638.                     thisPal = textutils.unserialize(textutils.serialize(globalPal))
  639.                 end
  640.                
  641.                 local used = {}
  642.                 for i = 0, #thisPal do
  643.                     local closest, difference, thisPalI = 0, 10000, thisPal[i]
  644.  
  645.                     for j = 0, #pal do
  646.                         local palJ = pal[j]
  647.                         local thisDiff = math.abs(palJ[1] - thisPalI[1]) + math.abs(palJ[2] - thisPalI[2]) + math.abs(palJ[3] - thisPalI[3])
  648.  
  649.                         if thisDiff < difference then
  650.                             difference = thisDiff
  651.                             closest = j
  652.                             if difference == 0 then break end
  653.                         end
  654.                     end
  655.  
  656.                     thisPal[i] = closest
  657.                     if i ~= transparent then used[closest + 1] = true end
  658.                 end
  659.                
  660.                 local bgCol, bgCol2  = 2 ^ math.min(#used, 15), colourNum[math.min(#used, 15)]
  661.                 if bgCol > results.backgroundCol then
  662.                     results.backgroundCol = bgCol
  663.                     results.backgroundCol2 = bgCol2
  664.                 end
  665.                
  666.                 -- Image data follows, go read it:
  667.                 break
  668.             elseif record == 59 then
  669.                 -- End of file.
  670.                 fileread.close()
  671.                 return flattenGIF(results, false, true)
  672.             end
  673.         end
  674.        
  675.         local xStart, yStart, xEnd, yEnd = thisImg.xstart, thisImg.ystart, thisImg.xend, thisImg.yend
  676.        
  677.         if thisImg.disposal == 3 then
  678.             cacheBackup = {}
  679.             for y = 1, math.min(height - yStart, yEnd) do
  680.                 local cacheBackupY, cacheFrameY = {}, cacheFrame[y + yStart]
  681.                 for x = 1, math.min(width - xStart, xEnd) do cacheBackupY[x]  = cacheFrameY[x + xStart] end
  682.                 cacheBackup[y] = cacheBackupY
  683.             end
  684.         end
  685.        
  686.         -- Read image body:
  687.        
  688.         fileread = bbpack.open(fileread, "rb", 2 ^ fileread.read())
  689.         readByte = fileread.read
  690.  
  691.         local y, passnum, stepsize = 1, 8, 8
  692.         while y < yEnd + 1 do
  693.             local cacheFrameY = cacheFrame[y + yStart]
  694.             if y <= height then
  695.                 for x = 1, xEnd do
  696.                     local thisVal = readByte()
  697.                     if thisVal ~= transparent and x + xStart <= width then cacheFrameY[x + xStart] = thisPal[thisVal] end
  698.                 end
  699.             else for x = 1, xEnd do readByte() end end
  700.            
  701.             if interlace then
  702.                 y = y + stepsize
  703.  
  704.                 while y > yEnd and passnum > 1 do
  705.                     stepsize = passnum
  706.                     passnum = passnum / 2
  707.                     y = passnum + 1
  708.                 end
  709.             else y = y + 1 end
  710.            
  711.             snooze()
  712.         end
  713.        
  714.         for y = 1, height do
  715.             local thisImgY, cacheFrameY = {}, cacheFrame[y]
  716.             for x = 1, width do thisImgY[x] = cacheFrameY[x] end
  717.             thisImg[y] = thisImgY
  718.         end
  719.         if not thisImg.delay then thisImg.delay = 0.1 end
  720.        
  721.         if thisImg.disposal == 2 then
  722.             for y = 1, math.min(height - yStart, yEnd) do
  723.                 local cacheFrameY = cacheFrame[y + yStart]
  724.                 for x = 1, math.min(width - xStart, xEnd) do cacheFrameY[x + xStart] = " " end
  725.             end
  726.         elseif thisImg.disposal == 3 then
  727.             for y = 1, math.min(height - yStart, yEnd) do
  728.                 local cacheFrameY, cacheBackupY = cacheFrame[y + yStart], cacheBackup[y]
  729.                 for x = 1, math.min(width - xStart, xEnd) do cacheFrameY[x + xStart] = cacheBackupY[x] end
  730.             end
  731.             cacheBackup = nil
  732.         end
  733.        
  734.         fileread = fileread.extractHandle()
  735.         readByte = fileread.read
  736.        
  737.         results[#results + 1] = thisImg
  738.     end
  739. end
  740.  
  741. function setBackgroundColour(image, colour)
  742.     if type(colour) ~= "number" and not colourNum[colour] then error("GIF.setBackgroundColour: Bad colour.", 2) end
  743.    
  744.     if type(colour) == "number" then
  745.         local counter = 0
  746.         while colour > 1 do
  747.             colour = bit.brshift(colour, 1)
  748.             counter = counter + 1
  749.         end
  750.         image.backgroundCol = 2 ^ counter
  751.         image.backgroundCol2 = colourNum[counter]
  752.     else
  753.         image.backgroundCol = 2 ^ colourNum[colour]
  754.         image.backgroundCol2 = colour
  755.     end
  756. end
  757. setBackgroundColor = setBackgroundColour
  758.  
  759. function toPaintutils(image)
  760.     if type(image[1][1]) == "table" then image = image[1] end
  761.    
  762.     local results = {}
  763.    
  764.     if not image.yend then error("wtf",2) end
  765.     for y = 1, image.yend do
  766.         local resultsY, imageY = {}, image[y]
  767.         local x = image.xstart + 1
  768.         for i = 1, #imageY do
  769.             local imageYI = imageY[i]
  770.             if type(imageYI) == "number" then
  771.                 for j = 1, imageYI do resultsY[#resultsY + 1] = 0 end
  772.             else
  773.                 for j = 1, #imageYI do resultsY[#resultsY + 1] = 2 ^ colourNum[imageYI:sub(j, j)] end
  774.             end
  775.         end
  776.         results[y] = resultsY
  777.     end
  778.    
  779.     for i = #results[1] + 1, image.xstart + image.xend do results[1][i] = 0 end
  780.    
  781.     return results
  782. end
  783.  
  784. function buildGIF(...)
  785.     local colourChar, hex, width, height, used = {[0] = " "}, "0123456789abcdef", 0, 0, 0
  786.     for i = 1, 16 do colourChar[2 ^ (i - 1)] = hex:sub(i, i) end
  787.    
  788.     for i = 1, #arg do
  789.         local argI = arg[i]
  790.         if #argI > height then height = #argI end
  791.         for y = 1, #argI do if #argI[y] > width then width = #argI[y] end end
  792.     end
  793.    
  794.     for i = 1, #arg do
  795.         local thisImage, argI = {["delay"] = 0.1}, arg[i]
  796.        
  797.         for y = 1, #argI do
  798.             local thisLine, argIY = {}, argI[y]
  799.            
  800.             for x = 1, width do
  801.                 local argIYX = argIY[x] or 0
  802.                 if argIYX > used then used = argIYX end
  803.                 thisLine[x] = colourNum[colourChar[argIYX]] or " "
  804.             end
  805.                
  806.             thisImage[y] = thisLine
  807.         end
  808.        
  809.         for y = #argI + 1, height do
  810.             local thisLine = {}
  811.             for x = 1, width do thisLine[x] = " " end
  812.             thisImage[y] = thisLine
  813.         end
  814.        
  815.         arg[i] = thisImage
  816.     end
  817.    
  818.     arg.width, arg.height, arg.backgroundCol, arg.backgroundCol2 = width, height, used, colourChar[used]
  819.    
  820.     return flattenGIF(arg, true, true)
  821. end
  822.  
  823. function saveGIF(image, file)
  824.     if not (image.width or image.xstart) then
  825.         -- "paintutils" image.
  826.         image = buildGIF(image)
  827.     elseif not image.width then
  828.         -- Single frame of an image.
  829.         image = {width = image.xstart + image.xend, height = image.ystart + image.yend, image}
  830.     end
  831.    
  832.     local file, power, transCol = fs.open(file, "wb"), 5
  833.     if not file then error("Unable to open file for output.", 2) end
  834.     local writeByte = file.write
  835.    
  836.     local function writeBytes(bytes)
  837.         for i = 1, #bytes do writeByte(bytes[i]) end
  838.     end
  839.    
  840.     local function writeInt(num)
  841.         writeByte(bit.band(num, 255))
  842.         writeByte(bit.brshift(num, 8))
  843.     end
  844.  
  845.     -- GIF signature:
  846.     writeBytes(GIF89a)
  847.    
  848.     -- Screen discriptor:
  849.     writeInt(image.width)
  850.     writeInt(image.height)
  851.    
  852.     if image.pal then
  853.         local pal = image.pal
  854.         power = 1
  855.         while 2 ^ power < (#pal + 1) do power = power + 1 end
  856.         writeByte(127 + power)
  857.         transCol = 0
  858.         writeInt(transCol)
  859.  
  860.         -- Palette:
  861.         for i = 0, #pal do
  862.             local palI = pal[i]
  863.             for j = 1, 3 do writeByte(palI[j]) end
  864.         end
  865.         for i = #pal + 2, 2 ^ power do writeBytes({127, 127, 127}) end
  866.     else
  867.         writeByte(132)
  868.         transCol = #CCPal + 1
  869.         writeInt(transCol)
  870.  
  871.         -- Palette:
  872.         for i = 0, 15 do writeBytes(CCPal[i]) end
  873.         for i = 0, 15 do writeBytes({127, 127, 127}) end
  874.     end
  875.    
  876.     -- Loop extension:
  877.     if #image > 1 then
  878.         writeBytes({33, 255, 11})
  879.         for i = 1, 11 do writeByte(string.byte("NETSCAPE2.0", i)) end
  880.         writeBytes({3, 1, 0, 0, 0})
  881.     end
  882.    
  883.     -- Write image records:
  884.     for i = 1, #image do
  885.         local image = image[i]
  886.        
  887.         writeBytes({33, 249, 4})
  888.         writeByte(image.disposal and (1 + bit.blshift(image.disposal, 2)) or 1)
  889.         writeInt((not image.delay) and 0 or math.floor(image.delay * 100))
  890.         writeInt(transCol)
  891.        
  892.         writeByte(44)
  893.         writeInt(image.xstart)
  894.         writeInt(image.ystart)
  895.         writeInt(image.xend)
  896.         writeInt(image.yend)
  897.         writeByte(0)
  898.         writeByte(power)
  899.  
  900.         file = bbpack.open(file, "wb", 2 ^ power)
  901.         writeByte = file.write
  902.        
  903.         for y = 1, image.yend do
  904.             local rowLen, imageY = 0, image[y]
  905.             for i = 1, #imageY do
  906.                 local imageYI = imageY[i]
  907.                 if type(imageYI) == "number" then
  908.                     for j = 1, imageYI do writeByte(transCol) end
  909.                     rowLen = rowLen + imageYI
  910.                 elseif type(imageYI) == "string" then
  911.                     for j = 1, #imageYI do writeByte(colourNum[imageYI:sub(j, j)]) end
  912.                     rowLen = rowLen + #imageYI
  913.                 else
  914.                     for j = 1, #imageYI do writeByte(imageYI[j]) end
  915.                     rowLen = rowLen + #imageYI
  916.                 end
  917.             end
  918.             for i = 1, image.xend - rowLen do writeByte(transCol) end
  919.             snooze()
  920.         end
  921.        
  922.         file = file.extractHandle()
  923.         writeByte = file.write
  924.     end
  925.    
  926.     writeByte(59)
  927.     file.close()
  928. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Not a member of Pastebin yet?
Sign Up, it unlocks many cool features!
 
Top