Advertisement
LDDestroier

PAIN BETA

Apr 28th, 2016
409
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[
  2.     PAIN image editor for ComputerCraft
  3.     Get it with
  4.      wget https://raw.githubusercontent.com/LDDestroier/CC/beta/pain.lua painb
  5.      pastebin get eWpW2W5n painb
  6.      std ld pain pain
  7.    
  8.     This is a beta release. You fool!
  9.     To do:
  10.      + add file > open using LDDFM
  11. --]]
  12. local askToSerialize = false
  13. local defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name
  14. --[[
  15.     defaultSaveFormat possible parameters:
  16.     1. NFP (paint)
  17.     2. NFT (npaintpro)
  18.     3. BLT (blittle)
  19.     4. Native PAIN
  20.     5. GIF
  21.     6. UCG
  22.     7. BMP
  23. --]]
  24. local readNonImageAsNFP = true
  25. local useFlattenGIF = true
  26. local undoBufferSize = 8
  27.  
  28. local doFillDiagonal = false    -- checks for diagonal dots when using fill tool
  29. local doFillAnimation = false   -- whether or not to animate the fill tool
  30.  
  31. local progname = fs.getName(shell.getRunningProgram())
  32.  
  33. local displayHelp = function()
  34.     print(progname)
  35.     print(progname.." <filename>")
  36.     print(progname.." [-h/--help]")
  37.     print("Press F1 in program for more.")
  38. end
  39.  
  40. local tsv = term.current().setVisible
  41. local undoBuffer
  42. local undoPos = 1
  43. local pMode = 0
  44. local scr_x, scr_y = term.getSize()
  45. screenEdges = {
  46.     scr_x,
  47.     scr_y,
  48. }
  49.  
  50. local tArg = {...}
  51. if (tArg[1] == "--help" or tArg[1] == "-h") and shell then
  52.     return displayHelp()
  53. end
  54.  
  55. if tArg[2] == "view" then
  56.     pMode = 1
  57. elseif (tArg[2] == "moo") and (not fs.exists("moo")) then
  58.     return print("This PAIN does not have Super Cow Powers.")
  59. end
  60.  
  61. local fileName
  62. if (not term.isColor()) and (pMode ~= 1) then
  63.     error("PAIN only works with Advanced Computers at the moment.")
  64. end
  65. local barmsg = "Press F1 for help."
  66. local tse = textutils.serialise
  67. local tun = textutils.unserialise
  68. local paintEncoded
  69. local lastPaintEncoded
  70. local frame = 1
  71. local doRender = false
  72. local metaHistory = {}
  73. local bepimode = false      -- this is a family-friendly program! now stand still while I murder you
  74. local evenDrawGrid = true   -- will you evenDraw(the)Grid ?
  75. local renderBlittle = false -- whether or not to render all in blittle
  76. local firstTerm, blittleTerm = term.current()
  77. local firstBG = term.getBackgroundColor()
  78. local firstTX = term.getTextColor()
  79. local changedImage = false
  80. local isCurrentlyFilling = false
  81.  
  82. local _
  83. local tableconcat = table.concat
  84.  
  85. local rendback = {
  86.     b = colors.black,
  87.     t = colors.gray,
  88. }
  89.  
  90. local grid
  91.  
  92. local yield = function()
  93.     os.queueEvent("yield")
  94.     os.pullEvent("yield")
  95. end
  96.  
  97. local paint = {
  98.     scrollX = 0,
  99.     scrollY = 0,
  100.     t = colors.gray,
  101.     b = colors.white,
  102.     m = 1,  --  in case you want to use PAIN as a level editor or something
  103.     c = " ",
  104.     doGray = false,
  105. }
  106. local boxchar = {topLeft = true, topRight = true, left = true, right = true, bottomLeft = true, bottomRight = true}
  107. local swapColors = false -- swaps background and text colors, for use with those tricky box characters
  108. local scrollX, scrollY = 0, 0
  109.  
  110. local keysDown = {}
  111. local miceDown = {}
  112.  
  113. local doRenderBar = 1 -- Not true or false
  114.  
  115. local fixstr = function(str)
  116.     return str:gsub("\\(%d%d%d)",string.char)
  117. end
  118.  
  119. local choice = function(input,breakkeys,returnNumber)
  120.     local fpos = 0
  121.     repeat
  122.         event, key = os.pullEvent("key")
  123.         if type(key) == "number" then key = keys.getName(key) end
  124.         if key == nil then key = " " end
  125.         if type(breakkeys) == "table" then
  126.             for a = 1, #breakkeys do
  127.                 if key == breakkeys[a] then
  128.                     return ""
  129.                 end
  130.             end
  131.         end
  132.         fpos = string.find(input, key)
  133.     until fpos
  134.     return returnNumber and fpos or key
  135. end
  136. local explode = function(div,str)
  137.     if (div=='') then return false end
  138.     local pos,arr = 0,{}
  139.     for st,sp in function() return string.find(str,div,pos,true) end do
  140.         arr[#arr+1] = str:sub(pos,st-1)
  141.         pos = sp + 1
  142.     end
  143.     arr[#arr+1] = str:sub(pos)
  144.     return arr
  145. end
  146.  
  147. local cutString = function(max_line_length, str) -- from stack overflow
  148.    local lines = {}
  149.    local line
  150.    str:gsub('(%s*)(%S+)',
  151.       function(spc, word)
  152.          if not line or #line + #spc + #word > max_line_length then
  153.             lines[#lines+1] = line
  154.             line = word
  155.          else
  156.             line = line..spc..word
  157.          end
  158.       end
  159.    )
  160.    lines[#lines+1] = line
  161.    return lines
  162. end
  163.  
  164. local getDrawingCharacter = function(topLeft, topRight, left, right, bottomLeft, bottomRight) -- thank you oli414
  165.   local data = 128
  166.   if not bottomRight then
  167.         data = data + (topLeft and 1 or 0)
  168.         data = data + (topRight and 2 or 0)
  169.         data = data + (left and 4 or 0)
  170.         data = data + (right and 8 or 0)
  171.         data = data + (bottomLeft and 16 or 0)
  172.   else
  173.         data = data + (topLeft and 0 or 1)
  174.         data = data + (topRight and 0 or 2)
  175.         data = data + (left and 0 or 4)
  176.         data = data + (right and 0 or 8)
  177.         data = data + (bottomLeft and 0 or 16)
  178.   end
  179.   return {char = string.char(data), inverted = bottomRight}
  180. end
  181.  
  182. local cutUp = function(len,tbl)
  183.     local output = {}
  184.     local e = 0
  185.     local s
  186.     for a = 1, #tbl do
  187.         if #(tbl[a]:gsub(" ","")) == 0 then
  188.             s = {""}
  189.         else
  190.             s = cutString(len,tbl[a])
  191.         end
  192.         for b = 1, #s do
  193.             output[#output+1] = s[b]
  194.         end
  195.     end
  196.     return output
  197. end
  198.  
  199. local getEvents = function(...)
  200.     local arg, output = table.pack(...)
  201.     while true do
  202.         output = {os.pullEvent()}
  203.         for a = 1, #arg do
  204.             if type(arg[a]) == "boolean" then
  205.                 if doRender == arg[a] then
  206.                     return {}
  207.                 end
  208.             elseif output[1] == arg[a] then
  209.                 return unpack(output)
  210.             end
  211.         end
  212.     end
  213. end
  214.  
  215.  
  216.  
  217. local sanitize = function(sani,tize)
  218.     local _,x = string.find(sani,tize)
  219.     if x then
  220.         return sani:sub(x+1)
  221.     else
  222.         return sani
  223.     end
  224. end
  225. local ro = function(input, max)
  226.     return math.floor(input % max)
  227. end
  228.  
  229. local guiHelp = function(inputText)
  230.     term.redirect(firstTerm)
  231.     scr_x, scr_y = term.current().getSize()
  232.     local _helpText = inputText or [[
  233.  
  234.  'PAIN' Help Page
  235. Programmed by LDDestroier/EldidiStroyrr
  236.  
  237. (use UP/DOWN or scrollwheel, exit with Q)
  238. If you want to use PAIN to its full capacity, then READ EVERYTHING HERE! Its not TOO long, and it's completely worth it!
  239.  
  240. Syntax:
  241. >pain <filename> [view]
  242. >pain [-n]
  243. >pain [-h/--help]
  244.  
  245. [view]: disable all writing capability to view a file
  246. "-n" or no arguments: create new document, declare name upon saving
  247. "-h" or "--help": display short syntax help
  248.  
  249. You can see what colors are selected based on the word "PAIN" on the hotbar.
  250.  
  251. Hotkeys:
  252. left/right ctrl: toggle the menu
  253.  
  254. left click:
  255.  +shift = drag and let go to make a line
  256.  -alone = place pixel
  257.  
  258. right click: delete pixel
  259.  
  260. middle click OR "t": place text down with current colors, cancel with X
  261.  
  262. "z":
  263.  +left alt = redo
  264.  -alone = undo
  265.  
  266. "p": pick colors from position onscreen, cancel with X
  267.  
  268. "n":
  269.  +left shift = change character to that of a special character
  270.  -alone = change box character for drawing
  271.  (cancel with CTRL, N, or by clicking outside)
  272.  
  273. "[" or mouse scroll down:
  274.  +shift = change to previous text color
  275.  -alone = change to previous background color
  276.  
  277. "]" or mouse scroll up:
  278.  +shift = change to next text color
  279.  -alone = change to next background color
  280.  
  281. "F1":
  282.  -alone = access help screen
  283.  
  284. "F3:"
  285.  -alone = view all connected monitors
  286.  
  287. spacebar:
  288.  +shift = toggle grid
  289.  -alone = toggle bar visibility
  290.  
  291. arrow keys:
  292.  +shift = move entire picture
  293.  +tab = move one pixel at a time
  294.  -alone = looks around the canvas smoothly
  295.  
  296. "+" (or equals):
  297.  +left alt = swap the current frame with the next frame
  298.  -alone = change to next frame
  299.  
  300. "-":
  301.  +left alt = swap the current frame with the previous frame
  302.  -alone = change to previous frame
  303.  
  304. "a": set the coordinates to 0,0
  305.  
  306. "n": open block character selection
  307.  
  308. "b": toggle redirect to blittle, to preview in teletext characters
  309.  
  310. "c": input coordinates to scroll over to
  311.  
  312. "g": toggle grayscale mode. everything is in shades of gray. if you Save, it saves in grayscale.
  313.  
  314. "f":
  315.  +shift = fill all empty pixels with background color and selected box character
  316.  -alone = activate fill tool - click anywhere to fill with color
  317.  
  318. "m": set metadata for pixels (for game makers, otherwise safe to ignore)
  319.  
  320.  
  321. Thy Menu (accessible with CTRL):
  322.  
  323. -left click on a menu item to select it.
  324. -if you click on the menubar, let go on an option to select it.
  325.  
  326. "File > Save"
  327. Saves all frames to a specially formatted PAIN paint file. The format PAIN uses is very inefficient despite my best efforts, so Export if you don\39t use text or multiple frame.
  328.  
  329. "File > Save As"
  330. Same as "File > Save", but you change the filename.
  331.  
  332. "File > Export"
  333. Exports current frame to NFP, NFT, BLT, or the horribly inefficient PAIN format.
  334.  
  335. "Edit > Delete Frame"
  336. Deletes the current frame. Tells you off if you try to delete the only frame.
  337.  
  338. "Edit > Clear"
  339. Deletes all pixels on the current frame.
  340.  
  341. "Edit > Crop Frame"
  342. Deletes all pixels that are outside of the screen.
  343.  
  344. "Edit > Change Box Character"
  345. Opens the block character selection. Used for making those delicious subpixel pictures.
  346.  
  347. "Edit > Change Special Character"
  348. Opens the special character selector, which lets you change the paint character to that of byte 0 to 255.
  349.  
  350. "Edit > BLittle Shrink"
  351. Shrinks the current frame using the BLittle API. Very lossy, and unreversable without Undo.
  352.  
  353. "Window > Set Screen Size"
  354. Sets the sizes of the screen border references displayed on the canvas.
  355.  
  356. "Window > Set Grid Colors"
  357. Sets the backdrop colors to your currently selected color configuration.
  358.  
  359. "About > PAIN"
  360. Tells you about PAIN and its developer.
  361.  
  362. "About > File Formats"
  363. Tells you the ins and outs of the file formats, and a brief description of their creators.
  364.  
  365. "About > Help"
  366. Opens up this help page.
  367.  
  368. "Exit"
  369. Durr I dunno, I think it exits.
  370.  
  371.  
  372. I hope my PAIN causes you joy.
  373. ]]
  374.     _helpText = explode("\n",_helpText)
  375.     helpText = cutUp(scr_x,_helpText)
  376.     local helpscroll = 0
  377.     term.setBackgroundColor(colors.gray)
  378.     term.setTextColor(colors.white)
  379.     term.clear()
  380.     local evt, key
  381.     while true do
  382.         term.clear()
  383.         for a = 1, scr_y do
  384.             term.setCursorPos(1,a)
  385.             term.clearLine()
  386.             write(helpText[a-helpscroll] or "")
  387.         end
  388.         repeat
  389.             evt,key = os.pullEvent()
  390.         until evt == "key" or evt == "mouse_scroll"
  391.         if evt == "key" then
  392.             if key == keys.up then
  393.                 helpscroll = helpscroll + 1
  394.             elseif key == keys.down then
  395.                 helpscroll = helpscroll - 1
  396.             elseif key == keys.pageUp then
  397.                 helpscroll = helpscroll + scr_y
  398.             elseif key == keys.pageDown then
  399.                 helpscroll = helpscroll - scr_y
  400.             elseif (key == keys.q) or (key == keys.space) then
  401.                 doRender = true
  402.                 if renderBlittle then term.redirect(blittleTerm) end
  403.                 scr_x, scr_y = term.current().getSize()
  404.                 return
  405.             end
  406.         elseif evt == "mouse_scroll" then
  407.             helpscroll = helpscroll - key
  408.         end
  409.         if helpscroll > 0 then
  410.             helpscroll = 0
  411.         elseif helpscroll < -(#helpText-(scr_y-3)) then
  412.             helpscroll = -(#helpText-(scr_y-3))
  413.         end
  414.     end
  415. end
  416.  
  417. local tableRemfind = function(tbl, str)
  418.     local out = tbl
  419.     for a = 1, #tbl do
  420.         if tbl[a] == str then
  421.             table.remove(out,a)
  422.             return out,a
  423.         end
  424.     end
  425.     return {}
  426. end
  427.  
  428. local stringShift = function(str,amt)
  429.     return str:sub(ro(amt-1,#str)+1)..str:sub(1,ro(amt-1,#str))
  430. end
  431.  
  432. local deepCopy
  433. deepCopy = function(obj)
  434.     if type(obj) ~= 'table' then return obj end
  435.     local res = {}
  436.     for k, v in pairs(obj) do res[deepCopy(k)] = deepCopy(v) end
  437.     return res
  438. end
  439.  
  440. local clearLines = function(y1, y2)
  441.     local cx,cy = term.getCursorPos()
  442.     for y = y1, y2 do
  443.         term.setCursorPos(1,y)
  444.         term.clearLine()
  445.     end
  446.     term.setCursorPos(cx,cy)
  447. end
  448.  
  449. local renderBottomBar = function(txt,extraClearY)
  450.     term.setCursorPos(1,scr_y - math.floor(#txt/scr_x))
  451.     term.setBackgroundColor(colors.lightGray)
  452.     term.setTextColor(colors.black)
  453.     clearLines(scr_y - (math.floor(#txt/scr_x) - (extraClearY or 0)), scr_y)
  454.     return write(txt)
  455. end
  456.  
  457. local bottomPrompt = function(txt,history,cho,breakkeys,returnNumber,writeIndent)
  458.     local writeIndent = renderBottomBar(txt,writeIndent)
  459.     local out
  460.     sleep(0)
  461.     if cho then
  462.         out = choice(cho,breakkeys,returnNumber)
  463.     else
  464.         out = read(_,history)
  465.     end
  466.     return out,writeIndent
  467. end
  468.  
  469. local makeSubMenu = function(x,y,options)
  470.     local longestLen = 0
  471.     for a = 1, #options do
  472.         if #options[a] > longestLen then
  473.             longestLen = #options[a]
  474.         end
  475.     end
  476.     longestLen = longestLen + 1
  477.     term.setTextColor(colors.black)
  478.     local sel = 1
  479.     local rend = function()
  480.         for a = #options, 1, -1 do
  481.             term.setCursorPos(x or 1, ((y or (scr_y-1)) - (#options-1)) + (a - 1))
  482.             term.setBackgroundColor(a == sel and colors.white or colors.lightGray)
  483.             term.write(options[a])
  484.             term.setBackgroundColor(colors.lightGray)
  485.             term.write((" "):rep(longestLen-#options[a]))
  486.         end
  487.     end
  488.     local usingMouse = false
  489.     while true do
  490.         rend()
  491.         local evt, key, mx, my = os.pullEvent()
  492.         if evt == "key" then
  493.             if key == keys.up then
  494.                 sel = sel - 1
  495.             elseif key == keys.down then
  496.                 sel = sel + 1
  497.             elseif (key == keys.enter) or (key == keys.right) then
  498.                 return sel, longestLen
  499.             elseif (key == keys.leftCtrl) or (key == keys.rightCtrl) or (key == keys.backspace) or (key == keys.left) then
  500.                 return false, longestLen
  501.             end
  502.         elseif evt == "mouse_drag" or evt == "mouse_click" then
  503.             if (mx >= x) and (mx < x+longestLen) and (my <= y and my > y-#options) then
  504.                 sel = math.min(#options,math.max(1,(my+#options) - y))
  505.                 usingMouse = true
  506.             else
  507.                 usingMouse = false
  508.                 if evt == "mouse_click" then
  509.                     return false
  510.                 end
  511.             end
  512.         elseif evt == "mouse_up" then
  513.             if usingMouse then
  514.                 return sel, longestLen
  515.             end
  516.         end
  517.         if sel > #options then sel = 1 elseif sel < 1 then sel = #options end
  518.     end
  519. end
  520.  
  521. local getDotsInLine = function( startX, startY, endX, endY ) -- stolen from the paintutils API...nwehehehe
  522.     local out = {}
  523.     startX = math.floor(startX)
  524.     startY = math.floor(startY)
  525.     endX = math.floor(endX)
  526.     endY = math.floor(endY)
  527.     if startX == endX and startY == endY then
  528.         out = {{x=startX,y=startY}}
  529.         return out
  530.     end
  531.    local minX = math.min( startX, endX )
  532.     if minX == startX then
  533.         minY = startY
  534.         maxX = endX
  535.         maxY = endY
  536.     else
  537.         minY = endY
  538.         maxX = startX
  539.         maxY = startY
  540.     end
  541.     local xDiff = maxX - minX
  542.     local yDiff = maxY - minY
  543.     if xDiff > math.abs(yDiff) then
  544.        local y = minY
  545.        local dy = yDiff / xDiff
  546.        for x=minX,maxX do
  547.            out[#out+1] = {x=x,y=math.floor(y+0.5)}
  548.            y = y + dy
  549.        end
  550.    else
  551.        local x = minX
  552.        local dx = xDiff / yDiff
  553.        if maxY >= minY then
  554.            for y=minY,maxY do
  555.                out[#out+1] = {x=math.floor(x+0.5),y=y}
  556.                x = x + dx
  557.            end
  558.        else
  559.            for y=minY,maxY,-1 do
  560.                out[#out+1] = {x=math.floor(x+0.5),y=y}
  561.                x = x - dx
  562.            end
  563.        end
  564.    end
  565.    return out
  566. end
  567.  
  568. local movePaintEncoded = function(pe,xdiff,ydiff)
  569.     local outpootis = deepCopy(pe)
  570.     for a = 1, #outpootis do
  571.         outpootis[a].x = outpootis[a].x+xdiff
  572.         outpootis[a].y = outpootis[a].y+ydiff
  573.     end
  574.     return outpootis
  575. end
  576.  
  577. local clearRedundant = function(dots)
  578.     local input = {}
  579.     local pheight = 0
  580.     local pwidth = 0
  581.     local minX, minY = 0, 0
  582.     for a = 1, #dots do
  583.         pheight = math.max(pheight, dots[a].y)
  584.         pwidth = math.max(pwidth, dots[a].x)
  585.         minX = math.min(minX, dots[a].x)
  586.         minY = math.min(minY, dots[a].y)
  587.     end
  588.     for a = 1, #dots do
  589.         if not input[dots[a].y] then input[dots[a].y] = {} end
  590.         input[dots[a].y][dots[a].x] = dots[a]
  591.     end
  592.     local output = {}
  593.     local frame = 0
  594.     for y = minY, pheight do
  595.         for x = minX, pwidth do
  596.             if input[y] then
  597.                 if input[y][x] then
  598.                     output[#output+1] = input[y][x]
  599.                 end
  600.             end
  601.             if frame >= 50 then
  602.                 -- yield()
  603.                 frame = 0
  604.             end
  605.         end
  606.     end
  607.     return output
  608. end
  609.  
  610. local grayOut = function(color)
  611.     local c = deepCopy(_G.colors)
  612.     local grays = {
  613.         [c.white] = c.white,
  614.         [c.orange] = c.lightGray,
  615.         [c.magenta] = c.lightGray,
  616.         [c.lightBlue] = c.lightGray,
  617.         [c.yellow] = c.white,
  618.         [c.lime] = c.lightGray,
  619.         [c.pink] = c.lightGray,
  620.         [c.gray] = c.gray,
  621.         [c.lightGray] = c.lightGray,
  622.         [c.cyan] = c.lightGray,
  623.         [c.purple] = c.gray,
  624.         [c.blue] = c.gray,
  625.         [c.brown] = c.gray,
  626.         [c.green] = c.lightGray,
  627.         [c.red] = c.gray,
  628.         [c.black] = c.black,
  629.     }
  630.     if (not color) or (color == " ") then return color end
  631.     local newColor = grays[color] or 1
  632.     return newColor
  633. end
  634.  
  635. local getOnscreenCoords = function(tbl,_x,_y)
  636.     local screenTbl = {}
  637.     for a = 1, #tbl do
  638.         if tbl[a].x+paint.scrollX > 0 and tbl[a].x+paint.scrollX <= scr_x then
  639.             if tbl[a].y+paint.scrollY > 0 and tbl[a].y+paint.scrollY <= scr_y then
  640.                 screenTbl[#screenTbl+1] = {tbl[a].x+paint.scrollX,tbl[a].y+paint.scrollY}
  641.             end
  642.         end
  643.     end
  644.     if not _x and _y then
  645.         return screenTbl
  646.     else
  647.         for a = 1, #screenTbl do
  648.             if screenTbl[a][1] == _x and screenTbl[a][2] == _y then
  649.                 return true
  650.             end
  651.         end
  652.         return false
  653.     end
  654. end
  655.  
  656. local clearAllRedundant = function(info)
  657.     local output = {}
  658.     for a = 1, #info do
  659.         output[a] = clearRedundant(info[a])
  660.         if a % 4 == 0 then yield() end
  661.     end
  662.     return output
  663. end
  664.  
  665. local saveFile = function(path,info)
  666.     local output = clearAllRedundant(info)
  667.     local fileout = textutils.serialize(output):gsub("  ",""):gsub("\n",""):gsub(" = ","="):gsub(",}","}"):gsub("}},{{","}},\n{{")
  668.     if #fileout >= fs.getFreeSpace(fs.getDir(path)) then
  669.         barmsg = "Not enough space."
  670.         return
  671.     end
  672.     local file = fs.open(path,"w")
  673.     file.write(fileout)
  674.     file.close()
  675. end
  676. local renderBar = function(msg,dontSetVisible)
  677.     if (doRenderBar == 0) or renderBlittle then return end
  678.     if tsv and (not dontSetVisible) then tsv(false) end
  679.     term.setCursorPos(1,scr_y)
  680.     term.setBackgroundColor(colors.lightGray)
  681.     term.setTextColor(colors.black)
  682.     term.clearLine()
  683.     term.setBackgroundColor(paint.b or rendback.b)
  684.     term.setTextColor(paint.t or rendback.t)
  685.     term.setCursorPos(2,scr_y)
  686.     term.write("PAIN")
  687.     term.setBackgroundColor(colors.lightGray)
  688.     term.setTextColor(colors.black)
  689.     local fmsg = tableconcat({"Fr:",frame,"/",#paintEncoded," (",paint.scrollX,",",paint.scrollY,")"})
  690.     term.setCursorPos(7,scr_y)
  691.     term.write(msg)
  692.     term.setCursorPos(scr_x-(#fmsg),scr_y)
  693.     term.write(fmsg)
  694.     if tsv and (not dontSetVisible) then tsv(true) end
  695. end
  696.  
  697. local getTablePaint = function(pe)
  698.     local output = {}
  699.     for a = 1, #pe do
  700.         if not output[pe[a].y] then output[pe[a].y] = {} end
  701.         output[pe[a].y][pe[a].x] = pe[a]
  702.     end
  703.     return output
  704. end
  705.  
  706. local renderPainyThings = function(xscroll,yscroll,doGrid)
  707.     local yadjust = (renderBlittle and 0 or doRenderBar)
  708.     if bepimode then
  709.         grid = {
  710.             "Bepis",
  711.             "episB",
  712.             "pisBe",
  713.             "isBep",
  714.             "sBepi",
  715.         }
  716.     else
  717.         grid = {
  718.             "%%..",
  719.             "%%..",
  720.             "%%..",
  721.             "..%%",
  722.             "..%%",
  723.             "..%%",
  724.         }
  725.     end
  726.     term.setBackgroundColor(rendback.b)
  727.     term.setTextColor(rendback.t)
  728.     local badchar = "/"
  729.     local blittlelabel = "blittle max"
  730.     local screenlabel = "screen max"
  731.    
  732.     if doGrid then
  733.         for y = 1, scr_y - yadjust do
  734.             term.setCursorPos(1,y)
  735.             -- the single most convoluted line I've ever written that works, and I love it
  736.             term.write(stringShift(grid[ro(y+(yscroll+2),#grid)+1],xscroll+1):rep(math.ceil(scr_x/#grid[ro(y+(yscroll+2),#grid)+1])):sub(1,scr_x))
  737.             term.setCursorPos((xscroll <= 0) and (1-xscroll) or 0,y)
  738.             if ((screenEdges[2]+1)-yscroll) == y then --regular limit
  739.                 term.write( (string.rep("@", math.max(0,( (screenEdges[1])     ) - (#screenlabel+1)  )) ..screenlabel:gsub(" ","@"):upper().."@@"):sub(xscroll>0 and xscroll or 0):sub(1,1+screenEdges[1]) )
  740.             elseif (((screenEdges[2]*3)+1)-yscroll) == y then --blittle limit
  741.                 term.write( (string.rep("@", math.max(0,( ((screenEdges[1]*2))   ) - (#blittlelabel+1) ))..blittlelabel:gsub(" ","@"):upper().."@@"):sub(xscroll>0 and xscroll or 0):sub(1,1+screenEdges[1]*2) )
  742.             end
  743.             -- Stupid easter eggs, ho! --
  744.             if 1000-yscroll == y then
  745.                 term.setCursorPos(1000-xscroll,y)
  746.                 term.write(" What ARE you doing? Stop messing around! ")
  747.             end
  748.             if 2016-yscroll == y then
  749.                 term.setCursorPos(200-xscroll,y)
  750.                 term.write(" Lines don't like to be intersected, you know. ")
  751.             end
  752.             if 2017-yscroll == y then
  753.                 term.setCursorPos(200-xscroll,y)
  754.                 term.write(" It makes them very crossed. ")
  755.             end
  756.             if 800-yscroll == y then
  757.                 term.setCursorPos(1700-xscroll,y)
  758.                 term.write(" You stare deeply into the void. ")
  759.             end
  760.             if 801-yscroll == y then
  761.                 term.setCursorPos(1704-xscroll,y)
  762.                 term.write(" And the void ")
  763.             end
  764.             if 802-yscroll == y then
  765.                 term.setCursorPos(1704-xscroll,y)
  766.                 term.write(" stares back. ")
  767.             end
  768.             --Is this the end?--
  769.             if (xscroll > ((screenEdges[1]*2)-scr_x)) then
  770.                 for y = 1, scr_y do
  771.                     if y+yscroll <= (screenEdges[2]*3) then
  772.                         if not (y == scr_y and doRenderBar == 1) then
  773.                             term.setCursorPos((screenEdges[1]+1)-(xscroll-screenEdges[1]),y)
  774.                             term.write("@")
  775.                         end
  776.                     end
  777.                 end
  778.             end
  779.             if (xscroll > (screenEdges[1]-scr_x)) then --regular limit
  780.                 for y = 1, scr_y do
  781.                     if y+yscroll <= screenEdges[2] then
  782.                         if not (y == scr_y and doRenderBar == 1) then
  783.                             term.setCursorPos((screenEdges[1]+1)-xscroll,y)
  784.                             term.write("@")
  785.                         end
  786.                     end
  787.                 end
  788.             end
  789.         end
  790.         --render areas that won't save
  791.         if xscroll < 0 then
  792.             for y = 1, scr_y do
  793.                 if not (y == scr_y and doRenderBar == 1) then
  794.                     term.setCursorPos(1,y)
  795.                     term.write(badchar:rep(-xscroll))
  796.                 end
  797.             end
  798.         end
  799.         if yscroll < 0 then
  800.             for y = 1, -yscroll do
  801.                 if not (y == scr_y and doRenderBar == 1) then
  802.                     term.setCursorPos(1,y)
  803.                     term.write(badchar:rep(scr_x))
  804.                 end
  805.             end
  806.         end
  807.     else
  808.         for y = 1, scr_y - yadjust do
  809.             term.setCursorPos(1,y)
  810.             term.clearLine()
  811.         end
  812.     end
  813. end
  814.  
  815. CTB = function(_color) --Color To Blit
  816.     local blitcolors = {
  817.         [0] = " ",
  818.         [colors.white] = "0",
  819.         [colors.orange] = "1",
  820.         [colors.magenta] = "2",
  821.         [colors.lightBlue] = "3",
  822.         [colors.yellow] = "4",
  823.         [colors.lime] = "5",
  824.         [colors.pink] = "6",
  825.         [colors.gray] = "7",
  826.         [colors.lightGray] = "8",
  827.         [colors.cyan] = "9",
  828.         [colors.purple] = "a",
  829.         [colors.blue] = "b",
  830.         [colors.brown] = "c",
  831.         [colors.green] = "d",
  832.         [colors.red] = "e",
  833.         [colors.black] = "f",
  834.     }
  835.     if _color == nil then return nil end
  836.     return blitcolors[_color] or "f"
  837. end
  838.  
  839. BTC = function(_color,allowZero) --Blit To Color
  840.     local blitcolors = {
  841.         [" "] = allowZero and 0 or nil,
  842.         ["0"] = colors.white,
  843.         ["1"] = colors.orange,
  844.         ["2"] = colors.magenta,
  845.         ["3"] = colors.lightBlue,
  846.         ["4"] = colors.yellow,
  847.         ["5"] = colors.lime,
  848.         ["6"] = colors.pink,
  849.         ["7"] = colors.gray,
  850.         ["8"] = colors.lightGray,
  851.         ["9"] = colors.cyan,
  852.         ["a"] = colors.purple,
  853.         ["b"] = colors.blue,
  854.         ["c"] = colors.brown,
  855.         ["d"] = colors.green,
  856.         ["e"] = colors.red,
  857.         ["f"] = colors.black,
  858.     }
  859.     if _color == nil then return nil end
  860.     return blitcolors[_color]
  861. end
  862.  
  863. importFromPaint = function(theInput)
  864.     local output = {}
  865.     local input
  866.     if type(theInput) == "string" then
  867.         input = explode("\n",theInput)
  868.     else
  869.         input = {}
  870.         for y = 1, #theInput do
  871.             input[y] = ""
  872.             for x = 1, #theInput[y] do
  873.                 input[y] = input[y]..(CTB(theInput[y][x]) or " ")
  874.             end
  875.         end
  876.     end
  877.     for a = 1, #input do
  878.         line = input[a]
  879.         for b = 1, #line do
  880.             if (line:sub(b,b) ~= " ") and BTC(line:sub(b,b)) then
  881.                 output[#output+1] = {
  882.                     x = b,
  883.                     y = a,
  884.                     t = colors.white,
  885.                     b = BTC(line:sub(b,b)) or colors.black,
  886.                     c = " ",
  887.                 }
  888.             end
  889.         end
  890.     end
  891.     return output
  892. end
  893.  
  894. local lddfm = {
  895.     scroll = 0,
  896.     ypaths = {},
  897. }
  898.  
  899. lddfm.scr_x, lddfm.scr_y = term.getSize()
  900.  
  901. lddfm.setPalate = function(_p)
  902.     if type(_p) ~= "table" then
  903.         _p = {}
  904.     end
  905.     lddfm.p = { --the DEFAULT color palate
  906.         bg =        _p.bg or colors.gray,           -- whole background color
  907.         d_txt =     _p.d_txt or colors.yellow,      -- directory text color
  908.         d_bg =      _p.d_bg or colors.gray,         -- directory bg color
  909.         f_txt =     _p.f_txt or colors.white,       -- file text color
  910.         f_bg =      _p.f_bg or colors.gray,         -- file bg color
  911.         p_txt =     _p.p_txt or colors.black,       -- path text color
  912.         p_bg =      _p.p_bg or colors.lightGray,    -- path bg color
  913.         close_txt = _p.close_txt or colors.gray,    -- close button text color
  914.         close_bg =  _p.close_bg or colors.lightGray,-- close button bg color
  915.         scr =       _p.scr or colors.lightGray,     -- scrollbar color
  916.         scrbar =    _p.scrbar or colors.gray,       -- scroll tab color
  917.     }
  918. end
  919.  
  920. lddfm.setPalate()
  921.  
  922. lddfm.foldersOnTop = function(floop,path)
  923.     local output = {}
  924.     for a = 1, #floop do
  925.         if fs.isDir(fs.combine(path,floop[a])) then
  926.             table.insert(output,1,floop[a])
  927.         else
  928.             table.insert(output,floop[a])
  929.         end
  930.     end
  931.     return output
  932. end
  933.  
  934. lddfm.filterFileFolders = function(list,path,_noFiles,_noFolders,_noCD,_doHidden)
  935.     local output = {}
  936.     for a = 1, #list do
  937.         local entry = fs.combine(path,list[a])
  938.         if fs.isDir(entry) then
  939.             if entry == ".." then
  940.                 if not (_noCD or _noFolders) then table.insert(output,list[a]) end
  941.             else
  942.                 if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
  943.                     if not _noFolders then table.insert(output,list[a]) end
  944.                 end
  945.             end
  946.         else
  947.             if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
  948.                 if not _noFiles then table.insert(output,list[a]) end
  949.             end
  950.         end
  951.     end
  952.     return output
  953. end
  954.  
  955. lddfm.isColor = function(col)
  956.     for k,v in pairs(colors) do
  957.         if v == col then
  958.             return true, k
  959.         end
  960.     end
  961.     return false
  962. end
  963.  
  964. lddfm.clearLine = function(x1,x2,_y,_bg,_char)
  965.     local cbg, bg = term.getBackgroundColor()
  966.     local x,y = term.getCursorPos()
  967.     local sx,sy = term.getSize()
  968.     if type(_char) == "string" then char = _char else char = " " end
  969.     if type(_bg) == "number" then
  970.         if lddfm.isColor(_bg) then bg = _bg
  971.         else bg = cbg end
  972.     else bg = cbg end
  973.     term.setCursorPos(x1 or 1, _y or y)
  974.     term.setBackgroundColor(bg)
  975.     if x2 then --it pains me to add an if statement to something as simple as this
  976.         term.write((char or " "):rep(x2-x1))
  977.     else
  978.         term.write((char or " "):rep(sx-(x1 or 0)))
  979.     end
  980.     term.setBackgroundColor(cbg)
  981.     term.setCursorPos(x,y)
  982. end
  983.  
  984. lddfm.render = function(_x1,_y1,_x2,_y2,_rlist,_path,_rscroll,_canClose,_scrbarY)
  985.     local tsv = term.current().setVisible
  986.     local px,py = term.getCursorPos()
  987.     if tsv then tsv(false) end
  988.     local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
  989.     local rlist = _rlist or {"Invalid directory."}
  990.     local path = _path or "And that's terrible."
  991.     ypaths = {}
  992.     local rscroll = _rscroll or 0
  993.     for a = y1, y2 do
  994.         lddfm.clearLine(x1,x2,a,lddfm.p.bg)
  995.     end
  996.     term.setCursorPos(x1,y1)
  997.     term.setTextColor(lddfm.p.p_txt)
  998.     lddfm.clearLine(x1,x2+1,y1,lddfm.p.p_bg)
  999.     term.setBackgroundColor(lddfm.p.p_bg)
  1000.     term.write(("/"..path):sub(1,x2-x1))
  1001.     for a = 1,(y2-y1) do
  1002.         if rlist[a+rscroll] then
  1003.             term.setCursorPos(x1,a+(y1))
  1004.             if fs.isDir(fs.combine(path,rlist[a+rscroll])) then
  1005.                 lddfm.clearLine(x1,x2,a+(y1),lddfm.p.d_bg)
  1006.                 term.setTextColor(lddfm.p.d_txt)
  1007.                 term.setBackgroundColor(lddfm.p.d_bg)
  1008.             else
  1009.                 lddfm.clearLine(x1,x2,a+(y1),lddfm.p.f_bg)
  1010.                 term.setTextColor(lddfm.p.f_txt)
  1011.                 term.setBackgroundColor(lddfm.p.f_bg)
  1012.             end
  1013.             term.write(rlist[a+rscroll]:sub(1,x2-x1))
  1014.             ypaths[a+(y1)] = rlist[a+rscroll]
  1015.         else
  1016.             lddfm.clearLine(x1,x2,a+(y1),lddfm.p.bg)
  1017.         end
  1018.     end
  1019.     local scrbarY = _scrbarY or math.ceil( (y1+1)+( (_rscroll/(#_rlist-(y2-(y1+1))))*(y2-(y1+1)) ) )
  1020.     for a = y1+1, y2 do
  1021.         term.setCursorPos(x2,a)
  1022.         if a == scrbarY then
  1023.             term.setBackgroundColor(lddfm.p.scrbar)
  1024.         else
  1025.             term.setBackgroundColor(lddfm.p.scr)
  1026.         end
  1027.         term.write(" ")
  1028.     end
  1029.     if _canClose then
  1030.         term.setCursorPos(x2-4,y1)
  1031.         term.setTextColor(lddfm.p.close_txt)
  1032.         term.setBackgroundColor(lddfm.p.close_bg)
  1033.         term.write("close")
  1034.     end
  1035.     term.setCursorPos(px,py)
  1036.     if tsv then tsv(true) end
  1037.     return scrbarY
  1038. end
  1039.  
  1040. lddfm.coolOutro = function(x1,y1,x2,y2,_bg,_txt,char)
  1041.     local cx, cy = term.getCursorPos()
  1042.     local bg, txt = term.getBackgroundColor(), term.getTextColor()
  1043.     term.setTextColor(_txt or colors.white)
  1044.     term.setBackgroundColor(_bg or colors.black)
  1045.     local _uwah = 0
  1046.     for y = y1, y2 do
  1047.         for x = x1, x2 do
  1048.             _uwah = _uwah + 1
  1049.             term.setCursorPos(x,y)
  1050.             term.write(char or " ")
  1051.             if _uwah >= math.ceil((x2-x1)*1.63) then sleep(0) _uwah = 0 end
  1052.         end
  1053.     end
  1054.     term.setTextColor(txt)
  1055.     term.setBackgroundColor(bg)
  1056.     term.setCursorPos(cx,cy)
  1057. end
  1058.  
  1059. lddfm.scrollMenu = function(amount,list,y1,y2)
  1060.     if #list >= y2-y1 then
  1061.         lddfm.scroll = lddfm.scroll + amount
  1062.         if lddfm.scroll < 0 then
  1063.             lddfm.scroll = 0
  1064.         end
  1065.         if lddfm.scroll > #list-(y2-y1) then
  1066.             lddfm.scroll = #list-(y2-y1)
  1067.         end
  1068.     end
  1069. end
  1070.  
  1071. --[[
  1072.  a quick explanation of the arguments for lddfm.makeMenu:
  1073.  
  1074. x1 and y1: top-left corner coordinates of menu window. defaults to the top-left corner of the screen
  1075. x2 and y2: bottom-right corner coordinates of menu window. defaults to the bottom-right corner of the screen
  1076. _path: path to start viewing. defaults to "/"
  1077. _noFiles: whether or not to view files in the menu, mainly for picking a path for installing something. defaults to false
  1078. _noFolders: whether or not to view folders in the menu, mainly for choosing a file to run or whatever. defaults to false
  1079. _noCD: whether or not you can change the directory, mainly to limit choices to a single folder. defaults to false
  1080. _noSelectFolders: whether or not you can select folders to return. defaults to false
  1081. _doHidden: whether or not to hide hidden files (starts with "."). defaults to false
  1082. _p: the palate. has: bg, d_txt, d_bg, f_txt, t_bg, p_txt, p_bg, scr, scrbar. 'd' is for directory, 'f' is for file, 'p' is for path bar.
  1083. _canClose: whether or not you can click on the little top-right "Cancel" button.
  1084. --]]
  1085.  
  1086. lddfm.makeMenu = function(_x1,_y1,_x2,_y2,_path,_noFiles,_noFolders,_noCD,_noSelectFolders,_doHidden,_p,_canClose)
  1087.     if _noFiles and _noFolders then
  1088.         return false, "C'mon, man..."
  1089.     end
  1090.     if _x1 == true then
  1091.         return false, "arguments: x1, y1, x2, y2, path, noFiles, noFolders, noCD, noSelectFolders, doHidden, palate, canClose" -- a little help
  1092.     end
  1093.     lddfm.setPalate(_p)
  1094.     local path, list = _path or ""
  1095.     lddfm.scroll = 0
  1096.     local _pbg, _ptxt = term.getBackgroundColor(), term.getTextColor()
  1097.     local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
  1098.     local keysDown = {}
  1099.     local _barrY
  1100.     while true do
  1101.         list = lddfm.foldersOnTop(lddfm.filterFileFolders(fs.list(path),path,_noFiles,_noFolders,_noCD,_doHidden),path)
  1102.         if (fs.getDir(path) ~= "..") and not (_noCD or _noFolders) then
  1103.             table.insert(list,1,"..")
  1104.         end
  1105.         _res, _barrY = pcall( function() return lddfm.render(x1,y1,x2,y2,list,path,lddfm.scroll,_canClose) end)
  1106.         if not _res then
  1107.             local tsv = term.current().setVisible
  1108.             if tsv then tsv(true) end
  1109.             error(_barrY)
  1110.         end
  1111.         local evt = {os.pullEvent()}
  1112.         if evt[1] == "mouse_scroll" then
  1113.             lddfm.scrollMenu(evt[2],list,y1,y2)
  1114.         elseif evt[1] == "mouse_click" then
  1115.             local butt,mx,my = evt[2],evt[3],evt[4]
  1116.             if (butt == 1 and my == y1 and mx <= x2 and mx >= x2-4) and _canClose then
  1117.                 --lddfm.coolOutro(x1,y1,x2,y2)
  1118.                 term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
  1119.                 return false
  1120.             elseif ypaths[my] and (mx >= x1 and mx < x2) then --x2 is reserved for the scrollbar, breh
  1121.                 if fs.isDir(fs.combine(path,ypaths[my])) then
  1122.                     if _noCD or butt == 3 then
  1123.                         if not _noSelectFolders or _noFolders then
  1124.                             --lddfm.coolOutro(x1,y1,x2,y2)
  1125.                             term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
  1126.                             return fs.combine(path,ypaths[my])
  1127.                         end
  1128.                     else
  1129.                         path = fs.combine(path,ypaths[my])
  1130.                         lddfm.scroll = 0
  1131.                     end
  1132.                 else
  1133.                     term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
  1134.                     return fs.combine(path,ypaths[my])
  1135.                 end
  1136.             end
  1137.         elseif evt[1] == "key" then
  1138.             keysDown[evt[2]] = true
  1139.             if evt[2] == keys.enter and not (_noFolders or _noCD or _noSelectFolders) then --the logic for _noCD being you'd normally need to go back a directory to select the current directory.
  1140.                 --lddfm.coolOutro(x1,y1,x2,y2)
  1141.                 term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
  1142.                 return path
  1143.             end
  1144.             if evt[2] == keys.up then
  1145.                 lddfm.scrollMenu(-1,list,y1,y2)
  1146.             elseif evt[2] == keys.down then
  1147.                 lddfm.scrollMenu(1,list,y1,y2)
  1148.             end
  1149.             if evt[2] == keys.pageUp then
  1150.                 lddfm.scrollMenu(y1-y2,list,y1,y2)
  1151.             elseif evt[2] == keys.pageDown then
  1152.                 lddfm.scrollMenu(y2-y1,list,y1,y2)
  1153.             end
  1154.             if evt[2] == keys.home then
  1155.                 lddfm.scroll = 0
  1156.             elseif evt[2] == keys["end"] then
  1157.                 if #list > (y2-y1) then
  1158.                     lddfm.scroll = #list-(y2-y1)
  1159.                 end
  1160.             end
  1161.             if evt[2] == keys.h then
  1162.                 if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then
  1163.                     _doHidden = not _doHidden
  1164.                 end
  1165.             elseif _canClose and (evt[2] == keys.x or evt[2] == keys.q or evt[2] == keys.leftCtrl) then
  1166.                 --lddfm.coolOutro(x1,y1,x2,y2)
  1167.                 term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
  1168.                 return false
  1169.             end
  1170.         elseif evt[1] == "key_up" then
  1171.             keysDown[evt[2]] = false
  1172.         end
  1173.     end
  1174. end
  1175.  
  1176. local getBlittle = function()
  1177.     if not blittle then
  1178.         if fs.exists("/.painapi/blittle") then
  1179.             os.loadAPI("/.painapi/blittle")
  1180.             if not blittleTerm then
  1181.                 blittleTerm = blittle.createWindow()
  1182.             end
  1183.             return blittleTerm, firstTerm
  1184.         else
  1185.             local geet = http.get("http://pastebin.com/raw/ujchRSnU")
  1186.             if not geet then
  1187.                 return false
  1188.             else
  1189.                 geet = geet.readAll()
  1190.                 local file = fs.open("/.painapi/blittle","w")
  1191.                 file.write(geet)
  1192.                 file.close()
  1193.                 os.loadAPI("/.painapi/blittle")
  1194.                 --fs.delete("/.painapi/")
  1195.                 if not blittleTerm then
  1196.                     blittleTerm = blittle.createWindow()
  1197.                 end
  1198.                 return blittleTerm, firstTerm
  1199.             end
  1200.         end
  1201.     else
  1202.         if not blittleTerm then
  1203.             blittleTerm = blittle.createWindow()
  1204.         end
  1205.         return blittleTerm, firstTerm
  1206.     end
  1207. end
  1208.  
  1209. local getUCG = function()
  1210.     if not ucg then
  1211.         if fs.exists("/.painapi/ucg") then
  1212.             os.loadAPI("/.painapi/ucg")
  1213.             return true
  1214.         else
  1215.             local geet = http.get("https://raw.githubusercontent.com/ardera/libucg/master/src/libucg")
  1216.             if not geet then
  1217.                 return false
  1218.             else
  1219.                 geet = geet.readAll()
  1220.                 local file = fs.open("/.painapi/ucg","w")
  1221.                 file.write(geet)
  1222.                 file.close()
  1223.                 os.loadAPI("/.painapi/ucg")
  1224.             end
  1225.         end
  1226.     end
  1227. end
  1228.  
  1229. local getBitmap = function()
  1230.     if not bitmap then
  1231.         if fs.exists("/.painapi/bitmap") then
  1232.             os.loadAPI("/.painapi/bitmap")
  1233.             return true
  1234.         else
  1235.             local geet = http.get("https://pastebin.com/raw/Y3JeZWzV")
  1236.             if not geet then
  1237.                 return false
  1238.             else
  1239.                 geet = geet.readAll()
  1240.                 local file = fs.open("/.painapi/bitmap","w")
  1241.                 file.write(geet)
  1242.                 file.close()
  1243.                 os.loadAPI("/.painapi/bitmap")
  1244.             end
  1245.         end
  1246.     end
  1247. end
  1248.  
  1249. local getBBPack = function()
  1250.     if not bbpack then
  1251.         if fs.exists("/.painapi/bbpack") then
  1252.             os.loadAPI("/.painapi/bbpack")
  1253.             return true
  1254.         else
  1255.             local geet = http.get("https://pastebin.com/raw/cUYTGbpb")
  1256.             if not geet then
  1257.                 return false
  1258.             else
  1259.                 geet = geet.readAll()
  1260.                 local file = fs.open("/.painapi/bbpack","w")
  1261.                 file.write(geet)
  1262.                 file.close()
  1263.                 os.loadAPI("/.painapi/bbpack")
  1264.             end
  1265.         end
  1266.     end
  1267. end
  1268.  
  1269. local getGIF = function()
  1270.     getBBPack()
  1271.     if not GIF then
  1272.         if fs.exists("/.painapi/GIF") then
  1273.             os.loadAPI("/.painapi/GIF")
  1274.             return true
  1275.         else
  1276.             local geet = http.get("https://pastebin.com/raw/5uk9uRjC")
  1277.             if not geet then
  1278.                 return false
  1279.             else
  1280.                 geet = geet.readAll()
  1281.                 local file = fs.open("/.painapi/GIF","w")
  1282.                 file.write(geet)
  1283.                 file.close()
  1284.                 os.loadAPI("/.painapi/GIF")
  1285.             end
  1286.         end
  1287.     end
  1288. end
  1289.  
  1290. local NFPserializeImage = function(str)
  1291.     local bepis = explode("\n",str)
  1292.     local output = {}
  1293.     for y = 1, #bepis do
  1294.         output[y] = {}
  1295.         for x = 1, #bepis[y] do
  1296.             output[y][x] = BTC(bepis[y]:sub(x,x),true)
  1297.         end
  1298.     end
  1299.     return textutils.unserialize(textutils.serialize(output):gsub("\n",""):gsub(" ",""):gsub(",}","}"))
  1300. end
  1301.  
  1302. local importFromBitmap = function(fileName)
  1303.     getBitmap()
  1304.     local image = bitmap.ImageHelpers.parseFile(fileName).pixels
  1305.     local output = {}
  1306.     local closest, termSets = bitmap.Colors.findClosestColor, bitmap.Colors.termSets
  1307.     for y = 1, #image do
  1308.         local line = image[y]
  1309.         for x = 1, #line do
  1310.             output[#output+1] = {
  1311.                 x = x,
  1312.                 y = y,
  1313.                 t = colors.white,
  1314.                 c = " ",
  1315.                 b = closest(termSets, unpack(line[x])),
  1316.                 m = 0,
  1317.             }
  1318.         end
  1319.     end
  1320.     return output
  1321. end
  1322.  
  1323. local exportToBitmap = function(theInput)
  1324.     getBitmap()
  1325.     --er, I don't think this is possible at the moment
  1326. end
  1327.  
  1328. local importFromGIF = function(filename,verbose)
  1329.     getGIF()
  1330.     local output = {}
  1331.     local image
  1332.     local rawGif = GIF.loadGIF(filename)
  1333.     if useFlattenGIF then
  1334.         if verbose then
  1335.             print("Flattening...")
  1336.         end
  1337.         rawGif = GIF.flattenGIF(rawGif)
  1338.         sleep(0)
  1339.     end
  1340.     local cx, cy = term.getCursorPos()
  1341.     for a = 1, #rawGif do
  1342.         output[a] = importFromPaint(GIF.toPaintutils(rawGif[a]))
  1343.         if verbose then
  1344.             term.setCursorPos(cx,cy)
  1345.             write("Did "..a.."/"..#rawGif.." ")
  1346.         end
  1347.         if a % 1 then sleep(0) end --used to be a % 2, might change later
  1348.     end
  1349.     return output
  1350. end
  1351.  
  1352. local exportToPaint
  1353.  
  1354. local exportToGIF = function(input)
  1355.     getGIF()
  1356.     local outGIF = {}
  1357.     for a = 1, #paintEncoded do
  1358.         outGIF[a] = NFPserializeImage(exportToPaint(paintEncoded[a]))
  1359.         sleep(0)
  1360.     end
  1361.     if useFlattenGIF then
  1362.         return GIF.flattenGIF(GIF.buildGIF(table.unpack(outGIF)),true)
  1363.     else
  1364.         return GIF.buildGIF(table.unpack(outGIF))
  1365.     end
  1366. end
  1367.  
  1368. local importFromUCG = function(filename)
  1369.     getUCG()
  1370.     return importFromPaint(ucg.readFile(filename))
  1371. end
  1372.  
  1373. local exportToUCG = function(filename, input)
  1374.     getUCG()
  1375.     ucg.writeFile(filename, NFPserializeImage(exportToPaint(input)))
  1376. end
  1377.  
  1378. renderPAIN = function(dots,xscroll,yscroll,doPain,dontRenderBar)
  1379.     if tsv then tsv(false) end
  1380.     local beforeTX,beforeBG = term.getTextColor(), term.getBackgroundColor()
  1381.     local cx,cy = term.getCursorPos()
  1382.     local FUCK, SHIT = pcall(function()
  1383.         if doPain then
  1384.             if (not renderBlittle) then
  1385.                 if not dontRenderBar then
  1386.                     renderBar(barmsg,true)
  1387.                 end
  1388.                 renderPainyThings(xscroll,yscroll,evenDrawGrid)
  1389.             else
  1390.                 term.clear()
  1391.             end
  1392.         end
  1393.         for a = 1, #dots do
  1394.             local d = dots[a]
  1395.             if doPain then
  1396.                 if not ((d.y-yscroll >= 1 and d.y-yscroll <= scr_y-(renderBlittle and 0 or doRenderBar)) and (d.x-xscroll >= 1 and d.x-xscroll <= scr_x)) then
  1397.                     d = nil
  1398.                 end
  1399.             end
  1400.             if d then
  1401.                 term.setCursorPos(d.x-(xscroll or 0),d.y-(yscroll or 0))
  1402.                 term.setTextColor(      (paint.doGray and grayOut(d.t) or d.t) or rendback.t)
  1403.                 term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b)
  1404.                 term.write(d.c or " ")
  1405.             end
  1406.         end
  1407.     end)
  1408.     term.setBackgroundColor(beforeBG or rendback.b)
  1409.     term.setTextColor(beforeTX or rendback.t)
  1410.     term.setCursorPos(cx,cy)
  1411.     if tsv then tsv(true) end
  1412.     if not FUCK then error(SHIT) end --GOD DAMN IT
  1413. end
  1414.  
  1415. renderPAINFS = function(filename,xscroll,yscroll,frameNo,doPain)
  1416.     local tun, tse = textutils.unserialize, textutils.serialize
  1417.     local file = fs.open(filename,"r")
  1418.     local contents = file.readAll()
  1419.     local amntFrames
  1420.     file.close()
  1421.     local tcontents = tun(contents)
  1422.     if type(tcontents) ~= "table" then
  1423.         tcontents = importFromPaint(contents)
  1424.     else
  1425.         amntFrames = #tcontents
  1426.         tcontents = tcontents[frameNo or 1]
  1427.     end
  1428.     renderPAIN(tcontents,xscroll,yscroll,doPain)
  1429.     return amntFrames
  1430. end
  1431.  
  1432. local putDotDown = function(dot) -- only 'x' and 'y' are required arguments
  1433.     paintEncoded[frame][#paintEncoded[frame]+1] = {
  1434.         x = dot.x + paint.scrollX,
  1435.         y = dot.y + paint.scrollY,
  1436.         c = dot.c or paint.c,
  1437.         b = dot.b or (swapColors and paint.t or paint.b),
  1438.         t = dot.t or (swapColors and paint.b or paint.t),
  1439.         m = dot.m or paint.m,
  1440.     }
  1441. end
  1442.  
  1443. local saveToUndoBuffer = function()
  1444.     if undoPos < #undoBuffer then
  1445.         for a = #undoBuffer, undoPos+1, -1 do
  1446.             table.remove(undoBuffer,a)
  1447.         end
  1448.     end
  1449.     if undoPos >= undoBufferSize then
  1450.         for a = 2, #undoBuffer do
  1451.             undoBuffer[a-1] = undoBuffer[a]
  1452.         end
  1453.         undoBuffer[#undoBuffer] = deepCopy(paintEncoded)
  1454.     else
  1455.         undoPos = undoPos + 1
  1456.         undoBuffer[undoPos] = deepCopy(paintEncoded)
  1457.     end
  1458. end
  1459.  
  1460. local doUndo = function()
  1461.     undoPos = math.max(1,undoPos-1)
  1462.     paintEncoded = deepCopy(undoBuffer[undoPos])
  1463.     if not paintEncoded[frame] then
  1464.         frame = #paintEncoded
  1465.     end
  1466. end
  1467.  
  1468. local doRedo = function()
  1469.     undoPos = math.min(#undoBuffer,undoPos+1)
  1470.     paintEncoded = deepCopy(undoBuffer[undoPos])
  1471.     if not paintEncoded[frame] then
  1472.         frame = #paintEncoded
  1473.     end
  1474. end
  1475.  
  1476. local putDownText = function(x,y)
  1477.     term.setCursorPos(x,y)
  1478.     term.setTextColor((paint.doGray and grayOut(paint.t or rendback.t)) or (paint.t or rendback.t))
  1479.     term.setBackgroundColor((paint.doGray and grayOut(paint.b or rendback.b)) or (paint.b or rendback.b))
  1480.     local msg = read()
  1481.     if #msg > 0 then
  1482.         for a = 1, #msg do
  1483.             putDotDown({x=(x+a)-1, y=y, c=msg:sub(a,a)})
  1484.         end
  1485.     end
  1486.     saveToUndoBuffer()
  1487. end
  1488.  
  1489. local deleteDot = function(x,y) --deletes all dots at point x,y
  1490.     local good = false
  1491.     for a = #paintEncoded[frame],1,-1 do
  1492.         local b = paintEncoded[frame][a]
  1493.         if (x == b.x) and (y == b.y) then
  1494.             table.remove(paintEncoded[frame],a)
  1495.             good = true
  1496.         end
  1497.     end
  1498.     return good
  1499. end
  1500.  
  1501. exportToPaint = function(input,noTransparent) --exports paintEncoded frame to regular paint format. input is expected to be paintEncoded[frame]
  1502.     local doopTXT, doopTXCOL, doopBGCOL = {}, {}, {}
  1503.     local p = input
  1504.     local pheight = 0
  1505.     local pwidth = 0
  1506.     for a = 1, #p do
  1507.         if p[a].y > pheight then
  1508.             pheight = p[a].y
  1509.         end
  1510.         if p[a].x > pwidth then
  1511.             pwidth = p[a].x
  1512.         end
  1513.     end
  1514.     for k,v in pairs(p) do
  1515.         if not doopBGCOL[v.y] then
  1516.             doopBGCOL[v.y] = {}
  1517.             doopTXCOL[v.y] = {}
  1518.             doopTXT[v.y] = {}
  1519.         end
  1520.         doopBGCOL[v.y][v.x] = CTB(v.b)
  1521.         doopTXCOL[v.y][v.x] = CTB(v.t)
  1522.         doopTXT[v.y][v.x] = v.c
  1523.     end
  1524.     local nfpoutputTXT, nfpoutputTXCOL, nfpoutputBGCOL = "", "", ""
  1525.     for y = 1, pheight do
  1526.         if doopBGCOL[y] then
  1527.             for x = 1, pwidth do
  1528.                 if doopBGCOL[y][x] then
  1529.                     nfpoutputBGCOL = nfpoutputBGCOL..doopBGCOL[y][x]
  1530.                     nfpoutputTXCOL = nfpoutputTXCOL..doopTXCOL[y][x]
  1531.                     nfpoutputTXT = nfpoutputTXT..(((doopTXT[y][x] == " " and noTransparent) and "\128" or doopTXT[y][x]) or " ")
  1532.                 else
  1533.                     nfpoutputBGCOL = nfpoutputBGCOL..(noTransparent and "0" or " ")
  1534.                     nfpoutputTXCOL = nfpoutputTXCOL..(noTransparent and "0" or " ")
  1535.                     nfpoutputTXT = nfpoutputTXT.." "
  1536.                 end
  1537.             end
  1538.         end
  1539.         if y ~= pheight then
  1540.             nfpoutputBGCOL = nfpoutputBGCOL.."\n"
  1541.             nfpoutputTXCOL = nfpoutputTXCOL.."\n"
  1542.             nfpoutputTXT = nfpoutputTXT.."\n"
  1543.         end
  1544.     end
  1545.     return nfpoutputBGCOL, pheight, pwidth
  1546. end
  1547.  
  1548. local exportToNFT = function(input)
  1549.     local pwidths = {}
  1550.     local doot = {}
  1551.     local pheight = 0
  1552.     for k, dot in pairs(input) do
  1553.         pwidths[dot.y] = math.max((pwidths[dot.y] or 0), dot.x)
  1554.         pheight = math.max(pheight, dot.y)
  1555.         doot[dot.y] = doot[dot.y] or {}
  1556.         doot[dot.y][dot.x] = {
  1557.             char = dot.c,
  1558.             text = CTB(dot.t),
  1559.             back = CTB(dot.b)
  1560.         }
  1561.     end
  1562.    
  1563.     local bgcode, txcode = "\30", "\31"
  1564.     local output = ""
  1565.     local text, back
  1566.    
  1567.     for y = 1, pheight do
  1568.         pwidths[y] = pwidths[y] or 0
  1569.         if doot[y] then
  1570.             for x = 1, pwidths[y] do
  1571.                 doot[y][x] = doot[y][x] or {
  1572.                     text = " ",
  1573.                     back = " ",
  1574.                     char = " ",
  1575.                 }
  1576.             end
  1577.         else
  1578.             doot[y] = false
  1579.         end
  1580.     end
  1581.  
  1582.     for y = 1, pheight do
  1583.        
  1584.         text, back = "0", "f"
  1585.         if doot[y] then
  1586.             for x = 1, pwidths[y] do
  1587.                
  1588.                 if doot[y][x] then
  1589.                     if doot[y][x].back ~= back then
  1590.                         back = doot[y][x].back
  1591.                         output = output .. bgcode .. back
  1592.                     end
  1593.                     if doot[y][x].text ~= text then
  1594.                         text = doot[y][x].text
  1595.                         output = output .. txcode .. text
  1596.                     end
  1597.                     output = output .. doot[y][x].char
  1598.                 else
  1599.                     output = output .. " "
  1600.                 end
  1601.                
  1602.             end
  1603.         end
  1604.        
  1605.         if y < pheight then
  1606.             output = output .. "\n"
  1607.         end
  1608.     end
  1609.     return output
  1610. end
  1611.  
  1612. local importFromNFT = function(input) --imports NFT formatted string image to paintEncoded[frame] formatted table image. please return a paintEncoded[frame] formatted table.
  1613.     local tinput = explode("\n",input)
  1614.     local tcol,bcol
  1615.     local cx --represents the x position in the picture
  1616.     local sx --represents the x position in the file
  1617.     local output = {}
  1618.     for y = 1, #tinput do
  1619.         tcol,bcol = colors.white,colors.black
  1620.         cx, sx = 1, 0
  1621.         while sx < #tinput[y] do
  1622.             sx = sx + 1
  1623.             if tinput[y]:sub(sx,sx) == "\30" then
  1624.                 bcol = BTC(tinput[y]:sub(sx+1,sx+1))
  1625.                 sx = sx + 1
  1626.             elseif tinput[y]:sub(sx,sx) == "\31" then
  1627.                 tcol = BTC(tinput[y]:sub(sx+1,sx+1))
  1628.                 sx = sx + 1
  1629.             else
  1630.                 if tcol and bcol then
  1631.                     output[#output+1] = {
  1632.                         ["x"] = cx,
  1633.                         ["y"] = y,
  1634.                         ["b"] = bcol,
  1635.                         ["t"] = tcol,
  1636.                         ["c"] = tinput[y]:sub(sx,sx),
  1637.                         ["m"] = 0,
  1638.                     }
  1639.                 end
  1640.                 cx = cx + 1
  1641.             end
  1642.         end
  1643.     end
  1644.     return output
  1645. end
  1646.  
  1647. exportToBLT = function(input,filename,doAllFrames,noSave)
  1648.     local output = {}
  1649.     local thisImage,pheight,pwidth,nfpinput
  1650.     getBlittle()
  1651.     for a = doAllFrames and 1 or frame, doAllFrames and #input or frame do
  1652.         output[#output+1] = blittle.shrink(NFPserializeImage(exportToPaint(input[a]),true),colors.black)
  1653.     end
  1654.     if #output == 1 then output = output[1] end
  1655.     if not noSave then
  1656.         blittle.save(output,filename)
  1657.     end
  1658.     return output
  1659. end
  1660.  
  1661. importFromBLT = function(input) --takes in filename, not contents
  1662.     local output = {}
  1663.     getBlittle()
  1664.     local wholePic = blittle.load(input)
  1665.     if wholePic.height then wholePic = {wholePic} end
  1666.     local image
  1667.     for a = 1, #wholePic do
  1668.         image = wholePic[a]
  1669.         output[#output+1] = {}
  1670.         for y = 1, image.height*3 do
  1671.             for x = 1, math.max(#image[1][math.ceil(y/3)],#image[2][math.ceil(y/3)],#image[3][math.ceil(y/3)])*2 do
  1672.                 output[#output][#output[#output]+1] = {
  1673.                     m = 0,
  1674.                     x = x,
  1675.                     y = y,
  1676.                     t = BTC((image[2][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
  1677.                     b = BTC((image[3][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
  1678.                     c = BTC((image[1][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).." "):sub(1,1)),
  1679.                 }
  1680.             end
  1681.         end
  1682.     end
  1683.     return output
  1684. end
  1685.  
  1686. local getTheDoots = function(pe)
  1687.     local hasBadDots = false
  1688.     local baddestX,baddestY = 1,1
  1689.     for b = 1, #pe do
  1690.         local doot = pe[b]
  1691.         if doot.x <= 0 or doot.y <= 0 then
  1692.             hasBadDots = true
  1693.             if doot.x < baddestX then
  1694.                 baddestX = doot.x
  1695.             end
  1696.             if doot.y < baddestY then
  1697.                 baddestY = doot.y
  1698.             end
  1699.         end
  1700.         if b % 64 == 0 then yield() end
  1701.     end
  1702.     return baddestX, baddestY
  1703. end
  1704.  
  1705. local checkBadDots = function()
  1706.     local hasBadDots = false
  1707.     for a = 1, #paintEncoded do
  1708.         local radx,rady = getTheDoots(paintEncoded[a])
  1709.         if radx ~= 1 or rady ~= 1 then
  1710.             hasBadDots = true
  1711.         end
  1712.     end
  1713.     if hasBadDots then
  1714.         local ting = bottomPrompt("Dot(s) are OoB! Save or fix? (Y/N/F)",_,"ynf",{keys.leftCtrl,keys.rightCtrl})
  1715.         if ting == "f" then
  1716.             for a = 1, #paintEncoded do
  1717.                 local baddestX, baddestY = getTheDoots(paintEncoded[a])
  1718.                 paintEncoded[a] = movePaintEncoded(paintEncoded[a],-(baddestX-1),-(baddestY-1))
  1719.             end
  1720.         elseif ting ~= "y" then
  1721.             barmsg = ""
  1722.             return false
  1723.         end
  1724.     end
  1725. end
  1726.  
  1727. local convertToGrayscale = function(pe)
  1728.     local output = pe
  1729.     for a = 1, #pe do
  1730.         for b = 1, #pe[a] do
  1731.             output[a][b].b = grayOut(pe[a][b].b)
  1732.             output[a][b].t = grayOut(pe[a][b].t)
  1733.             if not output[a][b].m then output[a][b].m = 1 end
  1734.         end
  1735.         if a % 2 == 0 then yield() end
  1736.     end
  1737.     return output
  1738. end
  1739.  
  1740. local reRenderPAIN = function(overrideRenderBar)
  1741.     local _reallyDoRenderBar = doRenderBar
  1742.     doRenderBar = 1
  1743.     renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true,overrideRenderBar)
  1744.     doRenderBar = _reallyDoRenderBar
  1745. end
  1746.  
  1747. local fillTool = function(_frame,cx,cy,dot) -- "_frame" is the frame NUMBER
  1748.     local maxX, maxY = 0, 0
  1749.     local minX, minY = 0, 0
  1750.     paintEncoded = clearAllRedundant(paintEncoded)
  1751.     local frame = paintEncoded[_frame]
  1752.     local scx, scy = cx+paint.scrollX, cy+paint.scrollY
  1753.     local output = {}
  1754.     for a = 1, #frame do
  1755.         maxX = math.max(maxX, frame[a].x)
  1756.         maxY = math.max(maxY, frame[a].y)
  1757.         minX = math.min(minX, frame[a].x)
  1758.         minY = math.min(minY, frame[a].y)
  1759.     end
  1760.  
  1761.     maxX = math.max(maxX, scx)
  1762.     maxY = math.max(maxY, scy)
  1763.     minX = math.min(minX, scx)
  1764.     minY = math.min(minY, scy)
  1765.  
  1766.     maxX = math.max(maxX, screenEdges[1])
  1767.     maxY = math.max(maxY, screenEdges[2])
  1768.  
  1769.     local doop = {}
  1770.     local touched = {}
  1771.     local check = {[scy] = {[scx] = true}}
  1772.     for y = minY, maxY do
  1773.         doop[y] = {}
  1774.         touched[y] = {}
  1775.         for x = minX, maxX do
  1776.             doop[y][x] = {
  1777.                 c = " ",
  1778.                 b = 0,
  1779.                 t = 0
  1780.             }
  1781.             touched[y][x] = false
  1782.         end
  1783.     end
  1784.     for a = 1, #frame do
  1785.         doop[frame[a].y][frame[a].x] = {
  1786.             c = frame[a].c,
  1787.             t = frame[a].t,
  1788.             b = frame[a].b
  1789.         }
  1790.     end
  1791.     local initDot = {
  1792.         c = doop[scy][scx].c,
  1793.         t = doop[scy][scx].t,
  1794.         b = doop[scy][scx].b
  1795.     }
  1796.     local chkpos = function(x, y, checkList)
  1797.         if (x < minX or x > maxX) or (y < minY or y > maxY) then
  1798.             return false
  1799.         else
  1800.             if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t) or (doop[y][x].c ~= initDot.c) then
  1801.                 return false
  1802.             end
  1803.             if check[y] then
  1804.                 if check[y][x] then
  1805.                     return false
  1806.                 end
  1807.             end
  1808.             if touched[y][x] then
  1809.                 return false
  1810.             end
  1811.             return true
  1812.         end
  1813.     end
  1814.     local doBreak
  1815.     local step = 0
  1816.     local currentlyOnScreen
  1817.     while true do
  1818.         doBreak = true
  1819.         for chY, v in pairs(check) do
  1820.             for chX, isTrue in pairs(v) do
  1821.                 currentlyOnScreen = (chX-paint.scrollX >= 1 and chX-paint.scrollX <= scr_x and chY-paint.scrollY >= 1 and chY-paint.scrollY <= scr_y)
  1822.                 if isTrue and (not touched[chY][chX]) then
  1823.                     step = step + 1
  1824.                     if doFillAnimation then
  1825.                         if currentlyOnScreen then
  1826.                             reRenderPAIN(true)
  1827.                         end
  1828.                     end
  1829.                     frame[#frame+1] = {
  1830.                         x = chX,
  1831.                         y = chY,
  1832.                         c = dot.c,
  1833.                         t = dot.t,
  1834.                         b = dot.b
  1835.                     }
  1836.                     touched[chY][chX] = true
  1837.                     -- check adjacent
  1838.                     if chkpos(chX+1, chY) then
  1839.                         check[chY][chX+1] = true
  1840.                         doBreak = false
  1841.                     end
  1842.                     if chkpos(chX-1, chY) then
  1843.                         check[chY][chX-1] = true
  1844.                         doBreak = false
  1845.                     end
  1846.                     if chkpos(chX, chY+1) then
  1847.                         check[chY+1] = check[chY+1] or {}
  1848.                         check[chY+1][chX] = true
  1849.                         doBreak = false
  1850.                     end
  1851.                     if chkpos(chX, chY-1) then
  1852.                         check[chY-1] = check[chY-1] or {}
  1853.                         check[chY-1][chX] = true
  1854.                         doBreak = false
  1855.                     end
  1856.                     -- check diagonal
  1857.                     if doFillDiagonal then
  1858.                         if chkpos(chX-1, chY-1) then
  1859.                             check[chY-1] = check[chY-1] or {}
  1860.                             check[chY-1][chX-1] = true
  1861.                             doBreak = false
  1862.                         end
  1863.                         if chkpos(chX+1, chY-1) then
  1864.                             check[chY-1] = check[chY-1] or {}
  1865.                             check[chY-1][chX+1] = true
  1866.                             doBreak = false
  1867.                         end
  1868.                         if chkpos(chX-1, chY+1) then
  1869.                             check[chY+1] = check[chY+1] or {}
  1870.                             check[chY+1][chX-1] = true
  1871.                             doBreak = false
  1872.                         end
  1873.                         if chkpos(chX+1, chY+1) then
  1874.                             check[chY+1] = check[chY+1] or {}
  1875.                             check[chY+1][chX+1] = true
  1876.                             doBreak = false
  1877.                         end
  1878.                     end
  1879.                     if step % ((doFillAnimation and currentlyOnScreen) and 4 or 1024) == 0 then -- tries to prevent crash
  1880.                         sleep(0)
  1881.                     end
  1882.                 end
  1883.             end
  1884.         end
  1885.         if doBreak then
  1886.             break
  1887.         end
  1888.     end
  1889.     paintEncoded = clearAllRedundant(paintEncoded)
  1890.     saveToUndoBuffer()
  1891. end
  1892.  
  1893. local boxCharSelector = function()
  1894.     local co = function(pos)
  1895.         if pos then
  1896.             term.setTextColor(colors.lime)
  1897.             term.setBackgroundColor(colors.green)
  1898.         else
  1899.             term.setTextColor(colors.lightGray)
  1900.             term.setBackgroundColor(colors.gray)
  1901.         end
  1902.     end
  1903.     local rend = function()
  1904.         term.setCursorPos(1,scr_y)
  1905.         term.setBackgroundColor(colors.lightGray)
  1906.         term.setTextColor(colors.black)
  1907.         term.clearLine()
  1908.         term.write("Press CTRL or 'N' when ready.")
  1909.         term.setCursorPos(1,scr_y-3) co(boxchar.topLeft) write("Q") co(boxchar.topRight) write("W")
  1910.         term.setCursorPos(1,scr_y-2) co(boxchar.left) write("A") co(boxchar.right) write("S")
  1911.         term.setCursorPos(1,scr_y-1) co(boxchar.bottomLeft) write("Z") co(boxchar.bottomRight) write("X")
  1912.     end
  1913.     while true do
  1914.         rend()
  1915.         local evt = {os.pullEvent()}
  1916.         if evt[1] == "key" then
  1917.             local key = evt[2]
  1918.             if key == keys.leftCtrl or key == keys.rightCtrl or key == keys.n then
  1919.                 break
  1920.             else
  1921.                 if key == keys.q then boxchar.topLeft = not boxchar.topLeft end
  1922.                 if key == keys.w then boxchar.topRight = not boxchar.topRight end
  1923.                 if key == keys.a then boxchar.left = not boxchar.left end
  1924.                 if key == keys.s then boxchar.right = not boxchar.right end
  1925.                 if key == keys.z then boxchar.bottomLeft = not boxchar.bottomLeft end
  1926.                 if key == keys.x then boxchar.bottomRight = not boxchar.bottomRight end
  1927.             end
  1928.         elseif evt[1] == "mouse_click" or evt[1] == "mouse_drag" then
  1929.             local button, mx, my = evt[2], evt[3], evt[4]
  1930.             if my >= scr_y-2 then
  1931.                 if mx == 1 then
  1932.                     if my == scr_y - 3 then boxchar.topLeft = not boxchar.topLeft end
  1933.                     if my == scr_y - 2 then boxchar.left = not boxchar.left end
  1934.                     if my == scr_y - 1 then boxchar.bottomLeft = not boxchar.bottomLeft end
  1935.                 elseif mx == 2 then
  1936.                     if my == scr_y - 3 then boxchar.topRight = not boxchar.topRight end
  1937.                     if my == scr_y - 2 then boxchar.right = not boxchar.right end
  1938.                     if my == scr_y - 1 then boxchar.bottomRight = not boxchar.bottomRight end
  1939.                 elseif evt[1] == "mouse_click" then
  1940.                     break
  1941.                 end
  1942.             elseif evt[1] == "mouse_click" then
  1943.                 break
  1944.             end
  1945.         end
  1946.     end
  1947.     if boxchar.topLeft and boxchar.topRight and boxchar.left and boxchar.right and boxchar.bottomLeft and boxchar.bottomRight then
  1948.         swapColors = false
  1949.         return " "
  1950.     else
  1951.         local output = getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight)
  1952.         swapColors = not output.inverted
  1953.         return output.char
  1954.     end
  1955. end
  1956.  
  1957. local specialCharSelector = function()
  1958.     local chars = {}
  1959.     local buff = 0
  1960.     for y = 1, 16 do
  1961.         for x = 1, 16 do
  1962.             chars[y] = chars[y] or {}
  1963.             chars[y][x] = string.char(buff)
  1964.                         buff = buff + 1
  1965.         end
  1966.     end
  1967.     local sy = scr_y - (#chars + 1)
  1968.     local char = paint.c
  1969.     local render = function()
  1970.         for y = 1, #chars do
  1971.             for x = 1, #chars do
  1972.                 term.setCursorPos(x,y+sy)
  1973.                 if chars[y][x] == char then
  1974.                     term.blit(chars[y][x], "5", "d")
  1975.                 else
  1976.                     term.blit(chars[y][x], "8", "7")
  1977.                 end
  1978.             end
  1979.         end
  1980.     end
  1981.     local evt, butt, x, y
  1982.     render()
  1983.    
  1984.     term.setCursorPos(1,scr_y)
  1985.     term.setBackgroundColor(colors.lightGray)
  1986.     term.setTextColor(colors.black)
  1987.     term.clearLine()
  1988.     term.write("Press CTRL or 'N' when ready.")
  1989.    
  1990.     while true do
  1991.         evt, butt, x, y = os.pullEvent()
  1992.         if (evt == "mouse_click" or evt == "mouse_drag") then
  1993.             if chars[y-sy] then
  1994.                 if chars[y-sy][x] then
  1995.                     if (chars[y-sy][x] ~= char) then
  1996.                         char = chars[y-sy][x]
  1997.                         render()
  1998.                     end
  1999.                 else
  2000.                     return char
  2001.                 end
  2002.             else
  2003.                 return char
  2004.             end
  2005.         elseif evt == "key" then
  2006.             if (butt == keys.n) or (butt == keys.leftCtrl) then
  2007.                 return char
  2008.             end
  2009.         end
  2010.     end
  2011. end
  2012.  
  2013. local dontDragThisTime = false
  2014. local resetInputState = function()
  2015.     miceDown = {}
  2016.     keysDown = {}
  2017.     isDragging = false
  2018.     dontDragThisTime = true
  2019. end
  2020.  
  2021. local gotoCoords = function()
  2022.     local newX = bottomPrompt("Goto X:")
  2023.     newX = tonumber(newX)
  2024.     local newY
  2025.         if newX then
  2026.         newY = bottomPrompt("Goto Y:")
  2027.         newY = tonumber(newY)
  2028.         paint.scrollX = newX or paint.scrollX
  2029.         paint.scrollY = newY or paint.scrollY
  2030.     end
  2031. end
  2032.  
  2033. local renderAllPAIN = function()
  2034.     renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
  2035. end
  2036.  
  2037. local checkIfNFP = function(str) --does not check table format, only string format
  2038.     local good = {
  2039.         [0] = true,
  2040.         [1] = true,
  2041.         [2] = true,
  2042.         [3] = true,
  2043.         [4] = true,
  2044.         [5] = true,
  2045.         [6] = true,
  2046.         [7] = true,
  2047.         [8] = true,
  2048.         [9] = true,
  2049.         a = true,
  2050.         b = true,
  2051.         c = true,
  2052.         d = true,
  2053.         e = true,
  2054.         f = true,
  2055.         [" "] = true,
  2056.         ["\n"] = true
  2057.     }
  2058.     for a = 1, #str do
  2059.         if not good[str:sub(a,a):lower()] then
  2060.             return false
  2061.         end
  2062.     end
  2063.     return true
  2064. end
  2065.  
  2066. local openNewFile = function(fname, allowNonImageNFP)
  2067.     local file = fs.open(fname,"r")
  2068.     local contents = file.readAll()
  2069.     file.close()
  2070.     if type(tun(contents)) ~= "table" then
  2071.         term.setTextColor(colors.white)
  2072.         if contents:sub(1,3) == "BLT" then --thank you bomb bloke for this easy identifier
  2073.             if pMode ~= 1 then print("Importing from BLT...") end
  2074.             return importFromBLT(fname), 3
  2075.         elseif contents:sub(1,2) == "BM" then
  2076.             if pMode ~= 1 then print("Importing from BMP...") end
  2077.             return {importFromBitmap(fname)}, 7
  2078.         elseif contents:sub(1,3) == "GIF" then
  2079.             if pMode ~= 1 then print("Importing from GIF, this'll take a while...") end
  2080.             return importFromGIF(fname,true), 5
  2081.         elseif contents:sub(1,4) == "?!7\2" then
  2082.             if pMode ~= 1 then print("Importing from UCG...") end
  2083.             return {importFromUCG(fname)}, 6
  2084.         elseif contents:find(string.char(30)) and contents:find(string.char(31)) then
  2085.             if pMode ~= 1 then print("Importing from NFT...") end
  2086.             return {importFromNFT(contents)}, 2
  2087.         elseif (checkIfNFP(contents) or allowNonImageNFP) then
  2088.             print("Importing from NFP...")
  2089.             return {importFromPaint(contents)}, 1
  2090.         else
  2091.             return false, "That is not a valid image file."
  2092.         end
  2093.     else
  2094.         return tun(contents), 4
  2095.     end
  2096. end
  2097.  
  2098. local displayMenu = function()
  2099.     menuOptions = {"File","Edit","Window","About","Exit"}
  2100.     local diss = " "..tableconcat(menuOptions," ")
  2101.     local cleary = scr_y-math.floor(#diss/scr_x)
  2102.  
  2103.     local fileSave = function()
  2104.         checkBadDots()
  2105.         local output = deepCopy(paintEncoded)
  2106.         if paint.doGray then
  2107.             output = convertToGrayscale(output)
  2108.         end
  2109.         doRender = true
  2110.         if not fileName then
  2111.             renderBottomBar("Save as: ")
  2112.             local fnguess = read()
  2113.             if fs.isReadOnly(fnguess) then
  2114.                 barmsg = "'"..fnguess.."' is read only."
  2115.                 return false
  2116.             elseif fnguess:gsub(" ","") == "" then
  2117.                 return false
  2118.             elseif fs.isDir(fnguess) then
  2119.                 barmsg = "'"..fnguess.."' is already a directory."
  2120.                 return false
  2121.             elseif #fnguess > 255 then
  2122.                 barmsg = "Filename is too long."
  2123.                 return false
  2124.             else
  2125.                 fileName = fnguess
  2126.             end
  2127.         end
  2128.         saveFile(fileName,output)
  2129.         term.setCursorPos(9,scr_y)
  2130.         return fileName
  2131.     end
  2132.     local filePrint = function()
  2133.         local usedDots, dot = {}, {}
  2134.         for a = 1, #paintEncoded[frame] do
  2135.             dot = paintEncoded[frame][a]
  2136.             if dot.x > paint.scrollX and dot.x < (paint.scrollX + 25) and dot.y > paint.scrollX and dot.y < (paint.scrollY + 21) then
  2137.                 if dot.c ~= " " then
  2138.                     usedDots[dot.t] = usedDots[dot.t] or {}
  2139.                     usedDots[dot.t][#usedDots[dot.t]+1] = {
  2140.                         x = dot.x - paint.scrollX,
  2141.                         y = dot.y - paint.scrollY,
  2142.                         char = dot.c
  2143.                     }
  2144.                 end
  2145.             end
  2146.         end
  2147.         local dyes = {
  2148.             [1] = "bonemeal",
  2149.             [2] = "orange dye",
  2150.             [4] = "magenta dye",
  2151.             [8] = "light blue dye",
  2152.             [16] = "dandelion yellow",
  2153.             [32] = "lime dye",
  2154.             [64] = "pink dye",
  2155.             [128] = "gray dye",
  2156.             [256] = "light gray dye",
  2157.             [512] = "cyan dye",
  2158.             [1024] = "purple dye",
  2159.             [2048] = "lapis lazuli",
  2160.             [4096] = "cocoa beans",
  2161.             [8192] = "cactus green",
  2162.             [16384] = "rose red",
  2163.             [32768] = "ink sac",
  2164.         }
  2165.         local printer = peripheral.find("printer")
  2166.         if not printer then
  2167.             barmsg = "No printer found."
  2168.             return false
  2169.         end
  2170.         local page
  2171.         for color, dotList in pairs(usedDots) do
  2172.             term.setBackgroundColor(colors.black)
  2173.             term.setTextColor((color == colors.black) and colors.gray or color)
  2174.             term.clear()
  2175.             cwrite("Please insert "..dyes[color].." into the printer.", nil, math.floor(scr_y/2))
  2176.             term.setTextColor(colors.lightGray)
  2177.             cwrite("Then, press spacebar.", nil, math.floor(scr_y/2) + 1)
  2178.             local evt
  2179.             sleep(0)
  2180.             repeat
  2181.                 evt = {os.pullEvent("key")}
  2182.             until evt[2] == keys.space
  2183.             page = page or printer.newPage()
  2184.             if not page then
  2185.                 barmsg = "Check ink/paper."
  2186.                 return
  2187.             end
  2188.             for k,v in pairs(usedDots[color]) do
  2189.                 printer.setCursorPos(v.x, v.y)
  2190.                 printer.write(v.char)
  2191.             end
  2192.         end
  2193.         printer.endPage()
  2194.         barmsg = "Printed."
  2195.     end
  2196.     local fileExport = function(menuX,getRightToIt,_fileName)
  2197.         local exportMode
  2198.         if not tonumber(getRightToIt) then
  2199.             exportMode = makeSubMenu(menuX or 8,scr_y-2,{"Paint","NFT","BLT","PAIN Native","GIF","UCG"})
  2200.         else
  2201.             exportMode = getRightToIt
  2202.         end
  2203.         doRender = true
  2204.         if exportMode == false then return false end
  2205.         local pe, exportName, writeIndent, result
  2206.         if exportMode == 4 then
  2207.             local exNm = fileSave()
  2208.             if exNm then
  2209.                 changedImage = false
  2210.                 return exNm
  2211.             else
  2212.                 return nil
  2213.             end
  2214.         else
  2215.             checkBadDots()
  2216.             if _fileName then
  2217.                 exportName, writeIndent = _fileName, #_fileName
  2218.             else
  2219.                 exportName, writeIndent = bottomPrompt("Export to: /")
  2220.             end
  2221.             nfpoutput = ""
  2222.             if fs.combine("",exportName) == "" then
  2223.                 barmsg = "Export cancelled."
  2224.                 return
  2225.             end
  2226.             if fs.isReadOnly(exportName) then
  2227.                 barmsg = "That's read-only."
  2228.                 return
  2229.             end
  2230.             if fs.exists(exportName) and (not _fileName) then
  2231.                 local plea = (progname == fs.combine("",exportName)) and "Overwrite ORIGINAL file!?" or "Overwrite?"
  2232.                 result, _wIn = bottomPrompt(plea.." (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
  2233.                 writeIndent = writeIndent + _wIn
  2234.                 if result ~= "y" then return end
  2235.             end
  2236.             local output
  2237.             pe = deepCopy(paintEncoded)
  2238.             if paint.doGray then
  2239.                 pe = convertToGrayscale(pe)
  2240.             end
  2241.             local doSerializeBLT = false
  2242.         end
  2243.         if exportMode == 1 then
  2244.             output = exportToPaint(pe[frame])
  2245.             if askToSerialize then
  2246.                 result, _wIn = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{})
  2247.                 writeIndent = writeIndent + _wIn
  2248.             else result, _wIn = "n", 0 end
  2249.             if result == "y" then
  2250.                 output = textutils.serialize(NFPserializeImage(output)):gsub(" ",""):gsub("\n",""):gsub(",}","}")
  2251.             end
  2252.         elseif exportMode == 2 then
  2253.             output = exportToNFT(pe[frame])
  2254.         elseif exportMode == 3 then
  2255.             local doAllFrames, _wIn = bottomPrompt("Save all frames, or current? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl},writeIndent)
  2256.             writeIndent = writeIndent + _wIn
  2257.             if askToSerialize then
  2258.                 doSerializeBLT = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{},writeIndent) == "y"
  2259.             end
  2260.             output = textutils.serialise(exportToBLT(pe,exportName,doAllFrames == "y",doSerializeBLT))
  2261.         elseif exportMode == 5 then
  2262.             getGIF()
  2263.             GIF.saveGIF(exportToGIF(pe),exportName)
  2264.         elseif exportMode == 6 then
  2265.             exportToUCG(exportName,pe[frame])
  2266.         end
  2267.         if ((exportMode ~= 3) and (exportMode ~= 4) and (exportMode ~= 5) and (exportMode ~= 6)) or doSerializeBLT then
  2268.             local file = fs.open(exportName,"w")
  2269.             file.write(output)
  2270.             file.close()
  2271.         end
  2272.         return exportName
  2273.     end
  2274.  
  2275.     local editDelFrame = function()
  2276.         local outcum = bottomPrompt("Thou art sure? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
  2277.         doRender = true
  2278.         if outcum == "y" then
  2279.             if #paintEncoded == 1 then
  2280.                 barmsg = "Fat chance."
  2281.                 return
  2282.             end
  2283.             table.remove(paintEncoded,frame)
  2284.             barmsg = "Deleted frame "..frame.."."
  2285.             if paintEncoded[frame-1] then
  2286.                 frame = frame - 1
  2287.             else
  2288.                 frame = frame + 1
  2289.             end
  2290.             if #paintEncoded < frame then
  2291.                 repeat
  2292.                     frame = frame - 1
  2293.                 until #paintEncoded >= frame
  2294.             end
  2295.             saveToUndoBuffer()
  2296.         end
  2297.     end
  2298.     local editClear = function()
  2299.         local outcum = bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
  2300.         if outcum == "y" then
  2301.             paintEncoded[frame] = {}
  2302.             saveToUndoBuffer()
  2303.             barmsg = "Cleared frame "..frame.."."
  2304.         end
  2305.         doRender = true
  2306.     end
  2307.     local editCrop = function()
  2308.         local outcum = bottomPrompt("Crop all but visible? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
  2309.         if outcum == "y" then
  2310.             local ppos = 1
  2311.             local deletedAmnt = 0
  2312.             for a = #paintEncoded[frame], 1, -1 do
  2313.                 local x, y = paintEncoded[frame][a].x, paintEncoded[frame][a].y
  2314.                 if (x <= paint.scrollX) or (x > paint.scrollX + scr_x) or (y <= paint.scrollY) or (y > paint.scrollY + scr_y) then
  2315.                     table.remove(paintEncoded[frame],a)
  2316.                     deletedAmnt = deletedAmnt + 1
  2317.                 else
  2318.                     ppos = ppos + 1
  2319.                 end
  2320.                 if ppos > #paintEncoded[frame] then break end
  2321.             end
  2322.             saveToUndoBuffer()
  2323.             barmsg = "Cropped frame."
  2324.         end
  2325.         doRender = true
  2326.     end
  2327.     local editBoxCharSelector = function()
  2328.         paint.c = boxCharSelector()
  2329.     end
  2330.     local editSpecialCharSelector = function()
  2331.         paint.c = boxCharSelector()
  2332.     end
  2333.  
  2334.     local windowSetScrSize = function()
  2335.         local x,y
  2336.         x = bottomPrompt("Scr.X OR monitor name:",{},nil,{keys.leftCtrl,keys.rightCtrl})
  2337.         if x == "" then
  2338.             return
  2339.         elseif x == "pocket" then
  2340.             screenEdges = {26,20}
  2341.         elseif x == "turtle" then
  2342.             screenEdges = {39,13}
  2343.         elseif x == "computer" then
  2344.             screenEdges = {51,19}
  2345.         elseif tonumber(x) then
  2346.             if tonumber(x) <= 0 then
  2347.                 barmsg = "Screen X must be greater than 0."
  2348.                 return
  2349.             end
  2350.             screenEdges[1] = math.abs(tonumber(x))
  2351.             y = bottomPrompt("Scr.Y:",{},nil,{keys.leftCtrl,keys.rightCtrl})
  2352.             if tonumber(y) then
  2353.                 if tonumber(y) <= 0 then
  2354.                     barmsg = "Screen Y must be greater than 0."
  2355.                     return
  2356.                 end
  2357.                 screenEdges[2] = math.abs(tonumber(y))
  2358.             end
  2359.             barmsg = "Screen size changed."
  2360.         else
  2361.             local mon = peripheral.wrap(x)
  2362.             if not mon then
  2363.                 barmsg = "No such monitor."
  2364.                 return
  2365.             else
  2366.                 if peripheral.getType(x) ~= "monitor" then
  2367.                     barmsg = "That's not a monitor."
  2368.                     return
  2369.                 else
  2370.                     screenEdges[1], screenEdges[2] = mon.getSize()
  2371.                     barmsg = "Screen size changed."
  2372.                     return
  2373.                 end
  2374.             end
  2375.         end
  2376.     end
  2377.     local aboutPAIN = function()
  2378.         local helpText = [[
  2379.  
  2380.       
  2381.          
  2382.        
  2383.            
  2384.          
  2385.  
  2386. Advanced Paint Program
  2387.  by LDDestroier
  2388.  or EldidiStroyrr
  2389.   if you please!
  2390.  
  2391. PAIN is a multi-frame paint program with the intention of becoming a stable, well-used, and mondo-useful CC drawing utility.
  2392.  
  2393. The main focus during development is to add more functions that you might see in MSPAINT such as lines or a proper fill tool (which I don't have, grr hiss boo), as well as to export/import to and from as many image formats as possible.
  2394.  
  2395. My ultimate goal is to have PAIN be the default paint program for most every operating system on the forums. In order to do this, I'll need to make sure that PAIN is stable, easy to use, and can be easily limited by an OS to work with more menial tasks like making a single icon or what have you.
  2396. ]]
  2397.         guiHelp(helpText)
  2398.     end
  2399.     local aboutFileFormats = function()
  2400.         local helpText = [[
  2401. Here's info on the file formats.
  2402.  
  2403. "NFP":
  2404. Used in rom/programs/paint, and the format for paintutils. It's a handy format, but the default rendering function is inefficient as hell, and it does not store text data, only background.
  2405. Cannot save multiple frames.
  2406.  
  2407.  "NFT":
  2408. Used in npaintpro and most everything else, it's my favorite of the file formats because it does what NFP does, but allows for text in the pictures. Useful for storing screenshots or small icons where an added level of detail is handy. Created by nitrogenfingers, thank him.
  2409. Cannot save multiple frames.
  2410.  
  2411. "BLT":
  2412. Used exclusively with Bomb Bloke's BLittle API, and as such is handy with making pictures with block characters. Just keep in mind that those 2*3 grid squares in PAIN represent individual characters in BLT.
  2413. BLT can save multiple frames!
  2414.  
  2415.  "PAIN Native":
  2416. The basic, tabular, and wholly inefficient format that PAIN uses. Useful for doing math within the program, not so much for long term file storage. It stores text, but just use NFT if you don't need multiple frames.
  2417. Obviously, this can save multiple frames.
  2418.  
  2419. "GIF":
  2420. The API was made by Bomb Bloke, huge thanks for that, but GIF is a universal file format used in real paint programs. Very useful for converting files on your computer to something like NFP, but doesn't store text. Be careful when opening up big GIF files, they can take a long time to load.
  2421. Being GIF, this saves multiple frames!
  2422.  
  2423.  "UCG":
  2424. Stands for Universal Compressed Graphics. This format was made by ardera, and uses Huffman Code and run-length encoding in order to reduce file sizes tremendously. However, it only saves backgrounds and not text data.
  2425. Cannot save multiple frames.
  2426.  
  2427.  
  2428. I recommend using NFT if you don't need multiple frames, NFP if you don't need text, UCG if the picture is really big, Native PAIN if you need both text and multiframe support, and GIF if you want to use something like MS Paint or Pinta or GIMP or whatever.
  2429. ]]
  2430.         guiHelp(helpText)
  2431.     end
  2432.     local menuPoses = {}
  2433.     local menuFunctions = {
  2434.         [1] = function() --File
  2435.             while true do
  2436.                 --renderAllPAIN()
  2437.                 local output, longestLen = makeSubMenu(1,cleary-1,{"Save","Save As","Export","Open",((peripheral.find("printer")) and "Print" or nil)})
  2438.                 doRender = true
  2439.                 if output == 1 then -- Save
  2440.                     local _fname = fileExport(_,defaultSaveFormat,fileName)
  2441.                     if _fname then
  2442.                         barmsg = "Saved as '".._fname.."'"
  2443.                         lastPaintEncoded = deepCopy(paintEncoded)
  2444.                         changedImage = false
  2445.                     end
  2446.                     break
  2447.                 elseif output == 2 then -- Save As
  2448.                     local oldfilename = fileName
  2449.                     fileName = nil
  2450.                     local res = fileExport(_,defaultSaveFormat)
  2451.                     if not res then
  2452.                         fileName = oldfilename
  2453.                     end
  2454.                     barmsg = "Saved as '"..fileName.."'"
  2455.                 elseif output == 3 then --Export
  2456.                     local res = fileExport(longestLen+1)
  2457.                     if res then
  2458.                         barmsg = "Exported as '"..res.."'"
  2459.                         break
  2460.                     end
  2461.                 elseif output == 4 then -- Open
  2462.                     renderBottomBar("Pick an image file.")
  2463.                     local newPath = lddfm.makeMenu(2, 2, scr_x-1, scr_y-2, fs.getDir(fileName or progname), false, false, false, true, false, nil, true)
  2464.                     if newPath then
  2465.                         local pen, form = openNewFile(newPath, readNonImageAsNFP)
  2466.                         if not pen then
  2467.                             barmsg = form
  2468.                         else
  2469.                             fileName = newPath
  2470.                             paintEncoded, lastPaintEncoded = pen, deepCopy(pen)
  2471.                             defaultSaveFormat = form
  2472.                             undoPos = 1
  2473.                             undoBuffer = {}
  2474.                             barmsg = "Opened '" .. fs.getName(newPath) .. "'"
  2475.                             paint.scrollX, paint.scrollY, paint.doGray = 1, 1, false
  2476.                             doRender = true
  2477.                         end
  2478.                     end
  2479.                     break
  2480.                 elseif output == 5 then -- Print
  2481.                     filePrint()
  2482.                     break
  2483.                 elseif output == false then
  2484.                     return "nobreak"
  2485.                 end
  2486.                 reRenderPAIN(true)
  2487.             end
  2488.         end,
  2489.         [2] = function() --Edit
  2490.             local output = makeSubMenu(6,cleary-1,{"Delete Frame","Clear Frame","Crop Frame","Choose Box Character","Choose Special Character","BLittle Shrink"})
  2491.             doRender = true
  2492.             if output == 1 then
  2493.                 editDelFrame()
  2494.             elseif output == 2 then
  2495.                 editClear()
  2496.             elseif output == 3 then
  2497.                 editCrop()
  2498.             elseif output == 4 then
  2499.                 editBoxCharSelector()
  2500.             elseif output == 5 then
  2501.                 editSpecialCharSelector()
  2502.             elseif output == 6 then
  2503.                 local res = bottomPrompt("You sure? It's unreversable! (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
  2504.                 if res == "y" then
  2505.                     getBlittle()
  2506.                     local bltPE = blittle.shrink(NFPserializeImage(exportToPaint(paintEncoded[frame])))
  2507.                     _G.SHRINKOUT = bltPE
  2508.                     paintEncoded[frame] = {}
  2509.                     for y = 1, bltPE.height do
  2510.                         for x = 1, bltPE.width do
  2511.                             paintEncoded[frame][#paintEncoded[frame]+1] = {
  2512.                                 c = bltPE[1][y]:sub(x,x),
  2513.                                 t = BTC(bltPE[2][y]:sub(x,x),true),
  2514.                                 b = BTC(bltPE[3][y]:sub(x,x),true),
  2515.                                 x = x,
  2516.                                 y = y,
  2517.                             }
  2518.                         end
  2519.                     end
  2520.                     saveToUndoBuffer()
  2521.                     doRender = true
  2522.                     barmsg = "Shrunk image."
  2523.                 end
  2524.             elseif output == false then
  2525.                 return "nobreak"
  2526.             end
  2527.         end,
  2528.         [3] = function() --Window
  2529.             local output = makeSubMenu(11,cleary-1,{"Set Screen Size","Set Scroll XY","Set Grid Colors"})
  2530.             doRender = true
  2531.             if output == 1 then
  2532.                 windowSetScrSize()
  2533.             elseif output == 2 then
  2534.                 gotoCoords()
  2535.             elseif output == 3 then
  2536.                 rendback.b = paint.b
  2537.                 rendback.t = paint.t
  2538.                 doRender = true
  2539.             elseif output == false then
  2540.                 return "nobreak"
  2541.             end
  2542.         end,
  2543.         [4] = function() --About
  2544.             local output = makeSubMenu(17,cleary-1,{"PAIN","File Formats","Help!"})
  2545.             doRender = true
  2546.             if output == 1 then
  2547.                 aboutPAIN()
  2548.             elseif output == 2 then
  2549.                 aboutFileFormats()
  2550.             elseif output == 3 then
  2551.                 guiHelp()
  2552.                 doRender = true
  2553.             end
  2554.         end,
  2555.         [5] = function() --Exit
  2556.             if changedImage then
  2557.                 local outcum = bottomPrompt("Abandon unsaved work? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
  2558.                 sleep(0)
  2559.                 if outcum == "y" then
  2560.                     return "exit"
  2561.                 else
  2562.                     doRender = true
  2563.                     return nil
  2564.                 end
  2565.             else
  2566.                 return "exit"
  2567.             end
  2568.         end,
  2569.     }
  2570.     local cursor = 1
  2571.     local redrawmenu = true
  2572.     local initial = os.time()
  2573.     local clickdelay = 0.003
  2574.  
  2575.     local redrawTheMenu = function()
  2576.         for a = cleary,scr_y do
  2577.             term.setCursorPos(1,a)
  2578.             term.setBackgroundColor(colors.lightGray)
  2579.             term.clearLine()
  2580.         end
  2581.         term.setCursorPos(2,cleary)
  2582.         for a = 1, #menuOptions do
  2583.             if a == cursor then
  2584.                 term.setTextColor(colors.black)
  2585.                 term.setBackgroundColor(colors.white)
  2586.             else
  2587.                 term.setTextColor(colors.black)
  2588.                 term.setBackgroundColor(colors.lightGray)
  2589.             end
  2590.             menuPoses[a] = {term.getCursorPos()}
  2591.             write(menuOptions[a])
  2592.             term.setBackgroundColor(colors.lightGray)
  2593.             if a ~= #menuOptions then
  2594.                 write(" ")
  2595.             end
  2596.         end
  2597.         redrawmenu = false
  2598.     end
  2599.  
  2600.     while true do
  2601.         if redrawmenu then
  2602.             redrawTheMenu()
  2603.             redrawmenu = false
  2604.         end
  2605.         local event,key,x,y = getEvents("key","char","mouse_click","mouse_up","mouse_drag")
  2606.         if event == "key" then
  2607.             if key == keys.left then
  2608.                 redrawmenu = true
  2609.                 cursor = cursor - 1
  2610.             elseif key == keys.right then
  2611.                 redrawmenu = true
  2612.                 cursor = cursor + 1
  2613.             elseif key == keys.enter then
  2614.                 redrawmenu = true
  2615.                 local res = menuFunctions[cursor]()
  2616.                 if res == "exit" then
  2617.                     return "exit"
  2618.                 elseif res == "nobreak" then
  2619.                     reRenderPAIN(true)
  2620.                 else
  2621.                     return
  2622.                 end
  2623.             elseif key == keys.leftCtrl or key == keys.rightCtrl then
  2624.                 doRender = true
  2625.                 return
  2626.             end
  2627.         elseif event == "char" then
  2628.             for a = 1, #menuOptions do
  2629.                 if key:lower() == menuOptions[a]:sub(1,1):lower() and a ~= cursor then
  2630.                     cursor = a
  2631.                     redrawmenu = true
  2632.                     break
  2633.                 end
  2634.             end
  2635.         elseif event == "mouse_click" or event == "mouse_up" then
  2636.             if y < cleary then
  2637.                 return
  2638.             elseif key == 1 and initial+clickdelay < os.time() then --key? more like button
  2639.                 for a = 1, #menuPoses do
  2640.                     if y == menuPoses[a][2] then
  2641.                         if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
  2642.                             cursor = a
  2643.                             redrawTheMenu()
  2644.                             local res = menuFunctions[a]()
  2645.                             coroutine.yield()
  2646.                             if res == "exit" then
  2647.                                 return "exit"
  2648.                             else
  2649.                                 return
  2650.                             end
  2651.                         end
  2652.                     end
  2653.                 end
  2654.             end
  2655.         end
  2656.         if (initial+clickdelay < os.time()) and string.find(event,"mouse") then
  2657.             if key == 1 then --key? key? what key? all I see is button!
  2658.                 for a = 1, #menuPoses do
  2659.                     if y == menuPoses[a][2] then
  2660.                         if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
  2661.                             cursor = a
  2662.                             redrawmenu = true
  2663.                             break
  2664.                         end
  2665.                     end
  2666.                 end
  2667.             end
  2668.         end
  2669.         if cursor < 1 then
  2670.             cursor = #menuOptions
  2671.         elseif cursor > #menuOptions then
  2672.             cursor = 1
  2673.         end
  2674.     end
  2675. end
  2676.  
  2677. local lastMX,lastMY,isDragging
  2678.  
  2679. local doNonEventDrivenMovement = function() --what a STUPID function name, dude
  2680.     local didMove
  2681.     while true do
  2682.         didMove = false
  2683.         if (not keysDown[keys.leftShift]) and (not isDragging) and (not keysDown[keys.tab]) then
  2684.             if keysDown[keys.right] then
  2685.                 paint.scrollX = paint.scrollX + 1
  2686.                 didMove = true
  2687.             elseif keysDown[keys.left] then
  2688.                 paint.scrollX = paint.scrollX - 1
  2689.                 didMove = true
  2690.             end
  2691.             if keysDown[keys.down] then
  2692.                 paint.scrollY = paint.scrollY + 1
  2693.                 didMove = true
  2694.             elseif keysDown[keys.up] then
  2695.                 paint.scrollY = paint.scrollY - 1
  2696.                 didMove = true
  2697.             end
  2698.             if didMove then
  2699.                 if lastMX and lastMY then
  2700.                     if miceDown[1] then
  2701.                         os.queueEvent("mouse_click",1,lastMX,lastMY)
  2702.                     end
  2703.                     if miceDown[2] then
  2704.                         os.queueEvent("mouse_click",2,lastMX,lastMY)
  2705.                     end
  2706.                 end
  2707.                 doRender = true
  2708.             end
  2709.         end
  2710.         sleep(0)
  2711.     end
  2712. end
  2713.  
  2714. local linePoses = {}
  2715. local dragPoses = {}
  2716.  
  2717. local listAllMonitors = function()
  2718.     term.setBackgroundColor(colors.gray)
  2719.     term.setTextColor(colors.white)
  2720.     local periphs = peripheral.getNames()
  2721.     local mons = {}
  2722.     for a = 1, #periphs do
  2723.         if peripheral.getType(periphs[a]) == "monitor" then
  2724.             mons[#mons+1] = periphs[a]
  2725.         end
  2726.     end
  2727.     if #mons == 0 then
  2728.         mons[1] = "No monitors found."
  2729.     end
  2730.     term.setCursorPos(3,1)
  2731.     term.clearLine()
  2732.     term.setTextColor(colors.yellow)
  2733.     term.write("All monitors:")
  2734.     term.setTextColor(colors.white)
  2735.     for y = 1, #mons do
  2736.         term.setCursorPos(2,y+1)
  2737.         term.clearLine()
  2738.         term.write(mons[y])
  2739.     end
  2740.     sleep(0)
  2741.     getEvents("char","mouse_click")
  2742.     doRender = true
  2743. end
  2744.  
  2745. local getInput = function() --gotta catch them all
  2746.     local button, x, y, oldmx, oldmy, origx, origy
  2747.     local isDragging = false
  2748.     local proceed = false
  2749.     renderBar(barmsg)
  2750.     while true do
  2751.         doRender = false
  2752.         local oldx,oldy = paint.scrollX,paint.scrollY
  2753.         local evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)}
  2754.         if (evt[1] == "mouse_scroll") and (not viewing) then
  2755.             local dir = evt[2]
  2756.             if dir == 1 then
  2757.                 if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
  2758.                     paint.t = paint.t * 2
  2759.                     if paint.t > 32768 then
  2760.                         paint.t = 32768
  2761.                     end
  2762.                 else
  2763.                     paint.b = paint.b * 2
  2764.                     if paint.b > 32768 then
  2765.                         paint.b = 32768
  2766.                     end
  2767.                 end
  2768.             else
  2769.                 if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
  2770.                     paint.t = math.ceil(paint.t / 2)
  2771.                     if paint.t < 1 then
  2772.                         paint.t = 1
  2773.                     end
  2774.                 else
  2775.                     paint.b = math.ceil(paint.b / 2)
  2776.                     if paint.b < 1 then
  2777.                         paint.b = 1
  2778.                     end
  2779.                 end
  2780.             end
  2781.             renderBar(barmsg)
  2782.         elseif ((evt[1] == "mouse_click") or (evt[1] == "mouse_drag")) and (not viewing) then
  2783.             if evt[1] == "mouse_click" then
  2784.                 origx, origy = evt[3], evt[4]
  2785.             end
  2786.             oldmx,oldmy = x or evt[3], y or evt[4]
  2787.             lastMX,lastMY = evt[3],evt[4]
  2788.             button,x,y = evt[2],evt[3],evt[4]
  2789.             if renderBlittle then
  2790.                 x = 2*x
  2791.                 y = 3*y
  2792.                 lastMX = 2*lastMX
  2793.                 lastMY = 3*lastMY
  2794.             end
  2795.             linePoses = {{x=oldmx,y=oldmy},{x=x,y=y}}
  2796.             miceDown[button] = true
  2797.             doRender = true
  2798.             if y <= scr_y-(renderBlittle and 0 or doRenderBar) then
  2799.                 if (button == 3) then
  2800.                     putDownText(x,y)
  2801.                     miceDown = {}
  2802.                     keysDown = {}
  2803.                 elseif button == 1 then
  2804.                     if keysDown[keys.leftShift] and evt[1] == "mouse_click" then
  2805.                         isDragging = true
  2806.                     end
  2807.                     if isDragging then
  2808.                         if evt[1] == "mouse_click" or dontDragThisTime then
  2809.                             dragPoses[1] = {x=x,y=y}
  2810.                         end
  2811.                         dragPoses[2] = {x=x,y=y}
  2812.                     elseif (not dontDragThisTime) then
  2813.                         if evt[1] == "mouse_drag" then
  2814.                             local points = getDotsInLine(linePoses[1].x,linePoses[1].y,linePoses[2].x,linePoses[2].y)
  2815.                             for a = 1, #points do
  2816.                                 putDotDown({x=points[a].x, y=points[a].y})
  2817.                             end
  2818.                         else
  2819.                             putDotDown({x=x, y=y})
  2820.                         end
  2821.                         changedImage = true
  2822.                     end
  2823.                     dontDragThisTime = false
  2824.                 elseif button == 2 and y <= scr_y-(renderBlittle and 0 or doRenderBar) then
  2825.                     deleteDot(x+paint.scrollX,y+paint.scrollY)
  2826.                     changedImage = true
  2827.                 end
  2828.             elseif origy >= scr_y-(renderBlittle and 0 or doRenderBar) then
  2829.                 keysDown = {}
  2830.                 local res = displayMenu()
  2831.                 if res == "exit" then break end
  2832.                 doRender = true
  2833.             end
  2834.         elseif (evt[1] == "mouse_up") and (not viewing) then
  2835.             origx,origy = 0,0
  2836.             local button = evt[2]
  2837.             miceDown[button] = false
  2838.             oldmx,oldmy = nil,nil
  2839.             lastMX, lastMY = nil,nil
  2840.             if isDragging then
  2841.                 local points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
  2842.                 for a = 1, #points do
  2843.                     putDotDown({x=points[a].x, y=points[a].y})
  2844.                 end
  2845.                 changedImage = true
  2846.                 doRender = true
  2847.             end
  2848.             saveToUndoBuffer()
  2849.             isDragging = false
  2850.         elseif evt[1] == "key" then
  2851.             local key = evt[2]
  2852.             if (not keysDown[keys.leftShift]) and (keysDown[keys.tab]) then
  2853.                 if key == keys.right and (not keysDown[keys.right]) then
  2854.                     paint.scrollX = paint.scrollX + 1
  2855.                     doRender = true
  2856.                 elseif key == keys.left and (not keysDown[keys.left]) then
  2857.                     paint.scrollX = paint.scrollX - 1
  2858.                     doRender = true
  2859.                 end
  2860.                 if key == keys.down and (not keysDown[keys.down]) then
  2861.                     paint.scrollY = paint.scrollY + 1
  2862.                     doRender = true
  2863.                 elseif key == keys.up and (not keysDown[keys.up]) then
  2864.                     paint.scrollY = paint.scrollY - 1
  2865.                     doRender = true
  2866.                 end
  2867.             end
  2868.             keysDown[key] = true
  2869.             if key == keys.space then
  2870.                 if keysDown[keys.leftShift] then
  2871.                     evenDrawGrid = not evenDrawGrid
  2872.                 else
  2873.                     doRenderBar = math.abs(doRenderBar-1)
  2874.                 end
  2875.                 doRender = true
  2876.             end
  2877.             if key == keys.b then
  2878.                 local blTerm, oldTerm = getBlittle()
  2879.                 renderBlittle = not renderBlittle
  2880.                 isDragging = false
  2881.                 term.setBackgroundColor(rendback.b)
  2882.                 term.clear()
  2883.                 if renderBlittle then
  2884.                     term.redirect(blTerm)
  2885.                     blTerm.setVisible(true)
  2886.                 else
  2887.                     term.redirect(oldTerm)
  2888.                     blTerm.setVisible(false)
  2889.                 end
  2890.                 doRender = true
  2891.                 scr_x, scr_y = term.current().getSize()
  2892.             end
  2893.             if (key == keys.c) and (not renderBlittle) then
  2894.                 gotoCoords()
  2895.                 resetInputState()
  2896.                 doRender = true
  2897.             end
  2898.             if (keysDown[keys.leftShift]) and (not isDragging) then
  2899.                 if key == keys.left then
  2900.                     paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0)
  2901.                     saveToUndoBuffer()
  2902.                     doRender = true
  2903.                     changedImage = true
  2904.                 elseif key == keys.right then
  2905.                     paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0)
  2906.                     saveToUndoBuffer()
  2907.                     doRender = true
  2908.                     changedImage = true
  2909.                 elseif key == keys.up then
  2910.                     paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1)
  2911.                     saveToUndoBuffer()
  2912.                     doRender = true
  2913.                     changedImage = true
  2914.                 elseif key == keys.down then
  2915.                     paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1)
  2916.                     saveToUndoBuffer()
  2917.                     doRender = true
  2918.                     changedImage = true
  2919.                 end
  2920.             end
  2921.             if keysDown[keys.leftAlt] then
  2922.                 if #paintEncoded > 1 then
  2923.                     if key == keys.equals and paintEncoded[frame+1] then --basically plus
  2924.                         local first = deepCopy(paintEncoded[frame])
  2925.                         local next = deepCopy(paintEncoded[frame+1])
  2926.                         paintEncoded[frame] = next
  2927.                         paintEncoded[frame+1] = first
  2928.                         frame = frame + 1
  2929.                         barmsg = "Swapped prev frame."
  2930.                         doRender = true
  2931.                         changedImage = true
  2932.                         saveToUndoBuffer()
  2933.                     end
  2934.                     if key == keys.minus and paintEncoded[frame-1] then
  2935.                         local first = deepCopy(paintEncoded[frame])
  2936.                         local next = deepCopy(paintEncoded[frame-1])
  2937.                         paintEncoded[frame] = next
  2938.                         paintEncoded[frame-1] = first
  2939.                         frame = frame - 1
  2940.                         barmsg = "Swapped next frame."
  2941.                         doRender = true
  2942.                         changedImage = true
  2943.                         saveToUndoBuffer()
  2944.                     end
  2945.                 end
  2946.             end
  2947.             if not keysDown[keys.leftAlt] then
  2948.                 if key == keys.equals then --basically 'plus'
  2949.                     if renderBlittle then
  2950.                         frame = frame + 1
  2951.                         if frame > #paintEncoded then frame = 1 end
  2952.                     else
  2953.                         if not paintEncoded[frame+1] then
  2954.                             paintEncoded[frame+1] = {}
  2955.                             local sheet = paintEncoded[frame]
  2956.                             if keysDown[keys.leftShift] then
  2957.                                 paintEncoded[frame+1] = deepCopy(sheet)
  2958.                             end
  2959.                         end
  2960.                         frame = frame + 1
  2961.                     end
  2962.                     saveToUndoBuffer()
  2963.                     doRender = true
  2964.                     changedImage = true
  2965.                 elseif key == keys.minus then
  2966.                     if renderBlittle then
  2967.                         frame = frame - 1
  2968.                         if frame < 1 then frame = #paintEncoded end
  2969.                     else
  2970.                         if frame > 1 then
  2971.                             frame = frame - 1
  2972.                         end
  2973.                     end
  2974.                     saveToUndoBuffer()
  2975.                     doRender = true
  2976.                     changedImage = true
  2977.                 end
  2978.             end
  2979.             if not renderBlittle then
  2980.                 if key == keys.m then
  2981.                     local incum = bottomPrompt("Set meta: ",metaHistory)
  2982.                     paint.m = incum:gsub(" ","") ~= "" and incum or paint.m
  2983.                     if paint.m ~= metaHistory[#metaHistory] then
  2984.                         metaHistory[#metaHistory+1] = paint.m
  2985.                     end
  2986.                     doRender = true
  2987.                     isDragging = false
  2988.                 end
  2989.                 if key == keys.f7 then
  2990.                     bepimode = not bepimode
  2991.                     doRender = true
  2992.                 end
  2993.                 if key == keys.t then
  2994.                     renderBottomBar("Click to place text.")
  2995.                     local mevt
  2996.                     repeat
  2997.                         mevt = {os.pullEvent()}
  2998.                     until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] == 1 and (mevt[4] or scr_y) < scr_y-(renderBlittle and 0 or doRenderBar))
  2999.                     if not (mevt[1] == "key" and mevt[2] == keys.x) then
  3000.                         local x,y = mevt[3],mevt[4]
  3001.                         if renderBlittle then
  3002.                             x = 2*x
  3003.                             y = 3*y
  3004.                         end
  3005.                         putDownText(x,y)
  3006.                         miceDown = {}
  3007.                         keysDown = {}
  3008.                     end
  3009.                     doRender = true
  3010.                     changedImage = true
  3011.                     isDragging = false
  3012.                 end
  3013.                 if key == keys.f and not (keysDown[keys.leftShift] or keysDown[keys.rightShift]) and (not isCurrentlyFilling) then
  3014.                     renderBottomBar("Click to fill area.")
  3015.                     local mevt
  3016.                     repeat
  3017.                         mevt = {os.pullEvent()}
  3018.                     until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] == 1 and (mevt[4] or scr_y) < scr_y-(renderBlittle and 0 or doRenderBar))
  3019.                     if not (mevt[1] == "key" and mevt[2] == keys.x) then
  3020.                         local x,y = mevt[3],mevt[4]
  3021.                         if renderBlittle then
  3022.                             x = 2*x
  3023.                             y = 3*y
  3024.                         end
  3025.                         os.queueEvent("filltool_async", frame, x, y, paint)
  3026.                         miceDown = {}
  3027.                         keysDown = {}
  3028.                     end
  3029.                     doRender = true
  3030.                     changedImage = true
  3031.                     isDragging = false
  3032.                 end
  3033.                 if key == keys.p then
  3034.                     renderBottomBar("Pick color with cursor:")
  3035.                     paintEncoded = clearAllRedundant(paintEncoded)
  3036.                     local mevt
  3037.                     repeat
  3038.                         mevt = {os.pullEvent()}
  3039.                     until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[2] == 1 and mevt[4] <= scr_y)
  3040.                     if not (mevt[1] == "key" and mevt[2] == keys.x) then
  3041.                         local x, y = mevt[3]+paint.scrollX, mevt[4]+paint.scrollY
  3042.                         if renderBlittle then
  3043.                             x = 2*x
  3044.                             y = 3*y
  3045.                         end
  3046.                         local p
  3047.                         for a = 1, #paintEncoded[frame] do
  3048.                             p = paintEncoded[frame][a]
  3049.                             if (p.x == x) and (p.y == y) then
  3050.                                 paint.t = p.t or paint.t
  3051.                                 paint.b = p.b or paint.b
  3052.                                 paint.c = p.c or paint.c
  3053.                                 paint.m = p.m or paint.m
  3054.                                 miceDown = {}
  3055.                                 keysDown = {}
  3056.                                 doRender = true
  3057.                                 isDragging = false
  3058.                                 break
  3059.                             end
  3060.                         end
  3061.                         miceDown = {}
  3062.                         keysDown = {}
  3063.                     end
  3064.                     doRender = true
  3065.                     isDragging = false
  3066.                 end
  3067.                 if (key == keys.leftCtrl or key == keys.rightCtrl) then
  3068.                     keysDown = {[207] = keysDown[207]}
  3069.                     isDragging = false
  3070.                     local res = displayMenu()
  3071.                     paintEncoded = clearAllRedundant(paintEncoded)
  3072.                     if res == "exit" then break end
  3073.                     doRender = true
  3074.                 end
  3075.             end
  3076.             if (key == keys.f and keysDown[keys.leftShift]) then
  3077.                 local deredots = {}
  3078.                 changedImage = true
  3079.                 for a = 1, #paintEncoded[frame] do
  3080.                     local dot = paintEncoded[frame][a]
  3081.                     if dot.x-paint.scrollX > 0 and dot.x-paint.scrollX <= scr_x then
  3082.                         if dot.y-paint.scrollY > 0 and dot.y-paint.scrollY <= scr_y then
  3083.                             deredots[#deredots+1] = {dot.x-paint.scrollX, dot.y-paint.scrollY}
  3084.                         end
  3085.                     end
  3086.                 end
  3087.                 for y = 1, scr_y do
  3088.                     for x = 1, scr_x do
  3089.                         local good = true
  3090.                         for a = 1, #deredots do
  3091.                             if (deredots[a][1] == x) and (deredots[a][2] == y) then
  3092.                                 good = bad
  3093.                                 break
  3094.                             end
  3095.                         end
  3096.                         if good then
  3097.                             putDotDown({x=x, y=y})
  3098.                         end
  3099.                     end
  3100.                 end
  3101.                 saveToUndoBuffer()
  3102.                 doRender = true
  3103.             end
  3104.             if key == keys.g then
  3105.                 paint.doGray = not paint.doGray
  3106.                 changedImage = true
  3107.                 saveToUndoBuffer()
  3108.                 doRender = true
  3109.             end
  3110.             if key == keys.a then
  3111.                 paint.scrollX = 0
  3112.                 paint.scrollY = 0
  3113.                 doRender = true
  3114.             end
  3115.             if key == keys.n then
  3116.                 if keysDown[keys.leftShift] then
  3117.                     paint.c = specialCharSelector()
  3118.                 else
  3119.                     paint.c = boxCharSelector()
  3120.                 end
  3121.                 resetInputState()
  3122.                 doRender = true
  3123.             end
  3124.             if key == keys.f1 then
  3125.                 guiHelp()
  3126.                 resetInputState()
  3127.                 isDragging = false
  3128.             end
  3129.             if key == keys.f3 then
  3130.                 listAllMonitors()
  3131.                 resetInputState()
  3132.                 isDragging = false
  3133.             end
  3134.             if key == keys.leftBracket then
  3135.                 os.queueEvent("mouse_scroll",2,1,1)
  3136.             elseif key == keys.rightBracket then
  3137.                 os.queueEvent("mouse_scroll",1,1,1)
  3138.             end
  3139.             if key == keys.z then
  3140.                 if keysDown[keys.leftAlt] and undoPos < #undoBuffer then
  3141.                     doRedo()
  3142.                     barmsg = "Redood."
  3143.                     doRender = true
  3144.                 elseif undoPos > 1 then
  3145.                     doUndo()
  3146.                     barmsg = "Undood."
  3147.                     doRender = true
  3148.                 end
  3149.             end
  3150.         elseif evt[1] == "key_up" then
  3151.             local key = evt[2]
  3152.             keysDown[key] = false
  3153.         end
  3154.         if (oldx~=paint.scrollX) or (oldy~=paint.scrollY) then
  3155.             doRender = true
  3156.         end
  3157.         if doRender then
  3158.             renderAllPAIN()
  3159.             doRender = false
  3160.         end
  3161.     end
  3162. end
  3163.  
  3164. runPainEditor = function(...) --needs to be cleaned up
  3165.     local tArg = table.pack(...)
  3166.     if not (tArg[1] == "-n" or (not tArg[1])) then
  3167.         fileName = shell.resolve(tostring(tArg[1]))
  3168.     end
  3169.    
  3170.     if not fileName then
  3171.         paintEncoded = {{}}
  3172.     elseif not fs.exists(fileName) then
  3173.         local ex = fileName:sub(-4):lower()
  3174.         if ex == ".nfp" then
  3175.             defaultSaveFormat = 1
  3176.         elseif ex == ".nft" then
  3177.             defaultSaveFormat = 2
  3178.         elseif ex == ".blt" then
  3179.             defaultSaveFormat = 3
  3180.         elseif ex == ".gif" then
  3181.             defaultSaveFormat = 5
  3182.         elseif ex == ".ucg" then
  3183.             defaultSaveFormat = 6
  3184.         else
  3185.             defaultSaveFormat = 4
  3186.         end
  3187.         paintEncoded = {{}}
  3188.     elseif fs.isDir(fileName) then
  3189.         if math.random(1,32) == 1 then
  3190.             write("Oh")
  3191.             sleep(0.2)
  3192.             write(" My")
  3193.             sleep(0.2)
  3194.             print(" God")
  3195.             sleep(0.3)
  3196.             write("That is a")
  3197.             sleep(0.1)
  3198.             term.setTextColor(colors.red)
  3199.             write(" DAMNED")
  3200.             sleep(0.4)
  3201.             print(" FOLDER.")
  3202.             term.setTextColor(colors.white)
  3203.             sleep(0.2)
  3204.             print("You crazy person.")
  3205.             sleep(0.2)
  3206.         else
  3207.             print("That's a folder.")
  3208.         end
  3209.         return
  3210.     else
  3211.         paintEncoded, defaultSaveFormat = openNewFile(fileName, readNonImageAsNFP)
  3212.         if not paintEncoded then
  3213.             return print(defaultSaveFormat)
  3214.         end
  3215.     end
  3216.    
  3217.     local asyncFillTool = function()
  3218.         local event, frameNo, x, y, dot
  3219.         isCurrentlyFilling = false
  3220.         while true do
  3221.             event, frameNo, x, y, dot = os.pullEvent("filltool_async")
  3222.             isCurrentlyFilling = true
  3223.             renderBottomBar("Filling area...")
  3224.             fillTool(frameNo, x, y, dot)
  3225.             isCurrentlyFilling = false
  3226.             reRenderPAIN(false)
  3227.         end
  3228.     end
  3229.    
  3230.     if not paintEncoded[frame] then paintEncoded = {paintEncoded} end
  3231.     if pMode == 1 then
  3232.         doRenderBar = 0
  3233.         renderPAIN(paintEncoded[tonumber(tArg[5]) or 1],-(tonumber(tArg[3]) or 0),-(tonumber(tArg[4]) or 0)) -- 'pain filename view X Y frame'
  3234.         sleep(0)
  3235.         return
  3236.     else
  3237.         renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
  3238.     end
  3239.     lastPaintEncoded = deepCopy(paintEncoded)
  3240.     undoBuffer = {deepCopy(paintEncoded)}
  3241.     parallel.waitForAny(getInput, doNonEventDrivenMovement, asyncFillTool)
  3242.    
  3243.     term.setCursorPos(1,scr_y)
  3244.     term.setBackgroundColor(colors.black)
  3245.     term.clearLine()
  3246. end
  3247.  
  3248. if not shell then return end
  3249.  
  3250. runPainEditor(...)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement